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