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.

564 lines
19KB

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