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.

947 lines
32KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - Raw Material Software Limited
  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 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #include <JuceHeader.h>
  19. #include "MainHostWindow.h"
  20. #include "../Plugins/InternalPlugins.h"
  21. constexpr const char* scanModeKey = "pluginScanMode";
  22. class CustomPluginScanner : public KnownPluginList::CustomScanner,
  23. private ChangeListener
  24. {
  25. public:
  26. CustomPluginScanner()
  27. {
  28. if (auto* file = getAppProperties().getUserSettings())
  29. file->addChangeListener (this);
  30. changeListenerCallback (nullptr);
  31. }
  32. ~CustomPluginScanner() override
  33. {
  34. if (auto* file = getAppProperties().getUserSettings())
  35. file->removeChangeListener (this);
  36. }
  37. bool findPluginTypesFor (AudioPluginFormat& format,
  38. OwnedArray<PluginDescription>& result,
  39. const String& fileOrIdentifier) override
  40. {
  41. if (scanInProcess)
  42. {
  43. superprocess = nullptr;
  44. format.findAllTypesForFile (result, fileOrIdentifier);
  45. return true;
  46. }
  47. if (superprocess == nullptr)
  48. {
  49. superprocess = std::make_unique<Superprocess> (*this);
  50. std::unique_lock<std::mutex> lock (mutex);
  51. connectionLost = false;
  52. }
  53. MemoryBlock block;
  54. MemoryOutputStream stream { block, true };
  55. stream.writeString (format.getName());
  56. stream.writeString (fileOrIdentifier);
  57. if (superprocess->sendMessageToWorker (block))
  58. {
  59. std::unique_lock<std::mutex> lock (mutex);
  60. gotResponse = false;
  61. pluginDescription = nullptr;
  62. for (;;)
  63. {
  64. if (condvar.wait_for (lock,
  65. std::chrono::milliseconds (50),
  66. [this] { return gotResponse || shouldExit(); }))
  67. {
  68. break;
  69. }
  70. }
  71. if (shouldExit())
  72. {
  73. superprocess = nullptr;
  74. return true;
  75. }
  76. if (connectionLost)
  77. {
  78. superprocess = nullptr;
  79. return false;
  80. }
  81. if (pluginDescription != nullptr)
  82. {
  83. for (const auto* item : pluginDescription->getChildIterator())
  84. {
  85. auto desc = std::make_unique<PluginDescription>();
  86. if (desc->loadFromXml (*item))
  87. result.add (std::move (desc));
  88. }
  89. }
  90. return true;
  91. }
  92. superprocess = nullptr;
  93. return false;
  94. }
  95. void scanFinished() override
  96. {
  97. superprocess = nullptr;
  98. }
  99. private:
  100. class Superprocess : private ChildProcessCoordinator
  101. {
  102. public:
  103. explicit Superprocess (CustomPluginScanner& o)
  104. : owner (o)
  105. {
  106. launchWorkerProcess (File::getSpecialLocation (File::currentExecutableFile), processUID, 0, 0);
  107. }
  108. using ChildProcessCoordinator::sendMessageToWorker;
  109. private:
  110. void handleMessageFromWorker (const MemoryBlock& mb) override
  111. {
  112. auto xml = parseXML (mb.toString());
  113. const std::lock_guard<std::mutex> lock (owner.mutex);
  114. owner.pluginDescription = std::move (xml);
  115. owner.gotResponse = true;
  116. owner.condvar.notify_one();
  117. }
  118. void handleConnectionLost() override
  119. {
  120. const std::lock_guard<std::mutex> lock (owner.mutex);
  121. owner.pluginDescription = nullptr;
  122. owner.gotResponse = true;
  123. owner.connectionLost = true;
  124. owner.condvar.notify_one();
  125. }
  126. CustomPluginScanner& owner;
  127. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Superprocess)
  128. };
  129. void changeListenerCallback (ChangeBroadcaster*) override
  130. {
  131. if (auto* file = getAppProperties().getUserSettings())
  132. scanInProcess = (file->getIntValue (scanModeKey) == 0);
  133. }
  134. std::unique_ptr<Superprocess> superprocess;
  135. std::mutex mutex;
  136. std::condition_variable condvar;
  137. std::unique_ptr<XmlElement> pluginDescription;
  138. bool gotResponse = false;
  139. bool connectionLost = false;
  140. std::atomic<bool> scanInProcess { true };
  141. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomPluginScanner)
  142. };
  143. //==============================================================================
  144. class CustomPluginListComponent : public PluginListComponent
  145. {
  146. public:
  147. CustomPluginListComponent (AudioPluginFormatManager& manager,
  148. KnownPluginList& listToRepresent,
  149. const File& pedal,
  150. PropertiesFile* props,
  151. bool async)
  152. : PluginListComponent (manager, listToRepresent, pedal, props, async)
  153. {
  154. addAndMakeVisible (validationModeLabel);
  155. addAndMakeVisible (validationModeBox);
  156. validationModeLabel.attachToComponent (&validationModeBox, true);
  157. validationModeLabel.setJustificationType (Justification::right);
  158. validationModeLabel.setSize (100, 30);
  159. auto unusedId = 1;
  160. for (const auto mode : { "In-process", "Out-of-process" })
  161. validationModeBox.addItem (mode, unusedId++);
  162. validationModeBox.setSelectedItemIndex (getAppProperties().getUserSettings()->getIntValue (scanModeKey));
  163. validationModeBox.onChange = [this]
  164. {
  165. getAppProperties().getUserSettings()->setValue (scanModeKey, validationModeBox.getSelectedItemIndex());
  166. };
  167. resized();
  168. }
  169. void resized() override
  170. {
  171. PluginListComponent::resized();
  172. const auto& buttonBounds = getOptionsButton().getBounds();
  173. validationModeBox.setBounds (buttonBounds.withWidth (130).withRightX (getWidth() - buttonBounds.getX()));
  174. }
  175. private:
  176. Label validationModeLabel { {}, "Scan mode" };
  177. ComboBox validationModeBox;
  178. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomPluginListComponent)
  179. };
  180. //==============================================================================
  181. class MainHostWindow::PluginListWindow : public DocumentWindow
  182. {
  183. public:
  184. PluginListWindow (MainHostWindow& mw, AudioPluginFormatManager& pluginFormatManager)
  185. : DocumentWindow ("Available Plugins",
  186. LookAndFeel::getDefaultLookAndFeel().findColour (ResizableWindow::backgroundColourId),
  187. DocumentWindow::minimiseButton | DocumentWindow::closeButton),
  188. owner (mw)
  189. {
  190. auto deadMansPedalFile = getAppProperties().getUserSettings()
  191. ->getFile().getSiblingFile ("RecentlyCrashedPluginsList");
  192. setContentOwned (new CustomPluginListComponent (pluginFormatManager,
  193. owner.knownPluginList,
  194. deadMansPedalFile,
  195. getAppProperties().getUserSettings(),
  196. true), true);
  197. setResizable (true, false);
  198. setResizeLimits (300, 400, 800, 1500);
  199. setTopLeftPosition (60, 60);
  200. restoreWindowStateFromString (getAppProperties().getUserSettings()->getValue ("listWindowPos"));
  201. setVisible (true);
  202. }
  203. ~PluginListWindow() override
  204. {
  205. getAppProperties().getUserSettings()->setValue ("listWindowPos", getWindowStateAsString());
  206. clearContentComponent();
  207. }
  208. void closeButtonPressed() override
  209. {
  210. owner.pluginListWindow = nullptr;
  211. }
  212. private:
  213. MainHostWindow& owner;
  214. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginListWindow)
  215. };
  216. //==============================================================================
  217. MainHostWindow::MainHostWindow()
  218. : DocumentWindow (JUCEApplication::getInstance()->getApplicationName(),
  219. LookAndFeel::getDefaultLookAndFeel().findColour (ResizableWindow::backgroundColourId),
  220. DocumentWindow::allButtons)
  221. {
  222. formatManager.addDefaultFormats();
  223. formatManager.addFormat (new InternalPluginFormat());
  224. auto safeThis = SafePointer<MainHostWindow> (this);
  225. RuntimePermissions::request (RuntimePermissions::recordAudio,
  226. [safeThis] (bool granted) mutable
  227. {
  228. auto savedState = getAppProperties().getUserSettings()->getXmlValue ("audioDeviceState");
  229. safeThis->deviceManager.initialise (granted ? 256 : 0, 256, savedState.get(), true);
  230. });
  231. #if JUCE_IOS || JUCE_ANDROID
  232. setFullScreen (true);
  233. #else
  234. setResizable (true, false);
  235. setResizeLimits (500, 400, 10000, 10000);
  236. centreWithSize (800, 600);
  237. #endif
  238. knownPluginList.setCustomScanner (std::make_unique<CustomPluginScanner>());
  239. graphHolder.reset (new GraphDocumentComponent (formatManager, deviceManager, knownPluginList));
  240. setContentNonOwned (graphHolder.get(), false);
  241. setUsingNativeTitleBar (true);
  242. restoreWindowStateFromString (getAppProperties().getUserSettings()->getValue ("mainWindowPos"));
  243. setVisible (true);
  244. InternalPluginFormat internalFormat;
  245. internalTypes = internalFormat.getAllTypes();
  246. if (auto savedPluginList = getAppProperties().getUserSettings()->getXmlValue ("pluginList"))
  247. knownPluginList.recreateFromXml (*savedPluginList);
  248. for (auto& t : internalTypes)
  249. knownPluginList.addType (t);
  250. pluginSortMethod = (KnownPluginList::SortMethod) getAppProperties().getUserSettings()
  251. ->getIntValue ("pluginSortMethod", KnownPluginList::sortByManufacturer);
  252. knownPluginList.addChangeListener (this);
  253. if (auto* g = graphHolder->graph.get())
  254. g->addChangeListener (this);
  255. addKeyListener (getCommandManager().getKeyMappings());
  256. Process::setPriority (Process::HighPriority);
  257. #if JUCE_IOS || JUCE_ANDROID
  258. graphHolder->burgerMenu.setModel (this);
  259. #else
  260. #if JUCE_MAC
  261. setMacMainMenu (this);
  262. #else
  263. setMenuBar (this);
  264. #endif
  265. #endif
  266. getCommandManager().setFirstCommandTarget (this);
  267. }
  268. MainHostWindow::~MainHostWindow()
  269. {
  270. pluginListWindow = nullptr;
  271. knownPluginList.removeChangeListener (this);
  272. if (auto* g = graphHolder->graph.get())
  273. g->removeChangeListener (this);
  274. getAppProperties().getUserSettings()->setValue ("mainWindowPos", getWindowStateAsString());
  275. clearContentComponent();
  276. #if ! (JUCE_ANDROID || JUCE_IOS)
  277. #if JUCE_MAC
  278. setMacMainMenu (nullptr);
  279. #else
  280. setMenuBar (nullptr);
  281. #endif
  282. #endif
  283. graphHolder = nullptr;
  284. }
  285. void MainHostWindow::closeButtonPressed()
  286. {
  287. tryToQuitApplication();
  288. }
  289. struct AsyncQuitRetrier : private Timer
  290. {
  291. AsyncQuitRetrier() { startTimer (500); }
  292. void timerCallback() override
  293. {
  294. stopTimer();
  295. delete this;
  296. if (auto app = JUCEApplicationBase::getInstance())
  297. app->systemRequestedQuit();
  298. }
  299. };
  300. void MainHostWindow::tryToQuitApplication()
  301. {
  302. if (graphHolder->closeAnyOpenPluginWindows())
  303. {
  304. // Really important thing to note here: if the last call just deleted any plugin windows,
  305. // we won't exit immediately - instead we'll use our AsyncQuitRetrier to let the message
  306. // loop run for another brief moment, then try again. This will give any plugins a chance
  307. // to flush any GUI events that may have been in transit before the app forces them to
  308. // be unloaded
  309. new AsyncQuitRetrier();
  310. return;
  311. }
  312. if (ModalComponentManager::getInstance()->cancelAllModalComponents())
  313. {
  314. new AsyncQuitRetrier();
  315. return;
  316. }
  317. if (graphHolder != nullptr)
  318. {
  319. auto releaseAndQuit = [this]
  320. {
  321. // Some plug-ins do not want [NSApp stop] to be called
  322. // before the plug-ins are not deallocated.
  323. graphHolder->releaseGraph();
  324. JUCEApplication::quit();
  325. };
  326. #if JUCE_ANDROID || JUCE_IOS
  327. if (graphHolder->graph->saveDocument (PluginGraph::getDefaultGraphDocumentOnMobile()))
  328. releaseAndQuit();
  329. #else
  330. SafePointer<MainHostWindow> parent { this };
  331. graphHolder->graph->saveIfNeededAndUserAgreesAsync ([parent, releaseAndQuit] (FileBasedDocument::SaveResult r)
  332. {
  333. if (parent == nullptr)
  334. return;
  335. if (r == FileBasedDocument::savedOk)
  336. releaseAndQuit();
  337. });
  338. #endif
  339. return;
  340. }
  341. JUCEApplication::quit();
  342. }
  343. void MainHostWindow::changeListenerCallback (ChangeBroadcaster* changed)
  344. {
  345. if (changed == &knownPluginList)
  346. {
  347. menuItemsChanged();
  348. // save the plugin list every time it gets changed, so that if we're scanning
  349. // and it crashes, we've still saved the previous ones
  350. if (auto savedPluginList = std::unique_ptr<XmlElement> (knownPluginList.createXml()))
  351. {
  352. getAppProperties().getUserSettings()->setValue ("pluginList", savedPluginList.get());
  353. getAppProperties().saveIfNeeded();
  354. }
  355. }
  356. else if (graphHolder != nullptr && changed == graphHolder->graph.get())
  357. {
  358. auto title = JUCEApplication::getInstance()->getApplicationName();
  359. auto f = graphHolder->graph->getFile();
  360. if (f.existsAsFile())
  361. title = f.getFileName() + " - " + title;
  362. setName (title);
  363. }
  364. }
  365. StringArray MainHostWindow::getMenuBarNames()
  366. {
  367. StringArray names;
  368. names.add ("File");
  369. names.add ("Plugins");
  370. names.add ("Options");
  371. names.add ("Windows");
  372. return names;
  373. }
  374. PopupMenu MainHostWindow::getMenuForIndex (int topLevelMenuIndex, const String& /*menuName*/)
  375. {
  376. PopupMenu menu;
  377. if (topLevelMenuIndex == 0)
  378. {
  379. // "File" menu
  380. #if ! (JUCE_IOS || JUCE_ANDROID)
  381. menu.addCommandItem (&getCommandManager(), CommandIDs::newFile);
  382. menu.addCommandItem (&getCommandManager(), CommandIDs::open);
  383. #endif
  384. RecentlyOpenedFilesList recentFiles;
  385. recentFiles.restoreFromString (getAppProperties().getUserSettings()
  386. ->getValue ("recentFilterGraphFiles"));
  387. PopupMenu recentFilesMenu;
  388. recentFiles.createPopupMenuItems (recentFilesMenu, 100, true, true);
  389. menu.addSubMenu ("Open recent file", recentFilesMenu);
  390. #if ! (JUCE_IOS || JUCE_ANDROID)
  391. menu.addCommandItem (&getCommandManager(), CommandIDs::save);
  392. menu.addCommandItem (&getCommandManager(), CommandIDs::saveAs);
  393. #endif
  394. menu.addSeparator();
  395. menu.addCommandItem (&getCommandManager(), StandardApplicationCommandIDs::quit);
  396. }
  397. else if (topLevelMenuIndex == 1)
  398. {
  399. // "Plugins" menu
  400. PopupMenu pluginsMenu;
  401. addPluginsToMenu (pluginsMenu);
  402. menu.addSubMenu ("Create Plug-in", pluginsMenu);
  403. menu.addSeparator();
  404. menu.addItem (250, "Delete All Plug-ins");
  405. }
  406. else if (topLevelMenuIndex == 2)
  407. {
  408. // "Options" menu
  409. menu.addCommandItem (&getCommandManager(), CommandIDs::showPluginListEditor);
  410. PopupMenu sortTypeMenu;
  411. sortTypeMenu.addItem (200, "List Plug-ins in Default Order", true, pluginSortMethod == KnownPluginList::defaultOrder);
  412. sortTypeMenu.addItem (201, "List Plug-ins in Alphabetical Order", true, pluginSortMethod == KnownPluginList::sortAlphabetically);
  413. sortTypeMenu.addItem (202, "List Plug-ins by Category", true, pluginSortMethod == KnownPluginList::sortByCategory);
  414. sortTypeMenu.addItem (203, "List Plug-ins by Manufacturer", true, pluginSortMethod == KnownPluginList::sortByManufacturer);
  415. sortTypeMenu.addItem (204, "List Plug-ins Based on the Directory Structure", true, pluginSortMethod == KnownPluginList::sortByFileSystemLocation);
  416. menu.addSubMenu ("Plug-in Menu Type", sortTypeMenu);
  417. menu.addSeparator();
  418. menu.addCommandItem (&getCommandManager(), CommandIDs::showAudioSettings);
  419. menu.addCommandItem (&getCommandManager(), CommandIDs::toggleDoublePrecision);
  420. if (autoScaleOptionAvailable)
  421. menu.addCommandItem (&getCommandManager(), CommandIDs::autoScalePluginWindows);
  422. menu.addSeparator();
  423. menu.addCommandItem (&getCommandManager(), CommandIDs::aboutBox);
  424. }
  425. else if (topLevelMenuIndex == 3)
  426. {
  427. menu.addCommandItem (&getCommandManager(), CommandIDs::allWindowsForward);
  428. }
  429. return menu;
  430. }
  431. void MainHostWindow::menuItemSelected (int menuItemID, int /*topLevelMenuIndex*/)
  432. {
  433. if (menuItemID == 250)
  434. {
  435. if (graphHolder != nullptr)
  436. if (auto* graph = graphHolder->graph.get())
  437. graph->clear();
  438. }
  439. #if ! (JUCE_ANDROID || JUCE_IOS)
  440. else if (menuItemID >= 100 && menuItemID < 200)
  441. {
  442. RecentlyOpenedFilesList recentFiles;
  443. recentFiles.restoreFromString (getAppProperties().getUserSettings()
  444. ->getValue ("recentFilterGraphFiles"));
  445. if (graphHolder != nullptr)
  446. {
  447. if (auto* graph = graphHolder->graph.get())
  448. {
  449. SafePointer<MainHostWindow> parent { this };
  450. graph->saveIfNeededAndUserAgreesAsync ([parent, recentFiles, menuItemID] (FileBasedDocument::SaveResult r)
  451. {
  452. if (parent == nullptr)
  453. return;
  454. if (r == FileBasedDocument::savedOk)
  455. parent->graphHolder->graph->loadFrom (recentFiles.getFile (menuItemID - 100), true);
  456. });
  457. }
  458. }
  459. }
  460. #endif
  461. else if (menuItemID >= 200 && menuItemID < 210)
  462. {
  463. if (menuItemID == 200) pluginSortMethod = KnownPluginList::defaultOrder;
  464. else if (menuItemID == 201) pluginSortMethod = KnownPluginList::sortAlphabetically;
  465. else if (menuItemID == 202) pluginSortMethod = KnownPluginList::sortByCategory;
  466. else if (menuItemID == 203) pluginSortMethod = KnownPluginList::sortByManufacturer;
  467. else if (menuItemID == 204) pluginSortMethod = KnownPluginList::sortByFileSystemLocation;
  468. getAppProperties().getUserSettings()->setValue ("pluginSortMethod", (int) pluginSortMethod);
  469. menuItemsChanged();
  470. }
  471. else
  472. {
  473. if (KnownPluginList::getIndexChosenByMenu (pluginDescriptions, menuItemID) >= 0)
  474. createPlugin (getChosenType (menuItemID), { proportionOfWidth (0.3f + Random::getSystemRandom().nextFloat() * 0.6f),
  475. proportionOfHeight (0.3f + Random::getSystemRandom().nextFloat() * 0.6f) });
  476. }
  477. }
  478. void MainHostWindow::menuBarActivated (bool isActivated)
  479. {
  480. if (isActivated && graphHolder != nullptr)
  481. graphHolder->unfocusKeyboardComponent();
  482. }
  483. void MainHostWindow::createPlugin (const PluginDescription& desc, Point<int> pos)
  484. {
  485. if (graphHolder != nullptr)
  486. graphHolder->createNewPlugin (desc, pos);
  487. }
  488. void MainHostWindow::addPluginsToMenu (PopupMenu& m)
  489. {
  490. if (graphHolder != nullptr)
  491. {
  492. int i = 0;
  493. for (auto& t : internalTypes)
  494. m.addItem (++i, t.name + " (" + t.pluginFormatName + ")");
  495. }
  496. m.addSeparator();
  497. pluginDescriptions = knownPluginList.getTypes();
  498. // This avoids showing the internal types again later on in the list
  499. pluginDescriptions.removeIf ([] (PluginDescription& desc)
  500. {
  501. return desc.pluginFormatName == InternalPluginFormat::getIdentifier();
  502. });
  503. KnownPluginList::addToMenu (m, pluginDescriptions, pluginSortMethod);
  504. }
  505. PluginDescription MainHostWindow::getChosenType (const int menuID) const
  506. {
  507. if (menuID >= 1 && menuID < (int) (1 + internalTypes.size()))
  508. return internalTypes[(size_t) (menuID - 1)];
  509. return pluginDescriptions[KnownPluginList::getIndexChosenByMenu (pluginDescriptions, menuID)];
  510. }
  511. //==============================================================================
  512. ApplicationCommandTarget* MainHostWindow::getNextCommandTarget()
  513. {
  514. return findFirstTargetParentComponent();
  515. }
  516. void MainHostWindow::getAllCommands (Array<CommandID>& commands)
  517. {
  518. // this returns the set of all commands that this target can perform..
  519. const CommandID ids[] = {
  520. #if ! (JUCE_IOS || JUCE_ANDROID)
  521. CommandIDs::newFile,
  522. CommandIDs::open,
  523. CommandIDs::save,
  524. CommandIDs::saveAs,
  525. #endif
  526. CommandIDs::showPluginListEditor,
  527. CommandIDs::showAudioSettings,
  528. CommandIDs::toggleDoublePrecision,
  529. CommandIDs::aboutBox,
  530. CommandIDs::allWindowsForward,
  531. CommandIDs::autoScalePluginWindows
  532. };
  533. commands.addArray (ids, numElementsInArray (ids));
  534. }
  535. void MainHostWindow::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
  536. {
  537. const String category ("General");
  538. switch (commandID)
  539. {
  540. #if ! (JUCE_IOS || JUCE_ANDROID)
  541. case CommandIDs::newFile:
  542. result.setInfo ("New", "Creates a new filter graph file", category, 0);
  543. result.defaultKeypresses.add(KeyPress('n', ModifierKeys::commandModifier, 0));
  544. break;
  545. case CommandIDs::open:
  546. result.setInfo ("Open...", "Opens a filter graph file", category, 0);
  547. result.defaultKeypresses.add (KeyPress ('o', ModifierKeys::commandModifier, 0));
  548. break;
  549. case CommandIDs::save:
  550. result.setInfo ("Save", "Saves the current graph to a file", category, 0);
  551. result.defaultKeypresses.add (KeyPress ('s', ModifierKeys::commandModifier, 0));
  552. break;
  553. case CommandIDs::saveAs:
  554. result.setInfo ("Save As...",
  555. "Saves a copy of the current graph to a file",
  556. category, 0);
  557. result.defaultKeypresses.add (KeyPress ('s', ModifierKeys::shiftModifier | ModifierKeys::commandModifier, 0));
  558. break;
  559. #endif
  560. case CommandIDs::showPluginListEditor:
  561. result.setInfo ("Edit the List of Available Plug-ins...", {}, category, 0);
  562. result.addDefaultKeypress ('p', ModifierKeys::commandModifier);
  563. break;
  564. case CommandIDs::showAudioSettings:
  565. result.setInfo ("Change the Audio Device Settings", {}, category, 0);
  566. result.addDefaultKeypress ('a', ModifierKeys::commandModifier);
  567. break;
  568. case CommandIDs::toggleDoublePrecision:
  569. updatePrecisionMenuItem (result);
  570. break;
  571. case CommandIDs::aboutBox:
  572. result.setInfo ("About...", {}, category, 0);
  573. break;
  574. case CommandIDs::allWindowsForward:
  575. result.setInfo ("All Windows Forward", "Bring all plug-in windows forward", category, 0);
  576. result.addDefaultKeypress ('w', ModifierKeys::commandModifier);
  577. break;
  578. case CommandIDs::autoScalePluginWindows:
  579. updateAutoScaleMenuItem (result);
  580. break;
  581. default:
  582. break;
  583. }
  584. }
  585. bool MainHostWindow::perform (const InvocationInfo& info)
  586. {
  587. switch (info.commandID)
  588. {
  589. #if ! (JUCE_IOS || JUCE_ANDROID)
  590. case CommandIDs::newFile:
  591. if (graphHolder != nullptr && graphHolder->graph != nullptr)
  592. {
  593. SafePointer<MainHostWindow> parent { this };
  594. graphHolder->graph->saveIfNeededAndUserAgreesAsync ([parent] (FileBasedDocument::SaveResult r)
  595. {
  596. if (parent == nullptr)
  597. return;
  598. if (r == FileBasedDocument::savedOk)
  599. parent->graphHolder->graph->newDocument();
  600. });
  601. }
  602. break;
  603. case CommandIDs::open:
  604. if (graphHolder != nullptr && graphHolder->graph != nullptr)
  605. {
  606. SafePointer<MainHostWindow> parent { this };
  607. graphHolder->graph->saveIfNeededAndUserAgreesAsync ([parent] (FileBasedDocument::SaveResult r)
  608. {
  609. if (parent == nullptr)
  610. return;
  611. if (r == FileBasedDocument::savedOk)
  612. parent->graphHolder->graph->loadFromUserSpecifiedFileAsync (true, [] (Result) {});
  613. });
  614. }
  615. break;
  616. case CommandIDs::save:
  617. if (graphHolder != nullptr && graphHolder->graph != nullptr)
  618. graphHolder->graph->saveAsync (true, true, nullptr);
  619. break;
  620. case CommandIDs::saveAs:
  621. if (graphHolder != nullptr && graphHolder->graph != nullptr)
  622. graphHolder->graph->saveAsAsync ({}, true, true, true, nullptr);
  623. break;
  624. #endif
  625. case CommandIDs::showPluginListEditor:
  626. if (pluginListWindow == nullptr)
  627. pluginListWindow.reset (new PluginListWindow (*this, formatManager));
  628. pluginListWindow->toFront (true);
  629. break;
  630. case CommandIDs::showAudioSettings:
  631. showAudioSettings();
  632. break;
  633. case CommandIDs::toggleDoublePrecision:
  634. if (auto* props = getAppProperties().getUserSettings())
  635. {
  636. auto newIsDoublePrecision = ! isDoublePrecisionProcessingEnabled();
  637. props->setValue ("doublePrecisionProcessing", var (newIsDoublePrecision));
  638. ApplicationCommandInfo cmdInfo (info.commandID);
  639. updatePrecisionMenuItem (cmdInfo);
  640. menuItemsChanged();
  641. if (graphHolder != nullptr)
  642. graphHolder->setDoublePrecision (newIsDoublePrecision);
  643. }
  644. break;
  645. case CommandIDs::autoScalePluginWindows:
  646. if (auto* props = getAppProperties().getUserSettings())
  647. {
  648. auto newAutoScale = ! isAutoScalePluginWindowsEnabled();
  649. props->setValue ("autoScalePluginWindows", var (newAutoScale));
  650. ApplicationCommandInfo cmdInfo (info.commandID);
  651. updateAutoScaleMenuItem (cmdInfo);
  652. menuItemsChanged();
  653. }
  654. break;
  655. case CommandIDs::aboutBox:
  656. // TODO
  657. break;
  658. case CommandIDs::allWindowsForward:
  659. {
  660. auto& desktop = Desktop::getInstance();
  661. for (int i = 0; i < desktop.getNumComponents(); ++i)
  662. desktop.getComponent (i)->toBehind (this);
  663. break;
  664. }
  665. default:
  666. return false;
  667. }
  668. return true;
  669. }
  670. void MainHostWindow::showAudioSettings()
  671. {
  672. auto* audioSettingsComp = new AudioDeviceSelectorComponent (deviceManager,
  673. 0, 256,
  674. 0, 256,
  675. true, true,
  676. true, false);
  677. audioSettingsComp->setSize (500, 450);
  678. DialogWindow::LaunchOptions o;
  679. o.content.setOwned (audioSettingsComp);
  680. o.dialogTitle = "Audio Settings";
  681. o.componentToCentreAround = this;
  682. o.dialogBackgroundColour = getLookAndFeel().findColour (ResizableWindow::backgroundColourId);
  683. o.escapeKeyTriggersCloseButton = true;
  684. o.useNativeTitleBar = false;
  685. o.resizable = false;
  686. auto* w = o.create();
  687. auto safeThis = SafePointer<MainHostWindow> (this);
  688. w->enterModalState (true,
  689. ModalCallbackFunction::create
  690. ([safeThis] (int)
  691. {
  692. auto audioState = safeThis->deviceManager.createStateXml();
  693. getAppProperties().getUserSettings()->setValue ("audioDeviceState", audioState.get());
  694. getAppProperties().getUserSettings()->saveIfNeeded();
  695. if (safeThis->graphHolder != nullptr)
  696. if (safeThis->graphHolder->graph != nullptr)
  697. safeThis->graphHolder->graph->graph.removeIllegalConnections();
  698. }), true);
  699. }
  700. bool MainHostWindow::isInterestedInFileDrag (const StringArray&)
  701. {
  702. return true;
  703. }
  704. void MainHostWindow::fileDragEnter (const StringArray&, int, int)
  705. {
  706. }
  707. void MainHostWindow::fileDragMove (const StringArray&, int, int)
  708. {
  709. }
  710. void MainHostWindow::fileDragExit (const StringArray&)
  711. {
  712. }
  713. void MainHostWindow::filesDropped (const StringArray& files, int x, int y)
  714. {
  715. if (graphHolder != nullptr)
  716. {
  717. #if ! (JUCE_ANDROID || JUCE_IOS)
  718. File firstFile { files[0] };
  719. if (files.size() == 1 && firstFile.hasFileExtension (PluginGraph::getFilenameSuffix()))
  720. {
  721. if (auto* g = graphHolder->graph.get())
  722. {
  723. SafePointer<MainHostWindow> parent;
  724. g->saveIfNeededAndUserAgreesAsync ([parent, g, firstFile] (FileBasedDocument::SaveResult r)
  725. {
  726. if (parent == nullptr)
  727. return;
  728. if (r == FileBasedDocument::savedOk)
  729. g->loadFrom (firstFile, true);
  730. });
  731. }
  732. }
  733. else
  734. #endif
  735. {
  736. OwnedArray<PluginDescription> typesFound;
  737. knownPluginList.scanAndAddDragAndDroppedFiles (formatManager, files, typesFound);
  738. auto pos = graphHolder->getLocalPoint (this, Point<int> (x, y));
  739. for (int i = 0; i < jmin (5, typesFound.size()); ++i)
  740. if (auto* desc = typesFound.getUnchecked(i))
  741. createPlugin (*desc, pos);
  742. }
  743. }
  744. }
  745. bool MainHostWindow::isDoublePrecisionProcessingEnabled()
  746. {
  747. if (auto* props = getAppProperties().getUserSettings())
  748. return props->getBoolValue ("doublePrecisionProcessing", false);
  749. return false;
  750. }
  751. bool MainHostWindow::isAutoScalePluginWindowsEnabled()
  752. {
  753. if (auto* props = getAppProperties().getUserSettings())
  754. return props->getBoolValue ("autoScalePluginWindows", false);
  755. return false;
  756. }
  757. void MainHostWindow::updatePrecisionMenuItem (ApplicationCommandInfo& info)
  758. {
  759. info.setInfo ("Double Floating-Point Precision Rendering", {}, "General", 0);
  760. info.setTicked (isDoublePrecisionProcessingEnabled());
  761. }
  762. void MainHostWindow::updateAutoScaleMenuItem (ApplicationCommandInfo& info)
  763. {
  764. info.setInfo ("Auto-Scale Plug-in Windows", {}, "General", 0);
  765. info.setTicked (isAutoScalePluginWindowsEnabled());
  766. }