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.

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