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.

854 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. ApplicationCommandManager& commandManager = ProjucerApplication::getCommandManager();
  41. // Register all the app commands..
  42. commandManager.registerAllCommandsForTarget (this);
  43. commandManager.registerAllCommandsForTarget (getProjectContentComponent());
  44. // update key mappings..
  45. {
  46. commandManager.getKeyMappings()->resetToDefaultMappings();
  47. if (auto keys = getGlobalProperties().getXmlValue ("keyMappings"))
  48. commandManager.getKeyMappings()->restoreFromXml (*keys);
  49. addKeyListener (commandManager.getKeyMappings());
  50. }
  51. // don't want the window to take focus when the title-bar is clicked..
  52. setWantsKeyboardFocus (false);
  53. getLookAndFeel().setColour (ColourSelector::backgroundColourId, Colours::transparentBlack);
  54. projectNameValue.addListener (this);
  55. setResizeLimits (600, 500, 32000, 32000);
  56. }
  57. MainWindow::~MainWindow()
  58. {
  59. #if ! JUCE_MAC
  60. setMenuBar (nullptr);
  61. #endif
  62. removeKeyListener (ProjucerApplication::getCommandManager().getKeyMappings());
  63. // save the current size and position to our settings file..
  64. getGlobalProperties().setValue ("lastMainWindowPos", getWindowStateAsString());
  65. clearContentComponent();
  66. currentProject.reset();
  67. }
  68. void MainWindow::createProjectContentCompIfNeeded()
  69. {
  70. if (getProjectContentComponent() == nullptr)
  71. {
  72. clearContentComponent();
  73. setContentOwned (new ProjectContentComponent(), false);
  74. jassert (getProjectContentComponent() != nullptr);
  75. }
  76. }
  77. void MainWindow::setTitleBarIcon()
  78. {
  79. if (auto* peer = getPeer())
  80. {
  81. if (currentProject != nullptr)
  82. {
  83. peer->setRepresentedFile (currentProject->getFile());
  84. peer->setIcon (ImageCache::getFromMemory (BinaryData::juce_icon_png, BinaryData::juce_icon_pngSize));
  85. }
  86. else
  87. {
  88. peer->setRepresentedFile ({});
  89. }
  90. }
  91. }
  92. void MainWindow::makeVisible()
  93. {
  94. restoreWindowPosition();
  95. setVisible (true);
  96. addToDesktop(); // (must add before restoring size so that fullscreen will work)
  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::closeProject (Project* project, bool askUserToSave)
  110. {
  111. jassert (project == currentProject.get() && project != nullptr);
  112. if (project == nullptr)
  113. return true;
  114. project->getStoredProperties().setValue (getProjectWindowPosName(), getWindowStateAsString());
  115. if (auto* pcc = getProjectContentComponent())
  116. {
  117. pcc->saveTreeViewState();
  118. pcc->saveOpenDocumentList();
  119. pcc->hideEditor();
  120. }
  121. if (! ProjucerApplication::getApp().openDocumentManager.closeAllDocumentsUsingProject (*project, askUserToSave))
  122. return false;
  123. if (! askUserToSave || (project->saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk))
  124. {
  125. setProject (nullptr);
  126. return true;
  127. }
  128. return false;
  129. }
  130. bool MainWindow::closeCurrentProject()
  131. {
  132. return currentProject == nullptr || closeProject (currentProject.get());
  133. }
  134. void MainWindow::moveProject (File newProjectFileToOpen)
  135. {
  136. auto openInIDE = currentProject->shouldOpenInIDEAfterSaving();
  137. closeProject (currentProject.get(), false);
  138. openFile (newProjectFileToOpen);
  139. if (currentProject != nullptr)
  140. {
  141. ProjucerApplication::getApp().getCommandManager().invokeDirectly (openInIDE ? CommandIDs::saveAndOpenInIDE
  142. : CommandIDs::saveProject,
  143. false);
  144. }
  145. }
  146. void MainWindow::setProject (Project* newProject)
  147. {
  148. createProjectContentCompIfNeeded();
  149. getProjectContentComponent()->setProject (newProject);
  150. currentProject.reset (newProject);
  151. if (currentProject != nullptr)
  152. projectNameValue.referTo (currentProject->getProjectValue (Ids::name));
  153. else
  154. projectNameValue.referTo (Value());
  155. if (newProject != nullptr)
  156. {
  157. if (auto* peer = getPeer())
  158. peer->setRepresentedFile (newProject->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. std::unique_ptr<Project> newDoc (new Project (file));
  183. auto result = newDoc->loadFrom (file, true);
  184. if (result.wasOk() && closeCurrentProject())
  185. {
  186. setProject (newDoc.get());
  187. newDoc.release()->setChangedFlag (false);
  188. jassert (getProjectContentComponent() != nullptr);
  189. getProjectContentComponent()->reloadLastOpenDocuments();
  190. if (auto* p = getProject())
  191. p->updateDeprecatedProjectSettingsInteractively();
  192. return true;
  193. }
  194. }
  195. else if (file.exists())
  196. {
  197. return getProjectContentComponent()->showEditorForFile (file, true);
  198. }
  199. return false;
  200. }
  201. bool MainWindow::tryToOpenPIP (const File& pipFile)
  202. {
  203. PIPGenerator generator (pipFile);
  204. if (! generator.hasValidPIP())
  205. return false;
  206. auto generatorResult = generator.createJucerFile();
  207. if (generatorResult != Result::ok())
  208. {
  209. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
  210. "PIP Error.",
  211. generatorResult.getErrorMessage());
  212. return false;
  213. }
  214. if (! generator.createMainCpp())
  215. {
  216. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
  217. "PIP Error.",
  218. "Failed to create Main.cpp.");
  219. return false;
  220. }
  221. if (! ProjucerApplication::getApp().mainWindowList.openFile (generator.getJucerFile()))
  222. return false;
  223. openPIP (generator);
  224. return true;
  225. }
  226. static bool isDivider (const String& line)
  227. {
  228. auto afterIndent = line.trim();
  229. if (afterIndent.startsWith ("//") && afterIndent.length() > 20)
  230. {
  231. afterIndent = afterIndent.substring (2);
  232. if (afterIndent.containsOnly ("=")
  233. || afterIndent.containsOnly ("/")
  234. || afterIndent.containsOnly ("-"))
  235. {
  236. return true;
  237. }
  238. }
  239. return false;
  240. }
  241. static bool isEndOfCommentBlock (const String& line)
  242. {
  243. if (line.contains ("*/"))
  244. return true;
  245. return false;
  246. }
  247. static int getIndexOfCommentBlockStart (const StringArray& lines, int blockEndIndex)
  248. {
  249. for (int i = blockEndIndex; i >= 0; --i)
  250. {
  251. if (lines[i].contains ("/*"))
  252. return i;
  253. }
  254. return 0;
  255. }
  256. static int findBestLineToScrollTo (StringArray lines, StringRef className)
  257. {
  258. for (auto line : lines)
  259. {
  260. if (line.contains ("struct " + className) || line.contains ("class " + className))
  261. {
  262. auto index = lines.indexOf (line);
  263. if (isDivider (lines[index - 1]))
  264. return index - 1;
  265. if (isEndOfCommentBlock (lines[index - 1]))
  266. {
  267. auto blockStartIndex = getIndexOfCommentBlockStart (lines, index - 1);
  268. if (blockStartIndex > 0 && isDivider (lines [blockStartIndex - 1]))
  269. return blockStartIndex - 1;
  270. return blockStartIndex;
  271. }
  272. return lines.indexOf (line);
  273. }
  274. }
  275. return 0;
  276. }
  277. void MainWindow::openPIP (PIPGenerator& generator)
  278. {
  279. if (auto* window = ProjucerApplication::getApp().mainWindowList.getMainWindowForFile (generator.getJucerFile()))
  280. {
  281. if (auto* project = window->getProject())
  282. {
  283. project->setTemporaryDirectory (generator.getOutputDirectory());
  284. ProjectSaver liveBuildSaver (*project, project->getFile());
  285. liveBuildSaver.saveContentNeededForLiveBuild();
  286. if (auto* pcc = window->getProjectContentComponent())
  287. {
  288. pcc->invokeDirectly (CommandIDs::toggleBuildEnabled, true);
  289. pcc->invokeDirectly (CommandIDs::buildNow, true);
  290. pcc->invokeDirectly (CommandIDs::toggleContinuousBuild, true);
  291. auto fileToDisplay = generator.getPIPFile();
  292. if (fileToDisplay != File())
  293. {
  294. pcc->showEditorForFile (fileToDisplay, true);
  295. if (auto* sourceCodeEditor = dynamic_cast <SourceCodeEditor*> (pcc->getEditorComponent()))
  296. {
  297. sourceCodeEditor->editor->scrollToLine (findBestLineToScrollTo (StringArray::fromLines (fileToDisplay.loadFileAsString()),
  298. generator.getMainClassName()));
  299. }
  300. }
  301. }
  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 (tryToOpenPIP (f))
  318. continue;
  319. if (! isPIPFile (f) && (canOpenFile (f) && openFile (f)))
  320. break;
  321. }
  322. }
  323. bool MainWindow::shouldDropFilesWhenDraggedExternally (const DragAndDropTarget::SourceDetails& sourceDetails,
  324. StringArray& files, bool& canMoveFiles)
  325. {
  326. if (auto* tv = dynamic_cast<TreeView*> (sourceDetails.sourceComponent.get()))
  327. {
  328. Array<JucerTreeViewBase*> selected;
  329. for (int i = tv->getNumSelectedItems(); --i >= 0;)
  330. if (auto* b = dynamic_cast<JucerTreeViewBase*> (tv->getSelectedItem(i)))
  331. selected.add (b);
  332. if (! selected.isEmpty())
  333. {
  334. for (int i = selected.size(); --i >= 0;)
  335. {
  336. if (auto* jtvb = selected.getUnchecked(i))
  337. {
  338. auto f = jtvb->getDraggableFile();
  339. if (f.existsAsFile())
  340. files.add (f.getFullPathName());
  341. }
  342. }
  343. canMoveFiles = false;
  344. return ! files.isEmpty();
  345. }
  346. }
  347. return false;
  348. }
  349. void MainWindow::activeWindowStatusChanged()
  350. {
  351. DocumentWindow::activeWindowStatusChanged();
  352. if (auto* pcc = getProjectContentComponent())
  353. pcc->updateMissingFileStatuses();
  354. ProjucerApplication::getApp().openDocumentManager.reloadModifiedFiles();
  355. if (auto* p = getProject())
  356. {
  357. if (p->hasProjectBeenModified())
  358. {
  359. Component::SafePointer<Component> safePointer (this);
  360. MessageManager::callAsync ([=] ()
  361. {
  362. if (safePointer == nullptr)
  363. return; // bail out if the window has been deleted
  364. auto result = AlertWindow::showOkCancelBox (AlertWindow::QuestionIcon,
  365. TRANS ("The .jucer file has been modified since the last save."),
  366. TRANS ("Do you want to keep the current project or re-load from disk?"),
  367. TRANS ("Keep"),
  368. TRANS ("Re-load from disk"));
  369. if (safePointer == nullptr)
  370. return;
  371. if (result == 0)
  372. {
  373. if (auto* project = getProject())
  374. {
  375. auto oldTemporaryDirectory = project->getTemporaryDirectory();
  376. auto projectFile = project->getFile();
  377. setProject (nullptr);
  378. openFile (projectFile);
  379. if (oldTemporaryDirectory != File())
  380. if (auto* newProject = getProject())
  381. newProject->setTemporaryDirectory (oldTemporaryDirectory);
  382. }
  383. }
  384. else
  385. {
  386. ProjucerApplication::getApp().getCommandManager().invokeDirectly (CommandIDs::saveProject, true);
  387. }
  388. });
  389. }
  390. }
  391. }
  392. void MainWindow::showStartPage()
  393. {
  394. jassert (currentProject == nullptr);
  395. setContentOwned (createNewProjectWizardComponent(), true);
  396. centreWithSize (900, 630);
  397. setVisible (true);
  398. addToDesktop();
  399. getContentComponent()->grabKeyboardFocus();
  400. }
  401. //==============================================================================
  402. ApplicationCommandTarget* MainWindow::getNextCommandTarget()
  403. {
  404. return nullptr;
  405. }
  406. void MainWindow::getAllCommands (Array <CommandID>& commands)
  407. {
  408. const CommandID ids[] =
  409. {
  410. CommandIDs::closeWindow,
  411. CommandIDs::goToPreviousWindow,
  412. CommandIDs::goToNextWindow
  413. };
  414. commands.addArray (ids, numElementsInArray (ids));
  415. }
  416. void MainWindow::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
  417. {
  418. switch (commandID)
  419. {
  420. case CommandIDs::closeWindow:
  421. result.setInfo ("Close Window", "Closes the current window", CommandCategories::general, 0);
  422. result.defaultKeypresses.add (KeyPress ('w', ModifierKeys::commandModifier, 0));
  423. break;
  424. case CommandIDs::goToPreviousWindow:
  425. result.setInfo ("Previous Window", "Activates the previous window", CommandCategories::general, 0);
  426. result.setActive (ProjucerApplication::getApp().mainWindowList.windows.size() > 1);
  427. result.defaultKeypresses.add (KeyPress (KeyPress::tabKey, ModifierKeys::shiftModifier | ModifierKeys::ctrlModifier, 0));
  428. break;
  429. case CommandIDs::goToNextWindow:
  430. result.setInfo ("Next Window", "Activates the next window", CommandCategories::general, 0);
  431. result.setActive (ProjucerApplication::getApp().mainWindowList.windows.size() > 1);
  432. result.defaultKeypresses.add (KeyPress (KeyPress::tabKey, ModifierKeys::ctrlModifier, 0));
  433. break;
  434. default:
  435. break;
  436. }
  437. }
  438. bool MainWindow::perform (const InvocationInfo& info)
  439. {
  440. switch (info.commandID)
  441. {
  442. case CommandIDs::closeWindow:
  443. closeButtonPressed();
  444. break;
  445. case CommandIDs::goToPreviousWindow:
  446. ProjucerApplication::getApp().mainWindowList.goToSiblingWindow (this, -1);
  447. break;
  448. case CommandIDs::goToNextWindow:
  449. ProjucerApplication::getApp().mainWindowList.goToSiblingWindow (this, 1);
  450. break;
  451. default:
  452. return false;
  453. }
  454. return true;
  455. }
  456. void MainWindow::valueChanged (Value&)
  457. {
  458. if (currentProject != nullptr)
  459. setName (currentProject->getProjectNameString() + " - Projucer");
  460. else
  461. setName ("Projucer");
  462. }
  463. //==============================================================================
  464. MainWindowList::MainWindowList()
  465. {
  466. }
  467. void MainWindowList::forceCloseAllWindows()
  468. {
  469. windows.clear();
  470. }
  471. bool MainWindowList::askAllWindowsToClose()
  472. {
  473. saveCurrentlyOpenProjectList();
  474. while (windows.size() > 0)
  475. {
  476. if (! windows[0]->closeCurrentProject())
  477. return false;
  478. windows.remove (0);
  479. }
  480. return true;
  481. }
  482. void MainWindowList::createWindowIfNoneAreOpen()
  483. {
  484. if (windows.size() == 0)
  485. createNewMainWindow()->showStartPage();
  486. }
  487. void MainWindowList::closeWindow (MainWindow* w)
  488. {
  489. jassert (windows.contains (w));
  490. #if ! JUCE_MAC
  491. if (windows.size() == 1 && ! isInReopenLastProjects)
  492. {
  493. JUCEApplicationBase::getInstance()->systemRequestedQuit();
  494. }
  495. else
  496. #endif
  497. {
  498. if (w->closeCurrentProject())
  499. {
  500. windows.removeObject (w);
  501. saveCurrentlyOpenProjectList();
  502. }
  503. }
  504. }
  505. void MainWindowList::goToSiblingWindow (MainWindow* w, int delta)
  506. {
  507. auto index = windows.indexOf (w);
  508. if (index >= 0)
  509. if (auto* next = windows[(index + delta + windows.size()) % windows.size()])
  510. next->toFront (true);
  511. }
  512. void MainWindowList::openDocument (OpenDocumentManager::Document* doc, bool grabFocus)
  513. {
  514. auto& desktop = Desktop::getInstance();
  515. for (int i = desktop.getNumComponents(); --i >= 0;)
  516. {
  517. if (auto* mw = dynamic_cast<MainWindow*> (desktop.getComponent(i)))
  518. {
  519. if (auto* pcc = mw->getProjectContentComponent())
  520. {
  521. if (pcc->hasFileInRecentList (doc->getFile()))
  522. {
  523. mw->toFront (true);
  524. mw->getProjectContentComponent()->showDocument (doc, grabFocus);
  525. return;
  526. }
  527. }
  528. }
  529. }
  530. getFrontmostWindow()->getProjectContentComponent()->showDocument (doc, grabFocus);
  531. }
  532. bool MainWindowList::openFile (const File& file, bool openInBackground)
  533. {
  534. for (auto* w : windows)
  535. {
  536. if (w->getProject() != nullptr && w->getProject()->getFile() == file)
  537. {
  538. w->toFront (true);
  539. return true;
  540. }
  541. }
  542. if (file.hasFileExtension (Project::projectFileExtension))
  543. {
  544. WeakReference<Component> previousFrontWindow (getFrontmostWindow());
  545. auto* w = getOrCreateEmptyWindow();
  546. bool ok = w->openFile (file);
  547. if (ok)
  548. {
  549. w->makeVisible();
  550. avoidSuperimposedWindows (w);
  551. }
  552. else
  553. {
  554. closeWindow (w);
  555. }
  556. if (openInBackground && previousFrontWindow != nullptr)
  557. previousFrontWindow->toFront (true);
  558. return ok;
  559. }
  560. if (getFrontmostWindow()->tryToOpenPIP (file))
  561. return true;
  562. if (! isPIPFile (file) && file.exists())
  563. return getFrontmostWindow()->openFile (file);
  564. return false;
  565. }
  566. MainWindow* MainWindowList::createNewMainWindow()
  567. {
  568. auto w = new MainWindow();
  569. windows.add (w);
  570. w->restoreWindowPosition();
  571. avoidSuperimposedWindows (w);
  572. return w;
  573. }
  574. MainWindow* MainWindowList::getFrontmostWindow (bool createIfNotFound)
  575. {
  576. if (windows.isEmpty())
  577. {
  578. if (createIfNotFound)
  579. {
  580. auto* w = createNewMainWindow();
  581. avoidSuperimposedWindows (w);
  582. w->makeVisible();
  583. return w;
  584. }
  585. return nullptr;
  586. }
  587. for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
  588. {
  589. auto* mw = dynamic_cast<MainWindow*> (Desktop::getInstance().getComponent (i));
  590. if (windows.contains (mw))
  591. return mw;
  592. }
  593. return windows.getLast();
  594. }
  595. MainWindow* MainWindowList::getOrCreateEmptyWindow()
  596. {
  597. if (windows.size() == 0)
  598. return createNewMainWindow();
  599. for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
  600. {
  601. auto* mw = dynamic_cast<MainWindow*> (Desktop::getInstance().getComponent (i));
  602. if (windows.contains (mw) && mw->getProject() == nullptr)
  603. return mw;
  604. }
  605. return createNewMainWindow();
  606. }
  607. MainWindow* MainWindowList::getMainWindowForFile (const File& file)
  608. {
  609. if (windows.size() > 0)
  610. {
  611. for (auto* window : windows)
  612. {
  613. if (auto* project = window->getProject())
  614. {
  615. if (project->getFile() == file)
  616. return window;
  617. }
  618. }
  619. }
  620. return nullptr;
  621. }
  622. void MainWindowList::avoidSuperimposedWindows (MainWindow* const mw)
  623. {
  624. for (int i = windows.size(); --i >= 0;)
  625. {
  626. auto* other = windows.getUnchecked(i);
  627. auto b1 = mw->getBounds();
  628. auto b2 = other->getBounds();
  629. if (mw != other
  630. && std::abs (b1.getX() - b2.getX()) < 3
  631. && std::abs (b1.getY() - b2.getY()) < 3
  632. && std::abs (b1.getRight() - b2.getRight()) < 3
  633. && std::abs (b1.getBottom() - b2.getBottom()) < 3)
  634. {
  635. int dx = 40, dy = 30;
  636. if (b1.getCentreX() >= mw->getScreenBounds().getCentreX()) dx = -dx;
  637. if (b1.getCentreY() >= mw->getScreenBounds().getCentreY()) dy = -dy;
  638. mw->setBounds (b1.translated (dx, dy));
  639. }
  640. }
  641. }
  642. void MainWindowList::saveCurrentlyOpenProjectList()
  643. {
  644. Array<File> projects;
  645. auto& desktop = Desktop::getInstance();
  646. for (int i = 0; i < desktop.getNumComponents(); ++i)
  647. {
  648. if (auto* mw = dynamic_cast<MainWindow*> (desktop.getComponent(i)))
  649. if (auto* p = mw->getProject())
  650. if (! p->isTemporaryProject())
  651. projects.add (p->getFile());
  652. }
  653. getAppSettings().setLastProjects (projects);
  654. }
  655. void MainWindowList::reopenLastProjects()
  656. {
  657. const ScopedValueSetter<bool> setter (isInReopenLastProjects, true);
  658. for (auto& p : getAppSettings().getLastProjects())
  659. if (p.existsAsFile())
  660. openFile (p, true);
  661. }
  662. void MainWindowList::sendLookAndFeelChange()
  663. {
  664. for (auto* w : windows)
  665. w->sendLookAndFeelChange();
  666. }
  667. Project* MainWindowList::getFrontmostProject()
  668. {
  669. auto& desktop = Desktop::getInstance();
  670. for (int i = desktop.getNumComponents(); --i >= 0;)
  671. if (auto* mw = dynamic_cast<MainWindow*> (desktop.getComponent(i)))
  672. if (auto* p = mw->getProject())
  673. return p;
  674. return nullptr;
  675. }