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.

583 lines
20KB

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