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