531 lines
17 KiB
C++
531 lines
17 KiB
C++
//
|
|
// IconMenu.cpp
|
|
// Light Host
|
|
//
|
|
// Created by Rolando Islas on 12/26/15.
|
|
//
|
|
//
|
|
|
|
#include "../JuceLibraryCode/JuceHeader.h"
|
|
#include "IconMenu.hpp"
|
|
#include "PluginWindow.h"
|
|
#include <ctime>
|
|
#if JUCE_WINDOWS
|
|
#include "Windows.h"
|
|
#endif
|
|
|
|
class IconMenu::PluginListWindow : public DocumentWindow
|
|
{
|
|
public:
|
|
PluginListWindow(IconMenu& owner_, AudioPluginFormatManager& pluginFormatManager)
|
|
: DocumentWindow("Available Plugins", Colours::white,
|
|
DocumentWindow::minimiseButton | DocumentWindow::closeButton),
|
|
owner(owner_)
|
|
{
|
|
const File deadMansPedalFile(getAppProperties().getUserSettings()
|
|
->getFile().getSiblingFile("RecentlyCrashedPluginsList"));
|
|
|
|
setContentOwned(new PluginListComponent(pluginFormatManager,
|
|
owner.knownPluginList,
|
|
deadMansPedalFile,
|
|
getAppProperties().getUserSettings()), true);
|
|
|
|
setUsingNativeTitleBar(true);
|
|
setResizable(true, false);
|
|
setResizeLimits(300, 400, 800, 1500);
|
|
setTopLeftPosition(60, 60);
|
|
|
|
restoreWindowStateFromString(getAppProperties().getUserSettings()->getValue("listWindowPos"));
|
|
setVisible(true);
|
|
}
|
|
|
|
~PluginListWindow()
|
|
{
|
|
getAppProperties().getUserSettings()->setValue("listWindowPos", getWindowStateAsString());
|
|
|
|
clearContentComponent();
|
|
}
|
|
|
|
void closeButtonPressed()
|
|
{
|
|
owner.removePluginsLackingInputOutput();
|
|
#if JUCE_MAC
|
|
Process::setDockIconVisible(false);
|
|
#endif
|
|
owner.pluginListWindow = nullptr;
|
|
}
|
|
|
|
private:
|
|
IconMenu& owner;
|
|
};
|
|
|
|
IconMenu::IconMenu() : INDEX_EDIT(1000000), INDEX_BYPASS(2000000), INDEX_DELETE(3000000), INDEX_MOVE_UP(4000000), INDEX_MOVE_DOWN(5000000)
|
|
{
|
|
// Initiialization
|
|
formatManager.addDefaultFormats();
|
|
#if JUCE_WINDOWS
|
|
x = y = 0;
|
|
#endif
|
|
// Audio device
|
|
ScopedPointer<XmlElement> savedAudioState (getAppProperties().getUserSettings()->getXmlValue("audioDeviceState"));
|
|
deviceManager.initialise(256, 256, savedAudioState, true);
|
|
player.setProcessor(&graph);
|
|
deviceManager.addAudioCallback(&player);
|
|
// Plugins - all
|
|
ScopedPointer<XmlElement> savedPluginList(getAppProperties().getUserSettings()->getXmlValue("pluginList"));
|
|
if (savedPluginList != nullptr)
|
|
knownPluginList.recreateFromXml(*savedPluginList);
|
|
pluginSortMethod = KnownPluginList::sortByManufacturer;
|
|
knownPluginList.addChangeListener(this);
|
|
// Plugins - active
|
|
ScopedPointer<XmlElement> savedPluginListActive(getAppProperties().getUserSettings()->getXmlValue("pluginListActive"));
|
|
if (savedPluginListActive != nullptr)
|
|
activePluginList.recreateFromXml(*savedPluginListActive);
|
|
loadActivePlugins();
|
|
activePluginList.addChangeListener(this);
|
|
setIcon();
|
|
setIconTooltip(JUCEApplication::getInstance()->getApplicationName());
|
|
};
|
|
|
|
IconMenu::~IconMenu()
|
|
{
|
|
savePluginStates();
|
|
}
|
|
|
|
void IconMenu::setIcon()
|
|
{
|
|
// Set menu icon
|
|
#if JUCE_MAC
|
|
if (exec("defaults read -g AppleInterfaceStyle").compare("Dark") == 1)
|
|
setIconImage(ImageFileFormat::loadFrom(BinaryData::menu_icon_white_png, BinaryData::menu_icon_white_pngSize));
|
|
else
|
|
setIconImage(ImageFileFormat::loadFrom(BinaryData::menu_icon_png, BinaryData::menu_icon_pngSize));
|
|
#else
|
|
String defaultColor;
|
|
#if JUCE_WINDOWS
|
|
defaultColor = "white";
|
|
#elif JUCE_LINUX
|
|
defaultColor = "black";
|
|
#endif
|
|
if (!getAppProperties().getUserSettings()->containsKey("icon"))
|
|
getAppProperties().getUserSettings()->setValue("icon", defaultColor);
|
|
String color = getAppProperties().getUserSettings()->getValue("icon");
|
|
Image icon;
|
|
if (color.equalsIgnoreCase("white"))
|
|
icon = ImageFileFormat::loadFrom(BinaryData::menu_icon_white_png, BinaryData::menu_icon_white_pngSize);
|
|
else if (color.equalsIgnoreCase("black"))
|
|
icon = ImageFileFormat::loadFrom(BinaryData::menu_icon_png, BinaryData::menu_icon_pngSize);
|
|
setIconImage(icon);
|
|
#endif
|
|
}
|
|
|
|
void IconMenu::loadActivePlugins()
|
|
{
|
|
const int INPUT = 1000000;
|
|
const int OUTPUT = INPUT + 1;
|
|
const int CHANNEL_ONE = 0;
|
|
const int CHANNEL_TWO = 1;
|
|
PluginWindow::closeAllCurrentlyOpenWindows();
|
|
graph.clear();
|
|
inputNode = graph.addNode(new AudioProcessorGraph::AudioGraphIOProcessor(AudioProcessorGraph::AudioGraphIOProcessor::audioInputNode), INPUT);
|
|
outputNode = graph.addNode(new AudioProcessorGraph::AudioGraphIOProcessor(AudioProcessorGraph::AudioGraphIOProcessor::audioOutputNode), OUTPUT);
|
|
if (activePluginList.getNumTypes() == 0)
|
|
{
|
|
graph.addConnection(INPUT, CHANNEL_ONE, OUTPUT, CHANNEL_ONE);
|
|
graph.addConnection(INPUT, CHANNEL_TWO, OUTPUT, CHANNEL_TWO);
|
|
}
|
|
int pluginTime = 0;
|
|
int lastId = 0;
|
|
bool hasInputConnected = false;
|
|
// NOTE: Node ids cannot begin at 0.
|
|
for (int i = 1; i <= activePluginList.getNumTypes(); i++)
|
|
{
|
|
PluginDescription plugin = getNextPluginOlderThanTime(pluginTime);
|
|
String errorMessage;
|
|
AudioPluginInstance* instance = formatManager.createPluginInstance(plugin, graph.getSampleRate(), graph.getBlockSize(), errorMessage);
|
|
String pluginUid = getKey("state", plugin);
|
|
String savedPluginState = getAppProperties().getUserSettings()->getValue(pluginUid);
|
|
MemoryBlock savedPluginBinary;
|
|
savedPluginBinary.fromBase64Encoding(savedPluginState);
|
|
instance->setStateInformation(savedPluginBinary.getData(), savedPluginBinary.getSize());
|
|
graph.addNode(instance, i);
|
|
String key = getKey("bypass", plugin);
|
|
bool bypass = getAppProperties().getUserSettings()->getBoolValue(key, false);
|
|
// Input to plugin
|
|
if ((!hasInputConnected) && (!bypass))
|
|
{
|
|
graph.addConnection(INPUT, CHANNEL_ONE, i, CHANNEL_ONE);
|
|
graph.addConnection(INPUT, CHANNEL_TWO, i, CHANNEL_TWO);
|
|
hasInputConnected = true;
|
|
}
|
|
// Connect previous plugin to current
|
|
else if (!bypass)
|
|
{
|
|
graph.addConnection(lastId, CHANNEL_ONE, i, CHANNEL_ONE);
|
|
graph.addConnection(lastId, CHANNEL_TWO, i, CHANNEL_TWO);
|
|
}
|
|
if (!bypass)
|
|
lastId = i;
|
|
}
|
|
if (lastId > 0)
|
|
{
|
|
// Last active plugin to output
|
|
graph.addConnection(lastId, CHANNEL_ONE, OUTPUT, CHANNEL_ONE);
|
|
graph.addConnection(lastId, CHANNEL_TWO, OUTPUT, CHANNEL_TWO);
|
|
}
|
|
}
|
|
|
|
PluginDescription IconMenu::getNextPluginOlderThanTime(int &time)
|
|
{
|
|
int timeStatic = time;
|
|
PluginDescription closest;
|
|
int diff = INT_MAX;
|
|
for (int i = 0; i < activePluginList.getNumTypes(); i++)
|
|
{
|
|
PluginDescription plugin = *activePluginList.getType(i);
|
|
String key = getKey("order", plugin);
|
|
String pluginTimeString = getAppProperties().getUserSettings()->getValue(key);
|
|
int pluginTime = atoi(pluginTimeString.toStdString().c_str());
|
|
if (pluginTime > timeStatic && abs(timeStatic - pluginTime) < diff)
|
|
{
|
|
diff = abs(timeStatic - pluginTime);
|
|
closest = plugin;
|
|
time = pluginTime;
|
|
}
|
|
}
|
|
return closest;
|
|
}
|
|
|
|
void IconMenu::changeListenerCallback(ChangeBroadcaster* changed)
|
|
{
|
|
if (changed == &knownPluginList)
|
|
{
|
|
ScopedPointer<XmlElement> savedPluginList (knownPluginList.createXml());
|
|
if (savedPluginList != nullptr)
|
|
{
|
|
getAppProperties().getUserSettings()->setValue ("pluginList", savedPluginList);
|
|
getAppProperties().saveIfNeeded();
|
|
}
|
|
}
|
|
else if (changed == &activePluginList)
|
|
{
|
|
ScopedPointer<XmlElement> savedPluginList (activePluginList.createXml());
|
|
if (savedPluginList != nullptr)
|
|
{
|
|
getAppProperties().getUserSettings()->setValue ("pluginListActive", savedPluginList);
|
|
getAppProperties().saveIfNeeded();
|
|
}
|
|
}
|
|
}
|
|
|
|
#if JUCE_MAC
|
|
std::string IconMenu::exec(const char* cmd)
|
|
{
|
|
std::shared_ptr<FILE> pipe(popen(cmd, "r"), pclose);
|
|
if (!pipe) return "ERROR";
|
|
char buffer[128];
|
|
std::string result = "";
|
|
while (!feof(pipe.get()))
|
|
{
|
|
if (fgets(buffer, 128, pipe.get()) != NULL)
|
|
result += buffer;
|
|
}
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
void IconMenu::timerCallback()
|
|
{
|
|
stopTimer();
|
|
menu.clear();
|
|
menu.addSectionHeader(JUCEApplication::getInstance()->getApplicationName());
|
|
if (menuIconLeftClicked) {
|
|
menu.addItem(1, "Preferences");
|
|
menu.addItem(2, "Edit Plugins");
|
|
menu.addSeparator();
|
|
menu.addSectionHeader("Active Plugins");
|
|
// Active plugins
|
|
int time = 0;
|
|
for (int i = 0; i < activePluginList.getNumTypes(); i++)
|
|
{
|
|
PopupMenu options;
|
|
options.addItem(INDEX_EDIT + i, "Edit");
|
|
std::vector<PluginDescription> timeSorted = getTimeSortedList();
|
|
String key = getKey("bypass", timeSorted[i]);
|
|
bool bypass = getAppProperties().getUserSettings()->getBoolValue(key);
|
|
options.addItem(INDEX_BYPASS + i, "Bypass", true, bypass);
|
|
options.addSeparator();
|
|
options.addItem(INDEX_MOVE_UP + i, "Move Up", i > 0);
|
|
options.addItem(INDEX_MOVE_DOWN + i, "Move Down", i < timeSorted.size() - 1);
|
|
options.addSeparator();
|
|
options.addItem(INDEX_DELETE + i, "Delete");
|
|
PluginDescription plugin = getNextPluginOlderThanTime(time);
|
|
menu.addSubMenu(plugin.name, options);
|
|
}
|
|
menu.addSeparator();
|
|
menu.addSectionHeader("Avaliable Plugins");
|
|
// All plugins
|
|
knownPluginList.addToMenu(menu, pluginSortMethod);
|
|
}
|
|
else
|
|
{
|
|
menu.addItem(1, "Quit");
|
|
menu.addSeparator();
|
|
menu.addItem(2, "Delete Plugin States");
|
|
#if !JUCE_MAC
|
|
menu.addItem(3, "Invert Icon Color");
|
|
#endif
|
|
}
|
|
#if JUCE_MAC || JUCE_LINUX
|
|
menu.showMenuAsync(PopupMenu::Options().withTargetComponent(this), ModalCallbackFunction::forComponent(menuInvocationCallback, this));
|
|
#else
|
|
if (x == 0 || y == 0)
|
|
{
|
|
POINT iconLocation;
|
|
iconLocation.x = 0;
|
|
iconLocation.y = 0;
|
|
GetCursorPos(&iconLocation);
|
|
x = iconLocation.x;
|
|
y = iconLocation.y;
|
|
}
|
|
juce::Rectangle<int> rect(x, y, 1, 1);
|
|
menu.showMenuAsync(PopupMenu::Options().withTargetScreenArea(rect), ModalCallbackFunction::forComponent(menuInvocationCallback, this));
|
|
#endif
|
|
}
|
|
|
|
void IconMenu::mouseDown(const MouseEvent& e)
|
|
{
|
|
#if JUCE_MAC
|
|
Process::setDockIconVisible(true);
|
|
#endif
|
|
Process::makeForegroundProcess();
|
|
menuIconLeftClicked = e.mods.isLeftButtonDown();
|
|
startTimer(50);
|
|
}
|
|
|
|
void IconMenu::menuInvocationCallback(int id, IconMenu* im)
|
|
{
|
|
// Right click
|
|
if ((!im->menuIconLeftClicked))
|
|
{
|
|
if (id == 1)
|
|
{
|
|
im->savePluginStates();
|
|
return JUCEApplication::getInstance()->quit();
|
|
}
|
|
if (id == 2)
|
|
{
|
|
im->deletePluginStates();
|
|
return im->loadActivePlugins();
|
|
}
|
|
if (id == 3)
|
|
{
|
|
String color = getAppProperties().getUserSettings()->getValue("icon");
|
|
getAppProperties().getUserSettings()->setValue("icon", color.equalsIgnoreCase("black") ? "white" : "black");
|
|
return im->setIcon();
|
|
}
|
|
}
|
|
#if JUCE_MAC
|
|
// Click elsewhere
|
|
if (id == 0 && !PluginWindow::containsActiveWindows())
|
|
Process::setDockIconVisible(false);
|
|
#endif
|
|
// Audio settings
|
|
if (id == 1)
|
|
im->showAudioSettings();
|
|
// Reload
|
|
if (id == 2)
|
|
im->reloadPlugins();
|
|
// Plugins
|
|
if (id > 2)
|
|
{
|
|
// Delete plugin
|
|
if (id >= im->INDEX_DELETE && id < im->INDEX_DELETE + 1000000)
|
|
{
|
|
im->deletePluginStates();
|
|
|
|
int index = id - im->INDEX_DELETE;
|
|
std::vector<PluginDescription> timeSorted = im->getTimeSortedList();
|
|
String key = getKey("order", timeSorted[index]);
|
|
int unsortedIndex = 0;
|
|
for (int i = 0; im->activePluginList.getNumTypes(); i++)
|
|
{
|
|
PluginDescription current = *im->activePluginList.getType(i);
|
|
if (key.equalsIgnoreCase(getKey("order", current)))
|
|
{
|
|
unsortedIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Remove plugin order
|
|
getAppProperties().getUserSettings()->removeValue(key);
|
|
// Remove bypass entry
|
|
getAppProperties().getUserSettings()->removeValue(getKey("bypass", timeSorted[index]));
|
|
getAppProperties().saveIfNeeded();
|
|
|
|
// Remove plugin from list
|
|
im->activePluginList.removeType(unsortedIndex);
|
|
|
|
// Save current states
|
|
im->savePluginStates();
|
|
im->loadActivePlugins();
|
|
}
|
|
// Add plugin
|
|
else if (im->knownPluginList.getIndexChosenByMenu(id) > -1)
|
|
{
|
|
PluginDescription plugin = *im->knownPluginList.getType(im->knownPluginList.getIndexChosenByMenu(id));
|
|
String key = getKey("order", plugin);
|
|
int t = time(0);
|
|
getAppProperties().getUserSettings()->setValue(key, t);
|
|
getAppProperties().saveIfNeeded();
|
|
im->activePluginList.addType(plugin);
|
|
|
|
im->savePluginStates();
|
|
im->loadActivePlugins();
|
|
}
|
|
// Bypass plugin
|
|
else if (id >= im->INDEX_BYPASS && id < im->INDEX_BYPASS + 1000000)
|
|
{
|
|
int index = id - im->INDEX_BYPASS;
|
|
std::vector<PluginDescription> timeSorted = im->getTimeSortedList();
|
|
String key = getKey("bypass", timeSorted[index]);
|
|
|
|
// Set bypass flag
|
|
bool bypassed = getAppProperties().getUserSettings()->getBoolValue(key);
|
|
getAppProperties().getUserSettings()->setValue(key, !bypassed);
|
|
getAppProperties().saveIfNeeded();
|
|
|
|
im->savePluginStates();
|
|
im->loadActivePlugins();
|
|
}
|
|
// Show active plugin GUI
|
|
else if (id >= im->INDEX_EDIT && id < im->INDEX_EDIT + 1000000)
|
|
{
|
|
if (const AudioProcessorGraph::Node::Ptr f = im->graph.getNodeForId(id - im->INDEX_EDIT + 1))
|
|
if (PluginWindow* const w = PluginWindow::getWindowFor(f, PluginWindow::Normal))
|
|
w->toFront(true);
|
|
}
|
|
// Move plugin up the list
|
|
else if (id >= im->INDEX_MOVE_UP && id < im->INDEX_MOVE_UP + 1000000)
|
|
{
|
|
im->savePluginStates();
|
|
std::vector<PluginDescription> timeSorted = im->getTimeSortedList();
|
|
PluginDescription toMove = timeSorted[id - im->INDEX_MOVE_UP];
|
|
for (int i = 0; i < timeSorted.size(); i++)
|
|
{
|
|
bool move = getKey("move", toMove).equalsIgnoreCase(getKey("move", timeSorted[i]));
|
|
getAppProperties().getUserSettings()->setValue(getKey("order", timeSorted[i]), move ? i : i+1);
|
|
if (move)
|
|
getAppProperties().getUserSettings()->setValue(getKey("order", timeSorted[i-1]), i+1);
|
|
}
|
|
im->loadActivePlugins();
|
|
}
|
|
// Move plugin down the list
|
|
else if (id >= im->INDEX_MOVE_DOWN && id < im->INDEX_MOVE_DOWN + 1000000)
|
|
{
|
|
im->savePluginStates();
|
|
std::vector<PluginDescription> timeSorted = im->getTimeSortedList();
|
|
PluginDescription toMove = timeSorted[id - im->INDEX_MOVE_DOWN];
|
|
for (int i = 0; i < timeSorted.size(); i++)
|
|
{
|
|
bool move = getKey("move", toMove).equalsIgnoreCase(getKey("move", timeSorted[i]));
|
|
getAppProperties().getUserSettings()->setValue(getKey("order", timeSorted[i]), move ? i+2 : i+1);
|
|
if (move)
|
|
{
|
|
getAppProperties().getUserSettings()->setValue(getKey("order", timeSorted[i + 1]), i + 1);
|
|
i++;
|
|
}
|
|
}
|
|
im->loadActivePlugins();
|
|
}
|
|
// Update menu
|
|
im->startTimer(50);
|
|
}
|
|
}
|
|
|
|
std::vector<PluginDescription> IconMenu::getTimeSortedList()
|
|
{
|
|
int time = 0;
|
|
std::vector<PluginDescription> list;
|
|
for (int i = 0; i < activePluginList.getNumTypes(); i++)
|
|
list.push_back(getNextPluginOlderThanTime(time));
|
|
return list;
|
|
|
|
}
|
|
|
|
String IconMenu::getKey(String type, PluginDescription plugin)
|
|
{
|
|
String key = "plugin-" + type.toLowerCase() + "-" + plugin.name + plugin.version + plugin.pluginFormatName;
|
|
return key;
|
|
}
|
|
|
|
void IconMenu::deletePluginStates()
|
|
{
|
|
std::vector<PluginDescription> list = getTimeSortedList();
|
|
for (int i = 0; i < activePluginList.getNumTypes(); i++)
|
|
{
|
|
String pluginUid = getKey("state", list[i]);
|
|
getAppProperties().getUserSettings()->removeValue(pluginUid);
|
|
getAppProperties().saveIfNeeded();
|
|
}
|
|
}
|
|
|
|
void IconMenu::savePluginStates()
|
|
{
|
|
std::vector<PluginDescription> list = getTimeSortedList();
|
|
for (int i = 0; i < activePluginList.getNumTypes(); i++)
|
|
{
|
|
AudioProcessorGraph::Node* node = graph.getNodeForId(i + 1);
|
|
if (node == nullptr)
|
|
break;
|
|
AudioProcessor& processor = *node->getProcessor();
|
|
String pluginUid = getKey("state", list[i]);
|
|
MemoryBlock savedStateBinary;
|
|
processor.getStateInformation(savedStateBinary);
|
|
getAppProperties().getUserSettings()->setValue(pluginUid, savedStateBinary.toBase64Encoding());
|
|
getAppProperties().saveIfNeeded();
|
|
}
|
|
}
|
|
|
|
void IconMenu::showAudioSettings()
|
|
{
|
|
AudioDeviceSelectorComponent audioSettingsComp (deviceManager, 0, 256, 0, 256, false, false, true, true);
|
|
audioSettingsComp.setSize(500, 450);
|
|
|
|
DialogWindow::LaunchOptions o;
|
|
o.content.setNonOwned(&audioSettingsComp);
|
|
o.dialogTitle = "Audio Settings";
|
|
o.componentToCentreAround = this;
|
|
o.dialogBackgroundColour = Colour::fromRGB(236, 236, 236);
|
|
o.escapeKeyTriggersCloseButton = true;
|
|
o.useNativeTitleBar = true;
|
|
o.resizable = false;
|
|
|
|
o.runModal();
|
|
|
|
ScopedPointer<XmlElement> audioState(deviceManager.createStateXml());
|
|
|
|
getAppProperties().getUserSettings()->setValue("audioDeviceState", audioState);
|
|
getAppProperties().getUserSettings()->saveIfNeeded();
|
|
}
|
|
|
|
void IconMenu::reloadPlugins()
|
|
{
|
|
if (pluginListWindow == nullptr)
|
|
pluginListWindow = new PluginListWindow(*this, formatManager);
|
|
pluginListWindow->toFront(true);
|
|
}
|
|
|
|
void IconMenu::removePluginsLackingInputOutput()
|
|
{
|
|
std::vector<int> removeIndex;
|
|
for (int i = 0; i < knownPluginList.getNumTypes(); i++)
|
|
{
|
|
PluginDescription* plugin = knownPluginList.getType(i);
|
|
if (plugin->numInputChannels < 2 || plugin->numOutputChannels < 2)
|
|
removeIndex.push_back(i);
|
|
}
|
|
for (int i = 0; i < removeIndex.size(); i++)
|
|
knownPluginList.removeType(removeIndex[i] - i);
|
|
} |