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.

940 lines
32KB

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