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.

867 lines
25KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  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 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #include "../Application/jucer_Headers.h"
  20. #include "jucer_Application.h"
  21. #include "jucer_MainWindow.h"
  22. #include "../Wizards/jucer_NewProjectWizardClasses.h"
  23. #include "../Utility/UI/jucer_JucerTreeViewBase.h"
  24. #include "../ProjectSaving/jucer_ProjectSaver.h"
  25. //==============================================================================
  26. MainWindow::MainWindow()
  27. : DocumentWindow (ProjucerApplication::getApp().getApplicationName(),
  28. ProjucerApplication::getApp().lookAndFeel.getCurrentColourScheme()
  29. .getUIColour (LookAndFeel_V4::ColourScheme::UIColour::windowBackground),
  30. DocumentWindow::allButtons,
  31. false)
  32. {
  33. setUsingNativeTitleBar (true);
  34. #if ! JUCE_MAC
  35. setMenuBar (ProjucerApplication::getApp().getMenuModel());
  36. #endif
  37. createProjectContentCompIfNeeded();
  38. setResizable (true, false);
  39. centreWithSize (800, 600);
  40. auto& commandManager = ProjucerApplication::getCommandManager();
  41. auto registerAllAppCommands = [&]
  42. {
  43. commandManager.registerAllCommandsForTarget (this);
  44. commandManager.registerAllCommandsForTarget (getProjectContentComponent());
  45. };
  46. auto updateAppKeyMappings = [&]
  47. {
  48. commandManager.getKeyMappings()->resetToDefaultMappings();
  49. if (auto keys = getGlobalProperties().getXmlValue ("keyMappings"))
  50. commandManager.getKeyMappings()->restoreFromXml (*keys);
  51. addKeyListener (commandManager.getKeyMappings());
  52. };
  53. registerAllAppCommands();
  54. updateAppKeyMappings();
  55. setWantsKeyboardFocus (false);
  56. getLookAndFeel().setColour (ColourSelector::backgroundColourId, Colours::transparentBlack);
  57. projectNameValue.addListener (this);
  58. setResizeLimits (600, 500, 32000, 32000);
  59. }
  60. MainWindow::~MainWindow()
  61. {
  62. #if ! JUCE_MAC
  63. setMenuBar (nullptr);
  64. #endif
  65. removeKeyListener (ProjucerApplication::getCommandManager().getKeyMappings());
  66. getGlobalProperties().setValue ("lastMainWindowPos", getWindowStateAsString());
  67. clearContentComponent();
  68. currentProject.reset();
  69. }
  70. void MainWindow::createProjectContentCompIfNeeded()
  71. {
  72. if (getProjectContentComponent() == nullptr)
  73. {
  74. clearContentComponent();
  75. setContentOwned (new ProjectContentComponent(), false);
  76. }
  77. }
  78. void MainWindow::setTitleBarIcon()
  79. {
  80. if (auto* peer = getPeer())
  81. {
  82. if (currentProject != nullptr)
  83. {
  84. peer->setRepresentedFile (currentProject->getFile());
  85. peer->setIcon (ImageCache::getFromMemory (BinaryData::juce_icon_png, BinaryData::juce_icon_pngSize));
  86. }
  87. else
  88. {
  89. peer->setRepresentedFile ({});
  90. }
  91. }
  92. }
  93. void MainWindow::makeVisible()
  94. {
  95. setVisible (true);
  96. addToDesktop();
  97. restoreWindowPosition();
  98. setTitleBarIcon();
  99. getContentComponent()->grabKeyboardFocus();
  100. }
  101. ProjectContentComponent* MainWindow::getProjectContentComponent() const
  102. {
  103. return dynamic_cast<ProjectContentComponent*> (getContentComponent());
  104. }
  105. void MainWindow::closeButtonPressed()
  106. {
  107. ProjucerApplication::getApp().mainWindowList.closeWindow (this);
  108. }
  109. bool MainWindow::closeCurrentProject (bool askUserToSave)
  110. {
  111. if (currentProject == nullptr)
  112. return true;
  113. currentProject->getStoredProperties().setValue (getProjectWindowPosName(), getWindowStateAsString());
  114. if (auto* pcc = getProjectContentComponent())
  115. {
  116. pcc->saveTreeViewState();
  117. pcc->saveOpenDocumentList();
  118. pcc->hideEditor();
  119. }
  120. if (ProjucerApplication::getApp().openDocumentManager
  121. .closeAllDocumentsUsingProject (*currentProject, askUserToSave))
  122. {
  123. if (! askUserToSave || (currentProject->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk))
  124. {
  125. setProject (nullptr);
  126. return true;
  127. }
  128. }
  129. return false;
  130. }
  131. void MainWindow::moveProject (File newProjectFileToOpen)
  132. {
  133. auto openInIDE = currentProject->shouldOpenInIDEAfterSaving();
  134. closeCurrentProject (false);
  135. openFile (newProjectFileToOpen);
  136. if (currentProject != nullptr)
  137. {
  138. ProjucerApplication::getApp().getCommandManager().invokeDirectly (openInIDE ? CommandIDs::saveAndOpenInIDE
  139. : CommandIDs::saveProject,
  140. false);
  141. }
  142. }
  143. void MainWindow::setProject (std::unique_ptr<Project> newProject)
  144. {
  145. if (newProject == nullptr)
  146. {
  147. getProjectContentComponent()->setProject (nullptr);
  148. projectNameValue.referTo (Value());
  149. currentProject.reset();
  150. }
  151. else
  152. {
  153. currentProject = std::move (newProject);
  154. createProjectContentCompIfNeeded();
  155. getProjectContentComponent()->setProject (currentProject.get());
  156. projectNameValue.referTo (currentProject->getProjectValue (Ids::name));
  157. if (auto* peer = getPeer())
  158. peer->setRepresentedFile (currentProject->getFile());
  159. }
  160. ProjucerApplication::getCommandManager().commandStatusChanged();
  161. }
  162. void MainWindow::restoreWindowPosition()
  163. {
  164. String windowState;
  165. if (currentProject != nullptr)
  166. windowState = currentProject->getStoredProperties().getValue (getProjectWindowPosName());
  167. if (windowState.isEmpty())
  168. windowState = getGlobalProperties().getValue ("lastMainWindowPos");
  169. restoreWindowStateFromString (windowState);
  170. }
  171. bool MainWindow::canOpenFile (const File& file) const
  172. {
  173. return (! file.isDirectory())
  174. && (file.hasFileExtension (Project::projectFileExtension)
  175. || ProjucerApplication::getApp().openDocumentManager.canOpenFile (file));
  176. }
  177. bool MainWindow::openFile (const File& file)
  178. {
  179. createProjectContentCompIfNeeded();
  180. if (file.hasFileExtension (Project::projectFileExtension))
  181. {
  182. auto newDoc = std::make_unique<Project> (file);
  183. auto result = newDoc->loadFrom (file, true);
  184. if (result.wasOk() && closeCurrentProject (true))
  185. {
  186. setProject (std::move (newDoc));
  187. currentProject->setChangedFlag (false);
  188. getProjectContentComponent()->reloadLastOpenDocuments();
  189. currentProject->updateDeprecatedProjectSettingsInteractively();
  190. return true;
  191. }
  192. }
  193. else if (file.exists())
  194. {
  195. return getProjectContentComponent()->showEditorForFile (file, true);
  196. }
  197. return false;
  198. }
  199. bool MainWindow::tryToOpenPIP (const File& pipFile)
  200. {
  201. PIPGenerator generator (pipFile);
  202. if (! generator.hasValidPIP())
  203. return false;
  204. auto generatorResult = generator.createJucerFile();
  205. if (generatorResult != Result::ok())
  206. {
  207. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
  208. "PIP Error.",
  209. generatorResult.getErrorMessage());
  210. return false;
  211. }
  212. if (! generator.createMainCpp())
  213. {
  214. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
  215. "PIP Error.",
  216. "Failed to create Main.cpp.");
  217. return false;
  218. }
  219. if (! ProjucerApplication::getApp().mainWindowList.openFile (generator.getJucerFile()))
  220. return false;
  221. openPIP (generator);
  222. return true;
  223. }
  224. static bool isDivider (const String& line)
  225. {
  226. auto afterIndent = line.trim();
  227. if (afterIndent.startsWith ("//") && afterIndent.length() > 20)
  228. {
  229. afterIndent = afterIndent.substring (2);
  230. if (afterIndent.containsOnly ("=")
  231. || afterIndent.containsOnly ("/")
  232. || afterIndent.containsOnly ("-"))
  233. {
  234. return true;
  235. }
  236. }
  237. return false;
  238. }
  239. static bool isEndOfCommentBlock (const String& line)
  240. {
  241. if (line.contains ("*/"))
  242. return true;
  243. return false;
  244. }
  245. static int getIndexOfCommentBlockStart (const StringArray& lines, int blockEndIndex)
  246. {
  247. for (int i = blockEndIndex; i >= 0; --i)
  248. {
  249. if (lines[i].contains ("/*"))
  250. return i;
  251. }
  252. return 0;
  253. }
  254. static int findBestLineToScrollTo (StringArray lines, StringRef className)
  255. {
  256. for (auto line : lines)
  257. {
  258. if (line.contains ("struct " + className) || line.contains ("class " + className))
  259. {
  260. auto index = lines.indexOf (line);
  261. if (isDivider (lines[index - 1]))
  262. return index - 1;
  263. if (isEndOfCommentBlock (lines[index - 1]))
  264. {
  265. auto blockStartIndex = getIndexOfCommentBlockStart (lines, index - 1);
  266. if (blockStartIndex > 0 && isDivider (lines [blockStartIndex - 1]))
  267. return blockStartIndex - 1;
  268. return blockStartIndex;
  269. }
  270. return lines.indexOf (line);
  271. }
  272. }
  273. return 0;
  274. }
  275. void MainWindow::openPIP (PIPGenerator& generator)
  276. {
  277. if (auto* window = ProjucerApplication::getApp().mainWindowList.getMainWindowForFile (generator.getJucerFile()))
  278. {
  279. if (auto* project = window->getProject())
  280. {
  281. project->setTemporaryDirectory (generator.getOutputDirectory());
  282. ProjectSaver liveBuildSaver (*project, project->getFile());
  283. liveBuildSaver.saveContentNeededForLiveBuild();
  284. if (auto* pcc = window->getProjectContentComponent())
  285. {
  286. pcc->invokeDirectly (CommandIDs::toggleBuildEnabled, true);
  287. pcc->invokeDirectly (CommandIDs::buildNow, true);
  288. pcc->invokeDirectly (CommandIDs::toggleContinuousBuild, true);
  289. auto fileToDisplay = generator.getPIPFile();
  290. if (fileToDisplay != File())
  291. {
  292. pcc->showEditorForFile (fileToDisplay, true);
  293. if (auto* sourceCodeEditor = dynamic_cast <SourceCodeEditor*> (pcc->getEditorComponent()))
  294. {
  295. sourceCodeEditor->editor->scrollToLine (findBestLineToScrollTo (StringArray::fromLines (fileToDisplay.loadFileAsString()),
  296. generator.getMainClassName()));
  297. }
  298. }
  299. }
  300. }
  301. }
  302. }
  303. bool MainWindow::isInterestedInFileDrag (const StringArray& filenames)
  304. {
  305. for (auto& filename : filenames)
  306. if (canOpenFile (File (filename)))
  307. return true;
  308. return false;
  309. }
  310. void MainWindow::filesDropped (const StringArray& filenames, int /*mouseX*/, int /*mouseY*/)
  311. {
  312. for (auto& filename : filenames)
  313. {
  314. const File f (filename);
  315. if (tryToOpenPIP (f))
  316. continue;
  317. if (! isPIPFile (f) && (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. if (auto* p = getProject())
  354. {
  355. if (p->hasProjectBeenModified())
  356. {
  357. Component::SafePointer<Component> safePointer (this);
  358. MessageManager::callAsync ([=] ()
  359. {
  360. if (safePointer == nullptr)
  361. return; // bail out if the window has been deleted
  362. auto result = AlertWindow::showOkCancelBox (AlertWindow::QuestionIcon,
  363. TRANS ("The .jucer file has been modified since the last save."),
  364. TRANS ("Do you want to keep the current project or re-load from disk?"),
  365. TRANS ("Keep"),
  366. TRANS ("Re-load from disk"));
  367. if (safePointer == nullptr)
  368. return;
  369. if (result == 0)
  370. {
  371. if (auto* project = getProject())
  372. {
  373. auto oldTemporaryDirectory = project->getTemporaryDirectory();
  374. auto projectFile = project->getFile();
  375. setProject (nullptr);
  376. openFile (projectFile);
  377. if (oldTemporaryDirectory != File())
  378. if (auto* newProject = getProject())
  379. newProject->setTemporaryDirectory (oldTemporaryDirectory);
  380. }
  381. }
  382. else
  383. {
  384. ProjucerApplication::getApp().getCommandManager().invokeDirectly (CommandIDs::saveProject, true);
  385. }
  386. });
  387. }
  388. }
  389. }
  390. void MainWindow::showStartPage()
  391. {
  392. jassert (currentProject == nullptr);
  393. setContentOwned (createNewProjectWizardComponent(), true);
  394. centreWithSize (900, 630);
  395. setVisible (true);
  396. addToDesktop();
  397. getContentComponent()->grabKeyboardFocus();
  398. }
  399. //==============================================================================
  400. ApplicationCommandTarget* MainWindow::getNextCommandTarget()
  401. {
  402. return nullptr;
  403. }
  404. void MainWindow::getAllCommands (Array <CommandID>& commands)
  405. {
  406. const CommandID ids[] =
  407. {
  408. CommandIDs::closeWindow,
  409. CommandIDs::goToPreviousWindow,
  410. CommandIDs::goToNextWindow
  411. };
  412. commands.addArray (ids, numElementsInArray (ids));
  413. }
  414. void MainWindow::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
  415. {
  416. switch (commandID)
  417. {
  418. case CommandIDs::closeWindow:
  419. result.setInfo ("Close Window", "Closes the current window", CommandCategories::general, 0);
  420. result.defaultKeypresses.add (KeyPress ('w', ModifierKeys::commandModifier, 0));
  421. break;
  422. case CommandIDs::goToPreviousWindow:
  423. result.setInfo ("Previous Window", "Activates the previous window", CommandCategories::general, 0);
  424. result.setActive (ProjucerApplication::getApp().mainWindowList.windows.size() > 1);
  425. result.defaultKeypresses.add (KeyPress (KeyPress::tabKey, ModifierKeys::shiftModifier | ModifierKeys::ctrlModifier, 0));
  426. break;
  427. case CommandIDs::goToNextWindow:
  428. result.setInfo ("Next Window", "Activates the next window", CommandCategories::general, 0);
  429. result.setActive (ProjucerApplication::getApp().mainWindowList.windows.size() > 1);
  430. result.defaultKeypresses.add (KeyPress (KeyPress::tabKey, ModifierKeys::ctrlModifier, 0));
  431. break;
  432. default:
  433. break;
  434. }
  435. }
  436. bool MainWindow::perform (const InvocationInfo& info)
  437. {
  438. switch (info.commandID)
  439. {
  440. case CommandIDs::closeWindow:
  441. closeButtonPressed();
  442. break;
  443. case CommandIDs::goToPreviousWindow:
  444. ProjucerApplication::getApp().mainWindowList.goToSiblingWindow (this, -1);
  445. break;
  446. case CommandIDs::goToNextWindow:
  447. ProjucerApplication::getApp().mainWindowList.goToSiblingWindow (this, 1);
  448. break;
  449. default:
  450. return false;
  451. }
  452. return true;
  453. }
  454. void MainWindow::valueChanged (Value&)
  455. {
  456. if (currentProject != nullptr)
  457. setName (currentProject->getProjectNameString() + " - Projucer");
  458. else
  459. setName ("Projucer");
  460. }
  461. //==============================================================================
  462. MainWindowList::MainWindowList()
  463. {
  464. }
  465. void MainWindowList::forceCloseAllWindows()
  466. {
  467. windows.clear();
  468. }
  469. bool MainWindowList::askAllWindowsToClose()
  470. {
  471. saveCurrentlyOpenProjectList();
  472. while (windows.size() > 0)
  473. {
  474. if (! windows[0]->closeCurrentProject (true))
  475. return false;
  476. windows.remove (0);
  477. }
  478. return true;
  479. }
  480. void MainWindowList::createWindowIfNoneAreOpen()
  481. {
  482. if (windows.isEmpty())
  483. createNewMainWindow()->showStartPage();
  484. }
  485. void MainWindowList::closeWindow (MainWindow* w)
  486. {
  487. jassert (windows.contains (w));
  488. #if ! JUCE_MAC
  489. if (windows.size() == 1 && ! isInReopenLastProjects)
  490. {
  491. JUCEApplicationBase::getInstance()->systemRequestedQuit();
  492. }
  493. else
  494. #endif
  495. {
  496. if (w->closeCurrentProject (true))
  497. {
  498. windows.removeObject (w);
  499. saveCurrentlyOpenProjectList();
  500. }
  501. }
  502. }
  503. void MainWindowList::goToSiblingWindow (MainWindow* w, int delta)
  504. {
  505. auto index = windows.indexOf (w);
  506. if (index >= 0)
  507. if (auto* next = windows[(index + delta + windows.size()) % windows.size()])
  508. next->toFront (true);
  509. }
  510. void MainWindowList::openDocument (OpenDocumentManager::Document* doc, bool grabFocus)
  511. {
  512. auto& desktop = Desktop::getInstance();
  513. for (int i = desktop.getNumComponents(); --i >= 0;)
  514. {
  515. if (auto* mw = dynamic_cast<MainWindow*> (desktop.getComponent(i)))
  516. {
  517. if (auto* pcc = mw->getProjectContentComponent())
  518. {
  519. if (pcc->hasFileInRecentList (doc->getFile()))
  520. {
  521. mw->toFront (true);
  522. mw->getProjectContentComponent()->showDocument (doc, grabFocus);
  523. return;
  524. }
  525. }
  526. }
  527. }
  528. getFrontmostWindow()->getProjectContentComponent()->showDocument (doc, grabFocus);
  529. }
  530. bool MainWindowList::openFile (const File& file, bool openInBackground)
  531. {
  532. for (auto* w : windows)
  533. {
  534. if (w->getProject() != nullptr && w->getProject()->getFile() == file)
  535. {
  536. w->toFront (true);
  537. return true;
  538. }
  539. }
  540. if (file.hasFileExtension (Project::projectFileExtension))
  541. {
  542. WeakReference<Component> previousFrontWindow (getFrontmostWindow());
  543. auto* w = getOrCreateEmptyWindow();
  544. jassert (w != nullptr);
  545. if (w->openFile (file))
  546. {
  547. w->makeVisible();
  548. checkWindowBounds (*w);
  549. if (openInBackground && previousFrontWindow != nullptr)
  550. previousFrontWindow->toFront (true);
  551. return true;
  552. }
  553. closeWindow (w);
  554. return false;
  555. }
  556. if (getFrontmostWindow()->tryToOpenPIP (file))
  557. return true;
  558. if (! isPIPFile (file) && file.exists())
  559. return getFrontmostWindow()->openFile (file);
  560. return false;
  561. }
  562. MainWindow* MainWindowList::createNewMainWindow()
  563. {
  564. windows.add (new MainWindow());
  565. return windows.getLast();
  566. }
  567. MainWindow* MainWindowList::getFrontmostWindow (bool createIfNotFound)
  568. {
  569. if (windows.isEmpty())
  570. {
  571. if (createIfNotFound)
  572. {
  573. auto* w = createNewMainWindow();
  574. jassert (w != nullptr);
  575. w->makeVisible();
  576. checkWindowBounds (*w);
  577. return w;
  578. }
  579. return nullptr;
  580. }
  581. for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
  582. {
  583. auto* mw = dynamic_cast<MainWindow*> (Desktop::getInstance().getComponent (i));
  584. if (windows.contains (mw))
  585. return mw;
  586. }
  587. return windows.getLast();
  588. }
  589. MainWindow* MainWindowList::getOrCreateEmptyWindow()
  590. {
  591. if (windows.size() == 0)
  592. return createNewMainWindow();
  593. for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
  594. {
  595. auto* mw = dynamic_cast<MainWindow*> (Desktop::getInstance().getComponent (i));
  596. if (windows.contains (mw) && mw->getProject() == nullptr)
  597. return mw;
  598. }
  599. return createNewMainWindow();
  600. }
  601. MainWindow* MainWindowList::getMainWindowForFile (const File& file)
  602. {
  603. if (windows.size() > 0)
  604. {
  605. for (auto* window : windows)
  606. {
  607. if (auto* project = window->getProject())
  608. {
  609. if (project->getFile() == file)
  610. return window;
  611. }
  612. }
  613. }
  614. return nullptr;
  615. }
  616. void MainWindowList::checkWindowBounds (MainWindow& windowToCheck)
  617. {
  618. auto avoidSuperimposedWindows = [&]
  619. {
  620. for (auto* otherWindow : windows)
  621. {
  622. if (otherWindow == nullptr || otherWindow == &windowToCheck)
  623. continue;
  624. auto boundsToCheck = windowToCheck.getScreenBounds();
  625. auto otherBounds = otherWindow->getScreenBounds();
  626. if (std::abs (boundsToCheck.getX() - otherBounds.getX()) < 3
  627. && std::abs (boundsToCheck.getY() - otherBounds.getY()) < 3
  628. && std::abs (boundsToCheck.getRight() - otherBounds.getRight()) < 3
  629. && std::abs (boundsToCheck.getBottom() - otherBounds.getBottom()) < 3)
  630. {
  631. int dx = 40, dy = 30;
  632. if (otherBounds.getCentreX() >= boundsToCheck.getCentreX()) dx = -dx;
  633. if (otherBounds.getCentreY() >= boundsToCheck.getCentreY()) dy = -dy;
  634. windowToCheck.setBounds (boundsToCheck.translated (dx, dy));
  635. }
  636. }
  637. };
  638. auto ensureWindowIsFullyOnscreen = [&]
  639. {
  640. auto windowBounds = windowToCheck.getScreenBounds();
  641. auto screenLimits = Desktop::getInstance().getDisplays().findDisplayForRect (windowBounds).userArea;
  642. if (auto* peer = windowToCheck.getPeer())
  643. peer->getFrameSize().subtractFrom (screenLimits);
  644. auto constrainedX = jlimit (screenLimits.getX(), jmax (screenLimits.getX(), screenLimits.getRight() - windowBounds.getWidth()), windowBounds.getX());
  645. auto constrainedY = jlimit (screenLimits.getY(), jmax (screenLimits.getY(), screenLimits.getBottom() - windowBounds.getHeight()), windowBounds.getY());
  646. Point<int> constrainedTopLeft (constrainedX, constrainedY);
  647. if (windowBounds.getPosition() != constrainedTopLeft)
  648. windowToCheck.setTopLeftPosition (constrainedTopLeft);
  649. };
  650. avoidSuperimposedWindows();
  651. ensureWindowIsFullyOnscreen();
  652. }
  653. void MainWindowList::saveCurrentlyOpenProjectList()
  654. {
  655. Array<File> projects;
  656. auto& desktop = Desktop::getInstance();
  657. for (int i = 0; i < desktop.getNumComponents(); ++i)
  658. {
  659. if (auto* mw = dynamic_cast<MainWindow*> (desktop.getComponent(i)))
  660. if (auto* p = mw->getProject())
  661. if (! p->isTemporaryProject())
  662. projects.add (p->getFile());
  663. }
  664. getAppSettings().setLastProjects (projects);
  665. }
  666. void MainWindowList::reopenLastProjects()
  667. {
  668. const ScopedValueSetter<bool> setter (isInReopenLastProjects, true);
  669. for (auto& p : getAppSettings().getLastProjects())
  670. if (p.existsAsFile())
  671. openFile (p, true);
  672. }
  673. void MainWindowList::sendLookAndFeelChange()
  674. {
  675. for (auto* w : windows)
  676. w->sendLookAndFeelChange();
  677. }
  678. Project* MainWindowList::getFrontmostProject()
  679. {
  680. auto& desktop = Desktop::getInstance();
  681. for (int i = desktop.getNumComponents(); --i >= 0;)
  682. if (auto* mw = dynamic_cast<MainWindow*> (desktop.getComponent(i)))
  683. if (auto* p = mw->getProject())
  684. return p;
  685. return nullptr;
  686. }