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.

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