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.

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