The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

605 lines
21KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #include "../JuceLibraryCode/JuceHeader.h"
  20. #include "MainHostWindow.h"
  21. #include "InternalFilters.h"
  22. //==============================================================================
  23. class MainHostWindow::PluginListWindow : public DocumentWindow
  24. {
  25. public:
  26. PluginListWindow (MainHostWindow& mw, AudioPluginFormatManager& pluginFormatManager)
  27. : DocumentWindow ("Available Plugins",
  28. LookAndFeel::getDefaultLookAndFeel().findColour (ResizableWindow::backgroundColourId),
  29. DocumentWindow::minimiseButton | DocumentWindow::closeButton),
  30. owner (mw)
  31. {
  32. auto deadMansPedalFile = getAppProperties().getUserSettings()
  33. ->getFile().getSiblingFile ("RecentlyCrashedPluginsList");
  34. setContentOwned (new PluginListComponent (pluginFormatManager,
  35. owner.knownPluginList,
  36. deadMansPedalFile,
  37. getAppProperties().getUserSettings(), true), true);
  38. setResizable (true, false);
  39. setResizeLimits (300, 400, 800, 1500);
  40. setTopLeftPosition (60, 60);
  41. restoreWindowStateFromString (getAppProperties().getUserSettings()->getValue ("listWindowPos"));
  42. setVisible (true);
  43. }
  44. ~PluginListWindow()
  45. {
  46. getAppProperties().getUserSettings()->setValue ("listWindowPos", getWindowStateAsString());
  47. clearContentComponent();
  48. }
  49. void closeButtonPressed() override
  50. {
  51. owner.pluginListWindow = nullptr;
  52. }
  53. private:
  54. MainHostWindow& owner;
  55. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginListWindow)
  56. };
  57. //==============================================================================
  58. MainHostWindow::MainHostWindow()
  59. : DocumentWindow (JUCEApplication::getInstance()->getApplicationName(),
  60. LookAndFeel::getDefaultLookAndFeel().findColour (ResizableWindow::backgroundColourId),
  61. DocumentWindow::allButtons)
  62. {
  63. formatManager.addDefaultFormats();
  64. formatManager.addFormat (new InternalPluginFormat());
  65. ScopedPointer<XmlElement> savedAudioState (getAppProperties().getUserSettings()
  66. ->getXmlValue ("audioDeviceState"));
  67. deviceManager.initialise (256, 256, savedAudioState, true);
  68. setResizable (true, false);
  69. setResizeLimits (500, 400, 10000, 10000);
  70. centreWithSize (800, 600);
  71. graphHolder = new GraphDocumentComponent (formatManager, deviceManager);
  72. setContentNonOwned (graphHolder, false);
  73. restoreWindowStateFromString (getAppProperties().getUserSettings()->getValue ("mainWindowPos"));
  74. setVisible (true);
  75. InternalPluginFormat internalFormat;
  76. internalFormat.getAllTypes (internalTypes);
  77. ScopedPointer<XmlElement> savedPluginList (getAppProperties().getUserSettings()->getXmlValue ("pluginList"));
  78. if (savedPluginList != nullptr)
  79. knownPluginList.recreateFromXml (*savedPluginList);
  80. pluginSortMethod = (KnownPluginList::SortMethod) getAppProperties().getUserSettings()
  81. ->getIntValue ("pluginSortMethod", KnownPluginList::sortByManufacturer);
  82. knownPluginList.addChangeListener (this);
  83. if (auto* filterGraph = graphHolder->graph.get())
  84. filterGraph->addChangeListener (this);
  85. addKeyListener (getCommandManager().getKeyMappings());
  86. Process::setPriority (Process::HighPriority);
  87. #if JUCE_MAC
  88. setMacMainMenu (this);
  89. #else
  90. setMenuBar (this);
  91. #endif
  92. getCommandManager().setFirstCommandTarget (this);
  93. }
  94. MainHostWindow::~MainHostWindow()
  95. {
  96. pluginListWindow = nullptr;
  97. knownPluginList.removeChangeListener (this);
  98. if (auto* filterGraph = graphHolder->graph.get())
  99. filterGraph->removeChangeListener (this);
  100. getAppProperties().getUserSettings()->setValue ("mainWindowPos", getWindowStateAsString());
  101. clearContentComponent();
  102. #if JUCE_MAC
  103. setMacMainMenu (nullptr);
  104. #else
  105. setMenuBar (nullptr);
  106. #endif
  107. graphHolder = nullptr;
  108. }
  109. void MainHostWindow::closeButtonPressed()
  110. {
  111. tryToQuitApplication();
  112. }
  113. struct AsyncQuitRetrier : private Timer
  114. {
  115. AsyncQuitRetrier() { startTimer (500); }
  116. void timerCallback() override
  117. {
  118. stopTimer();
  119. delete this;
  120. if (auto app = JUCEApplicationBase::getInstance())
  121. app->systemRequestedQuit();
  122. }
  123. };
  124. void MainHostWindow::tryToQuitApplication()
  125. {
  126. if (graphHolder->closeAnyOpenPluginWindows())
  127. {
  128. // Really important thing to note here: if the last call just deleted any plugin windows,
  129. // we won't exit immediately - instead we'll use our AsyncQuitRetrier to let the message
  130. // loop run for another brief moment, then try again. This will give any plugins a chance
  131. // to flush any GUI events that may have been in transit before the app forces them to
  132. // be unloaded
  133. new AsyncQuitRetrier();
  134. }
  135. else if (ModalComponentManager::getInstance()->cancelAllModalComponents())
  136. {
  137. new AsyncQuitRetrier();
  138. }
  139. else if (graphHolder == nullptr || graphHolder->graph->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk)
  140. {
  141. // Some plug-ins do not want [NSApp stop] to be called
  142. // before the plug-ins are not deallocated.
  143. graphHolder->releaseGraph();
  144. JUCEApplication::quit();
  145. }
  146. }
  147. void MainHostWindow::changeListenerCallback (ChangeBroadcaster* changed)
  148. {
  149. if (changed == &knownPluginList)
  150. {
  151. menuItemsChanged();
  152. // save the plugin list every time it gets chnaged, so that if we're scanning
  153. // and it crashes, we've still saved the previous ones
  154. ScopedPointer<XmlElement> savedPluginList (knownPluginList.createXml());
  155. if (savedPluginList != nullptr)
  156. {
  157. getAppProperties().getUserSettings()->setValue ("pluginList", savedPluginList);
  158. getAppProperties().saveIfNeeded();
  159. }
  160. }
  161. else if (graphHolder != nullptr && changed == graphHolder->graph)
  162. {
  163. auto title = JUCEApplication::getInstance()->getApplicationName();
  164. auto f = graphHolder->graph->getFile();
  165. if (f.existsAsFile())
  166. title = f.getFileName() + " - " + title;
  167. setName (title);
  168. }
  169. }
  170. StringArray MainHostWindow::getMenuBarNames()
  171. {
  172. StringArray names;
  173. names.add ("File");
  174. names.add ("Plugins");
  175. names.add ("Options");
  176. names.add ("Windows");
  177. return names;
  178. }
  179. PopupMenu MainHostWindow::getMenuForIndex (int topLevelMenuIndex, const String& /*menuName*/)
  180. {
  181. PopupMenu menu;
  182. if (topLevelMenuIndex == 0)
  183. {
  184. // "File" menu
  185. menu.addCommandItem (&getCommandManager(), CommandIDs::newFile);
  186. menu.addCommandItem (&getCommandManager(), CommandIDs::open);
  187. RecentlyOpenedFilesList recentFiles;
  188. recentFiles.restoreFromString (getAppProperties().getUserSettings()
  189. ->getValue ("recentFilterGraphFiles"));
  190. PopupMenu recentFilesMenu;
  191. recentFiles.createPopupMenuItems (recentFilesMenu, 100, true, true);
  192. menu.addSubMenu ("Open recent file", recentFilesMenu);
  193. menu.addCommandItem (&getCommandManager(), CommandIDs::save);
  194. menu.addCommandItem (&getCommandManager(), CommandIDs::saveAs);
  195. menu.addSeparator();
  196. menu.addCommandItem (&getCommandManager(), StandardApplicationCommandIDs::quit);
  197. }
  198. else if (topLevelMenuIndex == 1)
  199. {
  200. // "Plugins" menu
  201. PopupMenu pluginsMenu;
  202. addPluginsToMenu (pluginsMenu);
  203. menu.addSubMenu ("Create plugin", pluginsMenu);
  204. menu.addSeparator();
  205. menu.addItem (250, "Delete all plugins");
  206. }
  207. else if (topLevelMenuIndex == 2)
  208. {
  209. // "Options" menu
  210. menu.addCommandItem (&getCommandManager(), CommandIDs::showPluginListEditor);
  211. PopupMenu sortTypeMenu;
  212. sortTypeMenu.addItem (200, "List plugins in default order", true, pluginSortMethod == KnownPluginList::defaultOrder);
  213. sortTypeMenu.addItem (201, "List plugins in alphabetical order", true, pluginSortMethod == KnownPluginList::sortAlphabetically);
  214. sortTypeMenu.addItem (202, "List plugins by category", true, pluginSortMethod == KnownPluginList::sortByCategory);
  215. sortTypeMenu.addItem (203, "List plugins by manufacturer", true, pluginSortMethod == KnownPluginList::sortByManufacturer);
  216. sortTypeMenu.addItem (204, "List plugins based on the directory structure", true, pluginSortMethod == KnownPluginList::sortByFileSystemLocation);
  217. menu.addSubMenu ("Plugin menu type", sortTypeMenu);
  218. menu.addSeparator();
  219. menu.addCommandItem (&getCommandManager(), CommandIDs::showAudioSettings);
  220. menu.addCommandItem (&getCommandManager(), CommandIDs::toggleDoublePrecision);
  221. menu.addSeparator();
  222. menu.addCommandItem (&getCommandManager(), CommandIDs::aboutBox);
  223. }
  224. else if (topLevelMenuIndex == 3)
  225. {
  226. menu.addCommandItem (&getCommandManager(), CommandIDs::allWindowsForward);
  227. }
  228. return menu;
  229. }
  230. void MainHostWindow::menuItemSelected (int menuItemID, int /*topLevelMenuIndex*/)
  231. {
  232. if (menuItemID == 250)
  233. {
  234. if (graphHolder != nullptr)
  235. if (auto* graph = graphHolder->graph.get())
  236. graph->clear();
  237. }
  238. else if (menuItemID >= 100 && menuItemID < 200)
  239. {
  240. RecentlyOpenedFilesList recentFiles;
  241. recentFiles.restoreFromString (getAppProperties().getUserSettings()
  242. ->getValue ("recentFilterGraphFiles"));
  243. if (graphHolder != nullptr)
  244. if (auto* graph = graphHolder->graph.get())
  245. if (graph != nullptr && graph->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk)
  246. graph->loadFrom (recentFiles.getFile (menuItemID - 100), true);
  247. }
  248. else if (menuItemID >= 200 && menuItemID < 210)
  249. {
  250. if (menuItemID == 200) pluginSortMethod = KnownPluginList::defaultOrder;
  251. else if (menuItemID == 201) pluginSortMethod = KnownPluginList::sortAlphabetically;
  252. else if (menuItemID == 202) pluginSortMethod = KnownPluginList::sortByCategory;
  253. else if (menuItemID == 203) pluginSortMethod = KnownPluginList::sortByManufacturer;
  254. else if (menuItemID == 204) pluginSortMethod = KnownPluginList::sortByFileSystemLocation;
  255. getAppProperties().getUserSettings()->setValue ("pluginSortMethod", (int) pluginSortMethod);
  256. menuItemsChanged();
  257. }
  258. else
  259. {
  260. if (auto* desc = getChosenType (menuItemID))
  261. createPlugin (*desc,
  262. { proportionOfWidth (0.3f + Random::getSystemRandom().nextFloat() * 0.6f),
  263. proportionOfHeight (0.3f + Random::getSystemRandom().nextFloat() * 0.6f) });
  264. }
  265. }
  266. void MainHostWindow::menuBarActivated (bool isActivated)
  267. {
  268. if (isActivated && graphHolder != nullptr)
  269. graphHolder->unfocusKeyboardComponent();
  270. }
  271. void MainHostWindow::createPlugin (const PluginDescription& desc, Point<int> pos)
  272. {
  273. if (graphHolder != nullptr)
  274. graphHolder->createNewPlugin (desc, pos);
  275. }
  276. void MainHostWindow::addPluginsToMenu (PopupMenu& m) const
  277. {
  278. if (graphHolder != nullptr)
  279. {
  280. int i = 0;
  281. for (auto* t : internalTypes)
  282. m.addItem (++i, t->name + " (" + t->pluginFormatName + ")",
  283. graphHolder->graph->getNodeForName (t->name) == nullptr);
  284. }
  285. m.addSeparator();
  286. knownPluginList.addToMenu (m, pluginSortMethod);
  287. }
  288. const PluginDescription* MainHostWindow::getChosenType (const int menuID) const
  289. {
  290. if (menuID >= 1 && menuID < 1 + internalTypes.size())
  291. return internalTypes [menuID - 1];
  292. return knownPluginList.getType (knownPluginList.getIndexChosenByMenu (menuID));
  293. }
  294. //==============================================================================
  295. ApplicationCommandTarget* MainHostWindow::getNextCommandTarget()
  296. {
  297. return findFirstTargetParentComponent();
  298. }
  299. void MainHostWindow::getAllCommands (Array<CommandID>& commands)
  300. {
  301. // this returns the set of all commands that this target can perform..
  302. const CommandID ids[] = { CommandIDs::newFile,
  303. CommandIDs::open,
  304. CommandIDs::save,
  305. CommandIDs::saveAs,
  306. CommandIDs::showPluginListEditor,
  307. CommandIDs::showAudioSettings,
  308. CommandIDs::toggleDoublePrecision,
  309. CommandIDs::aboutBox,
  310. CommandIDs::allWindowsForward
  311. };
  312. commands.addArray (ids, numElementsInArray (ids));
  313. }
  314. void MainHostWindow::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
  315. {
  316. const String category ("General");
  317. switch (commandID)
  318. {
  319. case CommandIDs::newFile:
  320. result.setInfo ("New", "Creates a new filter graph file", category, 0);
  321. result.defaultKeypresses.add(KeyPress('n', ModifierKeys::commandModifier, 0));
  322. break;
  323. case CommandIDs::open:
  324. result.setInfo ("Open...", "Opens a filter graph file", category, 0);
  325. result.defaultKeypresses.add (KeyPress ('o', ModifierKeys::commandModifier, 0));
  326. break;
  327. case CommandIDs::save:
  328. result.setInfo ("Save", "Saves the current graph to a file", category, 0);
  329. result.defaultKeypresses.add (KeyPress ('s', ModifierKeys::commandModifier, 0));
  330. break;
  331. case CommandIDs::saveAs:
  332. result.setInfo ("Save As...",
  333. "Saves a copy of the current graph to a file",
  334. category, 0);
  335. result.defaultKeypresses.add (KeyPress ('s', ModifierKeys::shiftModifier | ModifierKeys::commandModifier, 0));
  336. break;
  337. case CommandIDs::showPluginListEditor:
  338. result.setInfo ("Edit the list of available plug-Ins...", String(), category, 0);
  339. result.addDefaultKeypress ('p', ModifierKeys::commandModifier);
  340. break;
  341. case CommandIDs::showAudioSettings:
  342. result.setInfo ("Change the audio device settings", String(), category, 0);
  343. result.addDefaultKeypress ('a', ModifierKeys::commandModifier);
  344. break;
  345. case CommandIDs::toggleDoublePrecision:
  346. updatePrecisionMenuItem (result);
  347. break;
  348. case CommandIDs::aboutBox:
  349. result.setInfo ("About...", String(), category, 0);
  350. break;
  351. case CommandIDs::allWindowsForward:
  352. result.setInfo ("All Windows Forward", "Bring all plug-in windows forward", category, 0);
  353. result.addDefaultKeypress ('w', ModifierKeys::commandModifier);
  354. break;
  355. default:
  356. break;
  357. }
  358. }
  359. bool MainHostWindow::perform (const InvocationInfo& info)
  360. {
  361. switch (info.commandID)
  362. {
  363. case CommandIDs::newFile:
  364. if (graphHolder != nullptr && graphHolder->graph != nullptr && graphHolder->graph->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk)
  365. graphHolder->graph->newDocument();
  366. break;
  367. case CommandIDs::open:
  368. if (graphHolder != nullptr && graphHolder->graph != nullptr && graphHolder->graph->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk)
  369. graphHolder->graph->loadFromUserSpecifiedFile (true);
  370. break;
  371. case CommandIDs::save:
  372. if (graphHolder != nullptr && graphHolder->graph != nullptr)
  373. graphHolder->graph->save (true, true);
  374. break;
  375. case CommandIDs::saveAs:
  376. if (graphHolder != nullptr && graphHolder->graph != nullptr)
  377. graphHolder->graph->saveAs (File(), true, true, true);
  378. break;
  379. case CommandIDs::showPluginListEditor:
  380. if (pluginListWindow == nullptr)
  381. pluginListWindow = new PluginListWindow (*this, formatManager);
  382. pluginListWindow->toFront (true);
  383. break;
  384. case CommandIDs::showAudioSettings:
  385. showAudioSettings();
  386. break;
  387. case CommandIDs::toggleDoublePrecision:
  388. if (auto* props = getAppProperties().getUserSettings())
  389. {
  390. bool newIsDoublePrecision = ! isDoublePrecisionProcessing();
  391. props->setValue ("doublePrecisionProcessing", var (newIsDoublePrecision));
  392. {
  393. ApplicationCommandInfo cmdInfo (info.commandID);
  394. updatePrecisionMenuItem (cmdInfo);
  395. menuItemsChanged();
  396. }
  397. if (graphHolder != nullptr)
  398. graphHolder->setDoublePrecision (newIsDoublePrecision);
  399. }
  400. break;
  401. case CommandIDs::aboutBox:
  402. // TODO
  403. break;
  404. case CommandIDs::allWindowsForward:
  405. {
  406. auto& desktop = Desktop::getInstance();
  407. for (int i = 0; i < desktop.getNumComponents(); ++i)
  408. desktop.getComponent (i)->toBehind (this);
  409. break;
  410. }
  411. default:
  412. return false;
  413. }
  414. return true;
  415. }
  416. void MainHostWindow::showAudioSettings()
  417. {
  418. AudioDeviceSelectorComponent audioSettingsComp (deviceManager,
  419. 0, 256,
  420. 0, 256,
  421. true, true, true, false);
  422. audioSettingsComp.setSize (500, 450);
  423. DialogWindow::LaunchOptions o;
  424. o.content.setNonOwned (&audioSettingsComp);
  425. o.dialogTitle = "Audio Settings";
  426. o.componentToCentreAround = this;
  427. o.dialogBackgroundColour = getLookAndFeel().findColour (ResizableWindow::backgroundColourId);
  428. o.escapeKeyTriggersCloseButton = true;
  429. o.useNativeTitleBar = false;
  430. o.resizable = false;
  431. o.runModal();
  432. ScopedPointer<XmlElement> audioState (deviceManager.createStateXml());
  433. getAppProperties().getUserSettings()->setValue ("audioDeviceState", audioState);
  434. getAppProperties().getUserSettings()->saveIfNeeded();
  435. if (graphHolder != nullptr)
  436. if (graphHolder->graph != nullptr)
  437. graphHolder->graph->graph.removeIllegalConnections();
  438. }
  439. bool MainHostWindow::isInterestedInFileDrag (const StringArray&)
  440. {
  441. return true;
  442. }
  443. void MainHostWindow::fileDragEnter (const StringArray&, int, int)
  444. {
  445. }
  446. void MainHostWindow::fileDragMove (const StringArray&, int, int)
  447. {
  448. }
  449. void MainHostWindow::fileDragExit (const StringArray&)
  450. {
  451. }
  452. void MainHostWindow::filesDropped (const StringArray& files, int x, int y)
  453. {
  454. if (graphHolder != nullptr)
  455. {
  456. if (files.size() == 1 && File (files[0]).hasFileExtension (FilterGraph::getFilenameSuffix()))
  457. {
  458. if (auto* filterGraph = graphHolder->graph.get())
  459. if (filterGraph->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk)
  460. filterGraph->loadFrom (File (files[0]), true);
  461. }
  462. else
  463. {
  464. OwnedArray<PluginDescription> typesFound;
  465. knownPluginList.scanAndAddDragAndDroppedFiles (formatManager, files, typesFound);
  466. auto pos = graphHolder->getLocalPoint (this, Point<int> (x, y));
  467. for (int i = 0; i < jmin (5, typesFound.size()); ++i)
  468. if (auto* desc = typesFound.getUnchecked(i))
  469. createPlugin (*desc, pos);
  470. }
  471. }
  472. }
  473. bool MainHostWindow::isDoublePrecisionProcessing()
  474. {
  475. if (auto* props = getAppProperties().getUserSettings())
  476. return props->getBoolValue ("doublePrecisionProcessing", false);
  477. return false;
  478. }
  479. void MainHostWindow::updatePrecisionMenuItem (ApplicationCommandInfo& info)
  480. {
  481. info.setInfo ("Double floating point precision rendering", String(), "General", 0);
  482. info.setTicked (isDoublePrecisionProcessing());
  483. }