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.

1007 lines
34KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - 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 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-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 (getIndexChosenByMenu (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 PluginDescriptionAndPreference& desc, Point<int> pos)
  484. {
  485. if (graphHolder != nullptr)
  486. graphHolder->createNewPlugin (desc, pos);
  487. }
  488. static bool containsDuplicateNames (const Array<PluginDescription>& plugins, const String& name)
  489. {
  490. int matches = 0;
  491. for (auto& p : plugins)
  492. if (p.name == name && ++matches > 1)
  493. return true;
  494. return false;
  495. }
  496. static constexpr int menuIDBase = 0x324503f4;
  497. static void addToMenu (const KnownPluginList::PluginTree& tree,
  498. PopupMenu& m,
  499. const Array<PluginDescription>& allPlugins,
  500. Array<PluginDescriptionAndPreference>& addedPlugins)
  501. {
  502. for (auto* sub : tree.subFolders)
  503. {
  504. PopupMenu subMenu;
  505. addToMenu (*sub, subMenu, allPlugins, addedPlugins);
  506. m.addSubMenu (sub->folder, subMenu, true, nullptr, false, 0);
  507. }
  508. auto addPlugin = [&] (const auto& descriptionAndPreference, const auto& pluginName)
  509. {
  510. addedPlugins.add (descriptionAndPreference);
  511. const auto menuID = addedPlugins.size() - 1 + menuIDBase;
  512. m.addItem (menuID, pluginName, true, false);
  513. };
  514. for (auto& plugin : tree.plugins)
  515. {
  516. auto name = plugin.name;
  517. if (containsDuplicateNames (tree.plugins, name))
  518. name << " (" << plugin.pluginFormatName << ')';
  519. addPlugin (PluginDescriptionAndPreference { plugin, PluginDescriptionAndPreference::UseARA::no }, name);
  520. #if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS)
  521. if (plugin.hasARAExtension)
  522. {
  523. name << " (ARA)";
  524. addPlugin (PluginDescriptionAndPreference { plugin }, name);
  525. }
  526. #endif
  527. }
  528. }
  529. void MainHostWindow::addPluginsToMenu (PopupMenu& m)
  530. {
  531. if (graphHolder != nullptr)
  532. {
  533. int i = 0;
  534. for (auto& t : internalTypes)
  535. m.addItem (++i, t.name + " (" + t.pluginFormatName + ")");
  536. }
  537. m.addSeparator();
  538. auto pluginDescriptions = knownPluginList.getTypes();
  539. // This avoids showing the internal types again later on in the list
  540. pluginDescriptions.removeIf ([] (PluginDescription& desc)
  541. {
  542. return desc.pluginFormatName == InternalPluginFormat::getIdentifier();
  543. });
  544. auto tree = KnownPluginList::createTree (pluginDescriptions, pluginSortMethod);
  545. pluginDescriptionsAndPreference = {};
  546. addToMenu (*tree, m, pluginDescriptions, pluginDescriptionsAndPreference);
  547. }
  548. int MainHostWindow::getIndexChosenByMenu (int menuID) const
  549. {
  550. const auto i = menuID - menuIDBase;
  551. return isPositiveAndBelow (i, pluginDescriptionsAndPreference.size()) ? i : -1;
  552. }
  553. PluginDescriptionAndPreference MainHostWindow::getChosenType (const int menuID) const
  554. {
  555. if (menuID >= 1 && menuID < (int) (1 + internalTypes.size()))
  556. return PluginDescriptionAndPreference { internalTypes[(size_t) (menuID - 1)] };
  557. return pluginDescriptionsAndPreference[getIndexChosenByMenu (menuID)];
  558. }
  559. //==============================================================================
  560. ApplicationCommandTarget* MainHostWindow::getNextCommandTarget()
  561. {
  562. return findFirstTargetParentComponent();
  563. }
  564. void MainHostWindow::getAllCommands (Array<CommandID>& commands)
  565. {
  566. // this returns the set of all commands that this target can perform..
  567. const CommandID ids[] = {
  568. #if ! (JUCE_IOS || JUCE_ANDROID)
  569. CommandIDs::newFile,
  570. CommandIDs::open,
  571. CommandIDs::save,
  572. CommandIDs::saveAs,
  573. #endif
  574. CommandIDs::showPluginListEditor,
  575. CommandIDs::showAudioSettings,
  576. CommandIDs::toggleDoublePrecision,
  577. CommandIDs::aboutBox,
  578. CommandIDs::allWindowsForward,
  579. CommandIDs::autoScalePluginWindows
  580. };
  581. commands.addArray (ids, numElementsInArray (ids));
  582. }
  583. void MainHostWindow::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
  584. {
  585. const String category ("General");
  586. switch (commandID)
  587. {
  588. #if ! (JUCE_IOS || JUCE_ANDROID)
  589. case CommandIDs::newFile:
  590. result.setInfo ("New", "Creates a new filter graph file", category, 0);
  591. result.defaultKeypresses.add(KeyPress('n', ModifierKeys::commandModifier, 0));
  592. break;
  593. case CommandIDs::open:
  594. result.setInfo ("Open...", "Opens a filter graph file", category, 0);
  595. result.defaultKeypresses.add (KeyPress ('o', ModifierKeys::commandModifier, 0));
  596. break;
  597. case CommandIDs::save:
  598. result.setInfo ("Save", "Saves the current graph to a file", category, 0);
  599. result.defaultKeypresses.add (KeyPress ('s', ModifierKeys::commandModifier, 0));
  600. break;
  601. case CommandIDs::saveAs:
  602. result.setInfo ("Save As...",
  603. "Saves a copy of the current graph to a file",
  604. category, 0);
  605. result.defaultKeypresses.add (KeyPress ('s', ModifierKeys::shiftModifier | ModifierKeys::commandModifier, 0));
  606. break;
  607. #endif
  608. case CommandIDs::showPluginListEditor:
  609. result.setInfo ("Edit the List of Available Plug-ins...", {}, category, 0);
  610. result.addDefaultKeypress ('p', ModifierKeys::commandModifier);
  611. break;
  612. case CommandIDs::showAudioSettings:
  613. result.setInfo ("Change the Audio Device Settings", {}, category, 0);
  614. result.addDefaultKeypress ('a', ModifierKeys::commandModifier);
  615. break;
  616. case CommandIDs::toggleDoublePrecision:
  617. updatePrecisionMenuItem (result);
  618. break;
  619. case CommandIDs::aboutBox:
  620. result.setInfo ("About...", {}, category, 0);
  621. break;
  622. case CommandIDs::allWindowsForward:
  623. result.setInfo ("All Windows Forward", "Bring all plug-in windows forward", category, 0);
  624. result.addDefaultKeypress ('w', ModifierKeys::commandModifier);
  625. break;
  626. case CommandIDs::autoScalePluginWindows:
  627. updateAutoScaleMenuItem (result);
  628. break;
  629. default:
  630. break;
  631. }
  632. }
  633. bool MainHostWindow::perform (const InvocationInfo& info)
  634. {
  635. switch (info.commandID)
  636. {
  637. #if ! (JUCE_IOS || JUCE_ANDROID)
  638. case CommandIDs::newFile:
  639. if (graphHolder != nullptr && graphHolder->graph != nullptr)
  640. {
  641. SafePointer<MainHostWindow> parent { this };
  642. graphHolder->graph->saveIfNeededAndUserAgreesAsync ([parent] (FileBasedDocument::SaveResult r)
  643. {
  644. if (parent == nullptr)
  645. return;
  646. if (r == FileBasedDocument::savedOk)
  647. parent->graphHolder->graph->newDocument();
  648. });
  649. }
  650. break;
  651. case CommandIDs::open:
  652. if (graphHolder != nullptr && graphHolder->graph != nullptr)
  653. {
  654. SafePointer<MainHostWindow> parent { this };
  655. graphHolder->graph->saveIfNeededAndUserAgreesAsync ([parent] (FileBasedDocument::SaveResult r)
  656. {
  657. if (parent == nullptr)
  658. return;
  659. if (r == FileBasedDocument::savedOk)
  660. parent->graphHolder->graph->loadFromUserSpecifiedFileAsync (true, [] (Result) {});
  661. });
  662. }
  663. break;
  664. case CommandIDs::save:
  665. if (graphHolder != nullptr && graphHolder->graph != nullptr)
  666. graphHolder->graph->saveAsync (true, true, nullptr);
  667. break;
  668. case CommandIDs::saveAs:
  669. if (graphHolder != nullptr && graphHolder->graph != nullptr)
  670. graphHolder->graph->saveAsAsync ({}, true, true, true, nullptr);
  671. break;
  672. #endif
  673. case CommandIDs::showPluginListEditor:
  674. if (pluginListWindow == nullptr)
  675. pluginListWindow.reset (new PluginListWindow (*this, formatManager));
  676. pluginListWindow->toFront (true);
  677. break;
  678. case CommandIDs::showAudioSettings:
  679. showAudioSettings();
  680. break;
  681. case CommandIDs::toggleDoublePrecision:
  682. if (auto* props = getAppProperties().getUserSettings())
  683. {
  684. auto newIsDoublePrecision = ! isDoublePrecisionProcessingEnabled();
  685. props->setValue ("doublePrecisionProcessing", var (newIsDoublePrecision));
  686. ApplicationCommandInfo cmdInfo (info.commandID);
  687. updatePrecisionMenuItem (cmdInfo);
  688. menuItemsChanged();
  689. if (graphHolder != nullptr)
  690. graphHolder->setDoublePrecision (newIsDoublePrecision);
  691. }
  692. break;
  693. case CommandIDs::autoScalePluginWindows:
  694. if (auto* props = getAppProperties().getUserSettings())
  695. {
  696. auto newAutoScale = ! isAutoScalePluginWindowsEnabled();
  697. props->setValue ("autoScalePluginWindows", var (newAutoScale));
  698. ApplicationCommandInfo cmdInfo (info.commandID);
  699. updateAutoScaleMenuItem (cmdInfo);
  700. menuItemsChanged();
  701. }
  702. break;
  703. case CommandIDs::aboutBox:
  704. // TODO
  705. break;
  706. case CommandIDs::allWindowsForward:
  707. {
  708. auto& desktop = Desktop::getInstance();
  709. for (int i = 0; i < desktop.getNumComponents(); ++i)
  710. desktop.getComponent (i)->toBehind (this);
  711. break;
  712. }
  713. default:
  714. return false;
  715. }
  716. return true;
  717. }
  718. void MainHostWindow::showAudioSettings()
  719. {
  720. auto* audioSettingsComp = new AudioDeviceSelectorComponent (deviceManager,
  721. 0, 256,
  722. 0, 256,
  723. true, true,
  724. true, false);
  725. audioSettingsComp->setSize (500, 450);
  726. DialogWindow::LaunchOptions o;
  727. o.content.setOwned (audioSettingsComp);
  728. o.dialogTitle = "Audio Settings";
  729. o.componentToCentreAround = this;
  730. o.dialogBackgroundColour = getLookAndFeel().findColour (ResizableWindow::backgroundColourId);
  731. o.escapeKeyTriggersCloseButton = true;
  732. o.useNativeTitleBar = false;
  733. o.resizable = false;
  734. auto* w = o.create();
  735. auto safeThis = SafePointer<MainHostWindow> (this);
  736. w->enterModalState (true,
  737. ModalCallbackFunction::create
  738. ([safeThis] (int)
  739. {
  740. auto audioState = safeThis->deviceManager.createStateXml();
  741. getAppProperties().getUserSettings()->setValue ("audioDeviceState", audioState.get());
  742. getAppProperties().getUserSettings()->saveIfNeeded();
  743. if (safeThis->graphHolder != nullptr)
  744. if (safeThis->graphHolder->graph != nullptr)
  745. safeThis->graphHolder->graph->graph.removeIllegalConnections();
  746. }), true);
  747. }
  748. bool MainHostWindow::isInterestedInFileDrag (const StringArray&)
  749. {
  750. return true;
  751. }
  752. void MainHostWindow::fileDragEnter (const StringArray&, int, int)
  753. {
  754. }
  755. void MainHostWindow::fileDragMove (const StringArray&, int, int)
  756. {
  757. }
  758. void MainHostWindow::fileDragExit (const StringArray&)
  759. {
  760. }
  761. void MainHostWindow::filesDropped (const StringArray& files, int x, int y)
  762. {
  763. if (graphHolder != nullptr)
  764. {
  765. #if ! (JUCE_ANDROID || JUCE_IOS)
  766. File firstFile { files[0] };
  767. if (files.size() == 1 && firstFile.hasFileExtension (PluginGraph::getFilenameSuffix()))
  768. {
  769. if (auto* g = graphHolder->graph.get())
  770. {
  771. SafePointer<MainHostWindow> parent;
  772. g->saveIfNeededAndUserAgreesAsync ([parent, g, firstFile] (FileBasedDocument::SaveResult r)
  773. {
  774. if (parent == nullptr)
  775. return;
  776. if (r == FileBasedDocument::savedOk)
  777. g->loadFrom (firstFile, true);
  778. });
  779. }
  780. }
  781. else
  782. #endif
  783. {
  784. OwnedArray<PluginDescription> typesFound;
  785. knownPluginList.scanAndAddDragAndDroppedFiles (formatManager, files, typesFound);
  786. auto pos = graphHolder->getLocalPoint (this, Point<int> (x, y));
  787. for (int i = 0; i < jmin (5, typesFound.size()); ++i)
  788. if (auto* desc = typesFound.getUnchecked(i))
  789. createPlugin (PluginDescriptionAndPreference { *desc }, pos);
  790. }
  791. }
  792. }
  793. bool MainHostWindow::isDoublePrecisionProcessingEnabled()
  794. {
  795. if (auto* props = getAppProperties().getUserSettings())
  796. return props->getBoolValue ("doublePrecisionProcessing", false);
  797. return false;
  798. }
  799. bool MainHostWindow::isAutoScalePluginWindowsEnabled()
  800. {
  801. if (auto* props = getAppProperties().getUserSettings())
  802. return props->getBoolValue ("autoScalePluginWindows", false);
  803. return false;
  804. }
  805. void MainHostWindow::updatePrecisionMenuItem (ApplicationCommandInfo& info)
  806. {
  807. info.setInfo ("Double Floating-Point Precision Rendering", {}, "General", 0);
  808. info.setTicked (isDoublePrecisionProcessingEnabled());
  809. }
  810. void MainHostWindow::updateAutoScaleMenuItem (ApplicationCommandInfo& info)
  811. {
  812. info.setInfo ("Auto-Scale Plug-in Windows", {}, "General", 0);
  813. info.setTicked (isAutoScalePluginWindowsEnabled());
  814. }