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.

1078 lines
33KB

  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. if (callback != nullptr)
  186. callback (true);
  187. return;
  188. }
  189. currentProject->getStoredProperties().setValue (getProjectWindowPosName(), getWindowStateAsString());
  190. if (auto* pcc = getProjectContentComponent())
  191. {
  192. pcc->saveOpenDocumentList();
  193. pcc->hideEditor();
  194. }
  195. ProjucerApplication::getApp().openDocumentManager
  196. .closeAllDocumentsUsingProjectAsync (*currentProject,
  197. askUserToSave,
  198. [parent = SafePointer<MainWindow> { this }, askUserToSave, callback] (bool closedSuccessfully)
  199. {
  200. if (parent == nullptr)
  201. return;
  202. if (! closedSuccessfully)
  203. {
  204. if (callback != nullptr)
  205. callback (false);
  206. return;
  207. }
  208. auto setProjectAndCallback = [parent, callback]
  209. {
  210. parent->setProject (nullptr);
  211. if (callback != nullptr)
  212. callback (true);
  213. };
  214. if (askUserToSave == OpenDocumentManager::SaveIfNeeded::no)
  215. {
  216. setProjectAndCallback();
  217. return;
  218. }
  219. parent->currentProject->saveIfNeededAndUserAgreesAsync ([parent, setProjectAndCallback, callback] (FileBasedDocument::SaveResult saveResult)
  220. {
  221. if (parent == nullptr)
  222. return;
  223. if (saveResult == FileBasedDocument::savedOk)
  224. setProjectAndCallback();
  225. else if (callback != nullptr)
  226. callback (false);
  227. });
  228. });
  229. }
  230. void MainWindow::moveProject (File newProjectFileToOpen, OpenInIDE openInIDE)
  231. {
  232. closeCurrentProject (OpenDocumentManager::SaveIfNeeded::no,
  233. [parent = SafePointer<MainWindow> { this }, newProjectFileToOpen, openInIDE] (bool)
  234. {
  235. if (parent == nullptr)
  236. return;
  237. parent->openFile (newProjectFileToOpen, [parent, openInIDE] (bool openedSuccessfully)
  238. {
  239. if (! (openedSuccessfully && parent != nullptr && parent->currentProject != nullptr && openInIDE == OpenInIDE::yes))
  240. return;
  241. // The project component knows how to process the saveAndOpenInIDE command, but the
  242. // main application does not. In order to process the command successfully, we need
  243. // to ensure that the project content component has focus.
  244. auto& manager = ProjucerApplication::getApp().getCommandManager();
  245. manager.setFirstCommandTarget (parent->getProjectContentComponent());
  246. ProjucerApplication::getApp().getCommandManager().invokeDirectly (CommandIDs::saveAndOpenInIDE, false);
  247. manager.setFirstCommandTarget (nullptr);
  248. });
  249. });
  250. }
  251. void MainWindow::setProject (std::unique_ptr<Project> newProject)
  252. {
  253. if (newProject == nullptr)
  254. {
  255. if (auto* content = getProjectContentComponent())
  256. content->setProject (nullptr);
  257. currentProject.reset();
  258. }
  259. else
  260. {
  261. currentProject = std::move (newProject);
  262. createProjectContentCompIfNeeded();
  263. getProjectContentComponent()->setProject (currentProject.get());
  264. }
  265. if (currentProject != nullptr)
  266. currentProject->addChangeListener (this);
  267. changeListenerCallback (currentProject.get());
  268. projectNameValue.referTo (currentProject != nullptr ? currentProject->getProjectValue (Ids::name) : Value());
  269. initialiseProjectWindow();
  270. ProjucerApplication::getCommandManager().commandStatusChanged();
  271. }
  272. void MainWindow::restoreWindowPosition()
  273. {
  274. String windowState;
  275. if (currentProject != nullptr)
  276. windowState = currentProject->getStoredProperties().getValue (getProjectWindowPosName());
  277. if (windowState.isEmpty())
  278. windowState = getGlobalProperties().getValue ("lastMainWindowPos");
  279. restoreWindowStateFromString (windowState);
  280. }
  281. bool MainWindow::canOpenFile (const File& file) const
  282. {
  283. return (! file.isDirectory())
  284. && (file.hasFileExtension (Project::projectFileExtension)
  285. || ProjucerApplication::getApp().openDocumentManager.canOpenFile (file));
  286. }
  287. void MainWindow::openFile (const File& file, std::function<void (bool)> callback)
  288. {
  289. if (file.hasFileExtension (Project::projectFileExtension))
  290. {
  291. auto newDoc = std::make_unique<Project> (file);
  292. auto result = newDoc->loadFrom (file, true);
  293. if (result.wasOk())
  294. {
  295. closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes,
  296. [parent = SafePointer<MainWindow> { this },
  297. sharedDoc = std::make_shared<std::unique_ptr<Project>> (std::move (newDoc)),
  298. callback] (bool saveResult)
  299. {
  300. if (parent == nullptr)
  301. return;
  302. if (saveResult)
  303. {
  304. parent->setProject (std::move (*sharedDoc.get()));
  305. parent->currentProject->setChangedFlag (false);
  306. parent->createProjectContentCompIfNeeded();
  307. parent->getProjectContentComponent()->reloadLastOpenDocuments();
  308. parent->currentProject->updateDeprecatedProjectSettingsInteractively();
  309. }
  310. if (callback != nullptr)
  311. callback (saveResult);
  312. });
  313. return;
  314. }
  315. if (callback != nullptr)
  316. callback (false);
  317. return;
  318. }
  319. if (file.exists())
  320. {
  321. SafePointer<MainWindow> parent { this };
  322. auto createCompAndShowEditor = [parent, file, callback]
  323. {
  324. if (parent != nullptr)
  325. {
  326. parent->createProjectContentCompIfNeeded();
  327. if (callback != nullptr)
  328. callback (parent->getProjectContentComponent()->showEditorForFile (file, true));
  329. }
  330. };
  331. if (isPIPFile (file))
  332. {
  333. openPIP (file, [parent, createCompAndShowEditor, callback] (bool openedSuccessfully)
  334. {
  335. if (parent == nullptr)
  336. return;
  337. if (openedSuccessfully)
  338. {
  339. if (callback != nullptr)
  340. callback (true);
  341. return;
  342. }
  343. createCompAndShowEditor();
  344. });
  345. return;
  346. }
  347. createCompAndShowEditor();
  348. return;
  349. }
  350. if (callback != nullptr)
  351. callback (false);
  352. }
  353. void MainWindow::openPIP (const File& pipFile, std::function<void (bool)> callback)
  354. {
  355. auto generator = std::make_shared<PIPGenerator> (pipFile);
  356. if (! generator->hasValidPIP())
  357. {
  358. if (callback != nullptr)
  359. callback (false);
  360. return;
  361. }
  362. auto generatorResult = generator->createJucerFile();
  363. if (generatorResult != Result::ok())
  364. {
  365. auto options = MessageBoxOptions::makeOptionsOk (MessageBoxIconType::WarningIcon,
  366. "PIP Error.",
  367. generatorResult.getErrorMessage());
  368. messageBox = AlertWindow::showScopedAsync (options, nullptr);
  369. if (callback != nullptr)
  370. callback (false);
  371. return;
  372. }
  373. if (! generator->createMainCpp())
  374. {
  375. auto options = MessageBoxOptions::makeOptionsOk (MessageBoxIconType::WarningIcon,
  376. "PIP Error.",
  377. "Failed to create Main.cpp.");
  378. messageBox = AlertWindow::showScopedAsync (options, nullptr);
  379. if (callback != nullptr)
  380. callback (false);
  381. return;
  382. }
  383. openFile (generator->getJucerFile(), [parent = SafePointer<MainWindow> { this }, generator, callback] (bool openedSuccessfully)
  384. {
  385. if (parent == nullptr)
  386. return;
  387. if (! openedSuccessfully)
  388. {
  389. auto options = MessageBoxOptions::makeOptionsOk (MessageBoxIconType::WarningIcon,
  390. "PIP Error.",
  391. "Failed to open .jucer file.");
  392. parent->messageBox = AlertWindow::showScopedAsync (options, nullptr);
  393. if (callback != nullptr)
  394. callback (false);
  395. return;
  396. }
  397. parent->setupTemporaryPIPProject (*generator);
  398. if (callback != nullptr)
  399. callback (true);
  400. });
  401. }
  402. void MainWindow::setupTemporaryPIPProject (PIPGenerator& generator)
  403. {
  404. jassert (currentProject != nullptr);
  405. currentProject->setTemporaryDirectory (generator.getOutputDirectory());
  406. if (auto* pcc = getProjectContentComponent())
  407. {
  408. auto fileToDisplay = generator.getPIPFile();
  409. if (fileToDisplay != File())
  410. {
  411. pcc->showEditorForFile (fileToDisplay, true);
  412. if (auto* sourceCodeEditor = dynamic_cast <SourceCodeEditor*> (pcc->getEditorComponent()))
  413. sourceCodeEditor->editor->scrollToLine (findBestLineToScrollToForClass (StringArray::fromLines (fileToDisplay.loadFileAsString()),
  414. generator.getMainClassName(), currentProject->getProjectType().isAudioPlugin()));
  415. }
  416. }
  417. }
  418. bool MainWindow::isInterestedInFileDrag (const StringArray& filenames)
  419. {
  420. for (auto& filename : filenames)
  421. if (canOpenFile (File (filename)))
  422. return true;
  423. return false;
  424. }
  425. static void filesDroppedRecursive (Component::SafePointer<MainWindow> parent, StringArray filenames)
  426. {
  427. if (filenames.isEmpty())
  428. return;
  429. auto f = filenames[0];
  430. filenames.remove (0);
  431. if (! parent->canOpenFile (f))
  432. {
  433. filesDroppedRecursive (parent, filenames);
  434. return;
  435. }
  436. parent->openFile (f, [parent, filenames] (bool openedSuccessfully)
  437. {
  438. if (parent == nullptr || ! openedSuccessfully)
  439. return;
  440. filesDroppedRecursive (parent, filenames);
  441. });
  442. }
  443. void MainWindow::filesDropped (const StringArray& filenames, int /*mouseX*/, int /*mouseY*/)
  444. {
  445. filesDroppedRecursive (this, filenames);
  446. }
  447. bool MainWindow::shouldDropFilesWhenDraggedExternally (const DragAndDropTarget::SourceDetails& sourceDetails,
  448. StringArray& files, bool& canMoveFiles)
  449. {
  450. if (auto* tv = dynamic_cast<TreeView*> (sourceDetails.sourceComponent.get()))
  451. {
  452. Array<JucerTreeViewBase*> selected;
  453. for (int i = tv->getNumSelectedItems(); --i >= 0;)
  454. if (auto* b = dynamic_cast<JucerTreeViewBase*> (tv->getSelectedItem(i)))
  455. selected.add (b);
  456. if (! selected.isEmpty())
  457. {
  458. for (int i = selected.size(); --i >= 0;)
  459. {
  460. if (auto* jtvb = selected.getUnchecked(i))
  461. {
  462. auto f = jtvb->getDraggableFile();
  463. if (f.existsAsFile())
  464. files.add (f.getFullPathName());
  465. }
  466. }
  467. canMoveFiles = false;
  468. return ! files.isEmpty();
  469. }
  470. }
  471. return false;
  472. }
  473. void MainWindow::activeWindowStatusChanged()
  474. {
  475. DocumentWindow::activeWindowStatusChanged();
  476. if (auto* pcc = getProjectContentComponent())
  477. pcc->updateMissingFileStatuses();
  478. ProjucerApplication::getApp().openDocumentManager.reloadModifiedFiles();
  479. }
  480. void MainWindow::initialiseProjectWindow()
  481. {
  482. setResizable (true, false);
  483. updateTitleBarIcon();
  484. }
  485. void MainWindow::showStartPage()
  486. {
  487. jassert (currentProject == nullptr);
  488. setContentOwned (new StartPageComponent ([this] (std::unique_ptr<Project>&& newProject) { setProject (std::move (newProject)); },
  489. [this] (const File& exampleFile) { openFile (exampleFile, nullptr); }),
  490. true);
  491. setResizable (false, false);
  492. setName ("New Project");
  493. addToDesktop();
  494. centreWithSize (getContentComponent()->getWidth(), getContentComponent()->getHeight());
  495. setVisible (true);
  496. getContentComponent()->grabKeyboardFocus();
  497. }
  498. void MainWindow::showLoginFormOverlay()
  499. {
  500. blurOverlayComponent = std::make_unique<BlurOverlayWithComponent> (*this, std::make_unique<LoginFormComponent> (*this));
  501. loginFormOpen = true;
  502. }
  503. void MainWindow::hideLoginFormOverlay()
  504. {
  505. blurOverlayComponent.reset();
  506. loginFormOpen = false;
  507. }
  508. //==============================================================================
  509. ApplicationCommandTarget* MainWindow::getNextCommandTarget()
  510. {
  511. return nullptr;
  512. }
  513. void MainWindow::getAllCommands (Array <CommandID>& commands)
  514. {
  515. const CommandID ids[] =
  516. {
  517. CommandIDs::closeWindow,
  518. CommandIDs::goToPreviousWindow,
  519. CommandIDs::goToNextWindow
  520. };
  521. commands.addArray (ids, numElementsInArray (ids));
  522. }
  523. void MainWindow::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
  524. {
  525. switch (commandID)
  526. {
  527. case CommandIDs::closeWindow:
  528. result.setInfo ("Close Window", "Closes the current window", CommandCategories::general, 0);
  529. result.defaultKeypresses.add (KeyPress ('w', ModifierKeys::commandModifier, 0));
  530. break;
  531. case CommandIDs::goToPreviousWindow:
  532. result.setInfo ("Previous Window", "Activates the previous window", CommandCategories::general, 0);
  533. result.setActive (ProjucerApplication::getApp().mainWindowList.windows.size() > 1);
  534. result.defaultKeypresses.add (KeyPress (KeyPress::tabKey, ModifierKeys::shiftModifier | ModifierKeys::ctrlModifier, 0));
  535. break;
  536. case CommandIDs::goToNextWindow:
  537. result.setInfo ("Next Window", "Activates the next window", CommandCategories::general, 0);
  538. result.setActive (ProjucerApplication::getApp().mainWindowList.windows.size() > 1);
  539. result.defaultKeypresses.add (KeyPress (KeyPress::tabKey, ModifierKeys::ctrlModifier, 0));
  540. break;
  541. default:
  542. break;
  543. }
  544. }
  545. bool MainWindow::perform (const InvocationInfo& info)
  546. {
  547. switch (info.commandID)
  548. {
  549. case CommandIDs::closeWindow:
  550. closeButtonPressed();
  551. break;
  552. case CommandIDs::goToPreviousWindow:
  553. ProjucerApplication::getApp().mainWindowList.goToSiblingWindow (this, -1);
  554. break;
  555. case CommandIDs::goToNextWindow:
  556. ProjucerApplication::getApp().mainWindowList.goToSiblingWindow (this, 1);
  557. break;
  558. default:
  559. return false;
  560. }
  561. return true;
  562. }
  563. void MainWindow::valueChanged (Value& value)
  564. {
  565. if (value == projectNameValue)
  566. setName (currentProject != nullptr ? currentProject->getProjectNameString() + " - Projucer"
  567. : "Projucer");
  568. }
  569. void MainWindow::changeListenerCallback (ChangeBroadcaster* source)
  570. {
  571. auto* project = getProject();
  572. if (source == project)
  573. if (auto* peer = getPeer())
  574. peer->setHasChangedSinceSaved (project != nullptr ? project->hasChangedSinceSaved()
  575. : false);
  576. }
  577. //==============================================================================
  578. MainWindowList::MainWindowList()
  579. {
  580. }
  581. void MainWindowList::forceCloseAllWindows()
  582. {
  583. windows.clear();
  584. }
  585. static void askAllWindowsToCloseRecursive (WeakReference<MainWindowList> parent, std::function<void (bool)> callback)
  586. {
  587. if (parent->windows.size() == 0)
  588. {
  589. if (callback != nullptr)
  590. callback (true);
  591. return;
  592. }
  593. parent->windows[0]->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes, [parent, callback] (bool closedSuccessfully)
  594. {
  595. if (parent == nullptr)
  596. return;
  597. if (! closedSuccessfully)
  598. {
  599. if (callback != nullptr)
  600. callback (false);
  601. return;
  602. }
  603. parent->windows.remove (0);
  604. askAllWindowsToCloseRecursive (parent, std::move (callback));
  605. });
  606. }
  607. void MainWindowList::askAllWindowsToClose (std::function<void (bool)> callback)
  608. {
  609. saveCurrentlyOpenProjectList();
  610. askAllWindowsToCloseRecursive (this, std::move (callback));
  611. }
  612. void MainWindowList::createWindowIfNoneAreOpen()
  613. {
  614. if (windows.isEmpty())
  615. createNewMainWindow()->showStartPage();
  616. }
  617. void MainWindowList::closeWindow (MainWindow* w)
  618. {
  619. jassert (windows.contains (w));
  620. #if ! JUCE_MAC
  621. if (windows.size() == 1 && ! isInReopenLastProjects)
  622. {
  623. JUCEApplicationBase::getInstance()->systemRequestedQuit();
  624. }
  625. else
  626. #endif
  627. {
  628. w->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes,
  629. [parent = WeakReference<MainWindowList> { this }, w] (bool closedSuccessfully)
  630. {
  631. if (parent == nullptr)
  632. return;
  633. if (closedSuccessfully)
  634. {
  635. parent->windows.removeObject (w);
  636. parent->saveCurrentlyOpenProjectList();
  637. }
  638. });
  639. }
  640. }
  641. void MainWindowList::goToSiblingWindow (MainWindow* w, int delta)
  642. {
  643. auto index = windows.indexOf (w);
  644. if (index >= 0)
  645. if (auto* next = windows[(index + delta + windows.size()) % windows.size()])
  646. next->toFront (true);
  647. }
  648. void MainWindowList::openDocument (OpenDocumentManager::Document* doc, bool grabFocus)
  649. {
  650. auto& desktop = Desktop::getInstance();
  651. for (int i = desktop.getNumComponents(); --i >= 0;)
  652. {
  653. if (auto* mw = dynamic_cast<MainWindow*> (desktop.getComponent(i)))
  654. {
  655. if (auto* pcc = mw->getProjectContentComponent())
  656. {
  657. if (pcc->hasFileInRecentList (doc->getFile()))
  658. {
  659. mw->toFront (true);
  660. mw->getProjectContentComponent()->showDocument (doc, grabFocus);
  661. return;
  662. }
  663. }
  664. }
  665. }
  666. getFrontmostWindow()->getProjectContentComponent()->showDocument (doc, grabFocus);
  667. }
  668. void MainWindowList::openFile (const File& file, std::function<void (bool)> callback, bool openInBackground)
  669. {
  670. if (! file.exists())
  671. {
  672. if (callback != nullptr)
  673. callback (false);
  674. return;
  675. }
  676. for (auto* w : windows)
  677. {
  678. if (w->getProject() != nullptr && w->getProject()->getFile() == file)
  679. {
  680. w->toFront (true);
  681. if (callback != nullptr)
  682. callback (true);
  683. return;
  684. }
  685. }
  686. WeakReference<MainWindowList> parent { this };
  687. if (file.hasFileExtension (Project::projectFileExtension)
  688. || isPIPFile (file))
  689. {
  690. WeakReference<Component> previousFrontWindow (getFrontmostWindow());
  691. auto* w = getOrCreateEmptyWindow();
  692. jassert (w != nullptr);
  693. w->openFile (file, [parent, previousFrontWindow, w, openInBackground, callback] (bool openedSuccessfully)
  694. {
  695. if (parent == nullptr)
  696. return;
  697. if (openedSuccessfully)
  698. {
  699. w->makeVisible();
  700. w->setResizable (true, false);
  701. parent->checkWindowBounds (*w);
  702. if (openInBackground && previousFrontWindow != nullptr)
  703. previousFrontWindow->toFront (true);
  704. }
  705. else
  706. {
  707. parent->closeWindow (w);
  708. }
  709. if (callback != nullptr)
  710. callback (openedSuccessfully);
  711. });
  712. return;
  713. }
  714. getFrontmostWindow()->openFile (file, [parent, callback] (bool openedSuccessfully)
  715. {
  716. if (parent != nullptr && callback != nullptr)
  717. callback (openedSuccessfully);
  718. });
  719. }
  720. MainWindow* MainWindowList::createNewMainWindow()
  721. {
  722. windows.add (new MainWindow());
  723. return windows.getLast();
  724. }
  725. MainWindow* MainWindowList::getFrontmostWindow (bool createIfNotFound)
  726. {
  727. if (windows.isEmpty())
  728. {
  729. if (createIfNotFound)
  730. {
  731. auto* w = createNewMainWindow();
  732. jassert (w != nullptr);
  733. w->makeVisible();
  734. checkWindowBounds (*w);
  735. return w;
  736. }
  737. return nullptr;
  738. }
  739. for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
  740. {
  741. auto* mw = dynamic_cast<MainWindow*> (Desktop::getInstance().getComponent (i));
  742. if (windows.contains (mw))
  743. return mw;
  744. }
  745. return windows.getLast();
  746. }
  747. MainWindow* MainWindowList::getOrCreateEmptyWindow()
  748. {
  749. if (windows.size() == 0)
  750. return createNewMainWindow();
  751. for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
  752. {
  753. auto* mw = dynamic_cast<MainWindow*> (Desktop::getInstance().getComponent (i));
  754. if (windows.contains (mw) && mw->getProject() == nullptr)
  755. return mw;
  756. }
  757. return createNewMainWindow();
  758. }
  759. MainWindow* MainWindowList::getMainWindowForFile (const File& file)
  760. {
  761. if (windows.size() > 0)
  762. {
  763. for (auto* window : windows)
  764. {
  765. if (auto* project = window->getProject())
  766. {
  767. if (project->getFile() == file)
  768. return window;
  769. }
  770. }
  771. }
  772. return nullptr;
  773. }
  774. MainWindow* MainWindowList::getMainWindowWithLoginFormOpen()
  775. {
  776. for (auto* window : windows)
  777. if (window->isShowingLoginForm())
  778. return window;
  779. return nullptr;
  780. }
  781. void MainWindowList::checkWindowBounds (MainWindow& windowToCheck)
  782. {
  783. auto avoidSuperimposedWindows = [&]
  784. {
  785. for (auto* otherWindow : windows)
  786. {
  787. if (otherWindow == nullptr || otherWindow == &windowToCheck)
  788. continue;
  789. auto boundsToCheck = windowToCheck.getScreenBounds();
  790. auto otherBounds = otherWindow->getScreenBounds();
  791. if (std::abs (boundsToCheck.getX() - otherBounds.getX()) < 3
  792. && std::abs (boundsToCheck.getY() - otherBounds.getY()) < 3
  793. && std::abs (boundsToCheck.getRight() - otherBounds.getRight()) < 3
  794. && std::abs (boundsToCheck.getBottom() - otherBounds.getBottom()) < 3)
  795. {
  796. int dx = 40, dy = 30;
  797. if (otherBounds.getCentreX() >= boundsToCheck.getCentreX()) dx = -dx;
  798. if (otherBounds.getCentreY() >= boundsToCheck.getCentreY()) dy = -dy;
  799. windowToCheck.setBounds (boundsToCheck.translated (dx, dy));
  800. }
  801. }
  802. };
  803. auto ensureWindowIsFullyOnscreen = [&]
  804. {
  805. auto windowBounds = windowToCheck.getScreenBounds();
  806. auto screenLimits = Desktop::getInstance().getDisplays().getDisplayForRect (windowBounds)->userArea;
  807. if (auto* peer = windowToCheck.getPeer())
  808. if (const auto frameSize = peer->getFrameSizeIfPresent())
  809. frameSize->subtractFrom (screenLimits);
  810. auto constrainedX = jlimit (screenLimits.getX(), jmax (screenLimits.getX(), screenLimits.getRight() - windowBounds.getWidth()), windowBounds.getX());
  811. auto constrainedY = jlimit (screenLimits.getY(), jmax (screenLimits.getY(), screenLimits.getBottom() - windowBounds.getHeight()), windowBounds.getY());
  812. Point<int> constrainedTopLeft (constrainedX, constrainedY);
  813. if (windowBounds.getPosition() != constrainedTopLeft)
  814. windowToCheck.setTopLeftPosition (constrainedTopLeft);
  815. };
  816. avoidSuperimposedWindows();
  817. ensureWindowIsFullyOnscreen();
  818. }
  819. void MainWindowList::saveCurrentlyOpenProjectList()
  820. {
  821. Array<File> projects;
  822. auto& desktop = Desktop::getInstance();
  823. for (int i = 0; i < desktop.getNumComponents(); ++i)
  824. {
  825. if (auto* mw = dynamic_cast<MainWindow*> (desktop.getComponent(i)))
  826. if (auto* p = mw->getProject())
  827. if (! p->isTemporaryProject())
  828. projects.add (p->getFile());
  829. }
  830. getAppSettings().setLastProjects (projects);
  831. }
  832. void MainWindowList::reopenLastProjects()
  833. {
  834. const ScopedValueSetter<bool> setter (isInReopenLastProjects, true);
  835. for (auto& p : getAppSettings().getLastProjects())
  836. if (p.existsAsFile())
  837. openFile (p, nullptr, true);
  838. }
  839. void MainWindowList::sendLookAndFeelChange()
  840. {
  841. for (auto* w : windows)
  842. w->sendLookAndFeelChange();
  843. }
  844. Project* MainWindowList::getFrontmostProject()
  845. {
  846. auto& desktop = Desktop::getInstance();
  847. for (int i = desktop.getNumComponents(); --i >= 0;)
  848. if (auto* mw = dynamic_cast<MainWindow*> (desktop.getComponent(i)))
  849. if (auto* p = mw->getProject())
  850. return p;
  851. return nullptr;
  852. }