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.

865 lines
26KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - 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 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-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. }
  45. void resized() override
  46. {
  47. setBounds (mainWindow.getLocalBounds());
  48. componentToShow->centreWithSize (componentToShow->getWidth(), componentToShow->getHeight());
  49. refreshBackgroundImage();
  50. }
  51. void paint (Graphics& g) override
  52. {
  53. g.drawImage (componentImage, getLocalBounds().toFloat());
  54. }
  55. private:
  56. void componentPeerChanged() override {}
  57. void componentVisibilityChanged() override {}
  58. using ComponentMovementWatcher::componentVisibilityChanged;
  59. void componentMovedOrResized (bool, bool) override { triggerAsyncUpdate(); }
  60. using ComponentMovementWatcher::componentMovedOrResized;
  61. void handleAsyncUpdate() override { resized(); }
  62. void mouseUp (const MouseEvent& event) override
  63. {
  64. if (event.eventComponent == this)
  65. mainWindow.hideLoginFormOverlay();
  66. }
  67. void lookAndFeelChanged() override
  68. {
  69. refreshBackgroundImage();
  70. repaint();
  71. }
  72. void refreshBackgroundImage()
  73. {
  74. setVisible (false);
  75. auto parentBounds = mainWindow.getBounds();
  76. componentImage = mainWindow.createComponentSnapshot (mainWindow.getLocalBounds())
  77. .rescaled (roundToInt ((float) parentBounds.getWidth() / 1.75f),
  78. roundToInt ((float) parentBounds.getHeight() / 1.75f));
  79. kernel.applyToImage (componentImage, componentImage, getLocalBounds());
  80. setVisible (true);
  81. }
  82. //==============================================================================
  83. MainWindow& mainWindow;
  84. std::unique_ptr<Component> componentToShow;
  85. ImageConvolutionKernel kernel { 3 };
  86. Image componentImage;
  87. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BlurOverlayWithComponent)
  88. };
  89. //==============================================================================
  90. MainWindow::MainWindow()
  91. : DocumentWindow (ProjucerApplication::getApp().getApplicationName(),
  92. ProjucerApplication::getApp().lookAndFeel.getCurrentColourScheme()
  93. .getUIColour (LookAndFeel_V4::ColourScheme::UIColour::windowBackground),
  94. DocumentWindow::allButtons,
  95. false)
  96. {
  97. setUsingNativeTitleBar (true);
  98. setResizable (true, false);
  99. setResizeLimits (600, 500, 32000, 32000);
  100. #if ! JUCE_MAC
  101. setMenuBar (ProjucerApplication::getApp().getMenuModel());
  102. #endif
  103. createProjectContentCompIfNeeded();
  104. auto& commandManager = ProjucerApplication::getCommandManager();
  105. auto registerAllAppCommands = [&]
  106. {
  107. commandManager.registerAllCommandsForTarget (this);
  108. commandManager.registerAllCommandsForTarget (getProjectContentComponent());
  109. };
  110. auto updateAppKeyMappings = [&]
  111. {
  112. commandManager.getKeyMappings()->resetToDefaultMappings();
  113. if (auto keys = getGlobalProperties().getXmlValue ("keyMappings"))
  114. commandManager.getKeyMappings()->restoreFromXml (*keys);
  115. addKeyListener (commandManager.getKeyMappings());
  116. };
  117. registerAllAppCommands();
  118. updateAppKeyMappings();
  119. setWantsKeyboardFocus (false);
  120. getLookAndFeel().setColour (ColourSelector::backgroundColourId, Colours::transparentBlack);
  121. projectNameValue.addListener (this);
  122. centreWithSize (800, 600);
  123. }
  124. MainWindow::~MainWindow()
  125. {
  126. #if ! JUCE_MAC
  127. setMenuBar (nullptr);
  128. #endif
  129. removeKeyListener (ProjucerApplication::getCommandManager().getKeyMappings());
  130. // save the current size and position to our settings file..
  131. getGlobalProperties().setValue ("lastMainWindowPos", getWindowStateAsString());
  132. clearContentComponent();
  133. }
  134. void MainWindow::createProjectContentCompIfNeeded()
  135. {
  136. if (getProjectContentComponent() == nullptr)
  137. {
  138. clearContentComponent();
  139. setContentOwned (new ProjectContentComponent(), false);
  140. }
  141. }
  142. void MainWindow::updateTitleBarIcon()
  143. {
  144. if (auto* peer = getPeer())
  145. {
  146. if (currentProject != nullptr)
  147. {
  148. peer->setRepresentedFile (currentProject->getFile());
  149. peer->setIcon (ImageCache::getFromMemory (BinaryData::juce_icon_png, BinaryData::juce_icon_pngSize));
  150. }
  151. else
  152. {
  153. peer->setRepresentedFile ({});
  154. }
  155. }
  156. }
  157. void MainWindow::makeVisible()
  158. {
  159. setVisible (true);
  160. addToDesktop();
  161. restoreWindowPosition();
  162. updateTitleBarIcon();
  163. getContentComponent()->grabKeyboardFocus();
  164. }
  165. ProjectContentComponent* MainWindow::getProjectContentComponent() const
  166. {
  167. return dynamic_cast<ProjectContentComponent*> (getContentComponent());
  168. }
  169. void MainWindow::closeButtonPressed()
  170. {
  171. ProjucerApplication::getApp().mainWindowList.closeWindow (this);
  172. }
  173. bool MainWindow::closeCurrentProject (OpenDocumentManager::SaveIfNeeded askUserToSave)
  174. {
  175. if (currentProject == nullptr)
  176. return true;
  177. currentProject->getStoredProperties().setValue (getProjectWindowPosName(), getWindowStateAsString());
  178. if (auto* pcc = getProjectContentComponent())
  179. {
  180. pcc->saveTreeViewState();
  181. pcc->saveOpenDocumentList();
  182. pcc->hideEditor();
  183. }
  184. if (ProjucerApplication::getApp().openDocumentManager
  185. .closeAllDocumentsUsingProject (*currentProject, askUserToSave))
  186. {
  187. if (askUserToSave == OpenDocumentManager::SaveIfNeeded::no
  188. || (currentProject->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk))
  189. {
  190. setProject (nullptr);
  191. return true;
  192. }
  193. }
  194. return false;
  195. }
  196. void MainWindow::moveProject (File newProjectFileToOpen, OpenInIDE openInIDE)
  197. {
  198. closeCurrentProject (OpenDocumentManager::SaveIfNeeded::no);
  199. openFile (newProjectFileToOpen);
  200. if (currentProject != nullptr)
  201. ProjucerApplication::getApp().getCommandManager()
  202. .invokeDirectly (openInIDE == OpenInIDE::yes ? CommandIDs::saveAndOpenInIDE
  203. : CommandIDs::saveProject,
  204. false);
  205. }
  206. void MainWindow::setProject (std::unique_ptr<Project> newProject)
  207. {
  208. if (newProject == nullptr)
  209. {
  210. if (auto* content = getProjectContentComponent())
  211. content->setProject (nullptr);
  212. currentProject.reset();
  213. }
  214. else
  215. {
  216. currentProject = std::move (newProject);
  217. createProjectContentCompIfNeeded();
  218. getProjectContentComponent()->setProject (currentProject.get());
  219. }
  220. projectNameValue.referTo (currentProject != nullptr ? currentProject->getProjectValue (Ids::name) : Value());
  221. initialiseProjectWindow();
  222. ProjucerApplication::getCommandManager().commandStatusChanged();
  223. }
  224. void MainWindow::restoreWindowPosition()
  225. {
  226. String windowState;
  227. if (currentProject != nullptr)
  228. windowState = currentProject->getStoredProperties().getValue (getProjectWindowPosName());
  229. if (windowState.isEmpty())
  230. windowState = getGlobalProperties().getValue ("lastMainWindowPos");
  231. restoreWindowStateFromString (windowState);
  232. }
  233. bool MainWindow::canOpenFile (const File& file) const
  234. {
  235. return (! file.isDirectory())
  236. && (file.hasFileExtension (Project::projectFileExtension)
  237. || ProjucerApplication::getApp().openDocumentManager.canOpenFile (file));
  238. }
  239. bool MainWindow::openFile (const File& file)
  240. {
  241. if (file.hasFileExtension (Project::projectFileExtension))
  242. {
  243. auto newDoc = std::make_unique<Project> (file);
  244. auto result = newDoc->loadFrom (file, true);
  245. if (result.wasOk() && closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes))
  246. {
  247. setProject (std::move (newDoc));
  248. currentProject->setChangedFlag (false);
  249. createProjectContentCompIfNeeded();
  250. getProjectContentComponent()->reloadLastOpenDocuments();
  251. currentProject->updateDeprecatedProjectSettingsInteractively();
  252. return true;
  253. }
  254. }
  255. else if (file.exists())
  256. {
  257. if (isPIPFile (file) && openPIP ({ file }))
  258. return true;
  259. createProjectContentCompIfNeeded();
  260. return getProjectContentComponent()->showEditorForFile (file, true);
  261. }
  262. return false;
  263. }
  264. bool MainWindow::openPIP (PIPGenerator generator)
  265. {
  266. if (! generator.hasValidPIP())
  267. return false;
  268. auto generatorResult = generator.createJucerFile();
  269. if (generatorResult != Result::ok())
  270. {
  271. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
  272. "PIP Error.",
  273. generatorResult.getErrorMessage());
  274. return false;
  275. }
  276. if (! generator.createMainCpp())
  277. {
  278. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
  279. "PIP Error.",
  280. "Failed to create Main.cpp.");
  281. return false;
  282. }
  283. if (! openFile (generator.getJucerFile()))
  284. {
  285. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
  286. "PIP Error.",
  287. "Failed to open .jucer file.");
  288. return false;
  289. }
  290. setupTemporaryPIPProject (generator);
  291. return true;
  292. }
  293. void MainWindow::setupTemporaryPIPProject (PIPGenerator& generator)
  294. {
  295. jassert (currentProject != nullptr);
  296. currentProject->setTemporaryDirectory (generator.getOutputDirectory());
  297. ProjectSaver liveBuildSaver (*currentProject);
  298. liveBuildSaver.saveContentNeededForLiveBuild();
  299. if (auto* pcc = getProjectContentComponent())
  300. {
  301. pcc->invokeDirectly (CommandIDs::toggleBuildEnabled, true);
  302. pcc->invokeDirectly (CommandIDs::buildNow, true);
  303. pcc->invokeDirectly (CommandIDs::toggleContinuousBuild, true);
  304. auto fileToDisplay = generator.getPIPFile();
  305. if (fileToDisplay != File())
  306. {
  307. pcc->showEditorForFile (fileToDisplay, true);
  308. if (auto* sourceCodeEditor = dynamic_cast <SourceCodeEditor*> (pcc->getEditorComponent()))
  309. sourceCodeEditor->editor->scrollToLine (findBestLineToScrollToForClass (StringArray::fromLines (fileToDisplay.loadFileAsString()),
  310. generator.getMainClassName(), currentProject->getProjectType().isAudioPlugin()));
  311. }
  312. }
  313. }
  314. bool MainWindow::isInterestedInFileDrag (const StringArray& filenames)
  315. {
  316. for (auto& filename : filenames)
  317. if (canOpenFile (File (filename)))
  318. return true;
  319. return false;
  320. }
  321. void MainWindow::filesDropped (const StringArray& filenames, int /*mouseX*/, int /*mouseY*/)
  322. {
  323. for (auto& filename : filenames)
  324. {
  325. const File f (filename);
  326. if (canOpenFile (f) && openFile (f))
  327. break;
  328. }
  329. }
  330. bool MainWindow::shouldDropFilesWhenDraggedExternally (const DragAndDropTarget::SourceDetails& sourceDetails,
  331. StringArray& files, bool& canMoveFiles)
  332. {
  333. if (auto* tv = dynamic_cast<TreeView*> (sourceDetails.sourceComponent.get()))
  334. {
  335. Array<JucerTreeViewBase*> selected;
  336. for (int i = tv->getNumSelectedItems(); --i >= 0;)
  337. if (auto* b = dynamic_cast<JucerTreeViewBase*> (tv->getSelectedItem(i)))
  338. selected.add (b);
  339. if (! selected.isEmpty())
  340. {
  341. for (int i = selected.size(); --i >= 0;)
  342. {
  343. if (auto* jtvb = selected.getUnchecked(i))
  344. {
  345. auto f = jtvb->getDraggableFile();
  346. if (f.existsAsFile())
  347. files.add (f.getFullPathName());
  348. }
  349. }
  350. canMoveFiles = false;
  351. return ! files.isEmpty();
  352. }
  353. }
  354. return false;
  355. }
  356. void MainWindow::activeWindowStatusChanged()
  357. {
  358. DocumentWindow::activeWindowStatusChanged();
  359. if (auto* pcc = getProjectContentComponent())
  360. pcc->updateMissingFileStatuses();
  361. ProjucerApplication::getApp().openDocumentManager.reloadModifiedFiles();
  362. }
  363. void MainWindow::initialiseProjectWindow()
  364. {
  365. setResizable (true, false);
  366. updateTitleBarIcon();
  367. }
  368. void MainWindow::showStartPage()
  369. {
  370. jassert (currentProject == nullptr);
  371. setContentOwned (new StartPageComponent ([this] (std::unique_ptr<Project>&& newProject) { setProject (std::move (newProject)); },
  372. [this] (const File& exampleFile) { openFile (exampleFile); }),
  373. true);
  374. setResizable (false, false);
  375. setName ("New Project");
  376. addToDesktop();
  377. centreWithSize (getContentComponent()->getWidth(), getContentComponent()->getHeight());
  378. setVisible (true);
  379. getContentComponent()->grabKeyboardFocus();
  380. }
  381. void MainWindow::showLoginFormOverlay()
  382. {
  383. blurOverlayComponent = std::make_unique<BlurOverlayWithComponent> (*this, std::make_unique<LoginFormComponent> (*this));
  384. loginFormOpen = true;
  385. }
  386. void MainWindow::hideLoginFormOverlay()
  387. {
  388. blurOverlayComponent.reset();
  389. loginFormOpen = false;
  390. }
  391. //==============================================================================
  392. ApplicationCommandTarget* MainWindow::getNextCommandTarget()
  393. {
  394. return nullptr;
  395. }
  396. void MainWindow::getAllCommands (Array <CommandID>& commands)
  397. {
  398. const CommandID ids[] =
  399. {
  400. CommandIDs::closeWindow,
  401. CommandIDs::goToPreviousWindow,
  402. CommandIDs::goToNextWindow
  403. };
  404. commands.addArray (ids, numElementsInArray (ids));
  405. }
  406. void MainWindow::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
  407. {
  408. switch (commandID)
  409. {
  410. case CommandIDs::closeWindow:
  411. result.setInfo ("Close Window", "Closes the current window", CommandCategories::general, 0);
  412. result.defaultKeypresses.add (KeyPress ('w', ModifierKeys::commandModifier, 0));
  413. break;
  414. case CommandIDs::goToPreviousWindow:
  415. result.setInfo ("Previous Window", "Activates the previous window", CommandCategories::general, 0);
  416. result.setActive (ProjucerApplication::getApp().mainWindowList.windows.size() > 1);
  417. result.defaultKeypresses.add (KeyPress (KeyPress::tabKey, ModifierKeys::shiftModifier | ModifierKeys::ctrlModifier, 0));
  418. break;
  419. case CommandIDs::goToNextWindow:
  420. result.setInfo ("Next Window", "Activates the next window", CommandCategories::general, 0);
  421. result.setActive (ProjucerApplication::getApp().mainWindowList.windows.size() > 1);
  422. result.defaultKeypresses.add (KeyPress (KeyPress::tabKey, ModifierKeys::ctrlModifier, 0));
  423. break;
  424. default:
  425. break;
  426. }
  427. }
  428. bool MainWindow::perform (const InvocationInfo& info)
  429. {
  430. switch (info.commandID)
  431. {
  432. case CommandIDs::closeWindow:
  433. closeButtonPressed();
  434. break;
  435. case CommandIDs::goToPreviousWindow:
  436. ProjucerApplication::getApp().mainWindowList.goToSiblingWindow (this, -1);
  437. break;
  438. case CommandIDs::goToNextWindow:
  439. ProjucerApplication::getApp().mainWindowList.goToSiblingWindow (this, 1);
  440. break;
  441. default:
  442. return false;
  443. }
  444. return true;
  445. }
  446. void MainWindow::valueChanged (Value& value)
  447. {
  448. if (value == projectNameValue)
  449. setName (currentProject != nullptr ? currentProject->getProjectNameString() + " - Projucer"
  450. : "Projucer");
  451. }
  452. //==============================================================================
  453. MainWindowList::MainWindowList()
  454. {
  455. }
  456. void MainWindowList::forceCloseAllWindows()
  457. {
  458. windows.clear();
  459. }
  460. bool MainWindowList::askAllWindowsToClose()
  461. {
  462. saveCurrentlyOpenProjectList();
  463. while (windows.size() > 0)
  464. {
  465. if (! windows[0]->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes))
  466. return false;
  467. windows.remove (0);
  468. }
  469. return true;
  470. }
  471. void MainWindowList::createWindowIfNoneAreOpen()
  472. {
  473. if (windows.isEmpty())
  474. createNewMainWindow()->showStartPage();
  475. }
  476. void MainWindowList::closeWindow (MainWindow* w)
  477. {
  478. jassert (windows.contains (w));
  479. #if ! JUCE_MAC
  480. if (windows.size() == 1 && ! isInReopenLastProjects)
  481. {
  482. JUCEApplicationBase::getInstance()->systemRequestedQuit();
  483. }
  484. else
  485. #endif
  486. {
  487. if (w->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes))
  488. {
  489. windows.removeObject (w);
  490. saveCurrentlyOpenProjectList();
  491. }
  492. }
  493. }
  494. void MainWindowList::goToSiblingWindow (MainWindow* w, int delta)
  495. {
  496. auto index = windows.indexOf (w);
  497. if (index >= 0)
  498. if (auto* next = windows[(index + delta + windows.size()) % windows.size()])
  499. next->toFront (true);
  500. }
  501. void MainWindowList::openDocument (OpenDocumentManager::Document* doc, bool grabFocus)
  502. {
  503. auto& desktop = Desktop::getInstance();
  504. for (int i = desktop.getNumComponents(); --i >= 0;)
  505. {
  506. if (auto* mw = dynamic_cast<MainWindow*> (desktop.getComponent(i)))
  507. {
  508. if (auto* pcc = mw->getProjectContentComponent())
  509. {
  510. if (pcc->hasFileInRecentList (doc->getFile()))
  511. {
  512. mw->toFront (true);
  513. mw->getProjectContentComponent()->showDocument (doc, grabFocus);
  514. return;
  515. }
  516. }
  517. }
  518. }
  519. getFrontmostWindow()->getProjectContentComponent()->showDocument (doc, grabFocus);
  520. }
  521. bool MainWindowList::openFile (const File& file, bool openInBackground)
  522. {
  523. if (! file.exists())
  524. return false;
  525. for (auto* w : windows)
  526. {
  527. if (w->getProject() != nullptr && w->getProject()->getFile() == file)
  528. {
  529. w->toFront (true);
  530. return true;
  531. }
  532. }
  533. if (file.hasFileExtension (Project::projectFileExtension))
  534. {
  535. WeakReference<Component> previousFrontWindow (getFrontmostWindow());
  536. auto* w = getOrCreateEmptyWindow();
  537. jassert (w != nullptr);
  538. if (w->openFile (file))
  539. {
  540. w->makeVisible();
  541. w->setResizable (true, false);
  542. checkWindowBounds (*w);
  543. if (openInBackground && previousFrontWindow != nullptr)
  544. previousFrontWindow->toFront (true);
  545. return true;
  546. }
  547. closeWindow (w);
  548. return false;
  549. }
  550. return getFrontmostWindow()->openFile (file);
  551. }
  552. MainWindow* MainWindowList::createNewMainWindow()
  553. {
  554. windows.add (new MainWindow());
  555. return windows.getLast();
  556. }
  557. MainWindow* MainWindowList::getFrontmostWindow (bool createIfNotFound)
  558. {
  559. if (windows.isEmpty())
  560. {
  561. if (createIfNotFound)
  562. {
  563. auto* w = createNewMainWindow();
  564. jassert (w != nullptr);
  565. w->makeVisible();
  566. checkWindowBounds (*w);
  567. return w;
  568. }
  569. return nullptr;
  570. }
  571. for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
  572. {
  573. auto* mw = dynamic_cast<MainWindow*> (Desktop::getInstance().getComponent (i));
  574. if (windows.contains (mw))
  575. return mw;
  576. }
  577. return windows.getLast();
  578. }
  579. MainWindow* MainWindowList::getOrCreateEmptyWindow()
  580. {
  581. if (windows.size() == 0)
  582. return createNewMainWindow();
  583. for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
  584. {
  585. auto* mw = dynamic_cast<MainWindow*> (Desktop::getInstance().getComponent (i));
  586. if (windows.contains (mw) && mw->getProject() == nullptr)
  587. return mw;
  588. }
  589. return createNewMainWindow();
  590. }
  591. MainWindow* MainWindowList::getMainWindowForFile (const File& file)
  592. {
  593. if (windows.size() > 0)
  594. {
  595. for (auto* window : windows)
  596. {
  597. if (auto* project = window->getProject())
  598. {
  599. if (project->getFile() == file)
  600. return window;
  601. }
  602. }
  603. }
  604. return nullptr;
  605. }
  606. MainWindow* MainWindowList::getMainWindowWithLoginFormOpen()
  607. {
  608. for (auto* window : windows)
  609. if (window->isShowingLoginForm())
  610. return window;
  611. return nullptr;
  612. }
  613. void MainWindowList::checkWindowBounds (MainWindow& windowToCheck)
  614. {
  615. auto avoidSuperimposedWindows = [&]
  616. {
  617. for (auto* otherWindow : windows)
  618. {
  619. if (otherWindow == nullptr || otherWindow == &windowToCheck)
  620. continue;
  621. auto boundsToCheck = windowToCheck.getScreenBounds();
  622. auto otherBounds = otherWindow->getScreenBounds();
  623. if (std::abs (boundsToCheck.getX() - otherBounds.getX()) < 3
  624. && std::abs (boundsToCheck.getY() - otherBounds.getY()) < 3
  625. && std::abs (boundsToCheck.getRight() - otherBounds.getRight()) < 3
  626. && std::abs (boundsToCheck.getBottom() - otherBounds.getBottom()) < 3)
  627. {
  628. int dx = 40, dy = 30;
  629. if (otherBounds.getCentreX() >= boundsToCheck.getCentreX()) dx = -dx;
  630. if (otherBounds.getCentreY() >= boundsToCheck.getCentreY()) dy = -dy;
  631. windowToCheck.setBounds (boundsToCheck.translated (dx, dy));
  632. }
  633. }
  634. };
  635. auto ensureWindowIsFullyOnscreen = [&]
  636. {
  637. auto windowBounds = windowToCheck.getScreenBounds();
  638. auto screenLimits = Desktop::getInstance().getDisplays().findDisplayForRect (windowBounds).userArea;
  639. if (auto* peer = windowToCheck.getPeer())
  640. peer->getFrameSize().subtractFrom (screenLimits);
  641. auto constrainedX = jlimit (screenLimits.getX(), jmax (screenLimits.getX(), screenLimits.getRight() - windowBounds.getWidth()), windowBounds.getX());
  642. auto constrainedY = jlimit (screenLimits.getY(), jmax (screenLimits.getY(), screenLimits.getBottom() - windowBounds.getHeight()), windowBounds.getY());
  643. Point<int> constrainedTopLeft (constrainedX, constrainedY);
  644. if (windowBounds.getPosition() != constrainedTopLeft)
  645. windowToCheck.setTopLeftPosition (constrainedTopLeft);
  646. };
  647. avoidSuperimposedWindows();
  648. ensureWindowIsFullyOnscreen();
  649. }
  650. void MainWindowList::saveCurrentlyOpenProjectList()
  651. {
  652. Array<File> projects;
  653. auto& desktop = Desktop::getInstance();
  654. for (int i = 0; i < desktop.getNumComponents(); ++i)
  655. {
  656. if (auto* mw = dynamic_cast<MainWindow*> (desktop.getComponent(i)))
  657. if (auto* p = mw->getProject())
  658. if (! p->isTemporaryProject())
  659. projects.add (p->getFile());
  660. }
  661. getAppSettings().setLastProjects (projects);
  662. }
  663. void MainWindowList::reopenLastProjects()
  664. {
  665. const ScopedValueSetter<bool> setter (isInReopenLastProjects, true);
  666. for (auto& p : getAppSettings().getLastProjects())
  667. if (p.existsAsFile())
  668. openFile (p, true);
  669. }
  670. void MainWindowList::sendLookAndFeelChange()
  671. {
  672. for (auto* w : windows)
  673. w->sendLookAndFeelChange();
  674. }
  675. Project* MainWindowList::getFrontmostProject()
  676. {
  677. auto& desktop = Desktop::getInstance();
  678. for (int i = desktop.getNumComponents(); --i >= 0;)
  679. if (auto* mw = dynamic_cast<MainWindow*> (desktop.getComponent(i)))
  680. if (auto* p = mw->getProject())
  681. return p;
  682. return nullptr;
  683. }