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.

604 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& owner_, AudioPluginFormatManager& pluginFormatManager)
  27. : DocumentWindow ("Available Plugins",
  28. LookAndFeel::getDefaultLookAndFeel().findColour (ResizableWindow::backgroundColourId),
  29. DocumentWindow::minimiseButton | DocumentWindow::closeButton),
  30. owner (owner_)
  31. {
  32. const File 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()
  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. setContentOwned (new GraphDocumentComponent (formatManager, deviceManager), false);
  72. restoreWindowStateFromString (getAppProperties().getUserSettings()->getValue ("mainWindowPos"));
  73. setVisible (true);
  74. InternalPluginFormat internalFormat;
  75. internalFormat.getAllTypes (internalTypes);
  76. ScopedPointer<XmlElement> savedPluginList (getAppProperties().getUserSettings()->getXmlValue ("pluginList"));
  77. if (savedPluginList != nullptr)
  78. knownPluginList.recreateFromXml (*savedPluginList);
  79. pluginSortMethod = (KnownPluginList::SortMethod) getAppProperties().getUserSettings()
  80. ->getIntValue ("pluginSortMethod", KnownPluginList::sortByManufacturer);
  81. knownPluginList.addChangeListener (this);
  82. if (auto* filterGraph = getGraphEditor()->graph.get())
  83. filterGraph->addChangeListener (this);
  84. addKeyListener (getCommandManager().getKeyMappings());
  85. Process::setPriority (Process::HighPriority);
  86. #if JUCE_MAC
  87. setMacMainMenu (this);
  88. #else
  89. setMenuBar (this);
  90. #endif
  91. getCommandManager().setFirstCommandTarget (this);
  92. }
  93. MainHostWindow::~MainHostWindow()
  94. {
  95. pluginListWindow = nullptr;
  96. knownPluginList.removeChangeListener (this);
  97. if (auto* filterGraph = getGraphEditor()->graph.get())
  98. filterGraph->removeChangeListener (this);
  99. getAppProperties().getUserSettings()->setValue ("mainWindowPos", getWindowStateAsString());
  100. clearContentComponent();
  101. #if JUCE_MAC
  102. setMacMainMenu (nullptr);
  103. #else
  104. setMenuBar (nullptr);
  105. #endif
  106. }
  107. void MainHostWindow::closeButtonPressed()
  108. {
  109. tryToQuitApplication();
  110. }
  111. struct AsyncQuitRetrier : private Timer
  112. {
  113. AsyncQuitRetrier() { startTimer (500); }
  114. void timerCallback() override
  115. {
  116. stopTimer();
  117. delete this;
  118. if (auto app = JUCEApplicationBase::getInstance())
  119. app->systemRequestedQuit();
  120. }
  121. };
  122. void MainHostWindow::tryToQuitApplication()
  123. {
  124. PluginWindow::closeAllCurrentlyOpenWindows();
  125. if (ModalComponentManager::getInstance()->cancelAllModalComponents())
  126. {
  127. new AsyncQuitRetrier();
  128. }
  129. else if (getGraphEditor() == nullptr
  130. || getGraphEditor()->graph->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk)
  131. {
  132. // Some plug-ins do not want [NSApp stop] to be called
  133. // before the plug-ins are not deallocated.
  134. getGraphEditor()->releaseGraph();
  135. JUCEApplication::quit();
  136. }
  137. }
  138. void MainHostWindow::changeListenerCallback (ChangeBroadcaster* changed)
  139. {
  140. if (changed == &knownPluginList)
  141. {
  142. menuItemsChanged();
  143. // save the plugin list every time it gets chnaged, so that if we're scanning
  144. // and it crashes, we've still saved the previous ones
  145. ScopedPointer<XmlElement> savedPluginList (knownPluginList.createXml());
  146. if (savedPluginList != nullptr)
  147. {
  148. getAppProperties().getUserSettings()->setValue ("pluginList", savedPluginList);
  149. getAppProperties().saveIfNeeded();
  150. }
  151. }
  152. else if (changed == getGraphEditor()->graph)
  153. {
  154. String title = JUCEApplication::getInstance()->getApplicationName();
  155. File f = getGraphEditor()->graph->getFile();
  156. if (f.existsAsFile())
  157. title = f.getFileName() + " - " + title;
  158. setName (title);
  159. }
  160. }
  161. StringArray MainHostWindow::getMenuBarNames()
  162. {
  163. StringArray names;
  164. names.add ("File");
  165. names.add ("Plugins");
  166. names.add ("Options");
  167. names.add ("Windows");
  168. return names;
  169. }
  170. PopupMenu MainHostWindow::getMenuForIndex (int topLevelMenuIndex, const String& /*menuName*/)
  171. {
  172. PopupMenu menu;
  173. if (topLevelMenuIndex == 0)
  174. {
  175. // "File" menu
  176. menu.addCommandItem (&getCommandManager(), CommandIDs::newFile);
  177. menu.addCommandItem (&getCommandManager(), CommandIDs::open);
  178. RecentlyOpenedFilesList recentFiles;
  179. recentFiles.restoreFromString (getAppProperties().getUserSettings()
  180. ->getValue ("recentFilterGraphFiles"));
  181. PopupMenu recentFilesMenu;
  182. recentFiles.createPopupMenuItems (recentFilesMenu, 100, true, true);
  183. menu.addSubMenu ("Open recent file", recentFilesMenu);
  184. menu.addCommandItem (&getCommandManager(), CommandIDs::save);
  185. menu.addCommandItem (&getCommandManager(), CommandIDs::saveAs);
  186. menu.addSeparator();
  187. menu.addCommandItem (&getCommandManager(), StandardApplicationCommandIDs::quit);
  188. }
  189. else if (topLevelMenuIndex == 1)
  190. {
  191. // "Plugins" menu
  192. PopupMenu pluginsMenu;
  193. addPluginsToMenu (pluginsMenu);
  194. menu.addSubMenu ("Create plugin", pluginsMenu);
  195. menu.addSeparator();
  196. menu.addItem (250, "Delete all plugins");
  197. }
  198. else if (topLevelMenuIndex == 2)
  199. {
  200. // "Options" menu
  201. menu.addCommandItem (&getCommandManager(), CommandIDs::showPluginListEditor);
  202. PopupMenu sortTypeMenu;
  203. sortTypeMenu.addItem (200, "List plugins in default order", true, pluginSortMethod == KnownPluginList::defaultOrder);
  204. sortTypeMenu.addItem (201, "List plugins in alphabetical order", true, pluginSortMethod == KnownPluginList::sortAlphabetically);
  205. sortTypeMenu.addItem (202, "List plugins by category", true, pluginSortMethod == KnownPluginList::sortByCategory);
  206. sortTypeMenu.addItem (203, "List plugins by manufacturer", true, pluginSortMethod == KnownPluginList::sortByManufacturer);
  207. sortTypeMenu.addItem (204, "List plugins based on the directory structure", true, pluginSortMethod == KnownPluginList::sortByFileSystemLocation);
  208. menu.addSubMenu ("Plugin menu type", sortTypeMenu);
  209. menu.addSeparator();
  210. menu.addCommandItem (&getCommandManager(), CommandIDs::showAudioSettings);
  211. menu.addCommandItem (&getCommandManager(), CommandIDs::toggleDoublePrecision);
  212. menu.addSeparator();
  213. menu.addCommandItem (&getCommandManager(), CommandIDs::aboutBox);
  214. }
  215. else if (topLevelMenuIndex == 3)
  216. {
  217. menu.addCommandItem (&getCommandManager(), CommandIDs::allWindowsForward);
  218. }
  219. return menu;
  220. }
  221. void MainHostWindow::menuItemSelected (int menuItemID, int /*topLevelMenuIndex*/)
  222. {
  223. if (menuItemID == 250)
  224. {
  225. if (auto* graphEditor = getGraphEditor())
  226. if (auto* filterGraph = graphEditor->graph.get())
  227. filterGraph->clear();
  228. }
  229. else if (menuItemID >= 100 && menuItemID < 200)
  230. {
  231. RecentlyOpenedFilesList recentFiles;
  232. recentFiles.restoreFromString (getAppProperties().getUserSettings()
  233. ->getValue ("recentFilterGraphFiles"));
  234. if (auto* graphEditor = getGraphEditor())
  235. if (graphEditor->graph != nullptr && graphEditor->graph->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk)
  236. graphEditor->graph->loadFrom (recentFiles.getFile (menuItemID - 100), true);
  237. }
  238. else if (menuItemID >= 200 && menuItemID < 210)
  239. {
  240. if (menuItemID == 200) pluginSortMethod = KnownPluginList::defaultOrder;
  241. else if (menuItemID == 201) pluginSortMethod = KnownPluginList::sortAlphabetically;
  242. else if (menuItemID == 202) pluginSortMethod = KnownPluginList::sortByCategory;
  243. else if (menuItemID == 203) pluginSortMethod = KnownPluginList::sortByManufacturer;
  244. else if (menuItemID == 204) pluginSortMethod = KnownPluginList::sortByFileSystemLocation;
  245. getAppProperties().getUserSettings()->setValue ("pluginSortMethod", (int) pluginSortMethod);
  246. menuItemsChanged();
  247. }
  248. else
  249. {
  250. if (auto* desc = getChosenType (menuItemID))
  251. createPlugin (*desc,
  252. { proportionOfWidth (0.3f + Random::getSystemRandom().nextFloat() * 0.6f),
  253. proportionOfHeight (0.3f + Random::getSystemRandom().nextFloat() * 0.6f) });
  254. }
  255. }
  256. void MainHostWindow::menuBarActivated (bool isActivated)
  257. {
  258. if (auto* graphEditor = getGraphEditor())
  259. if (isActivated)
  260. graphEditor->unfocusKeyboardComponent();
  261. }
  262. void MainHostWindow::createPlugin (const PluginDescription& desc, Point<int> pos)
  263. {
  264. if (auto* graphEditor = getGraphEditor())
  265. graphEditor->createNewPlugin (desc, pos);
  266. }
  267. void MainHostWindow::addPluginsToMenu (PopupMenu& m) const
  268. {
  269. if (auto* graphEditor = getGraphEditor())
  270. {
  271. int i = 0;
  272. for (auto* t : internalTypes)
  273. m.addItem (++i, t->name + " (" + t->pluginFormatName + ")",
  274. graphEditor->graph->getNodeForName (t->name) == nullptr);
  275. }
  276. m.addSeparator();
  277. knownPluginList.addToMenu (m, pluginSortMethod);
  278. }
  279. const PluginDescription* MainHostWindow::getChosenType (const int menuID) const
  280. {
  281. if (menuID >= 1 && menuID < 1 + internalTypes.size())
  282. return internalTypes [menuID - 1];
  283. return knownPluginList.getType (knownPluginList.getIndexChosenByMenu (menuID));
  284. }
  285. //==============================================================================
  286. ApplicationCommandTarget* MainHostWindow::getNextCommandTarget()
  287. {
  288. return findFirstTargetParentComponent();
  289. }
  290. void MainHostWindow::getAllCommands (Array<CommandID>& commands)
  291. {
  292. // this returns the set of all commands that this target can perform..
  293. const CommandID ids[] = { CommandIDs::newFile,
  294. CommandIDs::open,
  295. CommandIDs::save,
  296. CommandIDs::saveAs,
  297. CommandIDs::showPluginListEditor,
  298. CommandIDs::showAudioSettings,
  299. CommandIDs::toggleDoublePrecision,
  300. CommandIDs::aboutBox,
  301. CommandIDs::allWindowsForward
  302. };
  303. commands.addArray (ids, numElementsInArray (ids));
  304. }
  305. void MainHostWindow::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
  306. {
  307. const String category ("General");
  308. switch (commandID)
  309. {
  310. case CommandIDs::newFile:
  311. result.setInfo ("New", "Creates a new filter graph file", category, 0);
  312. result.defaultKeypresses.add(KeyPress('n', ModifierKeys::commandModifier, 0));
  313. break;
  314. case CommandIDs::open:
  315. result.setInfo ("Open...", "Opens a filter graph file", category, 0);
  316. result.defaultKeypresses.add (KeyPress ('o', ModifierKeys::commandModifier, 0));
  317. break;
  318. case CommandIDs::save:
  319. result.setInfo ("Save", "Saves the current graph to a file", category, 0);
  320. result.defaultKeypresses.add (KeyPress ('s', ModifierKeys::commandModifier, 0));
  321. break;
  322. case CommandIDs::saveAs:
  323. result.setInfo ("Save As...",
  324. "Saves a copy of the current graph to a file",
  325. category, 0);
  326. result.defaultKeypresses.add (KeyPress ('s', ModifierKeys::shiftModifier | ModifierKeys::commandModifier, 0));
  327. break;
  328. case CommandIDs::showPluginListEditor:
  329. result.setInfo ("Edit the list of available plug-Ins...", String(), category, 0);
  330. result.addDefaultKeypress ('p', ModifierKeys::commandModifier);
  331. break;
  332. case CommandIDs::showAudioSettings:
  333. result.setInfo ("Change the audio device settings", String(), category, 0);
  334. result.addDefaultKeypress ('a', ModifierKeys::commandModifier);
  335. break;
  336. case CommandIDs::toggleDoublePrecision:
  337. updatePrecisionMenuItem (result);
  338. break;
  339. case CommandIDs::aboutBox:
  340. result.setInfo ("About...", String(), category, 0);
  341. break;
  342. case CommandIDs::allWindowsForward:
  343. result.setInfo ("All Windows Forward", "Bring all plug-in windows forward", category, 0);
  344. result.addDefaultKeypress ('w', ModifierKeys::commandModifier);
  345. break;
  346. default:
  347. break;
  348. }
  349. }
  350. bool MainHostWindow::perform (const InvocationInfo& info)
  351. {
  352. auto* graphEditor = getGraphEditor();
  353. switch (info.commandID)
  354. {
  355. case CommandIDs::newFile:
  356. if (graphEditor != nullptr && graphEditor->graph != nullptr && graphEditor->graph->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk)
  357. graphEditor->graph->newDocument();
  358. break;
  359. case CommandIDs::open:
  360. if (graphEditor != nullptr && graphEditor->graph != nullptr && graphEditor->graph->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk)
  361. graphEditor->graph->loadFromUserSpecifiedFile (true);
  362. break;
  363. case CommandIDs::save:
  364. if (graphEditor != nullptr && graphEditor->graph != nullptr)
  365. graphEditor->graph->save (true, true);
  366. break;
  367. case CommandIDs::saveAs:
  368. if (graphEditor != nullptr && graphEditor->graph != nullptr)
  369. graphEditor->graph->saveAs (File(), true, true, true);
  370. break;
  371. case CommandIDs::showPluginListEditor:
  372. if (pluginListWindow == nullptr)
  373. pluginListWindow = new PluginListWindow (*this, formatManager);
  374. pluginListWindow->toFront (true);
  375. break;
  376. case CommandIDs::showAudioSettings:
  377. showAudioSettings();
  378. break;
  379. case CommandIDs::toggleDoublePrecision:
  380. if (auto* props = getAppProperties().getUserSettings())
  381. {
  382. bool newIsDoublePrecision = ! isDoublePrecisionProcessing();
  383. props->setValue ("doublePrecisionProcessing", var (newIsDoublePrecision));
  384. {
  385. ApplicationCommandInfo cmdInfo (info.commandID);
  386. updatePrecisionMenuItem (cmdInfo);
  387. menuItemsChanged();
  388. }
  389. if (graphEditor != nullptr)
  390. graphEditor->setDoublePrecision (newIsDoublePrecision);
  391. }
  392. break;
  393. case CommandIDs::aboutBox:
  394. // TODO
  395. break;
  396. case CommandIDs::allWindowsForward:
  397. {
  398. auto& desktop = Desktop::getInstance();
  399. for (int i = 0; i < desktop.getNumComponents(); ++i)
  400. desktop.getComponent (i)->toBehind (this);
  401. break;
  402. }
  403. default:
  404. return false;
  405. }
  406. return true;
  407. }
  408. void MainHostWindow::showAudioSettings()
  409. {
  410. AudioDeviceSelectorComponent audioSettingsComp (deviceManager,
  411. 0, 256,
  412. 0, 256,
  413. true, true, true, false);
  414. audioSettingsComp.setSize (500, 450);
  415. DialogWindow::LaunchOptions o;
  416. o.content.setNonOwned (&audioSettingsComp);
  417. o.dialogTitle = "Audio Settings";
  418. o.componentToCentreAround = this;
  419. o.dialogBackgroundColour = getLookAndFeel().findColour (ResizableWindow::backgroundColourId);
  420. o.escapeKeyTriggersCloseButton = true;
  421. o.useNativeTitleBar = false;
  422. o.resizable = false;
  423. o.runModal();
  424. ScopedPointer<XmlElement> audioState (deviceManager.createStateXml());
  425. getAppProperties().getUserSettings()->setValue ("audioDeviceState", audioState);
  426. getAppProperties().getUserSettings()->saveIfNeeded();
  427. if (auto* graphEditor = getGraphEditor())
  428. if (graphEditor->graph != nullptr)
  429. graphEditor->graph->removeIllegalConnections();
  430. }
  431. bool MainHostWindow::isInterestedInFileDrag (const StringArray&)
  432. {
  433. return true;
  434. }
  435. void MainHostWindow::fileDragEnter (const StringArray&, int, int)
  436. {
  437. }
  438. void MainHostWindow::fileDragMove (const StringArray&, int, int)
  439. {
  440. }
  441. void MainHostWindow::fileDragExit (const StringArray&)
  442. {
  443. }
  444. void MainHostWindow::filesDropped (const StringArray& files, int x, int y)
  445. {
  446. if (auto* graphEditor = getGraphEditor())
  447. {
  448. if (files.size() == 1 && File (files[0]).hasFileExtension (filenameSuffix))
  449. {
  450. if (auto* filterGraph = graphEditor->graph.get())
  451. if (filterGraph->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk)
  452. filterGraph->loadFrom (File (files[0]), true);
  453. }
  454. else
  455. {
  456. OwnedArray<PluginDescription> typesFound;
  457. knownPluginList.scanAndAddDragAndDroppedFiles (formatManager, files, typesFound);
  458. auto pos = graphEditor->getLocalPoint (this, Point<int> (x, y));
  459. for (int i = 0; i < jmin (5, typesFound.size()); ++i)
  460. if (auto* desc = typesFound.getUnchecked(i))
  461. createPlugin (*desc, pos);
  462. }
  463. }
  464. }
  465. GraphDocumentComponent* MainHostWindow::getGraphEditor() const
  466. {
  467. return dynamic_cast<GraphDocumentComponent*> (getContentComponent());
  468. }
  469. bool MainHostWindow::isDoublePrecisionProcessing()
  470. {
  471. if (auto* props = getAppProperties().getUserSettings())
  472. return props->getBoolValue ("doublePrecisionProcessing", false);
  473. return false;
  474. }
  475. void MainHostWindow::updatePrecisionMenuItem (ApplicationCommandInfo& info)
  476. {
  477. info.setInfo ("Double floating point precision rendering", String(), "General", 0);
  478. info.setTicked (isDoublePrecisionProcessing());
  479. }