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.

859 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->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 && openInIDE == OpenInIDE::yes)
  200. ProjucerApplication::getApp().getCommandManager().invokeDirectly (CommandIDs::openInIDE, false);
  201. }
  202. void MainWindow::setProject (std::unique_ptr<Project> newProject)
  203. {
  204. if (newProject == nullptr)
  205. {
  206. if (auto* content = getProjectContentComponent())
  207. content->setProject (nullptr);
  208. currentProject.reset();
  209. }
  210. else
  211. {
  212. currentProject = std::move (newProject);
  213. createProjectContentCompIfNeeded();
  214. getProjectContentComponent()->setProject (currentProject.get());
  215. }
  216. projectNameValue.referTo (currentProject != nullptr ? currentProject->getProjectValue (Ids::name) : Value());
  217. initialiseProjectWindow();
  218. ProjucerApplication::getCommandManager().commandStatusChanged();
  219. }
  220. void MainWindow::restoreWindowPosition()
  221. {
  222. String windowState;
  223. if (currentProject != nullptr)
  224. windowState = currentProject->getStoredProperties().getValue (getProjectWindowPosName());
  225. if (windowState.isEmpty())
  226. windowState = getGlobalProperties().getValue ("lastMainWindowPos");
  227. restoreWindowStateFromString (windowState);
  228. }
  229. bool MainWindow::canOpenFile (const File& file) const
  230. {
  231. return (! file.isDirectory())
  232. && (file.hasFileExtension (Project::projectFileExtension)
  233. || ProjucerApplication::getApp().openDocumentManager.canOpenFile (file));
  234. }
  235. bool MainWindow::openFile (const File& file)
  236. {
  237. if (file.hasFileExtension (Project::projectFileExtension))
  238. {
  239. auto newDoc = std::make_unique<Project> (file);
  240. auto result = newDoc->loadFrom (file, true);
  241. if (result.wasOk() && closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes))
  242. {
  243. setProject (std::move (newDoc));
  244. currentProject->setChangedFlag (false);
  245. createProjectContentCompIfNeeded();
  246. getProjectContentComponent()->reloadLastOpenDocuments();
  247. currentProject->updateDeprecatedProjectSettingsInteractively();
  248. return true;
  249. }
  250. }
  251. else if (file.exists())
  252. {
  253. if (isPIPFile (file) && openPIP ({ file }))
  254. return true;
  255. createProjectContentCompIfNeeded();
  256. return getProjectContentComponent()->showEditorForFile (file, true);
  257. }
  258. return false;
  259. }
  260. bool MainWindow::openPIP (PIPGenerator generator)
  261. {
  262. if (! generator.hasValidPIP())
  263. return false;
  264. auto generatorResult = generator.createJucerFile();
  265. if (generatorResult != Result::ok())
  266. {
  267. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
  268. "PIP Error.",
  269. generatorResult.getErrorMessage());
  270. return false;
  271. }
  272. if (! generator.createMainCpp())
  273. {
  274. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
  275. "PIP Error.",
  276. "Failed to create Main.cpp.");
  277. return false;
  278. }
  279. if (! openFile (generator.getJucerFile()))
  280. {
  281. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
  282. "PIP Error.",
  283. "Failed to open .jucer file.");
  284. return false;
  285. }
  286. setupTemporaryPIPProject (generator);
  287. return true;
  288. }
  289. void MainWindow::setupTemporaryPIPProject (PIPGenerator& generator)
  290. {
  291. jassert (currentProject != nullptr);
  292. currentProject->setTemporaryDirectory (generator.getOutputDirectory());
  293. if (auto* pcc = getProjectContentComponent())
  294. {
  295. auto fileToDisplay = generator.getPIPFile();
  296. if (fileToDisplay != File())
  297. {
  298. pcc->showEditorForFile (fileToDisplay, true);
  299. if (auto* sourceCodeEditor = dynamic_cast <SourceCodeEditor*> (pcc->getEditorComponent()))
  300. sourceCodeEditor->editor->scrollToLine (findBestLineToScrollToForClass (StringArray::fromLines (fileToDisplay.loadFileAsString()),
  301. generator.getMainClassName(), currentProject->getProjectType().isAudioPlugin()));
  302. }
  303. }
  304. }
  305. bool MainWindow::isInterestedInFileDrag (const StringArray& filenames)
  306. {
  307. for (auto& filename : filenames)
  308. if (canOpenFile (File (filename)))
  309. return true;
  310. return false;
  311. }
  312. void MainWindow::filesDropped (const StringArray& filenames, int /*mouseX*/, int /*mouseY*/)
  313. {
  314. for (auto& filename : filenames)
  315. {
  316. const File f (filename);
  317. if (canOpenFile (f) && openFile (f))
  318. break;
  319. }
  320. }
  321. bool MainWindow::shouldDropFilesWhenDraggedExternally (const DragAndDropTarget::SourceDetails& sourceDetails,
  322. StringArray& files, bool& canMoveFiles)
  323. {
  324. if (auto* tv = dynamic_cast<TreeView*> (sourceDetails.sourceComponent.get()))
  325. {
  326. Array<JucerTreeViewBase*> selected;
  327. for (int i = tv->getNumSelectedItems(); --i >= 0;)
  328. if (auto* b = dynamic_cast<JucerTreeViewBase*> (tv->getSelectedItem(i)))
  329. selected.add (b);
  330. if (! selected.isEmpty())
  331. {
  332. for (int i = selected.size(); --i >= 0;)
  333. {
  334. if (auto* jtvb = selected.getUnchecked(i))
  335. {
  336. auto f = jtvb->getDraggableFile();
  337. if (f.existsAsFile())
  338. files.add (f.getFullPathName());
  339. }
  340. }
  341. canMoveFiles = false;
  342. return ! files.isEmpty();
  343. }
  344. }
  345. return false;
  346. }
  347. void MainWindow::activeWindowStatusChanged()
  348. {
  349. DocumentWindow::activeWindowStatusChanged();
  350. if (auto* pcc = getProjectContentComponent())
  351. pcc->updateMissingFileStatuses();
  352. ProjucerApplication::getApp().openDocumentManager.reloadModifiedFiles();
  353. }
  354. void MainWindow::initialiseProjectWindow()
  355. {
  356. setResizable (true, false);
  357. updateTitleBarIcon();
  358. }
  359. void MainWindow::showStartPage()
  360. {
  361. jassert (currentProject == nullptr);
  362. setContentOwned (new StartPageComponent ([this] (std::unique_ptr<Project>&& newProject) { setProject (std::move (newProject)); },
  363. [this] (const File& exampleFile) { openFile (exampleFile); }),
  364. true);
  365. setResizable (false, false);
  366. setName ("New Project");
  367. addToDesktop();
  368. centreWithSize (getContentComponent()->getWidth(), getContentComponent()->getHeight());
  369. setVisible (true);
  370. getContentComponent()->grabKeyboardFocus();
  371. }
  372. void MainWindow::showLoginFormOverlay()
  373. {
  374. blurOverlayComponent = std::make_unique<BlurOverlayWithComponent> (*this, std::make_unique<LoginFormComponent> (*this));
  375. loginFormOpen = true;
  376. if (auto* loginForm = blurOverlayComponent->getChildComponent (0))
  377. if (auto* handler = loginForm->getAccessibilityHandler())
  378. handler->grabFocus();
  379. }
  380. void MainWindow::hideLoginFormOverlay()
  381. {
  382. blurOverlayComponent.reset();
  383. loginFormOpen = false;
  384. }
  385. //==============================================================================
  386. ApplicationCommandTarget* MainWindow::getNextCommandTarget()
  387. {
  388. return nullptr;
  389. }
  390. void MainWindow::getAllCommands (Array <CommandID>& commands)
  391. {
  392. const CommandID ids[] =
  393. {
  394. CommandIDs::closeWindow,
  395. CommandIDs::goToPreviousWindow,
  396. CommandIDs::goToNextWindow
  397. };
  398. commands.addArray (ids, numElementsInArray (ids));
  399. }
  400. void MainWindow::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
  401. {
  402. switch (commandID)
  403. {
  404. case CommandIDs::closeWindow:
  405. result.setInfo ("Close Window", "Closes the current window", CommandCategories::general, 0);
  406. result.defaultKeypresses.add (KeyPress ('w', ModifierKeys::commandModifier, 0));
  407. break;
  408. case CommandIDs::goToPreviousWindow:
  409. result.setInfo ("Previous Window", "Activates the previous window", CommandCategories::general, 0);
  410. result.setActive (ProjucerApplication::getApp().mainWindowList.windows.size() > 1);
  411. result.defaultKeypresses.add (KeyPress (KeyPress::tabKey, ModifierKeys::shiftModifier | ModifierKeys::ctrlModifier, 0));
  412. break;
  413. case CommandIDs::goToNextWindow:
  414. result.setInfo ("Next Window", "Activates the next window", CommandCategories::general, 0);
  415. result.setActive (ProjucerApplication::getApp().mainWindowList.windows.size() > 1);
  416. result.defaultKeypresses.add (KeyPress (KeyPress::tabKey, ModifierKeys::ctrlModifier, 0));
  417. break;
  418. default:
  419. break;
  420. }
  421. }
  422. bool MainWindow::perform (const InvocationInfo& info)
  423. {
  424. switch (info.commandID)
  425. {
  426. case CommandIDs::closeWindow:
  427. closeButtonPressed();
  428. break;
  429. case CommandIDs::goToPreviousWindow:
  430. ProjucerApplication::getApp().mainWindowList.goToSiblingWindow (this, -1);
  431. break;
  432. case CommandIDs::goToNextWindow:
  433. ProjucerApplication::getApp().mainWindowList.goToSiblingWindow (this, 1);
  434. break;
  435. default:
  436. return false;
  437. }
  438. return true;
  439. }
  440. void MainWindow::valueChanged (Value& value)
  441. {
  442. if (value == projectNameValue)
  443. setName (currentProject != nullptr ? currentProject->getProjectNameString() + " - Projucer"
  444. : "Projucer");
  445. }
  446. //==============================================================================
  447. MainWindowList::MainWindowList()
  448. {
  449. }
  450. void MainWindowList::forceCloseAllWindows()
  451. {
  452. windows.clear();
  453. }
  454. bool MainWindowList::askAllWindowsToClose()
  455. {
  456. saveCurrentlyOpenProjectList();
  457. while (windows.size() > 0)
  458. {
  459. if (! windows[0]->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes))
  460. return false;
  461. windows.remove (0);
  462. }
  463. return true;
  464. }
  465. void MainWindowList::createWindowIfNoneAreOpen()
  466. {
  467. if (windows.isEmpty())
  468. createNewMainWindow()->showStartPage();
  469. }
  470. void MainWindowList::closeWindow (MainWindow* w)
  471. {
  472. jassert (windows.contains (w));
  473. #if ! JUCE_MAC
  474. if (windows.size() == 1 && ! isInReopenLastProjects)
  475. {
  476. JUCEApplicationBase::getInstance()->systemRequestedQuit();
  477. }
  478. else
  479. #endif
  480. {
  481. if (w->closeCurrentProject (OpenDocumentManager::SaveIfNeeded::yes))
  482. {
  483. windows.removeObject (w);
  484. saveCurrentlyOpenProjectList();
  485. }
  486. }
  487. }
  488. void MainWindowList::goToSiblingWindow (MainWindow* w, int delta)
  489. {
  490. auto index = windows.indexOf (w);
  491. if (index >= 0)
  492. if (auto* next = windows[(index + delta + windows.size()) % windows.size()])
  493. next->toFront (true);
  494. }
  495. void MainWindowList::openDocument (OpenDocumentManager::Document* doc, bool grabFocus)
  496. {
  497. auto& desktop = Desktop::getInstance();
  498. for (int i = desktop.getNumComponents(); --i >= 0;)
  499. {
  500. if (auto* mw = dynamic_cast<MainWindow*> (desktop.getComponent(i)))
  501. {
  502. if (auto* pcc = mw->getProjectContentComponent())
  503. {
  504. if (pcc->hasFileInRecentList (doc->getFile()))
  505. {
  506. mw->toFront (true);
  507. mw->getProjectContentComponent()->showDocument (doc, grabFocus);
  508. return;
  509. }
  510. }
  511. }
  512. }
  513. getFrontmostWindow()->getProjectContentComponent()->showDocument (doc, grabFocus);
  514. }
  515. bool MainWindowList::openFile (const File& file, bool openInBackground)
  516. {
  517. if (! file.exists())
  518. return false;
  519. for (auto* w : windows)
  520. {
  521. if (w->getProject() != nullptr && w->getProject()->getFile() == file)
  522. {
  523. w->toFront (true);
  524. return true;
  525. }
  526. }
  527. if (file.hasFileExtension (Project::projectFileExtension)
  528. || isPIPFile (file))
  529. {
  530. WeakReference<Component> previousFrontWindow (getFrontmostWindow());
  531. auto* w = getOrCreateEmptyWindow();
  532. jassert (w != nullptr);
  533. if (w->openFile (file))
  534. {
  535. w->makeVisible();
  536. w->setResizable (true, false);
  537. checkWindowBounds (*w);
  538. if (openInBackground && previousFrontWindow != nullptr)
  539. previousFrontWindow->toFront (true);
  540. return true;
  541. }
  542. closeWindow (w);
  543. return false;
  544. }
  545. return getFrontmostWindow()->openFile (file);
  546. }
  547. MainWindow* MainWindowList::createNewMainWindow()
  548. {
  549. windows.add (new MainWindow());
  550. return windows.getLast();
  551. }
  552. MainWindow* MainWindowList::getFrontmostWindow (bool createIfNotFound)
  553. {
  554. if (windows.isEmpty())
  555. {
  556. if (createIfNotFound)
  557. {
  558. auto* w = createNewMainWindow();
  559. jassert (w != nullptr);
  560. w->makeVisible();
  561. checkWindowBounds (*w);
  562. return w;
  563. }
  564. return nullptr;
  565. }
  566. for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
  567. {
  568. auto* mw = dynamic_cast<MainWindow*> (Desktop::getInstance().getComponent (i));
  569. if (windows.contains (mw))
  570. return mw;
  571. }
  572. return windows.getLast();
  573. }
  574. MainWindow* MainWindowList::getOrCreateEmptyWindow()
  575. {
  576. if (windows.size() == 0)
  577. return createNewMainWindow();
  578. for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
  579. {
  580. auto* mw = dynamic_cast<MainWindow*> (Desktop::getInstance().getComponent (i));
  581. if (windows.contains (mw) && mw->getProject() == nullptr)
  582. return mw;
  583. }
  584. return createNewMainWindow();
  585. }
  586. MainWindow* MainWindowList::getMainWindowForFile (const File& file)
  587. {
  588. if (windows.size() > 0)
  589. {
  590. for (auto* window : windows)
  591. {
  592. if (auto* project = window->getProject())
  593. {
  594. if (project->getFile() == file)
  595. return window;
  596. }
  597. }
  598. }
  599. return nullptr;
  600. }
  601. MainWindow* MainWindowList::getMainWindowWithLoginFormOpen()
  602. {
  603. for (auto* window : windows)
  604. if (window->isShowingLoginForm())
  605. return window;
  606. return nullptr;
  607. }
  608. void MainWindowList::checkWindowBounds (MainWindow& windowToCheck)
  609. {
  610. auto avoidSuperimposedWindows = [&]
  611. {
  612. for (auto* otherWindow : windows)
  613. {
  614. if (otherWindow == nullptr || otherWindow == &windowToCheck)
  615. continue;
  616. auto boundsToCheck = windowToCheck.getScreenBounds();
  617. auto otherBounds = otherWindow->getScreenBounds();
  618. if (std::abs (boundsToCheck.getX() - otherBounds.getX()) < 3
  619. && std::abs (boundsToCheck.getY() - otherBounds.getY()) < 3
  620. && std::abs (boundsToCheck.getRight() - otherBounds.getRight()) < 3
  621. && std::abs (boundsToCheck.getBottom() - otherBounds.getBottom()) < 3)
  622. {
  623. int dx = 40, dy = 30;
  624. if (otherBounds.getCentreX() >= boundsToCheck.getCentreX()) dx = -dx;
  625. if (otherBounds.getCentreY() >= boundsToCheck.getCentreY()) dy = -dy;
  626. windowToCheck.setBounds (boundsToCheck.translated (dx, dy));
  627. }
  628. }
  629. };
  630. auto ensureWindowIsFullyOnscreen = [&]
  631. {
  632. auto windowBounds = windowToCheck.getScreenBounds();
  633. auto screenLimits = Desktop::getInstance().getDisplays().getDisplayForRect (windowBounds)->userArea;
  634. if (auto* peer = windowToCheck.getPeer())
  635. peer->getFrameSize().subtractFrom (screenLimits);
  636. auto constrainedX = jlimit (screenLimits.getX(), jmax (screenLimits.getX(), screenLimits.getRight() - windowBounds.getWidth()), windowBounds.getX());
  637. auto constrainedY = jlimit (screenLimits.getY(), jmax (screenLimits.getY(), screenLimits.getBottom() - windowBounds.getHeight()), windowBounds.getY());
  638. Point<int> constrainedTopLeft (constrainedX, constrainedY);
  639. if (windowBounds.getPosition() != constrainedTopLeft)
  640. windowToCheck.setTopLeftPosition (constrainedTopLeft);
  641. };
  642. avoidSuperimposedWindows();
  643. ensureWindowIsFullyOnscreen();
  644. }
  645. void MainWindowList::saveCurrentlyOpenProjectList()
  646. {
  647. Array<File> projects;
  648. auto& desktop = Desktop::getInstance();
  649. for (int i = 0; i < desktop.getNumComponents(); ++i)
  650. {
  651. if (auto* mw = dynamic_cast<MainWindow*> (desktop.getComponent(i)))
  652. if (auto* p = mw->getProject())
  653. if (! p->isTemporaryProject())
  654. projects.add (p->getFile());
  655. }
  656. getAppSettings().setLastProjects (projects);
  657. }
  658. void MainWindowList::reopenLastProjects()
  659. {
  660. const ScopedValueSetter<bool> setter (isInReopenLastProjects, true);
  661. for (auto& p : getAppSettings().getLastProjects())
  662. if (p.existsAsFile())
  663. openFile (p, true);
  664. }
  665. void MainWindowList::sendLookAndFeelChange()
  666. {
  667. for (auto* w : windows)
  668. w->sendLookAndFeelChange();
  669. }
  670. Project* MainWindowList::getFrontmostProject()
  671. {
  672. auto& desktop = Desktop::getInstance();
  673. for (int i = desktop.getNumComponents(); --i >= 0;)
  674. if (auto* mw = dynamic_cast<MainWindow*> (desktop.getComponent(i)))
  675. if (auto* p = mw->getProject())
  676. return p;
  677. return nullptr;
  678. }