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.

1048 lines
32KB

  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 "../Application/jucer_Headers.h"
  19. #include "jucer_Application.h"
  20. #include "jucer_MainWindow.h"
  21. #include "StartPage/jucer_StartPageComponent.h"
  22. #include "../Utility/UI/jucer_JucerTreeViewBase.h"
  23. #include "../ProjectSaving/jucer_ProjectSaver.h"
  24. #include "UserAccount/jucer_LoginFormComponent.h"
  25. #include "../Project/UI/jucer_ProjectContentComponent.h"
  26. //==============================================================================
  27. class BlurOverlayWithComponent final : public Component,
  28. private ComponentMovementWatcher,
  29. private AsyncUpdater
  30. {
  31. public:
  32. BlurOverlayWithComponent (MainWindow& window, std::unique_ptr<Component> comp)
  33. : ComponentMovementWatcher (&window),
  34. mainWindow (window),
  35. componentToShow (std::move (comp))
  36. {
  37. kernel.createGaussianBlur (1.25f);
  38. addAndMakeVisible (*componentToShow);
  39. setAlwaysOnTop (true);
  40. setOpaque (true);
  41. setVisible (true);
  42. static_cast<Component&> (mainWindow).addChildComponent (this);
  43. handleComponentMovedOrResized();
  44. enterModalState();
  45. }
  46. void resized() override
  47. {
  48. setBounds (mainWindow.getLocalBounds());
  49. componentToShow->centreWithSize (componentToShow->getWidth(), componentToShow->getHeight());
  50. refreshBackgroundImage();
  51. }
  52. void paint (Graphics& g) override
  53. {
  54. g.drawImage (componentImage, getLocalBounds().toFloat());
  55. }
  56. void inputAttemptWhenModal() override
  57. {
  58. mainWindow.hideLoginFormOverlay();
  59. }
  60. private:
  61. void componentPeerChanged() override {}
  62. void componentVisibilityChanged() override {}
  63. using ComponentMovementWatcher::componentVisibilityChanged;
  64. void handleComponentMovedOrResized() { triggerAsyncUpdate(); }
  65. void componentMovedOrResized (bool, bool) override { handleComponentMovedOrResized(); }
  66. using ComponentMovementWatcher::componentMovedOrResized;
  67. void handleAsyncUpdate() override { resized(); }
  68. void mouseUp (const MouseEvent& event) override
  69. {
  70. if (event.eventComponent == this)
  71. mainWindow.hideLoginFormOverlay();
  72. }
  73. void lookAndFeelChanged() override
  74. {
  75. refreshBackgroundImage();
  76. repaint();
  77. }
  78. void refreshBackgroundImage()
  79. {
  80. setAlwaysOnTop (false);
  81. toBack();
  82. auto parentBounds = mainWindow.getBounds();
  83. componentImage = mainWindow.createComponentSnapshot (mainWindow.getLocalBounds())
  84. .rescaled (roundToInt ((float) parentBounds.getWidth() / 1.75f),
  85. roundToInt ((float) parentBounds.getHeight() / 1.75f));
  86. kernel.applyToImage (componentImage, componentImage, getLocalBounds());
  87. setAlwaysOnTop (true);
  88. toFront (true);
  89. }
  90. //==============================================================================
  91. MainWindow& mainWindow;
  92. std::unique_ptr<Component> componentToShow;
  93. ImageConvolutionKernel kernel { 3 };
  94. Image componentImage;
  95. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BlurOverlayWithComponent)
  96. };
  97. //==============================================================================
  98. MainWindow::MainWindow()
  99. : DocumentWindow (ProjucerApplication::getApp().getApplicationName(),
  100. ProjucerApplication::getApp().lookAndFeel.getCurrentColourScheme()
  101. .getUIColour (LookAndFeel_V4::ColourScheme::UIColour::windowBackground),
  102. DocumentWindow::allButtons,
  103. false)
  104. {
  105. setUsingNativeTitleBar (true);
  106. setResizable (true, false);
  107. setResizeLimits (600, 500, 32000, 32000);
  108. #if ! JUCE_MAC
  109. setMenuBar (ProjucerApplication::getApp().getMenuModel());
  110. #endif
  111. createProjectContentCompIfNeeded();
  112. auto& commandManager = ProjucerApplication::getCommandManager();
  113. auto registerAllAppCommands = [&]
  114. {
  115. commandManager.registerAllCommandsForTarget (this);
  116. commandManager.registerAllCommandsForTarget (getProjectContentComponent());
  117. };
  118. auto updateAppKeyMappings = [&]
  119. {
  120. commandManager.getKeyMappings()->resetToDefaultMappings();
  121. if (auto keys = getGlobalProperties().getXmlValue ("keyMappings"))
  122. commandManager.getKeyMappings()->restoreFromXml (*keys);
  123. addKeyListener (commandManager.getKeyMappings());
  124. };
  125. registerAllAppCommands();
  126. updateAppKeyMappings();
  127. setWantsKeyboardFocus (false);
  128. getLookAndFeel().setColour (ColourSelector::backgroundColourId, Colours::transparentBlack);
  129. projectNameValue.addListener (this);
  130. centreWithSize (800, 600);
  131. }
  132. MainWindow::~MainWindow()
  133. {
  134. #if ! JUCE_MAC
  135. setMenuBar (nullptr);
  136. #endif
  137. removeKeyListener (ProjucerApplication::getCommandManager().getKeyMappings());
  138. // save the current size and position to our settings file..
  139. getGlobalProperties().setValue ("lastMainWindowPos", getWindowStateAsString());
  140. clearContentComponent();
  141. }
  142. void MainWindow::createProjectContentCompIfNeeded()
  143. {
  144. if (getProjectContentComponent() == nullptr)
  145. {
  146. clearContentComponent();
  147. setContentOwned (new ProjectContentComponent(), false);
  148. }
  149. }
  150. void MainWindow::updateTitleBarIcon()
  151. {
  152. if (auto* peer = getPeer())
  153. {
  154. if (currentProject != nullptr)
  155. {
  156. peer->setRepresentedFile (currentProject->getFile());
  157. peer->setIcon (ImageCache::getFromMemory (BinaryData::juce_icon_png, BinaryData::juce_icon_pngSize));
  158. }
  159. else
  160. {
  161. peer->setRepresentedFile ({});
  162. }
  163. }
  164. }
  165. void MainWindow::makeVisible()
  166. {
  167. setVisible (true);
  168. addToDesktop();
  169. restoreWindowPosition();
  170. updateTitleBarIcon();
  171. getContentComponent()->grabKeyboardFocus();
  172. }
  173. ProjectContentComponent* MainWindow::getProjectContentComponent() const
  174. {
  175. return dynamic_cast<ProjectContentComponent*> (getContentComponent());
  176. }
  177. void MainWindow::closeButtonPressed()
  178. {
  179. ProjucerApplication::getApp().mainWindowList.closeWindow (this);
  180. }
  181. void MainWindow::closeCurrentProject (OpenDocumentManager::SaveIfNeeded askUserToSave, std::function<void (bool)> callback)
  182. {
  183. if (currentProject == nullptr)
  184. {
  185. NullCheckedInvocation::invoke (callback, true);
  186. return;
  187. }
  188. currentProject->getStoredProperties().setValue (getProjectWindowPosName(), getWindowStateAsString());
  189. if (auto* pcc = getProjectContentComponent())
  190. {
  191. pcc->saveOpenDocumentList();
  192. pcc->hideEditor();
  193. }
  194. ProjucerApplication::getApp().openDocumentManager
  195. .closeAllDocumentsUsingProjectAsync (*currentProject,
  196. askUserToSave,
  197. [parent = SafePointer<MainWindow> { this }, askUserToSave, callback] (bool closedSuccessfully)
  198. {
  199. if (parent == nullptr)
  200. return;
  201. if (! closedSuccessfully)
  202. {
  203. NullCheckedInvocation::invoke (callback, false);
  204. return;
  205. }
  206. auto setProjectAndCallback = [parent, callback]
  207. {
  208. parent->setProject (nullptr);
  209. NullCheckedInvocation::invoke (callback, true);
  210. };
  211. if (askUserToSave == OpenDocumentManager::SaveIfNeeded::no)
  212. {
  213. setProjectAndCallback();
  214. return;
  215. }
  216. parent->currentProject->saveIfNeededAndUserAgreesAsync ([parent, setProjectAndCallback, callback] (FileBasedDocument::SaveResult saveResult)
  217. {
  218. if (parent == nullptr)
  219. return;
  220. if (saveResult == FileBasedDocument::savedOk)
  221. setProjectAndCallback();
  222. else
  223. NullCheckedInvocation::invoke (callback, false);
  224. });
  225. });
  226. }
  227. void MainWindow::moveProject (File newProjectFileToOpen, OpenInIDE openInIDE)
  228. {
  229. closeCurrentProject (OpenDocumentManager::SaveIfNeeded::no,
  230. [parent = SafePointer<MainWindow> { this }, newProjectFileToOpen, openInIDE] (bool)
  231. {
  232. if (parent == nullptr)
  233. return;
  234. parent->openFile (newProjectFileToOpen, [parent, openInIDE] (bool openedSuccessfully)
  235. {
  236. if (! (openedSuccessfully && parent != nullptr && parent->currentProject != nullptr && openInIDE == OpenInIDE::yes))
  237. return;
  238. // The project component knows how to process the saveAndOpenInIDE command, but the
  239. // main application does not. In order to process the command successfully, we need
  240. // to ensure that the project content component has focus.
  241. auto& manager = ProjucerApplication::getApp().getCommandManager();
  242. manager.setFirstCommandTarget (parent->getProjectContentComponent());
  243. ProjucerApplication::getApp().getCommandManager().invokeDirectly (CommandIDs::saveAndOpenInIDE, false);
  244. manager.setFirstCommandTarget (nullptr);
  245. });
  246. });
  247. }
  248. void MainWindow::setProject (std::unique_ptr<Project> newProject)
  249. {
  250. if (newProject == nullptr)
  251. {
  252. if (auto* content = getProjectContentComponent())
  253. content->setProject (nullptr);
  254. currentProject.reset();
  255. }
  256. else
  257. {
  258. currentProject = std::move (newProject);
  259. createProjectContentCompIfNeeded();
  260. getProjectContentComponent()->setProject (currentProject.get());
  261. }
  262. if (currentProject != nullptr)
  263. currentProject->addChangeListener (this);
  264. changeListenerCallback (currentProject.get());
  265. projectNameValue.referTo (currentProject != nullptr ? currentProject->getProjectValue (Ids::name) : Value());
  266. initialiseProjectWindow();
  267. ProjucerApplication::getCommandManager().commandStatusChanged();
  268. }
  269. void MainWindow::restoreWindowPosition()
  270. {
  271. String windowState;
  272. if (currentProject != nullptr)
  273. windowState = currentProject->getStoredProperties().getValue (getProjectWindowPosName());
  274. if (windowState.isEmpty())
  275. windowState = getGlobalProperties().getValue ("lastMainWindowPos");
  276. restoreWindowStateFromString (windowState);
  277. }
  278. bool MainWindow::canOpenFile (const File& file) const
  279. {
  280. return (! file.isDirectory())
  281. && (file.hasFileExtension (Project::projectFileExtension)
  282. || ProjucerApplication::getApp().openDocumentManager.canOpenFile (file));
  283. }
  284. void MainWindow::openFile (const File& file, std::function<void (bool)> callback)
  285. {
  286. if (file.hasFileExtension (Project::projectFileExtension))
  287. {
  288. auto newDoc = std::make_unique<Project> (file);
  289. auto result = newDoc->loadFrom (file, true);
  290. if (result.wasOk())
  291. {
  292. closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes,
  293. [parent = SafePointer<MainWindow> { this },
  294. sharedDoc = std::make_shared<std::unique_ptr<Project>> (std::move (newDoc)),
  295. callback] (bool saveResult)
  296. {
  297. if (parent == nullptr)
  298. return;
  299. if (saveResult)
  300. {
  301. parent->setProject (std::move (*sharedDoc.get()));
  302. parent->currentProject->setChangedFlag (false);
  303. parent->createProjectContentCompIfNeeded();
  304. parent->getProjectContentComponent()->reloadLastOpenDocuments();
  305. }
  306. NullCheckedInvocation::invoke (callback, saveResult);
  307. });
  308. return;
  309. }
  310. NullCheckedInvocation::invoke (callback, false);
  311. return;
  312. }
  313. if (file.exists())
  314. {
  315. SafePointer<MainWindow> parent { this };
  316. auto createCompAndShowEditor = [parent, file, callback]
  317. {
  318. if (parent != nullptr)
  319. {
  320. parent->createProjectContentCompIfNeeded();
  321. NullCheckedInvocation::invoke (callback, parent->getProjectContentComponent()->showEditorForFile (file, true));
  322. }
  323. };
  324. if (isPIPFile (file))
  325. {
  326. openPIP (file, [parent, createCompAndShowEditor, callback] (bool openedSuccessfully)
  327. {
  328. if (parent == nullptr)
  329. return;
  330. if (openedSuccessfully)
  331. {
  332. NullCheckedInvocation::invoke (callback, true);
  333. return;
  334. }
  335. createCompAndShowEditor();
  336. });
  337. return;
  338. }
  339. createCompAndShowEditor();
  340. return;
  341. }
  342. NullCheckedInvocation::invoke (callback, false);
  343. }
  344. void MainWindow::openPIP (const File& pipFile, std::function<void (bool)> callback)
  345. {
  346. auto generator = std::make_shared<PIPGenerator> (pipFile);
  347. if (! generator->hasValidPIP())
  348. {
  349. NullCheckedInvocation::invoke (callback, false);
  350. return;
  351. }
  352. auto generatorResult = generator->createJucerFile();
  353. if (generatorResult != Result::ok())
  354. {
  355. auto options = MessageBoxOptions::makeOptionsOk (MessageBoxIconType::WarningIcon,
  356. "PIP Error.",
  357. generatorResult.getErrorMessage());
  358. messageBox = AlertWindow::showScopedAsync (options, nullptr);
  359. NullCheckedInvocation::invoke (callback, false);
  360. return;
  361. }
  362. if (! generator->createMainCpp())
  363. {
  364. auto options = MessageBoxOptions::makeOptionsOk (MessageBoxIconType::WarningIcon,
  365. "PIP Error.",
  366. "Failed to create Main.cpp.");
  367. messageBox = AlertWindow::showScopedAsync (options, nullptr);
  368. NullCheckedInvocation::invoke (callback, false);
  369. return;
  370. }
  371. openFile (generator->getJucerFile(), [parent = SafePointer<MainWindow> { this }, generator, callback] (bool openedSuccessfully)
  372. {
  373. if (parent == nullptr)
  374. return;
  375. if (! openedSuccessfully)
  376. {
  377. auto options = MessageBoxOptions::makeOptionsOk (MessageBoxIconType::WarningIcon,
  378. "PIP Error.",
  379. "Failed to open .jucer file.");
  380. parent->messageBox = AlertWindow::showScopedAsync (options, nullptr);
  381. NullCheckedInvocation::invoke (callback, false);
  382. return;
  383. }
  384. parent->setupTemporaryPIPProject (*generator);
  385. NullCheckedInvocation::invoke (callback, true);
  386. });
  387. }
  388. void MainWindow::setupTemporaryPIPProject (PIPGenerator& generator)
  389. {
  390. jassert (currentProject != nullptr);
  391. currentProject->setTemporaryDirectory (generator.getOutputDirectory());
  392. if (auto* pcc = getProjectContentComponent())
  393. {
  394. auto fileToDisplay = generator.getPIPFile();
  395. if (fileToDisplay != File())
  396. {
  397. pcc->showEditorForFile (fileToDisplay, true);
  398. if (auto* sourceCodeEditor = dynamic_cast <SourceCodeEditor*> (pcc->getEditorComponent()))
  399. sourceCodeEditor->editor->scrollToLine (findBestLineToScrollToForClass (StringArray::fromLines (fileToDisplay.loadFileAsString()),
  400. generator.getMainClassName(), currentProject->getProjectType().isAudioPlugin()));
  401. }
  402. }
  403. }
  404. bool MainWindow::isInterestedInFileDrag (const StringArray& filenames)
  405. {
  406. for (auto& filename : filenames)
  407. if (canOpenFile (File (filename)))
  408. return true;
  409. return false;
  410. }
  411. static void filesDroppedRecursive (Component::SafePointer<MainWindow> parent, StringArray filenames)
  412. {
  413. if (filenames.isEmpty())
  414. return;
  415. auto f = filenames[0];
  416. filenames.remove (0);
  417. if (! parent->canOpenFile (f))
  418. {
  419. filesDroppedRecursive (parent, filenames);
  420. return;
  421. }
  422. parent->openFile (f, [parent, filenames] (bool openedSuccessfully)
  423. {
  424. if (parent == nullptr || ! openedSuccessfully)
  425. return;
  426. filesDroppedRecursive (parent, filenames);
  427. });
  428. }
  429. void MainWindow::filesDropped (const StringArray& filenames, int /*mouseX*/, int /*mouseY*/)
  430. {
  431. filesDroppedRecursive (this, filenames);
  432. }
  433. bool MainWindow::shouldDropFilesWhenDraggedExternally (const DragAndDropTarget::SourceDetails& sourceDetails,
  434. StringArray& files, bool& canMoveFiles)
  435. {
  436. if (auto* tv = dynamic_cast<TreeView*> (sourceDetails.sourceComponent.get()))
  437. {
  438. Array<JucerTreeViewBase*> selected;
  439. for (int i = tv->getNumSelectedItems(); --i >= 0;)
  440. if (auto* b = dynamic_cast<JucerTreeViewBase*> (tv->getSelectedItem (i)))
  441. selected.add (b);
  442. if (! selected.isEmpty())
  443. {
  444. for (int i = selected.size(); --i >= 0;)
  445. {
  446. if (auto* jtvb = selected.getUnchecked (i))
  447. {
  448. auto f = jtvb->getDraggableFile();
  449. if (f.existsAsFile())
  450. files.add (f.getFullPathName());
  451. }
  452. }
  453. canMoveFiles = false;
  454. return ! files.isEmpty();
  455. }
  456. }
  457. return false;
  458. }
  459. void MainWindow::activeWindowStatusChanged()
  460. {
  461. DocumentWindow::activeWindowStatusChanged();
  462. if (auto* pcc = getProjectContentComponent())
  463. pcc->updateMissingFileStatuses();
  464. ProjucerApplication::getApp().openDocumentManager.reloadModifiedFiles();
  465. }
  466. void MainWindow::initialiseProjectWindow()
  467. {
  468. setResizable (true, false);
  469. updateTitleBarIcon();
  470. }
  471. void MainWindow::showStartPage()
  472. {
  473. jassert (currentProject == nullptr);
  474. setContentOwned (new StartPageComponent ([this] (std::unique_ptr<Project>&& newProject) { setProject (std::move (newProject)); },
  475. [this] (const File& exampleFile) { openFile (exampleFile, nullptr); }),
  476. true);
  477. setResizable (false, false);
  478. setName ("New Project");
  479. addToDesktop();
  480. centreWithSize (getContentComponent()->getWidth(), getContentComponent()->getHeight());
  481. setVisible (true);
  482. getContentComponent()->grabKeyboardFocus();
  483. }
  484. void MainWindow::showLoginFormOverlay()
  485. {
  486. blurOverlayComponent = std::make_unique<BlurOverlayWithComponent> (*this, std::make_unique<LoginFormComponent> (*this));
  487. loginFormOpen = true;
  488. }
  489. void MainWindow::hideLoginFormOverlay()
  490. {
  491. blurOverlayComponent.reset();
  492. loginFormOpen = false;
  493. }
  494. //==============================================================================
  495. ApplicationCommandTarget* MainWindow::getNextCommandTarget()
  496. {
  497. return nullptr;
  498. }
  499. void MainWindow::getAllCommands (Array <CommandID>& commands)
  500. {
  501. const CommandID ids[] =
  502. {
  503. CommandIDs::closeWindow,
  504. CommandIDs::goToPreviousWindow,
  505. CommandIDs::goToNextWindow
  506. };
  507. commands.addArray (ids, numElementsInArray (ids));
  508. }
  509. void MainWindow::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
  510. {
  511. switch (commandID)
  512. {
  513. case CommandIDs::closeWindow:
  514. result.setInfo ("Close Window", "Closes the current window", CommandCategories::general, 0);
  515. result.defaultKeypresses.add (KeyPress ('w', ModifierKeys::commandModifier, 0));
  516. break;
  517. case CommandIDs::goToPreviousWindow:
  518. result.setInfo ("Previous Window", "Activates the previous window", CommandCategories::general, 0);
  519. result.setActive (ProjucerApplication::getApp().mainWindowList.windows.size() > 1);
  520. result.defaultKeypresses.add (KeyPress (KeyPress::tabKey, ModifierKeys::shiftModifier | ModifierKeys::ctrlModifier, 0));
  521. break;
  522. case CommandIDs::goToNextWindow:
  523. result.setInfo ("Next Window", "Activates the next window", CommandCategories::general, 0);
  524. result.setActive (ProjucerApplication::getApp().mainWindowList.windows.size() > 1);
  525. result.defaultKeypresses.add (KeyPress (KeyPress::tabKey, ModifierKeys::ctrlModifier, 0));
  526. break;
  527. default:
  528. break;
  529. }
  530. }
  531. bool MainWindow::perform (const InvocationInfo& info)
  532. {
  533. switch (info.commandID)
  534. {
  535. case CommandIDs::closeWindow:
  536. closeButtonPressed();
  537. break;
  538. case CommandIDs::goToPreviousWindow:
  539. ProjucerApplication::getApp().mainWindowList.goToSiblingWindow (this, -1);
  540. break;
  541. case CommandIDs::goToNextWindow:
  542. ProjucerApplication::getApp().mainWindowList.goToSiblingWindow (this, 1);
  543. break;
  544. default:
  545. return false;
  546. }
  547. return true;
  548. }
  549. void MainWindow::valueChanged (Value& value)
  550. {
  551. if (value == projectNameValue)
  552. setName (currentProject != nullptr ? currentProject->getProjectNameString() + " - Projucer"
  553. : "Projucer");
  554. }
  555. void MainWindow::changeListenerCallback (ChangeBroadcaster* source)
  556. {
  557. auto* project = getProject();
  558. if (source == project)
  559. if (auto* peer = getPeer())
  560. peer->setHasChangedSinceSaved (project != nullptr ? project->hasChangedSinceSaved()
  561. : false);
  562. }
  563. //==============================================================================
  564. MainWindowList::MainWindowList()
  565. {
  566. }
  567. void MainWindowList::forceCloseAllWindows()
  568. {
  569. windows.clear();
  570. }
  571. static void askAllWindowsToCloseRecursive (WeakReference<MainWindowList> parent, std::function<void (bool)> callback)
  572. {
  573. if (parent->windows.size() == 0)
  574. {
  575. NullCheckedInvocation::invoke (callback, true);
  576. return;
  577. }
  578. parent->windows[0]->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes, [parent, callback] (bool closedSuccessfully)
  579. {
  580. if (parent == nullptr)
  581. return;
  582. if (! closedSuccessfully)
  583. {
  584. NullCheckedInvocation::invoke (callback, false);
  585. return;
  586. }
  587. parent->windows.remove (0);
  588. askAllWindowsToCloseRecursive (parent, std::move (callback));
  589. });
  590. }
  591. void MainWindowList::askAllWindowsToClose (std::function<void (bool)> callback)
  592. {
  593. saveCurrentlyOpenProjectList();
  594. askAllWindowsToCloseRecursive (this, std::move (callback));
  595. }
  596. void MainWindowList::createWindowIfNoneAreOpen()
  597. {
  598. if (windows.isEmpty())
  599. createNewMainWindow()->showStartPage();
  600. }
  601. void MainWindowList::closeWindow (MainWindow* w)
  602. {
  603. jassert (windows.contains (w));
  604. #if ! JUCE_MAC
  605. if (windows.size() == 1 && ! isInReopenLastProjects)
  606. {
  607. JUCEApplicationBase::getInstance()->systemRequestedQuit();
  608. }
  609. else
  610. #endif
  611. {
  612. w->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes,
  613. [parent = WeakReference<MainWindowList> { this }, w] (bool closedSuccessfully)
  614. {
  615. if (parent == nullptr)
  616. return;
  617. if (closedSuccessfully)
  618. {
  619. parent->windows.removeObject (w);
  620. parent->saveCurrentlyOpenProjectList();
  621. }
  622. });
  623. }
  624. }
  625. void MainWindowList::goToSiblingWindow (MainWindow* w, int delta)
  626. {
  627. auto index = windows.indexOf (w);
  628. if (index >= 0)
  629. if (auto* next = windows[(index + delta + windows.size()) % windows.size()])
  630. next->toFront (true);
  631. }
  632. void MainWindowList::openDocument (OpenDocumentManager::Document* doc, bool grabFocus)
  633. {
  634. auto& desktop = Desktop::getInstance();
  635. for (int i = desktop.getNumComponents(); --i >= 0;)
  636. {
  637. if (auto* mw = dynamic_cast<MainWindow*> (desktop.getComponent (i)))
  638. {
  639. if (auto* pcc = mw->getProjectContentComponent())
  640. {
  641. if (pcc->hasFileInRecentList (doc->getFile()))
  642. {
  643. mw->toFront (true);
  644. mw->getProjectContentComponent()->showDocument (doc, grabFocus);
  645. return;
  646. }
  647. }
  648. }
  649. }
  650. getFrontmostWindow()->getProjectContentComponent()->showDocument (doc, grabFocus);
  651. }
  652. void MainWindowList::openFile (const File& file, std::function<void (bool)> callback, bool openInBackground)
  653. {
  654. if (! file.exists())
  655. {
  656. NullCheckedInvocation::invoke (callback, false);
  657. return;
  658. }
  659. for (auto* w : windows)
  660. {
  661. if (w->getProject() != nullptr && w->getProject()->getFile() == file)
  662. {
  663. w->toFront (true);
  664. NullCheckedInvocation::invoke (callback, true);
  665. return;
  666. }
  667. }
  668. WeakReference<MainWindowList> parent { this };
  669. if (file.hasFileExtension (Project::projectFileExtension)
  670. || isPIPFile (file))
  671. {
  672. WeakReference<Component> previousFrontWindow (getFrontmostWindow());
  673. auto* w = getOrCreateEmptyWindow();
  674. jassert (w != nullptr);
  675. w->openFile (file, [parent, previousFrontWindow, w, openInBackground, callback] (bool openedSuccessfully)
  676. {
  677. if (parent == nullptr)
  678. return;
  679. if (openedSuccessfully)
  680. {
  681. w->makeVisible();
  682. w->setResizable (true, false);
  683. parent->checkWindowBounds (*w);
  684. if (openInBackground && previousFrontWindow != nullptr)
  685. previousFrontWindow->toFront (true);
  686. }
  687. else
  688. {
  689. parent->closeWindow (w);
  690. }
  691. NullCheckedInvocation::invoke (callback, openedSuccessfully);
  692. });
  693. return;
  694. }
  695. getFrontmostWindow()->openFile (file, [parent, callback] (bool openedSuccessfully)
  696. {
  697. if (parent != nullptr)
  698. NullCheckedInvocation::invoke (callback, openedSuccessfully);
  699. });
  700. }
  701. MainWindow* MainWindowList::createNewMainWindow()
  702. {
  703. windows.add (new MainWindow());
  704. return windows.getLast();
  705. }
  706. MainWindow* MainWindowList::getFrontmostWindow (bool createIfNotFound)
  707. {
  708. if (windows.isEmpty())
  709. {
  710. if (createIfNotFound)
  711. {
  712. auto* w = createNewMainWindow();
  713. jassert (w != nullptr);
  714. w->makeVisible();
  715. checkWindowBounds (*w);
  716. return w;
  717. }
  718. return nullptr;
  719. }
  720. for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
  721. {
  722. auto* mw = dynamic_cast<MainWindow*> (Desktop::getInstance().getComponent (i));
  723. if (windows.contains (mw))
  724. return mw;
  725. }
  726. return windows.getLast();
  727. }
  728. MainWindow* MainWindowList::getOrCreateEmptyWindow()
  729. {
  730. if (windows.size() == 0)
  731. return createNewMainWindow();
  732. for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
  733. {
  734. auto* mw = dynamic_cast<MainWindow*> (Desktop::getInstance().getComponent (i));
  735. if (windows.contains (mw) && mw->getProject() == nullptr)
  736. return mw;
  737. }
  738. return createNewMainWindow();
  739. }
  740. MainWindow* MainWindowList::getMainWindowForFile (const File& file)
  741. {
  742. if (windows.size() > 0)
  743. {
  744. for (auto* window : windows)
  745. {
  746. if (auto* project = window->getProject())
  747. {
  748. if (project->getFile() == file)
  749. return window;
  750. }
  751. }
  752. }
  753. return nullptr;
  754. }
  755. MainWindow* MainWindowList::getMainWindowWithLoginFormOpen()
  756. {
  757. for (auto* window : windows)
  758. if (window->isShowingLoginForm())
  759. return window;
  760. return nullptr;
  761. }
  762. void MainWindowList::checkWindowBounds (MainWindow& windowToCheck)
  763. {
  764. auto avoidSuperimposedWindows = [&]
  765. {
  766. for (auto* otherWindow : windows)
  767. {
  768. if (otherWindow == nullptr || otherWindow == &windowToCheck)
  769. continue;
  770. auto boundsToCheck = windowToCheck.getScreenBounds();
  771. auto otherBounds = otherWindow->getScreenBounds();
  772. if (std::abs (boundsToCheck.getX() - otherBounds.getX()) < 3
  773. && std::abs (boundsToCheck.getY() - otherBounds.getY()) < 3
  774. && std::abs (boundsToCheck.getRight() - otherBounds.getRight()) < 3
  775. && std::abs (boundsToCheck.getBottom() - otherBounds.getBottom()) < 3)
  776. {
  777. int dx = 40, dy = 30;
  778. if (otherBounds.getCentreX() >= boundsToCheck.getCentreX()) dx = -dx;
  779. if (otherBounds.getCentreY() >= boundsToCheck.getCentreY()) dy = -dy;
  780. windowToCheck.setBounds (boundsToCheck.translated (dx, dy));
  781. }
  782. }
  783. };
  784. auto ensureWindowIsFullyOnscreen = [&]
  785. {
  786. auto windowBounds = windowToCheck.getScreenBounds();
  787. auto screenLimits = Desktop::getInstance().getDisplays().getDisplayForRect (windowBounds)->userArea;
  788. if (auto* peer = windowToCheck.getPeer())
  789. if (const auto frameSize = peer->getFrameSizeIfPresent())
  790. frameSize->subtractFrom (screenLimits);
  791. auto constrainedX = jlimit (screenLimits.getX(), jmax (screenLimits.getX(), screenLimits.getRight() - windowBounds.getWidth()), windowBounds.getX());
  792. auto constrainedY = jlimit (screenLimits.getY(), jmax (screenLimits.getY(), screenLimits.getBottom() - windowBounds.getHeight()), windowBounds.getY());
  793. Point<int> constrainedTopLeft (constrainedX, constrainedY);
  794. if (windowBounds.getPosition() != constrainedTopLeft)
  795. windowToCheck.setTopLeftPosition (constrainedTopLeft);
  796. };
  797. avoidSuperimposedWindows();
  798. ensureWindowIsFullyOnscreen();
  799. }
  800. void MainWindowList::saveCurrentlyOpenProjectList()
  801. {
  802. Array<File> projects;
  803. auto& desktop = Desktop::getInstance();
  804. for (int i = 0; i < desktop.getNumComponents(); ++i)
  805. {
  806. if (auto* mw = dynamic_cast<MainWindow*> (desktop.getComponent (i)))
  807. if (auto* p = mw->getProject())
  808. if (! p->isTemporaryProject())
  809. projects.add (p->getFile());
  810. }
  811. getAppSettings().setLastProjects (projects);
  812. }
  813. void MainWindowList::reopenLastProjects()
  814. {
  815. const ScopedValueSetter<bool> setter (isInReopenLastProjects, true);
  816. for (auto& p : getAppSettings().getLastProjects())
  817. if (p.existsAsFile())
  818. openFile (p, nullptr, true);
  819. }
  820. void MainWindowList::sendLookAndFeelChange()
  821. {
  822. for (auto* w : windows)
  823. w->sendLookAndFeelChange();
  824. }
  825. Project* MainWindowList::getFrontmostProject()
  826. {
  827. auto& desktop = Desktop::getInstance();
  828. for (int i = desktop.getNumComponents(); --i >= 0;)
  829. if (auto* mw = dynamic_cast<MainWindow*> (desktop.getComponent (i)))
  830. if (auto* p = mw->getProject())
  831. return p;
  832. return nullptr;
  833. }