diff --git a/extras/Introjucer/Source/Application/jucer_Application.h b/extras/Introjucer/Source/Application/jucer_Application.h index 9a807ca324..dc300a02ef 100644 --- a/extras/Introjucer/Source/Application/jucer_Application.h +++ b/extras/Introjucer/Source/Application/jucer_Application.h @@ -85,7 +85,7 @@ public: MenuBarModel::setMacMainMenu (menuModel, nullptr, "Open Recent"); #endif - versionChecker = new LatestVersionChecker(); + versionChecker = createVersionChecker(); } void initialiseBasics() @@ -169,6 +169,21 @@ public: const String getApplicationName() override { return "Introjucer"; } const String getApplicationVersion() override { return ProjectInfo::versionString; } + virtual String getVersionDescription() const + { + String s; + + const Time buildDate (Time::getCompilationDate()); + + s << "Introjucer " << ProjectInfo::versionString + << newLine + << "Build date: " << buildDate.getDayOfMonth() + << " " << Time::getMonthName (buildDate.getMonth(), true) + << " " << buildDate.getYear(); + + return s; + } + bool moreThanOneInstanceAllowed() override { return true; // this is handled manually in initialise() @@ -546,6 +561,12 @@ public: return new ProjectContentComponent(); } + //============================================================================== + virtual LatestVersionChecker* createVersionChecker() const + { + return new LatestVersionChecker(); + } + //============================================================================== IntrojucerLookAndFeel lookAndFeel; diff --git a/extras/Introjucer/Source/Application/jucer_AutoUpdater.cpp b/extras/Introjucer/Source/Application/jucer_AutoUpdater.cpp index 91d643ffe9..17f34f1a94 100644 --- a/extras/Introjucer/Source/Application/jucer_AutoUpdater.cpp +++ b/extras/Introjucer/Source/Application/jucer_AutoUpdater.cpp @@ -26,9 +26,15 @@ #include "jucer_AutoUpdater.h" LatestVersionChecker::JuceVersionTriple::JuceVersionTriple() - : major (JUCE_MAJOR_VERSION), - minor (JUCE_MINOR_VERSION), - build (JUCE_BUILDNUMBER) + : major ((ProjectInfo::versionNumber & 0xff0000) >> 16), + minor ((ProjectInfo::versionNumber & 0x00ff00) >> 8), + build ((ProjectInfo::versionNumber & 0x0000ff) >> 0) +{} + +LatestVersionChecker::JuceVersionTriple::JuceVersionTriple (int juceVersionNumber) + : major ((juceVersionNumber & 0xff0000) >> 16), + minor ((juceVersionNumber & 0x00ff00) >> 8), + build ((juceVersionNumber & 0x0000ff) >> 0) {} LatestVersionChecker::JuceVersionTriple::JuceVersionTriple (int majorInt, int minorInt, int buildNumber) @@ -118,16 +124,19 @@ struct RelaunchTimer : private Timer class DownloadNewVersionThread : public ThreadWithProgressWindow { public: - DownloadNewVersionThread (URL u, const String& extraHeaders, File target) + DownloadNewVersionThread (LatestVersionChecker& versionChecker,URL u, + const String& extraHeaders, File target) : ThreadWithProgressWindow ("Downloading New Version", true, true), + owner (versionChecker), result (Result::ok()), url (u), headers (extraHeaders), targetFolder (target) { } - static void performDownload (URL u, const String& extraHeaders, File targetFolder) + static void performDownload (LatestVersionChecker& versionChecker, URL u, + const String& extraHeaders, File targetFolder) { - DownloadNewVersionThread d (u, extraHeaders, targetFolder); + DownloadNewVersionThread d (versionChecker, u, extraHeaders, targetFolder); if (d.runThread()) { @@ -152,7 +161,10 @@ public: result = download (zipData); if (result.wasOk() && ! threadShouldExit()) - result = unzip (zipData); + { + setStatusMessage ("Installing..."); + result = owner.performUpdate (zipData, targetFolder); + } } Result download (MemoryBlock& dest) @@ -177,7 +189,7 @@ public: if (redirectPath.isEmpty()) break; - url = LatestVersionChecker::getLatestVersionURL (headers, redirectPath); + url = owner.getLatestVersionURL (headers, redirectPath); } if (in != nullptr && statusCode == 200) @@ -207,63 +219,7 @@ public: return Result::fail ("Failed to download from: " + url.toString (false)); } - Result unzip (const MemoryBlock& data) - { - setStatusMessage ("Installing..."); - - File unzipTarget; - bool isUsingTempFolder = false; - - { - MemoryInputStream input (data, false); - ZipFile zip (input); - - if (zip.getNumEntries() == 0) - return Result::fail ("The downloaded file wasn't a valid JUCE file!"); - - unzipTarget = targetFolder; - - if (unzipTarget.exists()) - { - isUsingTempFolder = true; - unzipTarget = targetFolder.getNonexistentSibling(); - - if (! unzipTarget.createDirectory()) - return Result::fail ("Couldn't create a folder to unzip the new version!"); - } - - Result r (zip.uncompressTo (unzipTarget)); - - if (r.failed()) - { - if (isUsingTempFolder) - unzipTarget.deleteRecursively(); - - return r; - } - } - - if (isUsingTempFolder) - { - File oldFolder (targetFolder.getSiblingFile (targetFolder.getFileNameWithoutExtension() + "_old") - .getNonexistentSibling()); - - if (! targetFolder.moveFileTo (oldFolder)) - { - unzipTarget.deleteRecursively(); - return Result::fail ("Could not remove the existing folder!"); - } - - if (! unzipTarget.moveFileTo (targetFolder)) - { - unzipTarget.deleteRecursively(); - return Result::fail ("Could not overwrite the existing folder!"); - } - } - - return Result::ok(); - } - + LatestVersionChecker& owner; Result result; URL url; String headers; @@ -278,13 +234,14 @@ class UpdateUserDialog : public Component, { public: UpdateUserDialog (const LatestVersionChecker::JuceVersionTriple& version, + const String& productName, const String& releaseNotes, const char* overwriteFolderPath) : hasOverwriteButton (overwriteFolderPath != nullptr) { addAndMakeVisible (titleLabel = new Label ("Title Label", - TRANS ("Download JUCE version 123?"). - replace ("123", version.toString()))); + TRANS ("Download \"123\" version 456?").replace ("123", productName) + .replace ("456", version.toString()))); titleLabel->setFont (Font (15.00f, Font::bold)); titleLabel->setJustificationType (Justification::centredLeft); @@ -293,7 +250,8 @@ public: titleLabel->setColour (TextEditor::backgroundColourId, Colour (0x00000000)); addAndMakeVisible (contentLabel = new Label ("Content Label", - TRANS("A new version of JUCE is available - would you like to download it?"))); + TRANS ("A new version of \"123\" is available - would you like to download it?") + .replace ("123", productName))); contentLabel->setFont (Font (15.00f, Font::plain)); contentLabel->setJustificationType (Justification::topLeft); contentLabel->setEditable (false, false, false); @@ -413,13 +371,16 @@ public: } static DialogWindow* launch (const LatestVersionChecker::JuceVersionTriple& version, + const String& productName, const String& releaseNotes, const char* overwritePath = nullptr) { - OptionalScopedPointer userDialog (new UpdateUserDialog (version, releaseNotes, overwritePath), true); + OptionalScopedPointer userDialog (new UpdateUserDialog (version, productName, + releaseNotes, overwritePath), true); DialogWindow::LaunchOptions lo; - lo.dialogTitle = TRANS ("Download JUCE version 123?").replace ("123", version.toString()); + lo.dialogTitle = TRANS ("Download \"123\" version 456?").replace ("456", version.toString()) + .replace ("123", productName); lo.dialogBackgroundColour = Colours::lightgrey; lo.content = userDialog; lo.componentToCentreAround = nullptr; @@ -528,12 +489,92 @@ String LatestVersionChecker::getOSString() else return SystemStats::getOperatingSystemName(); } -URL LatestVersionChecker::getLatestVersionURL (String& headers, const String& path) +const LatestVersionChecker::JuceServerLocationsAndKeys& LatestVersionChecker::getJuceServerURLsAndKeys() const { - static const char* updateSeverHostname = "https://my.roli.com"; - static const char* publicAPIKey = "495fb2d-cce9a8-3c52824-2da2679"; - static const int apiVersion = 1; - static const char* updatePath = "/software_versions/update_to/Introjucer/"; + static LatestVersionChecker::JuceServerLocationsAndKeys urlsAndKeys = + { + "https://my.roli.com", + "495fb2d-cce9a8-3c52824-2da2679", + 1, + "/software_versions/update_to/Introjucer/" + }; + + return urlsAndKeys; +} + +int LatestVersionChecker::getProductVersionNumber() const +{ + return ProjectInfo::versionNumber; +} + +const char* LatestVersionChecker::getProductName() const +{ + return ProjectInfo::projectName; +} + +bool LatestVersionChecker::allowCustomLocation() const +{ + return true; +} + +Result LatestVersionChecker::performUpdate (const MemoryBlock& data, File& targetFolder) +{ + File unzipTarget; + bool isUsingTempFolder = false; + + { + MemoryInputStream input (data, false); + ZipFile zip (input); + + if (zip.getNumEntries() == 0) + return Result::fail ("The downloaded file wasn't a valid JUCE file!"); + + unzipTarget = targetFolder; + + if (unzipTarget.exists()) + { + isUsingTempFolder = true; + unzipTarget = targetFolder.getNonexistentSibling(); + + if (! unzipTarget.createDirectory()) + return Result::fail ("Couldn't create a folder to unzip the new version!"); + } + + Result r (zip.uncompressTo (unzipTarget)); + + if (r.failed()) + { + if (isUsingTempFolder) + unzipTarget.deleteRecursively(); + + return r; + } + } + + if (isUsingTempFolder) + { + File oldFolder (targetFolder.getSiblingFile (targetFolder.getFileNameWithoutExtension() + "_old") + .getNonexistentSibling()); + + if (! targetFolder.moveFileTo (oldFolder)) + { + unzipTarget.deleteRecursively(); + return Result::fail ("Could not remove the existing folder!"); + } + + if (! unzipTarget.moveFileTo (targetFolder)) + { + unzipTarget.deleteRecursively(); + return Result::fail ("Could not overwrite the existing folder!"); + } + } + + return Result::ok(); +} + +URL LatestVersionChecker::getLatestVersionURL (String& headers, const String& path) const +{ + const LatestVersionChecker::JuceServerLocationsAndKeys& urlsAndKeys = getJuceServerURLsAndKeys(); String updateURL; bool isAbsolute = (path.startsWith ("http://") || path.startsWith ("https://")); @@ -545,12 +586,12 @@ URL LatestVersionChecker::getLatestVersionURL (String& headers, const String& pa } else { - updateURL << updateSeverHostname - << (isRedirect ? path : String (updatePath)); + updateURL << urlsAndKeys.updateSeverHostname + << (isRedirect ? path : String (urlsAndKeys.updatePath)); if (! isRedirect) { - updateURL << JuceVersionTriple().toString() << '/' + updateURL << JuceVersionTriple (getProductVersionNumber()).toString() << '/' << getOSString() << "?language=" << SystemStats::getUserLanguage(); } } @@ -559,19 +600,19 @@ URL LatestVersionChecker::getLatestVersionURL (String& headers, const String& pa if (! isAbsolute) { - headers << "X-API-Key: " << publicAPIKey; + headers << "X-API-Key: " << urlsAndKeys.publicAPIKey; if (! isRedirect) { headers << "\nContent-Type: application/json\n" - << "Accept: application/json; version=" << apiVersion; + << "Accept: application/json; version=" << urlsAndKeys.apiVersion; } } return URL (updateURL); } -URL LatestVersionChecker::getLatestVersionURL (String& headers) +URL LatestVersionChecker::getLatestVersionURL (String& headers) const { String emptyString; return getLatestVersionURL (headers, emptyString); @@ -656,24 +697,21 @@ void LatestVersionChecker::askUserAboutNewVersion (const LatestVersionChecker::J URL& newVersionToDownload, const String& extraHeaders) { - // Currently we do not show the release notes - ignoreUnused (releaseNotes); - - JuceVersionTriple currentVersion; + JuceVersionTriple currentVersion (getProductVersionNumber()); if (version > currentVersion) { File appParentFolder (File::getSpecialLocation (File::currentApplicationFile).getParentDirectory()); DialogWindow* modalDialog = nullptr; - if (isZipFolder (appParentFolder)) + if (isZipFolder (appParentFolder) && allowCustomLocation()) { - modalDialog = UpdateUserDialog::launch (version, releaseNotes, + modalDialog = UpdateUserDialog::launch (version, getProductName(), releaseNotes, appParentFolder.getFullPathName().toRawUTF8()); } else { - modalDialog = UpdateUserDialog::launch (version, releaseNotes); + modalDialog = UpdateUserDialog::launch (version, getProductName(), releaseNotes); } if (modalDialog != nullptr) @@ -695,11 +733,13 @@ void LatestVersionChecker::modalStateFinished (int result, const String& extraHeaders, File appParentFolder) { - if (result == 1) - DownloadNewVersionThread::performDownload (newVersionToDownload, extraHeaders, appParentFolder); - - if (result == 2) - askUserForLocationToDownload (newVersionToDownload, extraHeaders); + if (result == 1 || result == 2) + { + if (result == 1 || ! allowCustomLocation()) + DownloadNewVersionThread::performDownload (*this, newVersionToDownload, extraHeaders, appParentFolder); + else + askUserForLocationToDownload (newVersionToDownload, extraHeaders); + } } void LatestVersionChecker::askUserForLocationToDownload (URL& newVersionToDownload, const String& extraHeaders) @@ -752,7 +792,7 @@ void LatestVersionChecker::askUserForLocationToDownload (URL& newVersionToDownlo targetFolder = targetFolder.getChildFile ("JUCE").getNonexistentSibling(); } - DownloadNewVersionThread::performDownload (newVersionToDownload, extraHeaders, targetFolder); + DownloadNewVersionThread::performDownload (*this, newVersionToDownload, extraHeaders, targetFolder); } } diff --git a/extras/Introjucer/Source/Application/jucer_AutoUpdater.h b/extras/Introjucer/Source/Application/jucer_AutoUpdater.h index e547e19e1c..d0820cb8aa 100644 --- a/extras/Introjucer/Source/Application/jucer_AutoUpdater.h +++ b/extras/Introjucer/Source/Application/jucer_AutoUpdater.h @@ -35,6 +35,7 @@ public: struct JuceVersionTriple { JuceVersionTriple(); + JuceVersionTriple (int juceVersionNumber); JuceVersionTriple (int majorInt, int minorInt, int buildNumber); static bool fromString (const String& versionString, JuceVersionTriple& result); @@ -45,14 +46,23 @@ public: int major, minor, build; }; + //============================================================================== + struct JuceServerLocationsAndKeys + { + const char* updateSeverHostname; + const char* publicAPIKey; + const int apiVersion; + const char* updatePath; + }; + //============================================================================== LatestVersionChecker(); ~LatestVersionChecker(); static String getOSString(); - static URL getLatestVersionURL (String& headers, const String& path); - static URL getLatestVersionURL (String& headers); + URL getLatestVersionURL (String& headers, const String& path) const; + URL getLatestVersionURL (String& headers) const; void checkForNewVersion(); void processResult (var reply, const String& downloadPath); @@ -66,6 +76,14 @@ public: static bool isZipFolder (const File&); + virtual Result performUpdate (const MemoryBlock& data, File& targetFolder); + +protected: + virtual const JuceServerLocationsAndKeys& getJuceServerURLsAndKeys() const; + virtual int getProductVersionNumber() const; + virtual const char* getProductName() const; + virtual bool allowCustomLocation() const; + private: //============================================================================== friend class UpdaterDialogModalCallback; diff --git a/extras/Introjucer/Source/Code Editor/jucer_SourceCodeEditor.cpp b/extras/Introjucer/Source/Code Editor/jucer_SourceCodeEditor.cpp index 75e16d0add..318113eab6 100644 --- a/extras/Introjucer/Source/Code Editor/jucer_SourceCodeEditor.cpp +++ b/extras/Introjucer/Source/Code Editor/jucer_SourceCodeEditor.cpp @@ -146,7 +146,7 @@ SourceCodeEditor::SourceCodeEditor (OpenDocumentManager::Document* doc, CodeDocu setEditor (ed); } -SourceCodeEditor::SourceCodeEditor (OpenDocumentManager::Document* doc, CodeEditorComponent* ed) +SourceCodeEditor::SourceCodeEditor (OpenDocumentManager::Document* doc, GenericCodeEditorComponent* ed) : DocumentEditorComponent (doc) { setEditor (ed); @@ -163,7 +163,7 @@ SourceCodeEditor::~SourceCodeEditor() doc->updateLastState (*editor); } -void SourceCodeEditor::setEditor (CodeEditorComponent* newEditor) +void SourceCodeEditor::setEditor (GenericCodeEditorComponent* newEditor) { if (editor != nullptr) editor->getDocument().removeListener (this); @@ -322,6 +322,16 @@ bool GenericCodeEditorComponent::perform (const InvocationInfo& info) return CodeEditorComponent::perform (info); } +void GenericCodeEditorComponent::addListener (GenericCodeEditorComponent::Listener* listener) +{ + listeners.add (listener); +} + +void GenericCodeEditorComponent::removeListener (GenericCodeEditorComponent::Listener* listener) +{ + listeners.remove (listener); +} + //============================================================================== class GenericCodeEditorComponent::FindPanel : public Component, private TextEditor::Listener, @@ -527,6 +537,12 @@ void GenericCodeEditorComponent::handleEscapeKey() hideFindPanel(); } +void GenericCodeEditorComponent::editorViewportPositionChanged() +{ + CodeEditorComponent::editorViewportPositionChanged(); + listeners.call (&Listener::codeEditorViewportMoved, *this); +} + //============================================================================== static CPlusPlusCodeTokeniser cppTokeniser; diff --git a/extras/Introjucer/Source/Code Editor/jucer_SourceCodeEditor.h b/extras/Introjucer/Source/Code Editor/jucer_SourceCodeEditor.h index 299f5a8654..7c6c2d0f91 100644 --- a/extras/Introjucer/Source/Code Editor/jucer_SourceCodeEditor.h +++ b/extras/Introjucer/Source/Code Editor/jucer_SourceCodeEditor.h @@ -136,6 +136,8 @@ protected: void reloadInternal(); }; +class GenericCodeEditorComponent; + //============================================================================== class SourceCodeEditor : public DocumentEditorComponent, private ValueTree::Listener, @@ -143,13 +145,13 @@ class SourceCodeEditor : public DocumentEditorComponent, { public: SourceCodeEditor (OpenDocumentManager::Document*, CodeDocument&); - SourceCodeEditor (OpenDocumentManager::Document*, CodeEditorComponent*); + SourceCodeEditor (OpenDocumentManager::Document*, GenericCodeEditorComponent*); ~SourceCodeEditor(); void scrollToKeepRangeOnScreen (Range range); void highlight (Range range, bool cursorAtStart); - ScopedPointer editor; + ScopedPointer editor; private: void resized() override; @@ -164,7 +166,7 @@ private: void codeDocumentTextInserted (const String&, int) override; void codeDocumentTextDeleted (int, int) override; - void setEditor (CodeEditorComponent*); + void setEditor (GenericCodeEditorComponent*); void updateColourScheme(); void checkSaveState(); @@ -191,6 +193,7 @@ public: void findSelection(); void findNext (bool forwards, bool skipCurrentSelection); void handleEscapeKey() override; + void editorViewportPositionChanged() override; void resized() override; @@ -199,10 +202,20 @@ public: static bool isCaseSensitiveSearch() { return getAppSettings().getGlobalProperties().getBoolValue ("searchCaseSensitive"); } static void setCaseSensitiveSearch (bool b) { getAppSettings().getGlobalProperties().setValue ("searchCaseSensitive", b); } + struct Listener + { + virtual ~Listener() {} + virtual void codeEditorViewportMoved (CodeEditorComponent&) = 0; + }; + + void addListener (Listener* listener); + void removeListener (Listener* listener); + private: File file; class FindPanel; ScopedPointer findPanel; + ListenerList listeners; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GenericCodeEditorComponent) }; diff --git a/extras/Introjucer/Source/Project/jucer_ProjectContentComponent.cpp b/extras/Introjucer/Source/Project/jucer_ProjectContentComponent.cpp index 9e8857e63f..37493d6654 100644 --- a/extras/Introjucer/Source/Project/jucer_ProjectContentComponent.cpp +++ b/extras/Introjucer/Source/Project/jucer_ProjectContentComponent.cpp @@ -141,7 +141,7 @@ struct LogoComponent : public Component Rectangle r (getLocalBounds()); g.setFont (15.0f); - g.drawFittedText (getVersionInfo(), r.removeFromBottom (30), Justification::centred, 2); + g.drawFittedText (getVersionInfo(), r.removeFromBottom (50), Justification::centred, 3); const Path& logo = getIcons().mainJuceLogo; g.fillPath (logo, RectanglePlacement (RectanglePlacement::centred) @@ -150,16 +150,9 @@ struct LogoComponent : public Component static String getVersionInfo() { - const Time buildDate (Time::getCompilationDate()); - - String s; - - s << SystemStats::getJUCEVersion() << newLine - << "Introjucer built: " << buildDate.getDayOfMonth() - << " " << Time::getMonthName (buildDate.getMonth(), true) - << " " << buildDate.getYear(); - - return s; + return SystemStats::getJUCEVersion() + + newLine + + IntrojucerApp::getApp().getVersionDescription(); } }; diff --git a/extras/Introjucer/Source/Utility/jucer_JucerTreeViewBase.h b/extras/Introjucer/Source/Utility/jucer_JucerTreeViewBase.h index dde3d8ef2e..74e2ceb6f5 100644 --- a/extras/Introjucer/Source/Utility/jucer_JucerTreeViewBase.h +++ b/extras/Introjucer/Source/Utility/jucer_JucerTreeViewBase.h @@ -92,14 +92,14 @@ public: int textX; -protected: - ProjectContentComponent* getProjectContentComponent() const; - virtual void addSubItems() {} - Colour getBackgroundColour() const; Colour getContrastingColour (float contrast) const; Colour getContrastingColour (Colour targetColour, float minContrast) const; +protected: + ProjectContentComponent* getProjectContentComponent() const; + virtual void addSubItems() {} + private: class ItemSelectionTimer; friend class ItemSelectionTimer; @@ -195,7 +195,13 @@ public: { g.setColour (Colours::black); paintIcon (g); - item.paintContent (g, Rectangle (item.textX, 0, getWidth() - item.textX, getHeight())); + + int rightEdge = getWidth(); + + if (Component* c = buttons.getFirst()) + rightEdge = c->getX(); + + item.paintContent (g, Rectangle (item.textX, 0, rightEdge - item.textX, getHeight())); } void paintIcon (Graphics& g) @@ -207,9 +213,23 @@ public: void resized() override { item.textX = (int) item.getIconSize() + 8; + + Rectangle r (getLocalBounds()); + + for (int i = buttons.size(); --i >= 0;) + buttons.getUnchecked(i)->setBounds (r.removeFromRight (r.getHeight())); + } + + void addRightHandButton (Component* button) + { + buttons.add (button); + addAndMakeVisible (button); } JucerTreeViewBase& item; + OwnedArray buttons; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TreeItemComponent) }; diff --git a/extras/Introjucer/Source/Utility/jucer_PresetIDs.h b/extras/Introjucer/Source/Utility/jucer_PresetIDs.h index add7a9962a..aae1c94713 100644 --- a/extras/Introjucer/Source/Utility/jucer_PresetIDs.h +++ b/extras/Introjucer/Source/Utility/jucer_PresetIDs.h @@ -47,6 +47,7 @@ namespace Ids DECLARE_ID (source); DECLARE_ID (width); DECLARE_ID (height); + DECLARE_ID (bounds); DECLARE_ID (background); DECLARE_ID (initialState); DECLARE_ID (targetFolder);