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.

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