/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2020 - Raw Material Software Limited JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 6 End-User License Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). End User License Agreement: www.juce.com/juce-6-licence Privacy Policy: www.juce.com/juce-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ PopupMenu createGUIEditorMenu(); void handleGUIEditorMenuCommand (int); void registerGUIEditorCommands(); //============================================================================== struct ProjucerApplication::MainMenuModel : public MenuBarModel { MainMenuModel() { setApplicationCommandManagerToWatch (&getCommandManager()); } StringArray getMenuBarNames() override { return getApp().getMenuNames(); } PopupMenu getMenuForIndex (int /*topLevelMenuIndex*/, const String& menuName) override { return getApp().createMenu (menuName); } void menuItemSelected (int menuItemID, int /*topLevelMenuIndex*/) override { getApp().handleMainMenuCommand (menuItemID); } }; //============================================================================== void ProjucerApplication::initialise (const String& commandLine) { initialiseLogger ("IDE_Log_"); Logger::writeToLog (SystemStats::getOperatingSystemName()); Logger::writeToLog ("CPU: " + String (SystemStats::getCpuSpeedInMegahertz()) + "MHz Cores: " + String (SystemStats::getNumCpus()) + " " + String (SystemStats::getMemorySizeInMegabytes()) + "MB"); isRunningCommandLine = commandLine.isNotEmpty() && ! commandLine.startsWith ("-NSDocumentRevisionsDebugMode"); settings = std::make_unique(); if (isRunningCommandLine) { auto appReturnCode = performCommandLine (ArgumentList ("Projucer", commandLine)); if (appReturnCode != commandLineNotPerformed) { setApplicationReturnValue (appReturnCode); quit(); return; } isRunningCommandLine = false; } if (sendCommandLineToPreexistingInstance()) { DBG ("Another instance is running - quitting..."); quit(); return; } doBasicApplicationSetup(); // do further initialisation in a moment when the message loop has started triggerAsyncUpdate(); } bool ProjucerApplication::initialiseLogger (const char* filePrefix) { if (logger == nullptr) { #if JUCE_LINUX || JUCE_BSD String folder = "~/.config/Projucer/Logs"; #else String folder = "com.juce.projucer"; #endif logger.reset (FileLogger::createDateStampedLogger (folder, filePrefix, ".txt", getApplicationName() + " " + getApplicationVersion() + " --- Build date: " __DATE__)); Logger::setCurrentLogger (logger.get()); } return logger != nullptr; } void ProjucerApplication::initialiseWindows (const String& commandLine) { const String commandLineWithoutNSDebug (commandLine.replace ("-NSDocumentRevisionsDebugMode YES", StringRef())); if (commandLineWithoutNSDebug.trim().isNotEmpty() && ! commandLineWithoutNSDebug.trim().startsWithChar ('-')) anotherInstanceStarted (commandLine); else if (mainWindowList.windows.size() == 0) mainWindowList.reopenLastProjects(); mainWindowList.createWindowIfNoneAreOpen(); } void ProjucerApplication::handleAsyncUpdate() { rescanJUCEPathModules(); rescanUserPathModules(); menuModel.reset (new MainMenuModel()); #if JUCE_MAC rebuildAppleMenu(); appleMenuRebuildListener = std::make_unique(); #endif settings->appearance.refreshPresetSchemeList(); setColourScheme (getGlobalProperties().getIntValue ("COLOUR SCHEME"), false); setEditorColourScheme (getGlobalProperties().getIntValue ("EDITOR COLOUR SCHEME"), false); updateEditorColourSchemeIfNeeded(); ImageCache::setCacheTimeout (30 * 1000); tooltipWindow = std::make_unique (nullptr, 1200); if (isAutomaticVersionCheckingEnabled()) LatestVersionCheckerAndUpdater::getInstance()->checkForNewVersion (true); initialiseWindows (getCommandLineParameters()); } void ProjucerApplication::doBasicApplicationSetup() { licenseController = std::make_unique(); LookAndFeel::setDefaultLookAndFeel (&lookAndFeel); initCommandManager(); icons = std::make_unique(); } static void deleteTemporaryFiles() { auto tempDirectory = File::getSpecialLocation (File::SpecialLocationType::tempDirectory).getChildFile ("PIPs"); if (tempDirectory.exists()) tempDirectory.deleteRecursively(); } void ProjucerApplication::shutdown() { utf8Window.reset(); svgPathWindow.reset(); aboutWindow.reset(); pathsWindow.reset(); editorColourSchemeWindow.reset(); pipCreatorWindow.reset(); mainWindowList.forceCloseAllWindows(); openDocumentManager.clear(); #if JUCE_MAC MenuBarModel::setMacMainMenu (nullptr); #endif menuModel.reset(); commandManager.reset(); settings.reset(); if (! isRunningCommandLine) LookAndFeel::setDefaultLookAndFeel (nullptr); // clean up after ourselves and delete any temp project files that may have // been created from PIPs deleteTemporaryFiles(); if (! isRunningCommandLine) Logger::writeToLog ("Shutdown"); deleteLogger(); } struct AsyncQuitRetrier : private Timer { AsyncQuitRetrier() { startTimer (500); } void timerCallback() override { stopTimer(); delete this; if (auto* app = JUCEApplicationBase::getInstance()) app->systemRequestedQuit(); } JUCE_DECLARE_NON_COPYABLE (AsyncQuitRetrier) }; void ProjucerApplication::systemRequestedQuit() { if (ModalComponentManager::getInstance()->cancelAllModalComponents()) { new AsyncQuitRetrier(); } else { closeAllMainWindows ([] (bool closedSuccessfully) { if (closedSuccessfully) ProjucerApplication::quit(); }); } } //============================================================================== String ProjucerApplication::getVersionDescription() const { String s; const Time buildDate (Time::getCompilationDate()); s << "Projucer " << ProjectInfo::versionString << newLine << "Build date: " << buildDate.getDayOfMonth() << " " << Time::getMonthName (buildDate.getMonth(), true) << " " << buildDate.getYear(); return s; } void ProjucerApplication::anotherInstanceStarted (const String& commandLine) { if (! commandLine.trim().startsWithChar ('-')) { ArgumentList list ({}, commandLine); for (auto& arg : list.arguments) openFile (arg.resolveAsFile(), nullptr); } } ProjucerApplication& ProjucerApplication::getApp() { ProjucerApplication* const app = dynamic_cast (JUCEApplication::getInstance()); jassert (app != nullptr); return *app; } ApplicationCommandManager& ProjucerApplication::getCommandManager() { auto* cm = ProjucerApplication::getApp().commandManager.get(); jassert (cm != nullptr); return *cm; } //============================================================================== enum { recentProjectsBaseID = 100, openWindowsBaseID = 300, activeDocumentsBaseID = 400, showPathsID = 1999, examplesBaseID = 2000 }; MenuBarModel* ProjucerApplication::getMenuModel() { return menuModel.get(); } StringArray ProjucerApplication::getMenuNames() { StringArray currentMenuNames { "File", "Edit", "View", "Window", "Document", "GUI Editor", "Tools", "Help" }; if (! isGUIEditorEnabled()) currentMenuNames.removeString ("GUI Editor"); return currentMenuNames; } PopupMenu ProjucerApplication::createMenu (const String& menuName) { if (menuName == "File") return createFileMenu(); if (menuName == "Edit") return createEditMenu(); if (menuName == "View") return createViewMenu(); if (menuName == "Window") return createWindowMenu(); if (menuName == "Document") return createDocumentMenu(); if (menuName == "Tools") return createToolsMenu(); if (menuName == "Help") return createHelpMenu(); if (menuName == "GUI Editor") if (isGUIEditorEnabled()) return createGUIEditorMenu(); jassertfalse; // names have changed? return {}; } PopupMenu ProjucerApplication::createFileMenu() { PopupMenu menu; menu.addCommandItem (commandManager.get(), CommandIDs::newProject); menu.addCommandItem (commandManager.get(), CommandIDs::newProjectFromClipboard); menu.addCommandItem (commandManager.get(), CommandIDs::newPIP); menu.addSeparator(); menu.addCommandItem (commandManager.get(), CommandIDs::open); { PopupMenu recentFiles; settings->recentFiles.createPopupMenuItems (recentFiles, recentProjectsBaseID, true, true); if (recentFiles.getNumItems() > 0) { recentFiles.addSeparator(); recentFiles.addCommandItem (commandManager.get(), CommandIDs::clearRecentFiles); } menu.addSubMenu ("Open Recent", recentFiles); } menu.addSubMenu ("Open Example", createExamplesPopupMenu()); menu.addSeparator(); menu.addCommandItem (commandManager.get(), CommandIDs::closeDocument); menu.addCommandItem (commandManager.get(), CommandIDs::saveDocument); menu.addCommandItem (commandManager.get(), CommandIDs::saveDocumentAs); menu.addCommandItem (commandManager.get(), CommandIDs::saveAll); menu.addSeparator(); menu.addCommandItem (commandManager.get(), CommandIDs::closeProject); menu.addCommandItem (commandManager.get(), CommandIDs::saveProject); menu.addSeparator(); menu.addCommandItem (commandManager.get(), CommandIDs::openInIDE); menu.addCommandItem (commandManager.get(), CommandIDs::saveAndOpenInIDE); menu.addSeparator(); #if ! JUCER_ENABLE_GPL_MODE menu.addCommandItem (commandManager.get(), CommandIDs::loginLogout); #endif #if ! JUCE_MAC menu.addCommandItem (commandManager.get(), CommandIDs::showAboutWindow); menu.addCommandItem (commandManager.get(), CommandIDs::checkForNewVersion); menu.addCommandItem (commandManager.get(), CommandIDs::enableNewVersionCheck); menu.addCommandItem (commandManager.get(), CommandIDs::showGlobalPathsWindow); menu.addSeparator(); menu.addCommandItem (commandManager.get(), StandardApplicationCommandIDs::quit); #endif return menu; } PopupMenu ProjucerApplication::createEditMenu() { PopupMenu menu; menu.addCommandItem (commandManager.get(), StandardApplicationCommandIDs::undo); menu.addCommandItem (commandManager.get(), StandardApplicationCommandIDs::redo); menu.addSeparator(); menu.addCommandItem (commandManager.get(), StandardApplicationCommandIDs::cut); menu.addCommandItem (commandManager.get(), StandardApplicationCommandIDs::copy); menu.addCommandItem (commandManager.get(), StandardApplicationCommandIDs::paste); menu.addCommandItem (commandManager.get(), StandardApplicationCommandIDs::del); menu.addCommandItem (commandManager.get(), StandardApplicationCommandIDs::selectAll); menu.addCommandItem (commandManager.get(), StandardApplicationCommandIDs::deselectAll); menu.addSeparator(); menu.addCommandItem (commandManager.get(), CommandIDs::showFindPanel); menu.addCommandItem (commandManager.get(), CommandIDs::findSelection); menu.addCommandItem (commandManager.get(), CommandIDs::findNext); menu.addCommandItem (commandManager.get(), CommandIDs::findPrevious); return menu; } PopupMenu ProjucerApplication::createViewMenu() { PopupMenu menu; menu.addCommandItem (commandManager.get(), CommandIDs::showProjectSettings); menu.addCommandItem (commandManager.get(), CommandIDs::showFileExplorerPanel); menu.addCommandItem (commandManager.get(), CommandIDs::showModulesPanel); menu.addCommandItem (commandManager.get(), CommandIDs::showExportersPanel); menu.addCommandItem (commandManager.get(), CommandIDs::showExporterSettings); menu.addSeparator(); createColourSchemeItems (menu); return menu; } void ProjucerApplication::createColourSchemeItems (PopupMenu& menu) { { PopupMenu colourSchemeMenu; colourSchemeMenu.addItem (PopupMenu::Item ("Dark") .setTicked (selectedColourSchemeIndex == 0) .setAction ([this] { setColourScheme (0, true); updateEditorColourSchemeIfNeeded(); })); colourSchemeMenu.addItem (PopupMenu::Item ("Grey") .setTicked (selectedColourSchemeIndex == 1) .setAction ([this] { setColourScheme (1, true); updateEditorColourSchemeIfNeeded(); })); colourSchemeMenu.addItem (PopupMenu::Item ("Light") .setTicked (selectedColourSchemeIndex == 2) .setAction ([this] { setColourScheme (2, true); updateEditorColourSchemeIfNeeded(); })); menu.addSubMenu ("Colour Scheme", colourSchemeMenu); } { PopupMenu editorColourSchemeMenu; auto& appearanceSettings = getAppSettings().appearance; appearanceSettings.refreshPresetSchemeList(); auto schemes = appearanceSettings.getPresetSchemes(); auto i = 0; for (auto& s : schemes) { editorColourSchemeMenu.addItem (PopupMenu::Item (s) .setEnabled (editorColourSchemeWindow == nullptr) .setTicked (selectedEditorColourSchemeIndex == i) .setAction ([this, i] { setEditorColourScheme (i, true); })); ++i; } editorColourSchemeMenu.addSeparator(); editorColourSchemeMenu.addItem (PopupMenu::Item ("Create...") .setEnabled (editorColourSchemeWindow == nullptr) .setAction ([this] { showEditorColourSchemeWindow(); })); menu.addSubMenu ("Editor Colour Scheme", editorColourSchemeMenu); } } PopupMenu ProjucerApplication::createWindowMenu() { PopupMenu menu; menu.addCommandItem (commandManager.get(), CommandIDs::goToPreviousWindow); menu.addCommandItem (commandManager.get(), CommandIDs::goToNextWindow); menu.addCommandItem (commandManager.get(), CommandIDs::closeWindow); menu.addSeparator(); int counter = 0; for (auto* window : mainWindowList.windows) { if (window != nullptr) { if (auto* project = window->getProject()) menu.addItem (openWindowsBaseID + counter++, project->getProjectNameString()); } } menu.addSeparator(); menu.addCommandItem (commandManager.get(), CommandIDs::closeAllWindows); return menu; } PopupMenu ProjucerApplication::createDocumentMenu() { PopupMenu menu; menu.addCommandItem (commandManager.get(), CommandIDs::goToPreviousDoc); menu.addCommandItem (commandManager.get(), CommandIDs::goToNextDoc); menu.addCommandItem (commandManager.get(), CommandIDs::goToCounterpart); menu.addSeparator(); auto numDocs = jmin (50, openDocumentManager.getNumOpenDocuments()); for (int i = 0; i < numDocs; ++i) { OpenDocumentManager::Document* doc = openDocumentManager.getOpenDocument(i); menu.addItem (activeDocumentsBaseID + i, doc->getName()); } menu.addSeparator(); menu.addCommandItem (commandManager.get(), CommandIDs::closeAllDocuments); return menu; } PopupMenu ProjucerApplication::createToolsMenu() { PopupMenu menu; menu.addCommandItem (commandManager.get(), CommandIDs::showUTF8Tool); menu.addCommandItem (commandManager.get(), CommandIDs::showSVGPathTool); menu.addCommandItem (commandManager.get(), CommandIDs::showTranslationTool); menu.addSeparator(); menu.addCommandItem (commandManager.get(), CommandIDs::enableGUIEditor); return menu; } PopupMenu ProjucerApplication::createHelpMenu() { PopupMenu menu; menu.addCommandItem (commandManager.get(), CommandIDs::showForum); menu.addSeparator(); menu.addCommandItem (commandManager.get(), CommandIDs::showAPIModules); menu.addCommandItem (commandManager.get(), CommandIDs::showAPIClasses); menu.addCommandItem (commandManager.get(), CommandIDs::showTutorials); return menu; } PopupMenu ProjucerApplication::createExtraAppleMenuItems() { PopupMenu menu; menu.addCommandItem (commandManager.get(), CommandIDs::showAboutWindow); menu.addCommandItem (commandManager.get(), CommandIDs::checkForNewVersion); menu.addCommandItem (commandManager.get(), CommandIDs::enableNewVersionCheck); menu.addSeparator(); menu.addCommandItem (commandManager.get(), CommandIDs::showGlobalPathsWindow); return menu; } PopupMenu ProjucerApplication::createExamplesPopupMenu() noexcept { PopupMenu menu; numExamples = 0; for (auto& dir : getSortedExampleDirectories()) { PopupMenu m; for (auto& f : getSortedExampleFilesInDirectory (dir)) { m.addItem (examplesBaseID + numExamples, f.getFileNameWithoutExtension()); ++numExamples; } menu.addSubMenu (dir.getFileName(), m); } if (numExamples == 0) { menu.addItem (showPathsID, "Set path to JUCE..."); } else { menu.addSeparator(); menu.addCommandItem (commandManager.get(), CommandIDs::launchDemoRunner); } return menu; } #if JUCE_MAC void ProjucerApplication::rebuildAppleMenu() { auto extraAppleMenuItems = createExtraAppleMenuItems(); // workaround broken "Open Recent" submenu: not passing the // submenu's title here avoids the defect in JuceMainMenuHandler::addMenuItem MenuBarModel::setMacMainMenu (menuModel.get(), &extraAppleMenuItems); //, "Open Recent"); } #endif //============================================================================== File ProjucerApplication::getJUCEExamplesDirectoryPathFromGlobal() noexcept { auto globalPath = File::createFileWithoutCheckingPath (getAppSettings().getStoredPath (Ids::jucePath, TargetOS::getThisOS()).get().toString() .replace ("~", File::getSpecialLocation (File::userHomeDirectory).getFullPathName())); if (globalPath.exists()) return File (globalPath).getChildFile ("examples"); return {}; } Array ProjucerApplication::getSortedExampleDirectories() noexcept { Array exampleDirectories; auto examplesPath = getJUCEExamplesDirectoryPathFromGlobal(); if (! isValidJUCEExamplesDirectory (examplesPath)) return {}; for (const auto& iter : RangedDirectoryIterator (examplesPath, false, "*", File::findDirectories)) { auto exampleDirectory = iter.getFile(); if (exampleDirectory.getNumberOfChildFiles (File::findFiles | File::ignoreHiddenFiles) > 0 && exampleDirectory.getFileName() != "DemoRunner" && exampleDirectory.getFileName() != "Assets" && exampleDirectory.getFileName() != "CMake") { exampleDirectories.add (exampleDirectory); } } exampleDirectories.sort(); return exampleDirectories; } Array ProjucerApplication::getSortedExampleFilesInDirectory (const File& directory) noexcept { Array exampleFiles; for (const auto& iter : RangedDirectoryIterator (directory, false, "*.h", File::findFiles)) exampleFiles.add (iter.getFile()); exampleFiles.sort(); return exampleFiles; } void ProjucerApplication::findAndLaunchExample (int selectedIndex) { File example; for (auto& dir : getSortedExampleDirectories()) { auto exampleFiles = getSortedExampleFilesInDirectory (dir); if (selectedIndex < exampleFiles.size()) { example = exampleFiles.getUnchecked (selectedIndex); break; } selectedIndex -= exampleFiles.size(); } // example doesn't exist? jassert (example != File()); openFile (example, nullptr); } //============================================================================== static String getPlatformSpecificFileExtension() { #if JUCE_MAC return ".app"; #elif JUCE_WINDOWS return ".exe"; #elif JUCE_LINUX || JUCE_BSD return {}; #else jassertfalse; return {}; #endif } static File getPlatformSpecificProjectFolder() { auto examplesDir = ProjucerApplication::getJUCEExamplesDirectoryPathFromGlobal(); if (examplesDir == File()) return {}; auto buildsFolder = examplesDir.getChildFile ("DemoRunner").getChildFile ("Builds"); #if JUCE_MAC return buildsFolder.getChildFile ("MacOSX"); #elif JUCE_WINDOWS return buildsFolder.getChildFile ("VisualStudio2017"); #elif JUCE_LINUX || JUCE_BSD return buildsFolder.getChildFile ("LinuxMakefile"); #else jassertfalse; return {}; #endif } static File tryToFindDemoRunnerExecutableInBuilds() { auto projectFolder = getPlatformSpecificProjectFolder(); if (projectFolder == File()) return {}; #if JUCE_MAC projectFolder = projectFolder.getChildFile ("build"); auto demoRunnerExecutable = projectFolder.getChildFile ("Release").getChildFile ("DemoRunner.app"); if (demoRunnerExecutable.exists()) return demoRunnerExecutable; demoRunnerExecutable = projectFolder.getChildFile ("Debug").getChildFile ("DemoRunner.app"); if (demoRunnerExecutable.exists()) return demoRunnerExecutable; #elif JUCE_WINDOWS projectFolder = projectFolder.getChildFile ("x64"); auto demoRunnerExecutable = projectFolder.getChildFile ("Release").getChildFile ("App").getChildFile ("DemoRunner.exe"); if (demoRunnerExecutable.existsAsFile()) return demoRunnerExecutable; demoRunnerExecutable = projectFolder.getChildFile ("Debug").getChildFile ("App").getChildFile ("DemoRunner.exe"); if (demoRunnerExecutable.existsAsFile()) return demoRunnerExecutable; #elif JUCE_LINUX || JUCE_BSD projectFolder = projectFolder.getChildFile ("build"); auto demoRunnerExecutable = projectFolder.getChildFile ("DemoRunner"); if (demoRunnerExecutable.existsAsFile()) return demoRunnerExecutable; #endif return {}; } static File tryToFindPrebuiltDemoRunnerExecutable() { auto prebuiltFile = File (getAppSettings().getStoredPath (Ids::jucePath, TargetOS::getThisOS()).get().toString()) .getChildFile ("DemoRunner" + getPlatformSpecificFileExtension()); #if JUCE_MAC if (prebuiltFile.exists()) #else if (prebuiltFile.existsAsFile()) #endif return prebuiltFile; return {}; } void ProjucerApplication::checkIfGlobalJUCEPathHasChanged() { auto globalJUCEPath = File (getAppSettings().getStoredPath (Ids::jucePath, TargetOS::getThisOS()).get()); if (lastJUCEPath != globalJUCEPath) { hasScannedForDemoRunnerProject = false; hasScannedForDemoRunnerExecutable = false; lastJUCEPath = globalJUCEPath; } } File ProjucerApplication::tryToFindDemoRunnerExecutable() { checkIfGlobalJUCEPathHasChanged(); if (hasScannedForDemoRunnerExecutable) return lastDemoRunnerExectuableFile; hasScannedForDemoRunnerExecutable = true; auto demoRunnerExecutable = tryToFindDemoRunnerExecutableInBuilds(); if (demoRunnerExecutable == File()) demoRunnerExecutable = tryToFindPrebuiltDemoRunnerExecutable(); lastDemoRunnerExectuableFile = demoRunnerExecutable; return demoRunnerExecutable; } File ProjucerApplication::tryToFindDemoRunnerProject() { checkIfGlobalJUCEPathHasChanged(); if (hasScannedForDemoRunnerProject) return lastDemoRunnerProjectFile; hasScannedForDemoRunnerProject = true; auto projectFolder = getPlatformSpecificProjectFolder(); if (projectFolder == File()) { lastDemoRunnerProjectFile = File(); return {}; } #if JUCE_MAC auto demoRunnerProjectFile = projectFolder.getChildFile ("DemoRunner.xcodeproj"); #elif JUCE_WINDOWS auto demoRunnerProjectFile = projectFolder.getChildFile ("DemoRunner.sln"); #elif JUCE_LINUX || JUCE_BSD auto demoRunnerProjectFile = projectFolder.getChildFile ("Makefile"); #endif #if JUCE_MAC if (! demoRunnerProjectFile.exists()) #else if (! demoRunnerProjectFile.existsAsFile()) #endif demoRunnerProjectFile = File(); lastDemoRunnerProjectFile = demoRunnerProjectFile; return demoRunnerProjectFile; } void ProjucerApplication::launchDemoRunner() { auto demoRunnerFile = tryToFindDemoRunnerExecutable(); if (demoRunnerFile != File() && demoRunnerFile.startAsProcess()) return; demoRunnerFile = tryToFindDemoRunnerProject(); if (demoRunnerFile != File()) { auto& lf = Desktop::getInstance().getDefaultLookAndFeel(); #if JUCE_LINUX || JUCE_BSD demoRunnerAlert.reset (lf.createAlertWindow ("Open Project", "Couldn't find a compiled version of the Demo Runner." " Please compile the Demo Runner project in the JUCE examples directory.", "OK", {}, {}, MessageBoxIconType::WarningIcon, 1, mainWindowList.getFrontmostWindow (false))); demoRunnerAlert->enterModalState (true, ModalCallbackFunction::create ([this] (int) { demoRunnerAlert.reset (nullptr); }), false); #else demoRunnerAlert.reset (lf.createAlertWindow ("Open Project", "Couldn't find a compiled version of the Demo Runner." " Do you want to open the project?", "Open project", "Cancel", {}, MessageBoxIconType::QuestionIcon, 2, mainWindowList.getFrontmostWindow (false))); demoRunnerAlert->enterModalState (true, ModalCallbackFunction::create ([this, demoRunnerFile] (int retVal) { demoRunnerAlert.reset (nullptr); if (retVal == 1) demoRunnerFile.startAsProcess(); }), false); #endif } } //============================================================================== void ProjucerApplication::handleMainMenuCommand (int menuItemID) { if (menuItemID >= recentProjectsBaseID && menuItemID < (recentProjectsBaseID + 100)) { // open a file from the "recent files" menu openFile (settings->recentFiles.getFile (menuItemID - recentProjectsBaseID), nullptr); } else if (menuItemID >= openWindowsBaseID && menuItemID < (openWindowsBaseID + 100)) { if (auto* window = mainWindowList.windows.getUnchecked (menuItemID - openWindowsBaseID)) window->toFront (true); } else if (menuItemID >= activeDocumentsBaseID && menuItemID < (activeDocumentsBaseID + 200)) { if (auto* doc = openDocumentManager.getOpenDocument (menuItemID - activeDocumentsBaseID)) mainWindowList.openDocument (doc, true); else jassertfalse; } else if (menuItemID == showPathsID) { showPathsWindow (true); } else if (menuItemID >= examplesBaseID && menuItemID < (examplesBaseID + numExamples)) { findAndLaunchExample (menuItemID - examplesBaseID); } else { handleGUIEditorMenuCommand (menuItemID); } } //============================================================================== void ProjucerApplication::getAllCommands (Array & commands) { JUCEApplication::getAllCommands (commands); const CommandID ids[] = { CommandIDs::newProject, CommandIDs::newProjectFromClipboard, CommandIDs::newPIP, CommandIDs::open, CommandIDs::launchDemoRunner, CommandIDs::closeAllWindows, CommandIDs::closeAllDocuments, CommandIDs::clearRecentFiles, CommandIDs::saveAll, CommandIDs::showGlobalPathsWindow, CommandIDs::showUTF8Tool, CommandIDs::showSVGPathTool, CommandIDs::enableGUIEditor, CommandIDs::showAboutWindow, CommandIDs::checkForNewVersion, CommandIDs::enableNewVersionCheck, CommandIDs::showForum, CommandIDs::showAPIModules, CommandIDs::showAPIClasses, CommandIDs::showTutorials, CommandIDs::loginLogout }; commands.addArray (ids, numElementsInArray (ids)); } void ProjucerApplication::getCommandInfo (CommandID commandID, ApplicationCommandInfo& result) { switch (commandID) { case CommandIDs::newProject: result.setInfo ("New Project...", "Creates a new JUCE project", CommandCategories::general, 0); result.defaultKeypresses.add (KeyPress ('n', ModifierKeys::commandModifier, 0)); break; case CommandIDs::newProjectFromClipboard: result.setInfo ("New Project From Clipboard...", "Creates a new JUCE project from the clipboard contents", CommandCategories::general, 0); result.defaultKeypresses.add (KeyPress ('n', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0)); break; case CommandIDs::newPIP: result.setInfo ("New PIP...", "Opens the PIP Creator utility for creating a new PIP", CommandCategories::general, 0); result.defaultKeypresses.add (KeyPress ('p', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0)); break; case CommandIDs::launchDemoRunner: result.setInfo ("Launch Demo Runner", "Launches the JUCE demo runner application, or the project if it can't be found", CommandCategories::general, 0); result.setActive (tryToFindDemoRunnerExecutable() != File() || tryToFindDemoRunnerProject() != File()); break; case CommandIDs::open: result.setInfo ("Open...", "Opens a JUCE project", CommandCategories::general, 0); result.defaultKeypresses.add (KeyPress ('o', ModifierKeys::commandModifier, 0)); break; case CommandIDs::showGlobalPathsWindow: result.setInfo ("Global Paths...", "Shows the window to change the stored global paths.", CommandCategories::general, 0); break; case CommandIDs::closeAllWindows: result.setInfo ("Close All Windows", "Closes all open windows", CommandCategories::general, 0); result.setActive (mainWindowList.windows.size() > 0); break; case CommandIDs::closeAllDocuments: result.setInfo ("Close All Documents", "Closes all open documents", CommandCategories::general, 0); result.setActive (openDocumentManager.getNumOpenDocuments() > 0); break; case CommandIDs::clearRecentFiles: result.setInfo ("Clear Recent Files", "Clears all recent files from the menu", CommandCategories::general, 0); result.setActive (settings->recentFiles.getNumFiles() > 0); break; case CommandIDs::saveAll: result.setInfo ("Save All", "Saves all open documents", CommandCategories::general, 0); result.defaultKeypresses.add (KeyPress ('s', ModifierKeys::commandModifier | ModifierKeys::altModifier, 0)); break; case CommandIDs::showUTF8Tool: result.setInfo ("UTF-8 String-Literal Helper", "Shows the UTF-8 string literal utility", CommandCategories::general, 0); break; case CommandIDs::showSVGPathTool: result.setInfo ("SVG Path Converter", "Shows the SVG->Path data conversion utility", CommandCategories::general, 0); break; case CommandIDs::enableGUIEditor: result.setInfo ("GUI Editor Enabled", "Enables or disables the GUI editor functionality", CommandCategories::general, (isGUIEditorEnabled() ? ApplicationCommandInfo::isTicked : 0)); break; case CommandIDs::showAboutWindow: result.setInfo ("About Projucer", "Shows the Projucer's 'About' page.", CommandCategories::general, 0); break; case CommandIDs::checkForNewVersion: result.setInfo ("Check for New Version...", "Checks the web server for a new version of JUCE", CommandCategories::general, 0); break; case CommandIDs::enableNewVersionCheck: result.setInfo ("Automatically Check for New Versions", "Enables automatic background checking for new versions of JUCE.", CommandCategories::general, (isAutomaticVersionCheckingEnabled() ? ApplicationCommandInfo::isTicked : 0)); break; case CommandIDs::showForum: result.setInfo ("JUCE Community Forum", "Shows the JUCE community forum in a browser", CommandCategories::general, 0); break; case CommandIDs::showAPIModules: result.setInfo ("API Modules", "Shows the API modules documentation in a browser", CommandCategories::general, 0); break; case CommandIDs::showAPIClasses: result.setInfo ("API Classes", "Shows the API classes documentation in a browser", CommandCategories::general, 0); break; case CommandIDs::showTutorials: result.setInfo ("JUCE Tutorials", "Shows the JUCE tutorials in a browser", CommandCategories::general, 0); break; case CommandIDs::loginLogout: { auto licenseState = licenseController->getCurrentState(); if (licenseState.isGPL()) result.setInfo ("Disable GPL mode", "Disables GPL mode", CommandCategories::general, 0); else result.setInfo (licenseState.isSignedIn() ? String ("Sign out ") + licenseState.username + "..." : String ("Sign in..."), "Sign out of your JUCE account", CommandCategories::general, 0); break; } default: JUCEApplication::getCommandInfo (commandID, result); break; } } bool ProjucerApplication::perform (const InvocationInfo& info) { switch (info.commandID) { case CommandIDs::newProject: createNewProject(); break; case CommandIDs::newProjectFromClipboard: createNewProjectFromClipboard(); break; case CommandIDs::newPIP: createNewPIP(); break; case CommandIDs::open: askUserToOpenFile(); break; case CommandIDs::launchDemoRunner: launchDemoRunner(); break; case CommandIDs::saveAll: saveAllDocuments(); break; case CommandIDs::closeAllWindows: closeAllMainWindowsAndQuitIfNeeded(); break; case CommandIDs::closeAllDocuments: closeAllDocuments (OpenDocumentManager::SaveIfNeeded::yes); break; case CommandIDs::clearRecentFiles: clearRecentFiles(); break; case CommandIDs::showUTF8Tool: showUTF8ToolWindow(); break; case CommandIDs::showSVGPathTool: showSVGPathDataToolWindow(); break; case CommandIDs::enableGUIEditor: enableOrDisableGUIEditor(); break; case CommandIDs::showGlobalPathsWindow: showPathsWindow (false); break; case CommandIDs::showAboutWindow: showAboutWindow(); break; case CommandIDs::checkForNewVersion: LatestVersionCheckerAndUpdater::getInstance()->checkForNewVersion (false); break; case CommandIDs::enableNewVersionCheck: setAutomaticVersionCheckingEnabled (! isAutomaticVersionCheckingEnabled()); break; case CommandIDs::showForum: launchForumBrowser(); break; case CommandIDs::showAPIModules: launchModulesBrowser(); break; case CommandIDs::showAPIClasses: launchClassesBrowser(); break; case CommandIDs::showTutorials: launchTutorialsBrowser(); break; case CommandIDs::loginLogout: doLoginOrLogout(); break; default: return JUCEApplication::perform (info); } return true; } //============================================================================== void ProjucerApplication::createNewProject() { auto* mw = mainWindowList.getOrCreateEmptyWindow(); jassert (mw != nullptr); mw->showStartPage(); mainWindowList.checkWindowBounds (*mw); } void ProjucerApplication::createNewProjectFromClipboard() { auto tempFile = File::getSpecialLocation (File::SpecialLocationType::tempDirectory).getChildFile ("PIPs").getChildFile ("Clipboard") .getChildFile ("PIPFile_" + String (std::abs (Random::getSystemRandom().nextInt())) + ".h") .getNonexistentSibling(); if (tempFile.existsAsFile()) tempFile.deleteFile(); tempFile.create(); tempFile.appendText (SystemClipboard::getTextFromClipboard()); auto cleanup = [tempFile] (String errorString) { if (errorString.isNotEmpty()) { AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon, "Error", errorString); tempFile.deleteFile(); } }; if (! isPIPFile (tempFile)) { cleanup ("Clipboard does not contain a valid PIP."); return; } openFile (tempFile, [parent = WeakReference { this }, cleanup] (bool openedSuccessfully) { if (parent == nullptr) return; if (! openedSuccessfully) { cleanup ("Couldn't create project from clipboard contents."); parent->mainWindowList.closeWindow (parent->mainWindowList.windows.getLast()); } }); } void ProjucerApplication::createNewPIP() { showPIPCreatorWindow(); } void ProjucerApplication::askUserToOpenFile() { chooser = std::make_unique ("Open File"); auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles; chooser->launchAsync (flags, [this] (const FileChooser& fc) { const auto result = fc.getResult(); if (result != File{}) openFile (result, nullptr); }); } void ProjucerApplication::openFile (const File& file, std::function callback) { mainWindowList.openFile (file, std::move (callback)); } void ProjucerApplication::saveAllDocuments() { openDocumentManager.saveAllSyncWithoutAsking(); for (int i = 0; i < mainWindowList.windows.size(); ++i) if (auto* pcc = mainWindowList.windows.getUnchecked(i)->getProjectContentComponent()) pcc->refreshProjectTreeFileStatuses(); } void ProjucerApplication::closeAllDocuments (OpenDocumentManager::SaveIfNeeded askUserToSave) { openDocumentManager.closeAllAsync (askUserToSave, nullptr); } void ProjucerApplication::closeAllMainWindows (std::function callback) { mainWindowList.askAllWindowsToClose (std::move (callback)); } void ProjucerApplication::closeAllMainWindowsAndQuitIfNeeded() { closeAllMainWindows ([parent = WeakReference { this }] (bool closedSuccessfully) { #if JUCE_MAC ignoreUnused (parent, closedSuccessfully); #else if (parent == nullptr) return; if (closedSuccessfully && parent->mainWindowList.windows.size() == 0) parent->systemRequestedQuit(); #endif }); } void ProjucerApplication::clearRecentFiles() { settings->recentFiles.clear(); settings->recentFiles.clearRecentFilesNatively(); settings->flush(); menuModel->menuItemsChanged(); } //============================================================================== void ProjucerApplication::showUTF8ToolWindow() { if (utf8Window != nullptr) utf8Window->toFront (true); else new FloatingToolWindow ("UTF-8 String Literal Converter", "utf8WindowPos", new UTF8Component(), utf8Window, true, 500, 500, 300, 300, 1000, 1000); } void ProjucerApplication::showSVGPathDataToolWindow() { if (svgPathWindow != nullptr) svgPathWindow->toFront (true); else new FloatingToolWindow ("SVG Path Converter", "svgPathWindowPos", new SVGPathDataComponent(), svgPathWindow, true, 500, 500, 300, 300, 1000, 1000); } bool ProjucerApplication::isGUIEditorEnabled() const { return getGlobalProperties().getBoolValue (Ids::guiEditorEnabled); } void ProjucerApplication::enableOrDisableGUIEditor() { getGlobalProperties().setValue (Ids::guiEditorEnabled, ! isGUIEditorEnabled()); } void ProjucerApplication::showAboutWindow() { if (aboutWindow != nullptr) aboutWindow->toFront (true); else new FloatingToolWindow ({}, {}, new AboutWindowComponent(), aboutWindow, false, 500, 300, 500, 300, 500, 300); } void ProjucerApplication::showPathsWindow (bool highlightJUCEPath) { if (pathsWindow != nullptr) pathsWindow->toFront (true); else new FloatingToolWindow ("Global Paths", "pathsWindowPos", new GlobalPathsWindowComponent(), pathsWindow, false, 600, 700, 600, 700, 600, 700); if (highlightJUCEPath) if (auto* pathsComp = dynamic_cast (pathsWindow->getChildComponent (0))) pathsComp->highlightJUCEPath(); } void ProjucerApplication::showEditorColourSchemeWindow() { if (editorColourSchemeWindow != nullptr) editorColourSchemeWindow->toFront (true); else new FloatingToolWindow ("Editor Colour Scheme", "editorColourSchemeWindowPos", new EditorColourSchemeWindowComponent(), editorColourSchemeWindow, false, 500, 500, 500, 500, 500, 500); } void ProjucerApplication::showPIPCreatorWindow() { if (pipCreatorWindow != nullptr) pipCreatorWindow->toFront (true); else new FloatingToolWindow ("PIP Creator", "pipCreatorWindowPos", new PIPCreatorWindowComponent(), pipCreatorWindow, false, 600, 750, 600, 750, 600, 750); } void ProjucerApplication::launchForumBrowser() { URL forumLink ("https://forum.juce.com/"); if (forumLink.isWellFormed()) forumLink.launchInDefaultBrowser(); } void ProjucerApplication::launchModulesBrowser() { URL modulesLink ("https://docs.juce.com/master/modules.html"); if (modulesLink.isWellFormed()) modulesLink.launchInDefaultBrowser(); } void ProjucerApplication::launchClassesBrowser() { URL classesLink ("https://docs.juce.com/master/classes.html"); if (classesLink.isWellFormed()) classesLink.launchInDefaultBrowser(); } void ProjucerApplication::launchTutorialsBrowser() { URL tutorialsLink ("https://juce.com/learn/tutorials"); if (tutorialsLink.isWellFormed()) tutorialsLink.launchInDefaultBrowser(); } void ProjucerApplication::doLoginOrLogout() { if (licenseController->getCurrentState().isSignedIn()) { licenseController->resetState(); } else { if (auto* window = mainWindowList.getMainWindowWithLoginFormOpen()) { window->toFront (true); } else { mainWindowList.createWindowIfNoneAreOpen(); mainWindowList.getFrontmostWindow()->showLoginFormOverlay(); } } } //============================================================================== struct FileWithTime { FileWithTime (const File& f) : file (f), time (f.getLastModificationTime()) {} FileWithTime() {} bool operator< (const FileWithTime& other) const { return time < other.time; } bool operator== (const FileWithTime& other) const { return time == other.time; } File file; Time time; }; void ProjucerApplication::deleteLogger() { const int maxNumLogFilesToKeep = 50; Logger::setCurrentLogger (nullptr); if (logger != nullptr) { auto logFiles = logger->getLogFile().getParentDirectory().findChildFiles (File::findFiles, false); if (logFiles.size() > maxNumLogFilesToKeep) { Array files; for (auto& f : logFiles) files.addUsingDefaultSort (f); for (int i = 0; i < files.size() - maxNumLogFilesToKeep; ++i) files.getReference(i).file.deleteFile(); } } logger.reset(); } PropertiesFile::Options ProjucerApplication::getPropertyFileOptionsFor (const String& filename, bool isProjectSettings) { PropertiesFile::Options options; options.applicationName = filename; options.filenameSuffix = "settings"; options.osxLibrarySubFolder = "Application Support"; #if JUCE_LINUX || JUCE_BSD options.folderName = "~/.config/Projucer"; #else options.folderName = "Projucer"; #endif if (isProjectSettings) options.folderName += "/ProjectSettings"; return options; } void ProjucerApplication::initCommandManager() { commandManager.reset (new ApplicationCommandManager()); commandManager->registerAllCommandsForTarget (this); { CodeDocument doc; CppCodeEditorComponent ed (File(), doc); commandManager->registerAllCommandsForTarget (&ed); } registerGUIEditorCommands(); } static void rescanModules (AvailableModulesList& list, const Array& paths, bool async) { if (async) list.scanPathsAsync (paths); else list.scanPaths (paths); } void ProjucerApplication::rescanJUCEPathModules() { rescanModules (jucePathModulesList, { getAppSettings().getStoredPath (Ids::defaultJuceModulePath, TargetOS::getThisOS()).get().toString() }, ! isRunningCommandLine); } void ProjucerApplication::rescanUserPathModules() { rescanModules (userPathsModulesList, { getAppSettings().getStoredPath (Ids::defaultUserModulePath, TargetOS::getThisOS()).get().toString() }, ! isRunningCommandLine); } bool ProjucerApplication::isAutomaticVersionCheckingEnabled() const { return ! getGlobalProperties().getBoolValue (Ids::dontQueryForUpdate); } void ProjucerApplication::setAutomaticVersionCheckingEnabled (bool enabled) { getGlobalProperties().setValue (Ids::dontQueryForUpdate, ! enabled); } bool ProjucerApplication::shouldPromptUserAboutIncorrectJUCEPath() const { return ! getGlobalProperties().getBoolValue (Ids::dontAskAboutJUCEPath); } void ProjucerApplication::setShouldPromptUserAboutIncorrectJUCEPath (bool shouldPrompt) { getGlobalProperties().setValue (Ids::dontAskAboutJUCEPath, ! shouldPrompt); } void ProjucerApplication::selectEditorColourSchemeWithName (const String& schemeName) { auto& appearanceSettings = getAppSettings().appearance; auto schemes = appearanceSettings.getPresetSchemes(); auto schemeIndex = schemes.indexOf (schemeName); if (schemeIndex >= 0) setEditorColourScheme (schemeIndex, true); } void ProjucerApplication::setColourScheme (int index, bool saveSetting) { switch (index) { case 0: lookAndFeel.setColourScheme (LookAndFeel_V4::getDarkColourScheme()); break; case 1: lookAndFeel.setColourScheme (LookAndFeel_V4::getGreyColourScheme()); break; case 2: lookAndFeel.setColourScheme (LookAndFeel_V4::getLightColourScheme()); break; default: break; } lookAndFeel.setupColours(); mainWindowList.sendLookAndFeelChange(); if (utf8Window != nullptr) utf8Window->sendLookAndFeelChange(); if (svgPathWindow != nullptr) svgPathWindow->sendLookAndFeelChange(); if (aboutWindow != nullptr) aboutWindow->sendLookAndFeelChange(); if (pathsWindow != nullptr) pathsWindow->sendLookAndFeelChange(); if (editorColourSchemeWindow != nullptr) editorColourSchemeWindow->sendLookAndFeelChange(); if (pipCreatorWindow != nullptr) pipCreatorWindow->sendLookAndFeelChange(); auto* mcm = ModalComponentManager::getInstance(); for (auto i = 0; i < mcm->getNumModalComponents(); ++i) mcm->getModalComponent (i)->sendLookAndFeelChange(); if (saveSetting) { auto& properties = getGlobalProperties(); properties.setValue ("COLOUR SCHEME", index); } selectedColourSchemeIndex = index; getCommandManager().commandStatusChanged(); } void ProjucerApplication::setEditorColourScheme (int index, bool saveSetting) { auto& appearanceSettings = getAppSettings().appearance; auto schemes = appearanceSettings.getPresetSchemes(); index = jmin (index, schemes.size() - 1); appearanceSettings.selectPresetScheme (index); if (saveSetting) { auto& properties = getGlobalProperties(); properties.setValue ("EDITOR COLOUR SCHEME", index); } selectedEditorColourSchemeIndex = index; getCommandManager().commandStatusChanged(); } static bool isEditorColourSchemeADefaultScheme (const StringArray& schemes, int editorColourSchemeIndex) { auto& schemeName = schemes[editorColourSchemeIndex]; return (schemeName == "Default (Dark)" || schemeName == "Default (Light)"); } static int getEditorColourSchemeForGUIColourScheme (const StringArray& schemes, int guiColourSchemeIndex) { auto defaultDarkEditorIndex = schemes.indexOf ("Default (Dark)"); auto defaultLightEditorIndex = schemes.indexOf ("Default (Light)"); // Can't find default code editor colour schemes! jassert (defaultDarkEditorIndex != -1 && defaultLightEditorIndex != -1); return (guiColourSchemeIndex == 2 ? defaultLightEditorIndex : defaultDarkEditorIndex); } void ProjucerApplication::updateEditorColourSchemeIfNeeded() { auto& appearanceSettings = getAppSettings().appearance; auto schemes = appearanceSettings.getPresetSchemes(); if (isEditorColourSchemeADefaultScheme (schemes, selectedEditorColourSchemeIndex)) setEditorColourScheme (getEditorColourSchemeForGUIColourScheme (schemes, selectedColourSchemeIndex), true); }