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 PopupMenu createGUIEditorMenu();
27 void handleGUIEditorMenuCommand (int);
28 void registerGUIEditorCommands();
29 
30 
31 //==============================================================================
32 struct ProjucerApplication::MainMenuModel  : public MenuBarModel
33 {
MainMenuModelProjucerApplication::MainMenuModel34     MainMenuModel()
35     {
36         setApplicationCommandManagerToWatch (&getCommandManager());
37     }
38 
getMenuBarNamesProjucerApplication::MainMenuModel39     StringArray getMenuBarNames() override
40     {
41         return getApp().getMenuNames();
42     }
43 
getMenuForIndexProjucerApplication::MainMenuModel44     PopupMenu getMenuForIndex (int /*topLevelMenuIndex*/, const String& menuName) override
45     {
46         return getApp().createMenu (menuName);
47     }
48 
menuItemSelectedProjucerApplication::MainMenuModel49     void menuItemSelected (int menuItemID, int /*topLevelMenuIndex*/) override
50     {
51         getApp().handleMainMenuCommand (menuItemID);
52     }
53 };
54 
55 //==============================================================================
initialise(const String & commandLine)56 void ProjucerApplication::initialise (const String& commandLine)
57 {
58     if (commandLine.trimStart().startsWith ("--server"))
59     {
60         initialiseLogger ("Compiler_Log_");
61         LookAndFeel::setDefaultLookAndFeel (&lookAndFeel);
62 
63        #if JUCE_MAC
64         Process::setDockIconVisible (false);
65        #endif
66 
67         server = createClangServer (commandLine);
68     }
69     else
70     {
71         initialiseLogger ("IDE_Log_");
72         Logger::writeToLog (SystemStats::getOperatingSystemName());
73         Logger::writeToLog ("CPU: " + String (SystemStats::getCpuSpeedInMegahertz())
74                               + "MHz  Cores: " + String (SystemStats::getNumCpus())
75                               + "  " + String (SystemStats::getMemorySizeInMegabytes()) + "MB");
76 
77         isRunningCommandLine = commandLine.isNotEmpty()
78                                 && ! commandLine.startsWith ("-NSDocumentRevisionsDebugMode");
79 
80         settings = std::make_unique<StoredSettings>();
81 
82         if (isRunningCommandLine)
83         {
84             auto appReturnCode = performCommandLine (ArgumentList ("Projucer", commandLine));
85 
86             if (appReturnCode != commandLineNotPerformed)
87             {
88                 setApplicationReturnValue (appReturnCode);
89                 quit();
90                 return;
91             }
92 
93             isRunningCommandLine = false;
94         }
95 
96         if (sendCommandLineToPreexistingInstance())
97         {
98             DBG ("Another instance is running - quitting...");
99             quit();
100             return;
101         }
102 
103         doBasicApplicationSetup();
104 
105         // do further initialisation in a moment when the message loop has started
106         triggerAsyncUpdate();
107     }
108 }
109 
initialiseLogger(const char * filePrefix)110 bool ProjucerApplication::initialiseLogger (const char* filePrefix)
111 {
112     if (logger == nullptr)
113     {
114        #if JUCE_LINUX || JUCE_BSD
115         String folder = "~/.config/Projucer/Logs";
116        #else
117         String folder = "com.juce.projucer";
118        #endif
119 
120         logger.reset (FileLogger::createDateStampedLogger (folder, filePrefix, ".txt",
121                                                            getApplicationName() + " " + getApplicationVersion()
122                                                                + "  ---  Build date: " __DATE__));
123         Logger::setCurrentLogger (logger.get());
124     }
125 
126     return logger != nullptr;
127 }
128 
initialiseWindows(const String & commandLine)129 void ProjucerApplication::initialiseWindows (const String& commandLine)
130 {
131     const String commandLineWithoutNSDebug (commandLine.replace ("-NSDocumentRevisionsDebugMode YES", StringRef()));
132 
133     if (commandLineWithoutNSDebug.trim().isNotEmpty() && ! commandLineWithoutNSDebug.trim().startsWithChar ('-'))
134         anotherInstanceStarted (commandLine);
135     else if (mainWindowList.windows.size() == 0)
136         mainWindowList.reopenLastProjects();
137 
138     mainWindowList.createWindowIfNoneAreOpen();
139 }
140 
handleAsyncUpdate()141 void ProjucerApplication::handleAsyncUpdate()
142 {
143     rescanJUCEPathModules();
144     rescanUserPathModules();
145 
146     openDocumentManager.registerType (new ProjucerAppClasses::LiveBuildCodeEditorDocument::Type(), 2);
147 
148     menuModel.reset (new MainMenuModel());
149 
150    #if JUCE_MAC
151     rebuildAppleMenu();
152     appleMenuRebuildListener = std::make_unique<AppleMenuRebuildListener>();
153    #endif
154 
155     settings->appearance.refreshPresetSchemeList();
156     setColourScheme (getGlobalProperties().getIntValue ("COLOUR SCHEME"), false);
157     setEditorColourScheme (getGlobalProperties().getIntValue ("EDITOR COLOUR SCHEME"), false);
158     updateEditorColourSchemeIfNeeded();
159 
160     ImageCache::setCacheTimeout (30 * 1000);
161     tooltipWindow = std::make_unique<TooltipWindow> (nullptr, 1200);
162 
163     if (isAutomaticVersionCheckingEnabled())
164         LatestVersionCheckerAndUpdater::getInstance()->checkForNewVersion (true);
165 
166     initialiseWindows (getCommandLineParameters());
167 }
168 
doBasicApplicationSetup()169 void ProjucerApplication::doBasicApplicationSetup()
170 {
171     licenseController = std::make_unique<LicenseController>();
172     LookAndFeel::setDefaultLookAndFeel (&lookAndFeel);
173     initCommandManager();
174     childProcessCache = std::make_unique<ChildProcessCache>();
175     icons = std::make_unique<Icons>();
176 }
177 
deleteTemporaryFiles()178 static void deleteTemporaryFiles()
179 {
180     auto tempDirectory = File::getSpecialLocation (File::SpecialLocationType::tempDirectory).getChildFile ("PIPs");
181 
182     if (tempDirectory.exists())
183         tempDirectory.deleteRecursively();
184 }
185 
shutdown()186 void ProjucerApplication::shutdown()
187 {
188     if (server != nullptr)
189     {
190         destroyClangServer (server);
191         Logger::writeToLog ("Server shutdown cleanly");
192     }
193 
194     utf8Window.reset();
195     svgPathWindow.reset();
196     aboutWindow.reset();
197     pathsWindow.reset();
198     editorColourSchemeWindow.reset();
199     pipCreatorWindow.reset();
200 
201     mainWindowList.forceCloseAllWindows();
202     openDocumentManager.clear();
203 
204     childProcessCache.reset();
205 
206    #if JUCE_MAC
207     MenuBarModel::setMacMainMenu (nullptr);
208    #endif
209 
210     menuModel.reset();
211     commandManager.reset();
212     settings.reset();
213 
214     if (! isRunningCommandLine)
215         LookAndFeel::setDefaultLookAndFeel (nullptr);
216 
217     // clean up after ourselves and delete any temp project files that may have
218     // been created from PIPs
219     deleteTemporaryFiles();
220 
221     if (! isRunningCommandLine)
222         Logger::writeToLog ("Shutdown");
223 
224     deleteLogger();
225 }
226 
227 struct AsyncQuitRetrier  : private Timer
228 {
AsyncQuitRetrierAsyncQuitRetrier229     AsyncQuitRetrier()   { startTimer (500); }
230 
timerCallbackAsyncQuitRetrier231     void timerCallback() override
232     {
233         stopTimer();
234         delete this;
235 
236         if (auto* app = JUCEApplicationBase::getInstance())
237             app->systemRequestedQuit();
238     }
239 
240     JUCE_DECLARE_NON_COPYABLE (AsyncQuitRetrier)
241 };
242 
systemRequestedQuit()243 void ProjucerApplication::systemRequestedQuit()
244 {
245     if (server != nullptr)
246     {
247         sendQuitMessageToIDE (server);
248     }
249     else if (ModalComponentManager::getInstance()->cancelAllModalComponents())
250     {
251         new AsyncQuitRetrier();
252     }
253     else
254     {
255         if (closeAllMainWindows())
256             quit();
257     }
258 }
259 
260 //==============================================================================
getVersionDescription() const261 String ProjucerApplication::getVersionDescription() const
262 {
263     String s;
264 
265     const Time buildDate (Time::getCompilationDate());
266 
267     s << "Projucer " << ProjectInfo::versionString
268       << newLine
269       << "Build date: " << buildDate.getDayOfMonth()
270       << " " << Time::getMonthName (buildDate.getMonth(), true)
271       << " " << buildDate.getYear();
272 
273     return s;
274 }
275 
anotherInstanceStarted(const String & commandLine)276 void ProjucerApplication::anotherInstanceStarted (const String& commandLine)
277 {
278     if (server == nullptr && ! commandLine.trim().startsWithChar ('-'))
279     {
280         ArgumentList list ({}, commandLine);
281 
282         for (auto& arg : list.arguments)
283             openFile (arg.resolveAsFile());
284     }
285 }
286 
getApp()287 ProjucerApplication& ProjucerApplication::getApp()
288 {
289     ProjucerApplication* const app = dynamic_cast<ProjucerApplication*> (JUCEApplication::getInstance());
290     jassert (app != nullptr);
291     return *app;
292 }
293 
getCommandManager()294 ApplicationCommandManager& ProjucerApplication::getCommandManager()
295 {
296     auto* cm = ProjucerApplication::getApp().commandManager.get();
297     jassert (cm != nullptr);
298     return *cm;
299 }
300 
301 
302 //==============================================================================
303 enum
304 {
305     recentProjectsBaseID = 100,
306     openWindowsBaseID = 300,
307     activeDocumentsBaseID = 400,
308     showPathsID = 1999,
309     examplesBaseID = 2000
310 };
311 
getMenuModel()312 MenuBarModel* ProjucerApplication::getMenuModel()
313 {
314     return menuModel.get();
315 }
316 
getMenuNames()317 StringArray ProjucerApplication::getMenuNames()
318 {
319     StringArray currentMenuNames { "File", "Edit", "View", "Build", "Window", "Document", "GUI Editor", "Tools", "Help" };
320 
321     if (! isLiveBuildEnabled())  currentMenuNames.removeString ("Build");
322     if (! isGUIEditorEnabled())  currentMenuNames.removeString ("GUI Editor");
323 
324     return currentMenuNames;
325 }
326 
createMenu(const String & menuName)327 PopupMenu ProjucerApplication::createMenu (const String& menuName)
328 {
329     if (menuName == "File")
330         return createFileMenu();
331 
332     if (menuName == "Edit")
333         return createEditMenu();
334 
335     if (menuName == "View")
336         return createViewMenu();
337 
338     if (menuName == "Build")
339         if (isLiveBuildEnabled())
340             return createBuildMenu();
341 
342     if (menuName == "Window")
343         return createWindowMenu();
344 
345     if (menuName == "Document")
346         return createDocumentMenu();
347 
348     if (menuName == "Tools")
349         return createToolsMenu();
350 
351     if (menuName == "Help")
352         return createHelpMenu();
353 
354     if (menuName == "GUI Editor")
355         if (isGUIEditorEnabled())
356             return createGUIEditorMenu();
357 
358     jassertfalse; // names have changed?
359     return {};
360 }
361 
createFileMenu()362 PopupMenu ProjucerApplication::createFileMenu()
363 {
364     PopupMenu menu;
365     menu.addCommandItem (commandManager.get(), CommandIDs::newProject);
366     menu.addCommandItem (commandManager.get(), CommandIDs::newProjectFromClipboard);
367     menu.addCommandItem (commandManager.get(), CommandIDs::newPIP);
368     menu.addSeparator();
369     menu.addCommandItem (commandManager.get(), CommandIDs::open);
370 
371     {
372         PopupMenu recentFiles;
373 
374         settings->recentFiles.createPopupMenuItems (recentFiles, recentProjectsBaseID, true, true);
375 
376         if (recentFiles.getNumItems() > 0)
377         {
378             recentFiles.addSeparator();
379             recentFiles.addCommandItem (commandManager.get(), CommandIDs::clearRecentFiles);
380         }
381 
382         menu.addSubMenu ("Open Recent", recentFiles);
383     }
384 
385     menu.addSubMenu ("Open Example", createExamplesPopupMenu());
386 
387     menu.addSeparator();
388     menu.addCommandItem (commandManager.get(), CommandIDs::closeDocument);
389     menu.addCommandItem (commandManager.get(), CommandIDs::saveDocument);
390     menu.addCommandItem (commandManager.get(), CommandIDs::saveDocumentAs);
391     menu.addCommandItem (commandManager.get(), CommandIDs::saveAll);
392     menu.addSeparator();
393     menu.addCommandItem (commandManager.get(), CommandIDs::closeProject);
394     menu.addCommandItem (commandManager.get(), CommandIDs::saveProject);
395     menu.addSeparator();
396     menu.addCommandItem (commandManager.get(), CommandIDs::openInIDE);
397     menu.addCommandItem (commandManager.get(), CommandIDs::saveAndOpenInIDE);
398     menu.addSeparator();
399 
400    #if ! JUCER_ENABLE_GPL_MODE
401     menu.addCommandItem (commandManager.get(), CommandIDs::loginLogout);
402    #endif
403 
404    #if ! JUCE_MAC
405     menu.addCommandItem (commandManager.get(), CommandIDs::showAboutWindow);
406     menu.addCommandItem (commandManager.get(), CommandIDs::checkForNewVersion);
407     menu.addCommandItem (commandManager.get(), CommandIDs::enableNewVersionCheck);
408     menu.addCommandItem (commandManager.get(), CommandIDs::showGlobalPathsWindow);
409     menu.addSeparator();
410     menu.addCommandItem (commandManager.get(), StandardApplicationCommandIDs::quit);
411    #endif
412 
413     return menu;
414 }
415 
createEditMenu()416 PopupMenu ProjucerApplication::createEditMenu()
417 {
418     PopupMenu menu;
419     menu.addCommandItem (commandManager.get(), StandardApplicationCommandIDs::undo);
420     menu.addCommandItem (commandManager.get(), StandardApplicationCommandIDs::redo);
421     menu.addSeparator();
422     menu.addCommandItem (commandManager.get(), StandardApplicationCommandIDs::cut);
423     menu.addCommandItem (commandManager.get(), StandardApplicationCommandIDs::copy);
424     menu.addCommandItem (commandManager.get(), StandardApplicationCommandIDs::paste);
425     menu.addCommandItem (commandManager.get(), StandardApplicationCommandIDs::del);
426     menu.addCommandItem (commandManager.get(), StandardApplicationCommandIDs::selectAll);
427     menu.addCommandItem (commandManager.get(), StandardApplicationCommandIDs::deselectAll);
428     menu.addSeparator();
429     menu.addCommandItem (commandManager.get(), CommandIDs::showFindPanel);
430     menu.addCommandItem (commandManager.get(), CommandIDs::findSelection);
431     menu.addCommandItem (commandManager.get(), CommandIDs::findNext);
432     menu.addCommandItem (commandManager.get(), CommandIDs::findPrevious);
433     return menu;
434 }
435 
createViewMenu()436 PopupMenu ProjucerApplication::createViewMenu()
437 {
438     PopupMenu menu;
439     menu.addCommandItem (commandManager.get(), CommandIDs::showProjectSettings);
440     menu.addCommandItem (commandManager.get(), CommandIDs::showProjectTab);
441     menu.addCommandItem (commandManager.get(), CommandIDs::showBuildTab);
442     menu.addCommandItem (commandManager.get(), CommandIDs::showFileExplorerPanel);
443     menu.addCommandItem (commandManager.get(), CommandIDs::showModulesPanel);
444     menu.addCommandItem (commandManager.get(), CommandIDs::showExportersPanel);
445     menu.addCommandItem (commandManager.get(), CommandIDs::showExporterSettings);
446 
447     menu.addSeparator();
448     createColourSchemeItems (menu);
449 
450     return menu;
451 }
452 
createBuildMenu()453 PopupMenu ProjucerApplication::createBuildMenu()
454 {
455     PopupMenu menu;
456     menu.addCommandItem (commandManager.get(), CommandIDs::toggleBuildEnabled);
457     menu.addCommandItem (commandManager.get(), CommandIDs::buildNow);
458     menu.addCommandItem (commandManager.get(), CommandIDs::toggleContinuousBuild);
459     menu.addSeparator();
460     menu.addCommandItem (commandManager.get(), CommandIDs::launchApp);
461     menu.addCommandItem (commandManager.get(), CommandIDs::killApp);
462     menu.addCommandItem (commandManager.get(), CommandIDs::cleanAll);
463     menu.addSeparator();
464     menu.addCommandItem (commandManager.get(), CommandIDs::reinstantiateComp);
465     menu.addCommandItem (commandManager.get(), CommandIDs::showWarnings);
466     menu.addSeparator();
467     menu.addCommandItem (commandManager.get(), CommandIDs::nextError);
468     menu.addCommandItem (commandManager.get(), CommandIDs::prevError);
469     return menu;
470 }
471 
createColourSchemeItems(PopupMenu & menu)472 void ProjucerApplication::createColourSchemeItems (PopupMenu& menu)
473 {
474     {
475         PopupMenu colourSchemeMenu;
476 
477         colourSchemeMenu.addItem (PopupMenu::Item ("Dark")
478                                     .setTicked (selectedColourSchemeIndex == 0)
479                                     .setAction ([this] { setColourScheme (0, true); updateEditorColourSchemeIfNeeded(); }));
480 
481         colourSchemeMenu.addItem (PopupMenu::Item ("Grey")
482                                     .setTicked (selectedColourSchemeIndex == 1)
483                                     .setAction ([this] { setColourScheme (1, true); updateEditorColourSchemeIfNeeded(); }));
484 
485         colourSchemeMenu.addItem (PopupMenu::Item ("Light")
486                                     .setTicked (selectedColourSchemeIndex == 2)
487                                     .setAction ([this] { setColourScheme (2, true); updateEditorColourSchemeIfNeeded(); }));
488 
489         menu.addSubMenu ("Colour Scheme", colourSchemeMenu);
490     }
491 
492     {
493         PopupMenu editorColourSchemeMenu;
494 
495         auto& appearanceSettings = getAppSettings().appearance;
496 
497         appearanceSettings.refreshPresetSchemeList();
498         auto schemes = appearanceSettings.getPresetSchemes();
499 
500         auto i = 0;
501 
502         for (auto& s : schemes)
503         {
504             editorColourSchemeMenu.addItem (PopupMenu::Item (s)
505                                                .setEnabled (editorColourSchemeWindow == nullptr)
506                                                .setTicked (selectedEditorColourSchemeIndex == i)
507                                                .setAction ([this, i] { setEditorColourScheme (i, true); }));
508             ++i;
509         }
510 
511         editorColourSchemeMenu.addSeparator();
512         editorColourSchemeMenu.addItem (PopupMenu::Item ("Create...")
513                                            .setEnabled (editorColourSchemeWindow == nullptr)
514                                            .setAction ([this] { showEditorColourSchemeWindow(); }));
515 
516         menu.addSubMenu ("Editor Colour Scheme", editorColourSchemeMenu);
517     }
518 }
519 
createWindowMenu()520 PopupMenu ProjucerApplication::createWindowMenu()
521 {
522     PopupMenu menu;
523     menu.addCommandItem (commandManager.get(), CommandIDs::goToPreviousWindow);
524     menu.addCommandItem (commandManager.get(), CommandIDs::goToNextWindow);
525     menu.addCommandItem (commandManager.get(), CommandIDs::closeWindow);
526     menu.addSeparator();
527 
528     int counter = 0;
529 
530     for (auto* window : mainWindowList.windows)
531     {
532         if (window != nullptr)
533         {
534             if (auto* project = window->getProject())
535                 menu.addItem (openWindowsBaseID + counter++, project->getProjectNameString());
536         }
537     }
538 
539     menu.addSeparator();
540     menu.addCommandItem (commandManager.get(), CommandIDs::closeAllWindows);
541     return menu;
542 }
543 
createDocumentMenu()544 PopupMenu ProjucerApplication::createDocumentMenu()
545 {
546     PopupMenu menu;
547     menu.addCommandItem (commandManager.get(), CommandIDs::goToPreviousDoc);
548     menu.addCommandItem (commandManager.get(), CommandIDs::goToNextDoc);
549     menu.addCommandItem (commandManager.get(), CommandIDs::goToCounterpart);
550     menu.addSeparator();
551 
552     auto numDocs = jmin (50, openDocumentManager.getNumOpenDocuments());
553 
554     for (int i = 0; i < numDocs; ++i)
555     {
556         OpenDocumentManager::Document* doc = openDocumentManager.getOpenDocument(i);
557         menu.addItem (activeDocumentsBaseID + i, doc->getName());
558     }
559 
560     menu.addSeparator();
561     menu.addCommandItem (commandManager.get(), CommandIDs::closeAllDocuments);
562     return menu;
563 }
564 
createToolsMenu()565 PopupMenu ProjucerApplication::createToolsMenu()
566 {
567     PopupMenu menu;
568     menu.addCommandItem (commandManager.get(), CommandIDs::showUTF8Tool);
569     menu.addCommandItem (commandManager.get(), CommandIDs::showSVGPathTool);
570     menu.addCommandItem (commandManager.get(), CommandIDs::showTranslationTool);
571     menu.addSeparator();
572     menu.addCommandItem (commandManager.get(), CommandIDs::enableLiveBuild);
573     menu.addCommandItem (commandManager.get(), CommandIDs::enableGUIEditor);
574     return menu;
575 }
576 
createHelpMenu()577 PopupMenu ProjucerApplication::createHelpMenu()
578 {
579     PopupMenu menu;
580     menu.addCommandItem (commandManager.get(), CommandIDs::showForum);
581     menu.addSeparator();
582     menu.addCommandItem (commandManager.get(), CommandIDs::showAPIModules);
583     menu.addCommandItem (commandManager.get(), CommandIDs::showAPIClasses);
584     menu.addCommandItem (commandManager.get(), CommandIDs::showTutorials);
585     return menu;
586 }
587 
createExtraAppleMenuItems()588 PopupMenu ProjucerApplication::createExtraAppleMenuItems()
589 {
590     PopupMenu menu;
591     menu.addCommandItem (commandManager.get(), CommandIDs::showAboutWindow);
592     menu.addCommandItem (commandManager.get(), CommandIDs::checkForNewVersion);
593     menu.addCommandItem (commandManager.get(), CommandIDs::enableNewVersionCheck);
594     menu.addSeparator();
595     menu.addCommandItem (commandManager.get(), CommandIDs::showGlobalPathsWindow);
596     return menu;
597 }
598 
createExamplesPopupMenu()599 PopupMenu ProjucerApplication::createExamplesPopupMenu() noexcept
600 {
601     PopupMenu menu;
602     numExamples = 0;
603     for (auto& dir : getSortedExampleDirectories())
604     {
605         PopupMenu m;
606         for (auto& f : getSortedExampleFilesInDirectory (dir))
607         {
608             m.addItem (examplesBaseID + numExamples, f.getFileNameWithoutExtension());
609             ++numExamples;
610         }
611 
612         menu.addSubMenu (dir.getFileName(), m);
613     }
614 
615     if (numExamples == 0)
616     {
617         menu.addItem (showPathsID, "Set path to JUCE...");
618     }
619     else
620     {
621         menu.addSeparator();
622         menu.addCommandItem (commandManager.get(), CommandIDs::launchDemoRunner);
623     }
624 
625     return menu;
626 }
627 
628 #if JUCE_MAC
rebuildAppleMenu()629  void ProjucerApplication::rebuildAppleMenu()
630  {
631      auto extraAppleMenuItems = createExtraAppleMenuItems();
632 
633      // workaround broken "Open Recent" submenu: not passing the
634      // submenu's title here avoids the defect in JuceMainMenuHandler::addMenuItem
635      MenuBarModel::setMacMainMenu (menuModel.get(), &extraAppleMenuItems); //, "Open Recent");
636  }
637 #endif
638 
639 //==============================================================================
getJUCEExamplesDirectoryPathFromGlobal()640 File ProjucerApplication::getJUCEExamplesDirectoryPathFromGlobal() noexcept
641 {
642     auto globalPath = File::createFileWithoutCheckingPath (getAppSettings().getStoredPath (Ids::jucePath, TargetOS::getThisOS()).get().toString()
643                                                                            .replace ("~", File::getSpecialLocation (File::userHomeDirectory).getFullPathName()));
644 
645     if (globalPath.exists())
646         return File (globalPath).getChildFile ("examples");
647 
648     return {};
649 }
650 
getSortedExampleDirectories()651 Array<File> ProjucerApplication::getSortedExampleDirectories() noexcept
652 {
653     Array<File> exampleDirectories;
654 
655     auto examplesPath = getJUCEExamplesDirectoryPathFromGlobal();
656 
657     if (! isValidJUCEExamplesDirectory (examplesPath))
658         return {};
659 
660     for (const auto& iter : RangedDirectoryIterator (examplesPath, false, "*", File::findDirectories))
661     {
662         auto exampleDirectory = iter.getFile();
663 
664         if (exampleDirectory.getNumberOfChildFiles (File::findFiles | File::ignoreHiddenFiles) > 0
665             && exampleDirectory.getFileName() != "DemoRunner"
666             && exampleDirectory.getFileName() != "Assets"
667             && exampleDirectory.getFileName() != "CMake")
668         {
669             exampleDirectories.add (exampleDirectory);
670         }
671     }
672 
673     exampleDirectories.sort();
674 
675     return exampleDirectories;
676 }
677 
getSortedExampleFilesInDirectory(const File & directory)678 Array<File> ProjucerApplication::getSortedExampleFilesInDirectory (const File& directory) noexcept
679 {
680     Array<File> exampleFiles;
681 
682     for (const auto& iter : RangedDirectoryIterator (directory, false, "*.h", File::findFiles))
683         exampleFiles.add (iter.getFile());
684 
685     exampleFiles.sort();
686 
687     return exampleFiles;
688 }
689 
findAndLaunchExample(int selectedIndex)690 void ProjucerApplication::findAndLaunchExample (int selectedIndex)
691 {
692     File example;
693 
694     for (auto& dir : getSortedExampleDirectories())
695     {
696         auto exampleFiles = getSortedExampleFilesInDirectory (dir);
697 
698         if (selectedIndex < exampleFiles.size())
699         {
700             example = exampleFiles.getUnchecked (selectedIndex);
701             break;
702         }
703 
704         selectedIndex -= exampleFiles.size();
705     }
706 
707     // example doesn't exist?
708     jassert (example != File());
709 
710     openFile (example);
711 }
712 
713 //==============================================================================
getPlatformSpecificFileExtension()714 static String getPlatformSpecificFileExtension()
715 {
716    #if JUCE_MAC
717     return ".app";
718    #elif JUCE_WINDOWS
719     return ".exe";
720    #elif JUCE_LINUX || JUCE_BSD
721     return {};
722    #else
723     jassertfalse;
724     return {};
725    #endif
726 }
727 
getPlatformSpecificProjectFolder()728 static File getPlatformSpecificProjectFolder()
729 {
730     auto examplesDir = ProjucerApplication::getJUCEExamplesDirectoryPathFromGlobal();
731 
732     if (examplesDir == File())
733         return {};
734 
735     auto buildsFolder = examplesDir.getChildFile ("DemoRunner").getChildFile ("Builds");
736 
737    #if JUCE_MAC
738     return buildsFolder.getChildFile ("MacOSX");
739    #elif JUCE_WINDOWS
740     return buildsFolder.getChildFile ("VisualStudio2017");
741    #elif JUCE_LINUX || JUCE_BSD
742     return buildsFolder.getChildFile ("LinuxMakefile");
743    #else
744     jassertfalse;
745     return {};
746    #endif
747 }
748 
tryToFindDemoRunnerExecutableInBuilds()749 static File tryToFindDemoRunnerExecutableInBuilds()
750 {
751     auto projectFolder = getPlatformSpecificProjectFolder();
752 
753     if (projectFolder == File())
754         return {};
755 
756    #if JUCE_MAC
757     projectFolder = projectFolder.getChildFile ("build");
758     auto demoRunnerExecutable = projectFolder.getChildFile ("Release").getChildFile ("DemoRunner.app");
759 
760     if (demoRunnerExecutable.exists())
761         return demoRunnerExecutable;
762 
763     demoRunnerExecutable = projectFolder.getChildFile ("Debug").getChildFile ("DemoRunner.app");
764 
765     if (demoRunnerExecutable.exists())
766         return demoRunnerExecutable;
767    #elif JUCE_WINDOWS
768     projectFolder = projectFolder.getChildFile ("x64");
769     auto demoRunnerExecutable = projectFolder.getChildFile ("Release").getChildFile ("App").getChildFile ("DemoRunner.exe");
770 
771     if (demoRunnerExecutable.existsAsFile())
772         return demoRunnerExecutable;
773 
774     demoRunnerExecutable = projectFolder.getChildFile ("Debug").getChildFile ("App").getChildFile ("DemoRunner.exe");
775 
776     if (demoRunnerExecutable.existsAsFile())
777         return demoRunnerExecutable;
778    #elif JUCE_LINUX || JUCE_BSD
779     projectFolder = projectFolder.getChildFile ("LinuxMakefile").getChildFile ("build");
780     auto demoRunnerExecutable = projectFolder.getChildFile ("DemoRunner");
781 
782     if (demoRunnerExecutable.existsAsFile())
783         return demoRunnerExecutable;
784    #endif
785 
786     return {};
787 }
788 
tryToFindPrebuiltDemoRunnerExecutable()789 static File tryToFindPrebuiltDemoRunnerExecutable()
790 {
791     auto prebuiltFile = File (getAppSettings().getStoredPath (Ids::jucePath, TargetOS::getThisOS()).get().toString())
792                                .getChildFile ("DemoRunner" + getPlatformSpecificFileExtension());
793 
794    #if JUCE_MAC
795     if (prebuiltFile.exists())
796    #else
797     if (prebuiltFile.existsAsFile())
798    #endif
799         return prebuiltFile;
800 
801     return {};
802 }
803 
checkIfGlobalJUCEPathHasChanged()804 void ProjucerApplication::checkIfGlobalJUCEPathHasChanged()
805 {
806     auto globalJUCEPath = File (getAppSettings().getStoredPath (Ids::jucePath, TargetOS::getThisOS()).get());
807 
808     if (lastJUCEPath != globalJUCEPath)
809     {
810         hasScannedForDemoRunnerProject = false;
811         hasScannedForDemoRunnerExecutable = false;
812 
813         lastJUCEPath = globalJUCEPath;
814     }
815 }
816 
tryToFindDemoRunnerExecutable()817 File ProjucerApplication::tryToFindDemoRunnerExecutable()
818 {
819     checkIfGlobalJUCEPathHasChanged();
820 
821     if (hasScannedForDemoRunnerExecutable)
822         return lastDemoRunnerExectuableFile;
823 
824     hasScannedForDemoRunnerExecutable = true;
825 
826     auto demoRunnerExecutable = tryToFindDemoRunnerExecutableInBuilds();
827 
828     if (demoRunnerExecutable == File())
829         demoRunnerExecutable = tryToFindPrebuiltDemoRunnerExecutable();
830 
831     lastDemoRunnerExectuableFile = demoRunnerExecutable;
832 
833     return demoRunnerExecutable;
834 }
835 
tryToFindDemoRunnerProject()836 File ProjucerApplication::tryToFindDemoRunnerProject()
837 {
838     checkIfGlobalJUCEPathHasChanged();
839 
840     if (hasScannedForDemoRunnerProject)
841         return lastDemoRunnerProjectFile;
842 
843     hasScannedForDemoRunnerProject = true;
844 
845     auto projectFolder = getPlatformSpecificProjectFolder();
846 
847     if (projectFolder == File())
848     {
849         lastDemoRunnerProjectFile = File();
850         return {};
851     }
852 
853    #if JUCE_MAC
854     auto demoRunnerProjectFile = projectFolder.getChildFile ("DemoRunner.xcodeproj");
855    #elif JUCE_WINDOWS
856     auto demoRunnerProjectFile = projectFolder.getChildFile ("DemoRunner.sln");
857    #elif JUCE_LINUX || JUCE_BSD
858     auto demoRunnerProjectFile = projectFolder.getChildFile ("Makefile");
859    #endif
860 
861    #if JUCE_MAC
862     if (! demoRunnerProjectFile.exists())
863    #else
864     if (! demoRunnerProjectFile.existsAsFile())
865    #endif
866         demoRunnerProjectFile = File();
867 
868     lastDemoRunnerProjectFile = demoRunnerProjectFile;
869 
870     return demoRunnerProjectFile;
871 }
872 
launchDemoRunner()873 void ProjucerApplication::launchDemoRunner()
874 {
875     auto demoRunnerFile = tryToFindDemoRunnerExecutable();
876 
877     if (demoRunnerFile != File() && demoRunnerFile.startAsProcess())
878         return;
879 
880     demoRunnerFile = tryToFindDemoRunnerProject();
881 
882     if (demoRunnerFile != File())
883     {
884         auto& lf = Desktop::getInstance().getDefaultLookAndFeel();
885 
886         demoRunnerAlert.reset (lf.createAlertWindow ("Open Project",
887                                                      "Couldn't find a compiled version of the Demo Runner."
888                                                     #if JUCE_LINUX || JUCE_BSD
889                                                      " Do you want to build it now?", "Build project", "Cancel",
890                                                     #else
891                                                      " Do you want to open the project?", "Open project", "Cancel",
892                                                     #endif
893                                                      {},
894                                                      AlertWindow::QuestionIcon, 2,
895                                                      mainWindowList.getFrontmostWindow (false)));
896 
897         demoRunnerAlert->enterModalState (true, ModalCallbackFunction::create ([this, demoRunnerFile] (int retVal)
898                                                 {
899                                                     demoRunnerAlert.reset (nullptr);
900 
901                                                     if (retVal == 1)
902                                                     {
903                                                        #if JUCE_LINUX || JUCE_BSD
904                                                         String command ("make -C " + demoRunnerFile.getParentDirectory().getFullPathName() + " CONFIG=Release -j3");
905 
906                                                         if (! makeProcess.start (command))
907                                                             AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, "Error", "Error building Demo Runner.");
908                                                        #else
909                                                         demoRunnerFile.startAsProcess();
910                                                        #endif
911                                                     }
912                                                 }), false);
913     }
914 }
915 
916 //==============================================================================
handleMainMenuCommand(int menuItemID)917 void ProjucerApplication::handleMainMenuCommand (int menuItemID)
918 {
919     if (menuItemID >= recentProjectsBaseID && menuItemID < (recentProjectsBaseID + 100))
920     {
921         // open a file from the "recent files" menu
922         openFile (settings->recentFiles.getFile (menuItemID - recentProjectsBaseID));
923     }
924     else if (menuItemID >= openWindowsBaseID && menuItemID < (openWindowsBaseID + 100))
925     {
926         if (auto* window = mainWindowList.windows.getUnchecked (menuItemID - openWindowsBaseID))
927             window->toFront (true);
928     }
929     else if (menuItemID >= activeDocumentsBaseID && menuItemID < (activeDocumentsBaseID + 200))
930     {
931         if (auto* doc = openDocumentManager.getOpenDocument (menuItemID - activeDocumentsBaseID))
932             mainWindowList.openDocument (doc, true);
933         else
934             jassertfalse;
935     }
936     else if (menuItemID == showPathsID)
937     {
938         showPathsWindow (true);
939     }
940     else if (menuItemID >= examplesBaseID && menuItemID < (examplesBaseID + numExamples))
941     {
942         findAndLaunchExample (menuItemID - examplesBaseID);
943     }
944     else
945     {
946         handleGUIEditorMenuCommand (menuItemID);
947     }
948 }
949 
950 //==============================================================================
getAllCommands(Array<CommandID> & commands)951 void ProjucerApplication::getAllCommands (Array <CommandID>& commands)
952 {
953     JUCEApplication::getAllCommands (commands);
954 
955     const CommandID ids[] = { CommandIDs::newProject,
956                               CommandIDs::newProjectFromClipboard,
957                               CommandIDs::newPIP,
958                               CommandIDs::open,
959                               CommandIDs::launchDemoRunner,
960                               CommandIDs::closeAllWindows,
961                               CommandIDs::closeAllDocuments,
962                               CommandIDs::clearRecentFiles,
963                               CommandIDs::saveAll,
964                               CommandIDs::showGlobalPathsWindow,
965                               CommandIDs::showUTF8Tool,
966                               CommandIDs::showSVGPathTool,
967                               CommandIDs::enableLiveBuild,
968                               CommandIDs::enableGUIEditor,
969                               CommandIDs::showAboutWindow,
970                               CommandIDs::checkForNewVersion,
971                               CommandIDs::enableNewVersionCheck,
972                               CommandIDs::showForum,
973                               CommandIDs::showAPIModules,
974                               CommandIDs::showAPIClasses,
975                               CommandIDs::showTutorials,
976                               CommandIDs::loginLogout };
977 
978     commands.addArray (ids, numElementsInArray (ids));
979 }
980 
getCommandInfo(CommandID commandID,ApplicationCommandInfo & result)981 void ProjucerApplication::getCommandInfo (CommandID commandID, ApplicationCommandInfo& result)
982 {
983     switch (commandID)
984     {
985     case CommandIDs::newProject:
986         result.setInfo ("New Project...", "Creates a new JUCE project", CommandCategories::general, 0);
987         result.defaultKeypresses.add (KeyPress ('n', ModifierKeys::commandModifier, 0));
988         break;
989 
990     case CommandIDs::newProjectFromClipboard:
991         result.setInfo ("New Project From Clipboard...", "Creates a new JUCE project from the clipboard contents", CommandCategories::general, 0);
992         result.defaultKeypresses.add (KeyPress ('n', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0));
993         break;
994 
995     case CommandIDs::newPIP:
996         result.setInfo ("New PIP...", "Opens the PIP Creator utility for creating a new PIP", CommandCategories::general, 0);
997         result.defaultKeypresses.add (KeyPress ('p', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0));
998         break;
999 
1000     case CommandIDs::launchDemoRunner:
1001        #if JUCE_LINUX || JUCE_BSD
1002         if (makeProcess.isRunning())
1003         {
1004             result.setInfo ("Building Demo Runner...", "The Demo Runner project is currently building", CommandCategories::general, 0);
1005             result.setActive (false);
1006         }
1007         else
1008        #endif
1009         {
1010             result.setInfo ("Launch Demo Runner", "Launches the JUCE demo runner application, or the project if it can't be found", CommandCategories::general, 0);
1011             result.setActive (tryToFindDemoRunnerExecutable() != File() || tryToFindDemoRunnerProject() != File());
1012         }
1013         break;
1014 
1015     case CommandIDs::open:
1016         result.setInfo ("Open...", "Opens a JUCE project", CommandCategories::general, 0);
1017         result.defaultKeypresses.add (KeyPress ('o', ModifierKeys::commandModifier, 0));
1018         break;
1019 
1020     case CommandIDs::showGlobalPathsWindow:
1021         result.setInfo ("Global Paths...",
1022                         "Shows the window to change the stored global paths.",
1023                         CommandCategories::general, 0);
1024         break;
1025 
1026     case CommandIDs::closeAllWindows:
1027         result.setInfo ("Close All Windows", "Closes all open windows", CommandCategories::general, 0);
1028         result.setActive (mainWindowList.windows.size() > 0);
1029         break;
1030 
1031     case CommandIDs::closeAllDocuments:
1032         result.setInfo ("Close All Documents", "Closes all open documents", CommandCategories::general, 0);
1033         result.setActive (openDocumentManager.getNumOpenDocuments() > 0);
1034         break;
1035 
1036     case CommandIDs::clearRecentFiles:
1037         result.setInfo ("Clear Recent Files", "Clears all recent files from the menu", CommandCategories::general, 0);
1038         result.setActive (settings->recentFiles.getNumFiles() > 0);
1039         break;
1040 
1041     case CommandIDs::saveAll:
1042         result.setInfo ("Save All", "Saves all open documents", CommandCategories::general, 0);
1043         result.defaultKeypresses.add (KeyPress ('s', ModifierKeys::commandModifier | ModifierKeys::altModifier, 0));
1044         break;
1045 
1046     case CommandIDs::showUTF8Tool:
1047         result.setInfo ("UTF-8 String-Literal Helper", "Shows the UTF-8 string literal utility", CommandCategories::general, 0);
1048         break;
1049 
1050     case CommandIDs::showSVGPathTool:
1051         result.setInfo ("SVG Path Converter", "Shows the SVG->Path data conversion utility", CommandCategories::general, 0);
1052         break;
1053 
1054     case CommandIDs::enableLiveBuild:
1055         result.setInfo ("Live-Build Enabled",
1056                         "Enables or disables the live-build functionality",
1057                         CommandCategories::general,
1058                         (isLiveBuildEnabled() ? ApplicationCommandInfo::isTicked : 0));
1059         break;
1060 
1061     case CommandIDs::enableGUIEditor:
1062         result.setInfo ("GUI Editor Enabled",
1063                         "Enables or disables the GUI editor functionality",
1064                         CommandCategories::general,
1065                         (isGUIEditorEnabled() ? ApplicationCommandInfo::isTicked : 0));
1066         break;
1067 
1068     case CommandIDs::showAboutWindow:
1069         result.setInfo ("About Projucer", "Shows the Projucer's 'About' page.", CommandCategories::general, 0);
1070         break;
1071 
1072     case CommandIDs::checkForNewVersion:
1073         result.setInfo ("Check for New Version...", "Checks the web server for a new version of JUCE", CommandCategories::general, 0);
1074         break;
1075 
1076     case CommandIDs::enableNewVersionCheck:
1077         result.setInfo ("Automatically Check for New Versions",
1078                         "Enables automatic background checking for new versions of JUCE.",
1079                         CommandCategories::general,
1080                         (isAutomaticVersionCheckingEnabled() ? ApplicationCommandInfo::isTicked : 0));
1081         break;
1082 
1083     case CommandIDs::showForum:
1084         result.setInfo ("JUCE Community Forum", "Shows the JUCE community forum in a browser", CommandCategories::general, 0);
1085         break;
1086 
1087     case CommandIDs::showAPIModules:
1088         result.setInfo ("API Modules", "Shows the API modules documentation in a browser", CommandCategories::general, 0);
1089         break;
1090 
1091     case CommandIDs::showAPIClasses:
1092         result.setInfo ("API Classes", "Shows the API classes documentation in a browser", CommandCategories::general, 0);
1093         break;
1094 
1095     case CommandIDs::showTutorials:
1096         result.setInfo ("JUCE Tutorials", "Shows the JUCE tutorials in a browser", CommandCategories::general, 0);
1097         break;
1098 
1099     case CommandIDs::loginLogout:
1100         {
1101             auto licenseState = licenseController->getCurrentState();
1102 
1103             if (licenseState.isGPL())
1104                 result.setInfo ("Disable GPL mode", "Disables GPL mode", CommandCategories::general, 0);
1105             else
1106                 result.setInfo (licenseState.isSignedIn() ? String ("Sign out ") + licenseState.username + "..." : String ("Sign in..."),
1107                                 "Sign out of your JUCE account",
1108                                 CommandCategories::general, 0);
1109             break;
1110         }
1111 
1112     default:
1113         JUCEApplication::getCommandInfo (commandID, result);
1114         break;
1115     }
1116 }
1117 
perform(const InvocationInfo & info)1118 bool ProjucerApplication::perform (const InvocationInfo& info)
1119 {
1120     switch (info.commandID)
1121     {
1122         case CommandIDs::newProject:                createNewProject(); break;
1123         case CommandIDs::newProjectFromClipboard:   createNewProjectFromClipboard(); break;
1124         case CommandIDs::newPIP:                    createNewPIP(); break;
1125         case CommandIDs::open:                      askUserToOpenFile(); break;
1126         case CommandIDs::launchDemoRunner:          launchDemoRunner(); break;
1127         case CommandIDs::saveAll:                   saveAllDocuments(); break;
1128         case CommandIDs::closeAllWindows:           closeAllMainWindowsAndQuitIfNeeded(); break;
1129         case CommandIDs::closeAllDocuments:         closeAllDocuments (OpenDocumentManager::SaveIfNeeded::yes); break;
1130         case CommandIDs::clearRecentFiles:          clearRecentFiles(); break;
1131         case CommandIDs::showUTF8Tool:              showUTF8ToolWindow(); break;
1132         case CommandIDs::showSVGPathTool:           showSVGPathDataToolWindow(); break;
1133         case CommandIDs::enableLiveBuild:           enableOrDisableLiveBuild(); break;
1134         case CommandIDs::enableGUIEditor:           enableOrDisableGUIEditor(); break;
1135         case CommandIDs::showGlobalPathsWindow:     showPathsWindow (false); break;
1136         case CommandIDs::showAboutWindow:           showAboutWindow(); break;
1137         case CommandIDs::checkForNewVersion:        LatestVersionCheckerAndUpdater::getInstance()->checkForNewVersion (false); break;
1138         case CommandIDs::enableNewVersionCheck:     setAutomaticVersionCheckingEnabled (! isAutomaticVersionCheckingEnabled()); break;
1139         case CommandIDs::showForum:                 launchForumBrowser(); break;
1140         case CommandIDs::showAPIModules:            launchModulesBrowser(); break;
1141         case CommandIDs::showAPIClasses:            launchClassesBrowser(); break;
1142         case CommandIDs::showTutorials:             launchTutorialsBrowser(); break;
1143         case CommandIDs::loginLogout:               doLoginOrLogout(); break;
1144         default:                                    return JUCEApplication::perform (info);
1145     }
1146 
1147     return true;
1148 }
1149 
1150 //==============================================================================
createNewProject()1151 void ProjucerApplication::createNewProject()
1152 {
1153     auto* mw = mainWindowList.getOrCreateEmptyWindow();
1154     jassert (mw != nullptr);
1155 
1156     mw->showStartPage();
1157 
1158     mainWindowList.checkWindowBounds (*mw);
1159 }
1160 
createNewProjectFromClipboard()1161 void ProjucerApplication::createNewProjectFromClipboard()
1162 {
1163     auto tempFile = File::getSpecialLocation (File::SpecialLocationType::tempDirectory).getChildFile ("PIPs").getChildFile ("Clipboard")
1164                                                                                        .getChildFile ("PIPFile_" + String (std::abs (Random::getSystemRandom().nextInt())) + ".h")
1165                                                                                        .getNonexistentSibling();
1166 
1167     if (tempFile.existsAsFile())
1168         tempFile.deleteFile();
1169 
1170     tempFile.create();
1171     tempFile.appendText (SystemClipboard::getTextFromClipboard());
1172 
1173     String errorString;
1174 
1175     if (! isPIPFile (tempFile))
1176     {
1177         errorString = "Clipboard does not contain a valid PIP.";
1178     }
1179     else if (! openFile (tempFile))
1180     {
1181         errorString = "Couldn't create project from clipboard contents.";
1182         mainWindowList.closeWindow (mainWindowList.windows.getLast());
1183     }
1184 
1185     if (errorString.isNotEmpty())
1186     {
1187         AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, "Error", errorString);
1188         tempFile.deleteFile();
1189     }
1190 }
1191 
createNewPIP()1192 void ProjucerApplication::createNewPIP()
1193 {
1194     showPIPCreatorWindow();
1195 }
1196 
askUserToOpenFile()1197 void ProjucerApplication::askUserToOpenFile()
1198 {
1199     FileChooser fc ("Open File");
1200 
1201     if (fc.browseForFileToOpen())
1202         openFile (fc.getResult());
1203 }
1204 
openFile(const File & file)1205 bool ProjucerApplication::openFile (const File& file)
1206 {
1207     return mainWindowList.openFile (file);
1208 }
1209 
saveAllDocuments()1210 void ProjucerApplication::saveAllDocuments()
1211 {
1212     openDocumentManager.saveAll();
1213 
1214     for (int i = 0; i < mainWindowList.windows.size(); ++i)
1215         if (auto* pcc = mainWindowList.windows.getUnchecked(i)->getProjectContentComponent())
1216             pcc->refreshProjectTreeFileStatuses();
1217 }
1218 
closeAllDocuments(OpenDocumentManager::SaveIfNeeded askUserToSave)1219 bool ProjucerApplication::closeAllDocuments (OpenDocumentManager::SaveIfNeeded askUserToSave)
1220 {
1221     return openDocumentManager.closeAll (askUserToSave);
1222 }
1223 
closeAllMainWindows()1224 bool ProjucerApplication::closeAllMainWindows()
1225 {
1226     return server != nullptr || mainWindowList.askAllWindowsToClose();
1227 }
1228 
closeAllMainWindowsAndQuitIfNeeded()1229 void ProjucerApplication::closeAllMainWindowsAndQuitIfNeeded()
1230 {
1231     if (closeAllMainWindows())
1232     {
1233        #if ! JUCE_MAC
1234         if (mainWindowList.windows.size() == 0)
1235             systemRequestedQuit();
1236        #endif
1237     }
1238 }
1239 
clearRecentFiles()1240 void ProjucerApplication::clearRecentFiles()
1241 {
1242     settings->recentFiles.clear();
1243     settings->recentFiles.clearRecentFilesNatively();
1244     settings->flush();
1245     menuModel->menuItemsChanged();
1246 }
1247 
1248 //==============================================================================
showUTF8ToolWindow()1249 void ProjucerApplication::showUTF8ToolWindow()
1250 {
1251     if (utf8Window != nullptr)
1252         utf8Window->toFront (true);
1253     else
1254         new FloatingToolWindow ("UTF-8 String Literal Converter", "utf8WindowPos",
1255                                 new UTF8Component(), utf8Window, true,
1256                                 500, 500, 300, 300, 1000, 1000);
1257 }
1258 
showSVGPathDataToolWindow()1259 void ProjucerApplication::showSVGPathDataToolWindow()
1260 {
1261     if (svgPathWindow != nullptr)
1262         svgPathWindow->toFront (true);
1263     else
1264         new FloatingToolWindow ("SVG Path Converter", "svgPathWindowPos",
1265                                 new SVGPathDataComponent(), svgPathWindow, true,
1266                                 500, 500, 300, 300, 1000, 1000);
1267 }
1268 
isLiveBuildEnabled() const1269 bool ProjucerApplication::isLiveBuildEnabled() const
1270 {
1271     return getGlobalProperties().getBoolValue (Ids::liveBuildEnabled);
1272 }
1273 
enableOrDisableLiveBuild()1274 void ProjucerApplication::enableOrDisableLiveBuild()
1275 {
1276     getGlobalProperties().setValue (Ids::liveBuildEnabled, ! isLiveBuildEnabled());
1277 }
1278 
isGUIEditorEnabled() const1279 bool ProjucerApplication::isGUIEditorEnabled() const
1280 {
1281     return getGlobalProperties().getBoolValue (Ids::guiEditorEnabled);
1282 }
1283 
enableOrDisableGUIEditor()1284 void ProjucerApplication::enableOrDisableGUIEditor()
1285 {
1286     getGlobalProperties().setValue (Ids::guiEditorEnabled, ! isGUIEditorEnabled());
1287 }
1288 
showAboutWindow()1289 void ProjucerApplication::showAboutWindow()
1290 {
1291     if (aboutWindow != nullptr)
1292         aboutWindow->toFront (true);
1293     else
1294         new FloatingToolWindow ({}, {}, new AboutWindowComponent(),
1295                                 aboutWindow, false,
1296                                 500, 300, 500, 300, 500, 300);
1297 }
1298 
showPathsWindow(bool highlightJUCEPath)1299 void ProjucerApplication::showPathsWindow (bool highlightJUCEPath)
1300 {
1301     if (pathsWindow != nullptr)
1302         pathsWindow->toFront (true);
1303     else
1304         new FloatingToolWindow ("Global Paths", "pathsWindowPos",
1305                                 new GlobalPathsWindowComponent(), pathsWindow, false,
1306                                 600, 700, 600, 700, 600, 700);
1307 
1308     if (highlightJUCEPath)
1309         if (auto* pathsComp = dynamic_cast<GlobalPathsWindowComponent*> (pathsWindow->getChildComponent (0)))
1310             pathsComp->highlightJUCEPath();
1311 }
1312 
showEditorColourSchemeWindow()1313 void ProjucerApplication::showEditorColourSchemeWindow()
1314 {
1315     if (editorColourSchemeWindow != nullptr)
1316         editorColourSchemeWindow->toFront (true);
1317     else
1318         new FloatingToolWindow ("Editor Colour Scheme", "editorColourSchemeWindowPos",
1319                                 new EditorColourSchemeWindowComponent(), editorColourSchemeWindow, false,
1320                                 500, 500, 500, 500, 500, 500);
1321 }
1322 
showPIPCreatorWindow()1323 void ProjucerApplication::showPIPCreatorWindow()
1324 {
1325     if (pipCreatorWindow != nullptr)
1326         pipCreatorWindow->toFront (true);
1327     else
1328         new FloatingToolWindow ("PIP Creator", "pipCreatorWindowPos",
1329                                 new PIPCreatorWindowComponent(), pipCreatorWindow, false,
1330                                 600, 750, 600, 750, 600, 750);
1331 }
1332 
launchForumBrowser()1333 void ProjucerApplication::launchForumBrowser()
1334 {
1335     URL forumLink ("https://forum.juce.com/");
1336 
1337     if (forumLink.isWellFormed())
1338         forumLink.launchInDefaultBrowser();
1339 }
1340 
launchModulesBrowser()1341 void ProjucerApplication::launchModulesBrowser()
1342 {
1343     URL modulesLink ("https://docs.juce.com/master/modules.html");
1344 
1345     if (modulesLink.isWellFormed())
1346         modulesLink.launchInDefaultBrowser();
1347 }
1348 
launchClassesBrowser()1349 void ProjucerApplication::launchClassesBrowser()
1350 {
1351     URL classesLink ("https://docs.juce.com/master/classes.html");
1352 
1353     if (classesLink.isWellFormed())
1354         classesLink.launchInDefaultBrowser();
1355 }
1356 
launchTutorialsBrowser()1357 void ProjucerApplication::launchTutorialsBrowser()
1358 {
1359     URL tutorialsLink ("https://juce.com/learn/tutorials");
1360 
1361     if (tutorialsLink.isWellFormed())
1362         tutorialsLink.launchInDefaultBrowser();
1363 }
1364 
doLoginOrLogout()1365 void ProjucerApplication::doLoginOrLogout()
1366 {
1367     if (licenseController->getCurrentState().isSignedIn())
1368     {
1369         licenseController->resetState();
1370     }
1371     else
1372     {
1373         if (auto* window = mainWindowList.getMainWindowWithLoginFormOpen())
1374         {
1375             window->toFront (true);
1376         }
1377         else
1378         {
1379             mainWindowList.createWindowIfNoneAreOpen();
1380             mainWindowList.getFrontmostWindow()->showLoginFormOverlay();
1381         }
1382     }
1383 }
1384 
1385 //==============================================================================
1386 struct FileWithTime
1387 {
FileWithTimeFileWithTime1388     FileWithTime (const File& f) : file (f), time (f.getLastModificationTime()) {}
FileWithTimeFileWithTime1389     FileWithTime() {}
1390 
operator <FileWithTime1391     bool operator<  (const FileWithTime& other) const    { return time <  other.time; }
operator ==FileWithTime1392     bool operator== (const FileWithTime& other) const    { return time == other.time; }
1393 
1394     File file;
1395     Time time;
1396 };
1397 
deleteLogger()1398 void ProjucerApplication::deleteLogger()
1399 {
1400     const int maxNumLogFilesToKeep = 50;
1401 
1402     Logger::setCurrentLogger (nullptr);
1403 
1404     if (logger != nullptr)
1405     {
1406         auto logFiles = logger->getLogFile().getParentDirectory().findChildFiles (File::findFiles, false);
1407 
1408         if (logFiles.size() > maxNumLogFilesToKeep)
1409         {
1410             Array<FileWithTime> files;
1411 
1412             for (auto& f : logFiles)
1413                 files.addUsingDefaultSort (f);
1414 
1415             for (int i = 0; i < files.size() - maxNumLogFilesToKeep; ++i)
1416                 files.getReference(i).file.deleteFile();
1417         }
1418     }
1419 
1420     logger.reset();
1421 }
1422 
getPropertyFileOptionsFor(const String & filename,bool isProjectSettings)1423 PropertiesFile::Options ProjucerApplication::getPropertyFileOptionsFor (const String& filename, bool isProjectSettings)
1424 {
1425     PropertiesFile::Options options;
1426     options.applicationName     = filename;
1427     options.filenameSuffix      = "settings";
1428     options.osxLibrarySubFolder = "Application Support";
1429    #if JUCE_LINUX || JUCE_BSD
1430     options.folderName          = "~/.config/Projucer";
1431    #else
1432     options.folderName          = "Projucer";
1433    #endif
1434 
1435     if (isProjectSettings)
1436         options.folderName += "/ProjectSettings";
1437 
1438     return options;
1439 }
1440 
initCommandManager()1441 void ProjucerApplication::initCommandManager()
1442 {
1443     commandManager.reset (new ApplicationCommandManager());
1444     commandManager->registerAllCommandsForTarget (this);
1445 
1446     {
1447         CodeDocument doc;
1448         CppCodeEditorComponent ed (File(), doc);
1449         commandManager->registerAllCommandsForTarget (&ed);
1450     }
1451 
1452     registerGUIEditorCommands();
1453 }
1454 
rescanModules(AvailableModulesList & list,const Array<File> & paths,bool async)1455 static void rescanModules (AvailableModulesList& list, const Array<File>& paths, bool async)
1456 {
1457     if (async)
1458         list.scanPathsAsync (paths);
1459     else
1460         list.scanPaths (paths);
1461 }
1462 
rescanJUCEPathModules()1463 void ProjucerApplication::rescanJUCEPathModules()
1464 {
1465     rescanModules (jucePathModulesList, { getAppSettings().getStoredPath (Ids::defaultJuceModulePath, TargetOS::getThisOS()).get().toString() }, ! isRunningCommandLine);
1466 }
1467 
rescanUserPathModules()1468 void ProjucerApplication::rescanUserPathModules()
1469 {
1470     rescanModules (userPathsModulesList, { getAppSettings().getStoredPath (Ids::defaultUserModulePath, TargetOS::getThisOS()).get().toString() }, ! isRunningCommandLine);
1471 }
1472 
isAutomaticVersionCheckingEnabled() const1473 bool ProjucerApplication::isAutomaticVersionCheckingEnabled() const
1474 {
1475     return ! getGlobalProperties().getBoolValue (Ids::dontQueryForUpdate);
1476 }
1477 
setAutomaticVersionCheckingEnabled(bool enabled)1478 void ProjucerApplication::setAutomaticVersionCheckingEnabled (bool enabled)
1479 {
1480     getGlobalProperties().setValue (Ids::dontQueryForUpdate, ! enabled);
1481 }
1482 
shouldPromptUserAboutIncorrectJUCEPath() const1483 bool ProjucerApplication::shouldPromptUserAboutIncorrectJUCEPath() const
1484 {
1485     return ! getGlobalProperties().getBoolValue (Ids::dontAskAboutJUCEPath);
1486 }
1487 
setShouldPromptUserAboutIncorrectJUCEPath(bool shouldPrompt)1488 void ProjucerApplication::setShouldPromptUserAboutIncorrectJUCEPath (bool shouldPrompt)
1489 {
1490     getGlobalProperties().setValue (Ids::dontAskAboutJUCEPath, ! shouldPrompt);
1491 }
1492 
selectEditorColourSchemeWithName(const String & schemeName)1493 void ProjucerApplication::selectEditorColourSchemeWithName (const String& schemeName)
1494 {
1495     auto& appearanceSettings = getAppSettings().appearance;
1496     auto schemes = appearanceSettings.getPresetSchemes();
1497 
1498     auto schemeIndex = schemes.indexOf (schemeName);
1499 
1500     if (schemeIndex >= 0)
1501         setEditorColourScheme (schemeIndex, true);
1502 }
1503 
setColourScheme(int index,bool saveSetting)1504 void ProjucerApplication::setColourScheme (int index, bool saveSetting)
1505 {
1506     switch (index)
1507     {
1508         case 0: lookAndFeel.setColourScheme (LookAndFeel_V4::getDarkColourScheme());  break;
1509         case 1: lookAndFeel.setColourScheme (LookAndFeel_V4::getGreyColourScheme());  break;
1510         case 2: lookAndFeel.setColourScheme (LookAndFeel_V4::getLightColourScheme()); break;
1511         default: break;
1512     }
1513 
1514     lookAndFeel.setupColours();
1515     mainWindowList.sendLookAndFeelChange();
1516 
1517     if (utf8Window != nullptr)                  utf8Window->sendLookAndFeelChange();
1518     if (svgPathWindow != nullptr)               svgPathWindow->sendLookAndFeelChange();
1519     if (aboutWindow != nullptr)                 aboutWindow->sendLookAndFeelChange();
1520     if (pathsWindow != nullptr)                 pathsWindow->sendLookAndFeelChange();
1521     if (editorColourSchemeWindow != nullptr)    editorColourSchemeWindow->sendLookAndFeelChange();
1522     if (pipCreatorWindow != nullptr)            pipCreatorWindow->sendLookAndFeelChange();
1523 
1524     auto* mcm = ModalComponentManager::getInstance();
1525     for (auto i = 0; i < mcm->getNumModalComponents(); ++i)
1526         mcm->getModalComponent (i)->sendLookAndFeelChange();
1527 
1528     if (saveSetting)
1529     {
1530         auto& properties = getGlobalProperties();
1531         properties.setValue ("COLOUR SCHEME", index);
1532     }
1533 
1534     selectedColourSchemeIndex = index;
1535 
1536     getCommandManager().commandStatusChanged();
1537 }
1538 
setEditorColourScheme(int index,bool saveSetting)1539 void ProjucerApplication::setEditorColourScheme (int index, bool saveSetting)
1540 {
1541     auto& appearanceSettings = getAppSettings().appearance;
1542     auto schemes = appearanceSettings.getPresetSchemes();
1543 
1544     index = jmin (index, schemes.size() - 1);
1545 
1546     appearanceSettings.selectPresetScheme (index);
1547 
1548     if (saveSetting)
1549     {
1550         auto& properties = getGlobalProperties();
1551         properties.setValue ("EDITOR COLOUR SCHEME", index);
1552     }
1553 
1554     selectedEditorColourSchemeIndex = index;
1555 
1556     getCommandManager().commandStatusChanged();
1557 }
1558 
isEditorColourSchemeADefaultScheme(const StringArray & schemes,int editorColourSchemeIndex)1559 static bool isEditorColourSchemeADefaultScheme (const StringArray& schemes, int editorColourSchemeIndex)
1560 {
1561     auto& schemeName = schemes[editorColourSchemeIndex];
1562     return (schemeName == "Default (Dark)" || schemeName == "Default (Light)");
1563 }
1564 
getEditorColourSchemeForGUIColourScheme(const StringArray & schemes,int guiColourSchemeIndex)1565 static int getEditorColourSchemeForGUIColourScheme (const StringArray& schemes, int guiColourSchemeIndex)
1566 {
1567     auto defaultDarkEditorIndex  = schemes.indexOf ("Default (Dark)");
1568     auto defaultLightEditorIndex = schemes.indexOf ("Default (Light)");
1569 
1570     // Can't find default code editor colour schemes!
1571     jassert (defaultDarkEditorIndex != -1 && defaultLightEditorIndex != -1);
1572 
1573     return (guiColourSchemeIndex == 2 ? defaultLightEditorIndex : defaultDarkEditorIndex);
1574 }
1575 
updateEditorColourSchemeIfNeeded()1576 void ProjucerApplication::updateEditorColourSchemeIfNeeded()
1577 {
1578     auto& appearanceSettings = getAppSettings().appearance;
1579     auto schemes = appearanceSettings.getPresetSchemes();
1580 
1581     if (isEditorColourSchemeADefaultScheme (schemes, selectedEditorColourSchemeIndex))
1582         setEditorColourScheme (getEditorColourSchemeForGUIColourScheme (schemes, selectedColourSchemeIndex), true);
1583 }
1584