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.

1052 lines
31KB

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