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.

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