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.

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