| @@ -28,7 +28,8 @@ | |||
| //============================================================================== | |||
| class GlobalPathsWindowComponent : public Component | |||
| class GlobalPathsWindowComponent : public Component, | |||
| private Timer | |||
| { | |||
| public: | |||
| GlobalPathsWindowComponent() | |||
| @@ -66,6 +67,12 @@ public: | |||
| g.fillAll (findColour (backgroundColourId)); | |||
| } | |||
| void paintOverChildren (Graphics& g) override | |||
| { | |||
| g.setColour (findColour (defaultHighlightColourId).withAlpha (flashAlpha)); | |||
| g.fillRect (boundsToHighlight); | |||
| } | |||
| void resized() override | |||
| { | |||
| auto b = getLocalBounds().reduced (10); | |||
| @@ -77,6 +84,8 @@ public: | |||
| info.setBounds (osSelector.getBounds().withWidth (osSelector.getHeight()).translated ((osSelector.getWidth() + 5), 0).reduced (2)); | |||
| int labelIndex = 0; | |||
| bool isFirst = true; | |||
| for (auto* pathComp : pathPropertyComponents) | |||
| { | |||
| if (pathComp == nullptr) | |||
| @@ -87,9 +96,28 @@ public: | |||
| } | |||
| else | |||
| { | |||
| if (isFirst) | |||
| b.removeFromTop (20); | |||
| pathComp->setBounds (b.removeFromTop (pathComp->getPreferredHeight())); | |||
| b.removeFromTop (5); | |||
| } | |||
| isFirst = false; | |||
| } | |||
| } | |||
| void highlightJUCEPath() | |||
| { | |||
| if (! isTimerRunning() && isSelectedOSThisOS()) | |||
| { | |||
| if (auto* jucePathComp = pathPropertyComponents.getFirst()) | |||
| boundsToHighlight = jucePathComp->getBounds(); | |||
| flashAlpha = 0.0f; | |||
| hasFlashed = false; | |||
| startTimer (25); | |||
| } | |||
| } | |||
| @@ -100,6 +128,30 @@ private: | |||
| ComboBox osSelector; | |||
| InfoButton info; | |||
| Rectangle<int> boundsToHighlight; | |||
| float flashAlpha = 0.0f; | |||
| bool hasFlashed = false; | |||
| //============================================================================== | |||
| void timerCallback() override | |||
| { | |||
| flashAlpha += (hasFlashed ? -0.05f : 0.05f); | |||
| if (flashAlpha > 0.75f) | |||
| { | |||
| hasFlashed = true; | |||
| } | |||
| else if (flashAlpha < 0.0f) | |||
| { | |||
| flashAlpha = 0.0f; | |||
| boundsToHighlight = {}; | |||
| stopTimer(); | |||
| } | |||
| repaint(); | |||
| } | |||
| //============================================================================== | |||
| bool isSelectedOSThisOS() { return TargetOS::getThisOS() == getSelectedOS(); } | |||
| @@ -126,6 +178,9 @@ private: | |||
| if (isSelectedOSThisOS()) | |||
| { | |||
| addAndMakeVisible (pathPropertyComponents.add (new FilePathPropertyComponent (settings.getStoredPath (Ids::jucePath), | |||
| "Path to JUCE", true))); | |||
| pathPropertyComponents.add (nullptr); | |||
| addAndMakeVisible (pathPropertyComponents.add (new FilePathPropertyComponent (settings.getStoredPath (Ids::defaultJuceModulePath), | |||
| @@ -177,6 +177,9 @@ void ProjucerApplication::handleAsyncUpdate() | |||
| setAnalyticsEnabled (licenseController->getState().applicationUsageDataState == LicenseState::ApplicationUsageData::enabled); | |||
| Analytics::getInstance()->logEvent ("Startup", {}, ProjucerAnalyticsEvent::appEvent); | |||
| } | |||
| if (! isRunningCommandLine && settings->shouldAskUserToSetJUCEPath()) | |||
| showSetJUCEPathAlert(); | |||
| } | |||
| void ProjucerApplication::initialiseWindows (const String& commandLine) | |||
| @@ -345,7 +348,8 @@ enum | |||
| activeDocumentsBaseID = 400, | |||
| colourSchemeBaseID = 1000, | |||
| codeEditorColourSchemeBaseID = 1500, | |||
| examplesBaseID = 2000, | |||
| showPathsID = 1999, | |||
| examplesBaseID = 2000 | |||
| }; | |||
| MenuBarModel* ProjucerApplication::getMenuModel() | |||
| @@ -592,7 +596,7 @@ void ProjucerApplication::createExamplesPopupMenu (PopupMenu& menu) noexcept | |||
| if (numExamples == 0) | |||
| { | |||
| menu.addCommandItem (commandManager, CommandIDs::showGlobalPathsWindow, "Set path to JUCE..."); | |||
| menu.addItem (showPathsID, "Set path to JUCE..."); | |||
| } | |||
| else | |||
| { | |||
| @@ -615,7 +619,7 @@ Array<File> ProjucerApplication::getSortedExampleDirectories() const noexcept | |||
| { | |||
| auto exampleDirectory = iter.getFile(); | |||
| if (exampleDirectory.getFileName() != "DemoRunner" && exampleDirectory.getFileName() != "Resources") | |||
| if (exampleDirectory.getFileName() != "DemoRunner" && exampleDirectory.getFileName() != "Assets") | |||
| exampleDirectories.add (exampleDirectory); | |||
| } | |||
| @@ -860,6 +864,10 @@ void ProjucerApplication::handleMainMenuCommand (int menuItemID) | |||
| { | |||
| showEditorColourSchemeWindow(); | |||
| } | |||
| else if (menuItemID == showPathsID) | |||
| { | |||
| showPathsWindow (true); | |||
| } | |||
| else if (menuItemID >= examplesBaseID && menuItemID < (examplesBaseID + numExamples)) | |||
| { | |||
| findAndLaunchExample (menuItemID - examplesBaseID); | |||
| @@ -1028,7 +1036,7 @@ bool ProjucerApplication::perform (const InvocationInfo& info) | |||
| case CommandIDs::clearRecentFiles: clearRecentFiles(); break; | |||
| case CommandIDs::showUTF8Tool: showUTF8ToolWindow(); break; | |||
| case CommandIDs::showSVGPathTool: showSVGPathDataToolWindow(); break; | |||
| case CommandIDs::showGlobalPathsWindow: showPathsWindow(); break; | |||
| case CommandIDs::showGlobalPathsWindow: showPathsWindow (false); break; | |||
| case CommandIDs::showAboutWindow: showAboutWindow(); break; | |||
| case CommandIDs::showAppUsageWindow: showApplicationUsageDataAgreementPopup(); break; | |||
| case CommandIDs::showForum: launchForumBrowser(); break; | |||
| @@ -1166,7 +1174,7 @@ void ProjucerApplication::dismissApplicationUsageDataAgreementPopup() | |||
| applicationUsageDataWindow.reset(); | |||
| } | |||
| void ProjucerApplication::showPathsWindow() | |||
| void ProjucerApplication::showPathsWindow (bool highlightJUCEPath) | |||
| { | |||
| if (pathsWindow != nullptr) | |||
| pathsWindow->toFront (true); | |||
| @@ -1174,7 +1182,11 @@ void ProjucerApplication::showPathsWindow() | |||
| new FloatingToolWindow ("Global Paths", | |||
| "pathsWindowPos", | |||
| new GlobalPathsWindowComponent(), pathsWindow, false, | |||
| 600, 550, 600, 550, 600, 550); | |||
| 600, 650, 600, 650, 600, 650); | |||
| if (highlightJUCEPath) | |||
| if (auto* pathsComp = dynamic_cast<GlobalPathsWindowComponent*> (pathsWindow->getChildComponent (0))) | |||
| pathsComp->highlightJUCEPath(); | |||
| } | |||
| void ProjucerApplication::showEditorColourSchemeWindow() | |||
| @@ -1345,6 +1357,27 @@ void ProjucerApplication::setupAnalytics() | |||
| Analytics::getInstance()->setUserProperties (userData); | |||
| } | |||
| void ProjucerApplication::showSetJUCEPathAlert() | |||
| { | |||
| auto& lf = Desktop::getInstance().getDefaultLookAndFeel(); | |||
| pathAlert = lf.createAlertWindow ("Set JUCE Path", "Your global JUCE path is invalid. This path is used to access the JUCE examples and demo project - " | |||
| "would you like to set it now?", | |||
| "Set path", "Cancel", "Don't ask again", | |||
| AlertWindow::WarningIcon, 3, | |||
| mainWindowList.getFrontmostWindow (false)); | |||
| pathAlert->enterModalState (true, ModalCallbackFunction::create ([this] (int retVal) | |||
| { | |||
| pathAlert.reset (nullptr); | |||
| if (retVal == 1) | |||
| showPathsWindow (true); | |||
| else if (retVal == 0) | |||
| settings->setDontAskAboutJUCEPathAgain(); | |||
| })); | |||
| } | |||
| void ProjucerApplication::selectEditorColourSchemeWithName (const String& schemeName) | |||
| { | |||
| auto& appearanceSettings = getAppSettings().appearance; | |||
| @@ -108,7 +108,7 @@ public: | |||
| void showApplicationUsageDataAgreementPopup(); | |||
| void dismissApplicationUsageDataAgreementPopup(); | |||
| void showPathsWindow(); | |||
| void showPathsWindow (bool highlightJUCEPath = false); | |||
| void showEditorColourSchemeWindow(); | |||
| void launchForumBrowser(); | |||
| @@ -194,6 +194,9 @@ private: | |||
| void resetAnalytics() noexcept; | |||
| void setupAnalytics(); | |||
| void showSetJUCEPathAlert(); | |||
| ScopedPointer<AlertWindow> pathAlert; | |||
| //============================================================================== | |||
| void setColourScheme (int index, bool saveSetting); | |||
| @@ -717,10 +717,6 @@ namespace | |||
| #endif | |||
| auto settingsFile = userAppData.getChildFile ("Projucer").getChildFile ("Projucer.settings"); | |||
| if (! settingsFile.existsAsFile()) | |||
| throw CommandLineError ("Expected settings file at " + settingsFile.getFullPathName() + " not found!"); | |||
| ScopedPointer<XmlElement> xml (XmlDocument::parse (settingsFile)); | |||
| auto settingsTree = ValueTree::fromXml (*xml); | |||
| @@ -743,15 +739,14 @@ namespace | |||
| if (! childToSet.isValid()) | |||
| throw CommandLineError ("Failed to set the requested setting!"); | |||
| childToSet.setProperty (args[2], args[3], nullptr); | |||
| childToSet.setProperty (args[2], File::getCurrentWorkingDirectory().getChildFile (args[3]).getFullPathName(), nullptr); | |||
| settingsFile.replaceWithText (settingsTree.toXmlString()); | |||
| } | |||
| static void createProjectFromPIP (const StringArray& args) | |||
| { | |||
| if (args.size() < 3) | |||
| throw CommandLineError ("Not enough arguments. Usage: --create-project-from-pip path/to/PIP path/to/output."); | |||
| checkArgumentCount (args, 3); | |||
| auto pipFile = File::getCurrentWorkingDirectory().getChildFile (args[1].unquoted()); | |||
| if (! pipFile.existsAsFile()) | |||
| @@ -37,6 +37,9 @@ ProjucerAnalyticsDestination::ProjucerAnalyticsDestination() | |||
| } | |||
| auto dataDir = File::getSpecialLocation (File::userApplicationDataDirectory) | |||
| #if JUCE_MAC | |||
| .getChildFile ("Application Support") | |||
| #endif | |||
| .getChildFile ("Projucer") | |||
| .getChildFile ("Analytics"); | |||
| @@ -491,13 +491,39 @@ void Project::moveTemporaryDirectory (const File& newParentDirectory) | |||
| auto newDirectory = newParentDirectory.getChildFile (tempDirectory.getFileName()); | |||
| auto oldJucerFileName = getFile().getFileName(); | |||
| saveProjectRootToFile(); | |||
| tempDirectory.copyDirectoryTo (newDirectory); | |||
| tempDirectory.deleteRecursively(); | |||
| tempDirectory = File(); | |||
| // reload project from new location | |||
| if (auto* window = ProjucerApplication::getApp().mainWindowList.getMainWindowForFile (getFile())) | |||
| window->moveProject (newDirectory.getChildFile (oldJucerFileName)); | |||
| { | |||
| Component::SafePointer<MainWindow> safeWindow (window); | |||
| MessageManager::callAsync ([safeWindow, newDirectory, oldJucerFileName] | |||
| { | |||
| if (safeWindow != nullptr) | |||
| safeWindow.getComponent()->moveProject (newDirectory.getChildFile (oldJucerFileName)); | |||
| }); | |||
| } | |||
| } | |||
| bool Project::saveProjectRootToFile() | |||
| { | |||
| ScopedPointer<XmlElement> xml (projectRoot.createXml()); | |||
| if (xml == nullptr) | |||
| { | |||
| jassertfalse; | |||
| return false; | |||
| } | |||
| MemoryOutputStream mo; | |||
| xml->writeToStream (mo, {}); | |||
| return FileHelpers::overwriteFileWithNewDataIfDifferent (getFile(), mo); | |||
| } | |||
| //============================================================================== | |||
| @@ -367,6 +367,7 @@ private: | |||
| void askUserWhereToSaveProject(); | |||
| void moveTemporaryDirectory (const File&); | |||
| bool saveProjectRootToFile(); | |||
| //============================================================================== | |||
| bool hasSentGUIBuilderAnalyticsEvent = false; | |||
| @@ -47,6 +47,8 @@ StoredSettings::StoredSettings() | |||
| { | |||
| updateOldProjectSettingsFiles(); | |||
| reload(); | |||
| checkJUCEPaths(); | |||
| projectDefaults.addListener (this); | |||
| fallbackPaths.addListener (this); | |||
| } | |||
| @@ -195,6 +197,34 @@ void StoredSettings::updateOldProjectSettingsFiles() | |||
| } | |||
| } | |||
| void StoredSettings::checkJUCEPaths() | |||
| { | |||
| auto moduleFolder = projectDefaults.getProperty (Ids::defaultJuceModulePath).toString(); | |||
| auto juceFolder = projectDefaults.getProperty (Ids::jucePath).toString(); | |||
| auto validModuleFolder = isGlobalPathValid ({}, Ids::defaultJuceModulePath, moduleFolder); | |||
| auto validJuceFolder = isGlobalPathValid ({}, Ids::jucePath, juceFolder); | |||
| if (validModuleFolder && ! validJuceFolder) | |||
| projectDefaults.getPropertyAsValue (Ids::jucePath, nullptr) = File (moduleFolder).getParentDirectory().getFullPathName(); | |||
| else if (! validModuleFolder && validJuceFolder) | |||
| projectDefaults.getPropertyAsValue (Ids::defaultJuceModulePath, nullptr) = File (juceFolder).getChildFile ("modules").getFullPathName(); | |||
| } | |||
| bool StoredSettings::shouldAskUserToSetJUCEPath() noexcept | |||
| { | |||
| if (! isGlobalPathValid ({}, Ids::jucePath, projectDefaults.getProperty (Ids::jucePath).toString()) | |||
| && getGlobalProperties().getValue ("dontAskAboutJUCEPath", {}).isEmpty()) | |||
| return true; | |||
| return false; | |||
| } | |||
| void StoredSettings::setDontAskAboutJUCEPathAgain() noexcept | |||
| { | |||
| getGlobalProperties().setValue ("dontAskAboutJUCEPath", 1); | |||
| } | |||
| //============================================================================== | |||
| void StoredSettings::loadSwatchColours() | |||
| { | |||
| @@ -271,7 +301,12 @@ Value StoredSettings::getFallbackPathForOS (const Identifier& key, DependencyPat | |||
| if (v.toString().isEmpty()) | |||
| { | |||
| if (key == Ids::defaultJuceModulePath) | |||
| if (key == Ids::jucePath) | |||
| { | |||
| v = (os == TargetOS::windows ? "C:\\JUCE" | |||
| : "~/JUCE"); | |||
| } | |||
| else if (key == Ids::defaultJuceModulePath) | |||
| { | |||
| v = (os == TargetOS::windows ? "C:\\JUCE\\modules" | |||
| : "~/JUCE/modules"); | |||
| @@ -334,13 +369,13 @@ Value StoredSettings::getFallbackPathForOS (const Identifier& key, DependencyPat | |||
| return v; | |||
| } | |||
| static bool doesSDKPathContainFile (const File& relativeTo, const String& path, const String& fileToCheckFor) | |||
| static bool doesSDKPathContainFile (const File& relativeTo, const String& path, const String& fileToCheckFor) noexcept | |||
| { | |||
| auto actualPath = path.replace ("${user.home}", File::getSpecialLocation (File::userHomeDirectory).getFullPathName()); | |||
| return relativeTo.getChildFile (actualPath + "/" + fileToCheckFor).exists(); | |||
| } | |||
| bool StoredSettings::isGlobalPathValid (const File& relativeTo, const Identifier& key, const String& path) | |||
| bool StoredSettings::isGlobalPathValid (const File& relativeTo, const Identifier& key, const String& path) const noexcept | |||
| { | |||
| String fileToCheckFor; | |||
| @@ -390,6 +425,10 @@ bool StoredSettings::isGlobalPathValid (const File& relativeTo, const Identifier | |||
| fileToCheckFor = "../clion.sh"; | |||
| #endif | |||
| } | |||
| else if (key == Ids::jucePath) | |||
| { | |||
| fileToCheckFor = "ChangeList.txt"; | |||
| } | |||
| else | |||
| { | |||
| // didn't recognise the key provided! | |||
| @@ -70,7 +70,11 @@ public: | |||
| Value getStoredPath (const Identifier& key); | |||
| Value getFallbackPathForOS (const Identifier& key, DependencyPathOS); | |||
| bool isGlobalPathValid (const File& relativeTo, const Identifier& key, const String& path); | |||
| bool isGlobalPathValid (const File& relativeTo, const Identifier& key, const String& path) const noexcept; | |||
| //============================================================================== | |||
| bool shouldAskUserToSetJUCEPath() noexcept; | |||
| void setDontAskAboutJUCEPathAgain() noexcept; | |||
| private: | |||
| OwnedArray<PropertiesFile> propertyFiles; | |||
| @@ -96,6 +100,7 @@ private: | |||
| void saveSwatchColours(); | |||
| void updateOldProjectSettingsFiles(); | |||
| void checkJUCEPaths(); | |||
| //============================================================================== | |||
| void valueTreePropertyChanged (ValueTree& vt, const Identifier&) override { changed (vt == projectDefaults); } | |||
| @@ -405,7 +405,12 @@ bool isPIPFile (const File& file) noexcept | |||
| File getJUCEExamplesDirectoryPathFromGlobal() noexcept | |||
| { | |||
| return File (getAppSettings().getStoredPath (Ids::defaultJuceModulePath).toString()).getSiblingFile ("examples"); | |||
| auto globalPath = getAppSettings().getStoredPath (Ids::jucePath).toString(); | |||
| if (globalPath.isNotEmpty()) | |||
| return File (getAppSettings().getStoredPath (Ids::jucePath).toString()).getChildFile ("examples"); | |||
| return {}; | |||
| } | |||
| bool isValidJUCEExamplesDirectory (const File& directory) noexcept | |||
| @@ -413,5 +418,5 @@ bool isValidJUCEExamplesDirectory (const File& directory) noexcept | |||
| if (! directory.exists() || ! directory.isDirectory() || ! directory.containsSubDirectories()) | |||
| return false; | |||
| return directory.getChildFile ("Resources").getChildFile ("juce_icon.png").existsAsFile(); | |||
| return directory.getChildFile ("Assets").getChildFile ("juce_icon.png").existsAsFile(); | |||
| } | |||
| @@ -65,6 +65,7 @@ namespace Ids | |||
| DECLARE_ID (osxFallback); | |||
| DECLARE_ID (windowsFallback); | |||
| DECLARE_ID (linuxFallback); | |||
| DECLARE_ID (jucePath); | |||
| DECLARE_ID (defaultJuceModulePath); | |||
| DECLARE_ID (defaultUserModulePath); | |||
| DECLARE_ID (vst3Folder); | |||
| @@ -256,15 +256,15 @@ void PIPGenerator::createFiles (ValueTree& jucerTree) | |||
| if (relativeFiles.size() > 0) | |||
| { | |||
| ValueTree resources (Ids::GROUP); | |||
| resources.setProperty (Ids::ID, createAlphaNumericUID(), nullptr); | |||
| resources.setProperty (Ids::name, "Resources", nullptr); | |||
| ValueTree assets (Ids::GROUP); | |||
| assets.setProperty (Ids::ID, createAlphaNumericUID(), nullptr); | |||
| assets.setProperty (Ids::name, "Assets", nullptr); | |||
| for (auto& f : relativeFiles) | |||
| if (copyRelativeFileToLocalSourceDirectory (f)) | |||
| addFileToTree (resources, f.getFileName(), f.getFileExtension() == ".cpp", "Source/" + f.getFileName()); | |||
| addFileToTree (assets, f.getFileName(), f.getFileExtension() == ".cpp", "Source/" + f.getFileName()); | |||
| mainGroup.addChild (resources, -1, nullptr); | |||
| mainGroup.addChild (assets, -1, nullptr); | |||
| } | |||
| } | |||
| @@ -400,7 +400,7 @@ Result PIPGenerator::setProjectSettings (ValueTree& jucerTree) | |||
| if (isValidJUCEExamplesDirectory (examplesDirectory)) | |||
| { | |||
| defines += ((defines.isEmpty() ? "" : " ") + String ("PIP_JUCE_EXAMPLES_DIRECTORY=") | |||
| + examplesDirectory.getFullPathName()); | |||
| + Base64::toBase64 (examplesDirectory.getFullPathName())); | |||
| } | |||
| else | |||
| { | |||