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.

650 lines
23KB

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