From 9b7e944a548368dcd9a323db45d6fc90be74c3e0 Mon Sep 17 00:00:00 2001 From: hogliux Date: Thu, 4 May 2017 15:24:28 +0100 Subject: [PATCH] Added a popup to the Projucer informing the user about the collection of analytics data --- extras/Projucer/JuceLibraryCode/AppConfig.h | 4 +- extras/Projucer/Projucer.jucer | 4 +- .../Source/Application/jucer_Application.cpp | 59 +++++-- .../Source/Application/jucer_Application.h | 8 +- .../Source/Application/jucer_CommandIDs.h | 1 + .../Source/Application/jucer_Main.cpp | 1 + .../Licenses/jucer_LicenseController.cpp | 77 ++++++-- .../Source/Licenses/jucer_LicenseController.h | 15 +- .../Project Saving/jucer_ProjectSaver.h | 1 - .../Source/Project/jucer_HeaderComponent.h | 2 +- .../Projucer/Source/Project/jucer_Project.cpp | 16 +- ...ucer_ApplicationUsageDataWindowComponent.h | 167 ++++++++++++++++++ .../Source/Utility/jucer_FloatingToolWindow.h | 7 +- 13 files changed, 322 insertions(+), 40 deletions(-) create mode 100644 extras/Projucer/Source/Utility/jucer_ApplicationUsageDataWindowComponent.h diff --git a/extras/Projucer/JuceLibraryCode/AppConfig.h b/extras/Projucer/JuceLibraryCode/AppConfig.h index d85805a50b..3f7db84b1e 100644 --- a/extras/Projucer/JuceLibraryCode/AppConfig.h +++ b/extras/Projucer/JuceLibraryCode/AppConfig.h @@ -53,8 +53,8 @@ // BEGIN SECTION A -#define JUCE_DISPLAY_SPLASH_SCREEN 1 -#define JUCE_REPORT_APP_USAGE 1 +#define JUCE_DISPLAY_SPLASH_SCREEN 0 +#define JUCE_REPORT_APP_USAGE 0 // END SECTION A diff --git a/extras/Projucer/Projucer.jucer b/extras/Projucer/Projucer.jucer index 103cf4bac2..74ab13bfa8 100644 --- a/extras/Projucer/Projucer.jucer +++ b/extras/Projucer/Projucer.jucer @@ -2,8 +2,8 @@ + defines="" includeBinaryInAppConfig="1" splashScreenColour="Dark" + displaySplashScreen="0" reportAppUsage="0"> addLicenseStatusChangedCallback (this); + + if (isRunningCommandLine) { - isRunningCommandLine = true; const int appReturnCode = performCommandLine (commandLine); if (appReturnCode != commandLineNotPerformed) @@ -149,9 +153,8 @@ bool ProjucerApplication::initialiseLogger (const char* filePrefix) void ProjucerApplication::handleAsyncUpdate() { - licenseController = new LicenseController; - licenseController->addLicenseStatusChangedCallback (this); - licenseStateChanged (licenseController->getState()); + if (licenseController != nullptr) + licenseController->startWebviewIfNeeded(); #if JUCE_MAC PopupMenu extraAppleMenuItems; @@ -175,6 +178,9 @@ void ProjucerApplication::initialiseWindows (const String& commandLine) mainWindowList.reopenLastProjects(); mainWindowList.createWindowIfNoneAreOpen(); + + if (licenseController->getState().applicationUsageDataState == LicenseState::ApplicationUsageData::notChosenYet) + showApplicationUsageDataAgreementPopup(); } void ProjucerApplication::shutdown() @@ -255,8 +261,12 @@ void ProjucerApplication::systemRequestedQuit() //============================================================================== void ProjucerApplication::licenseStateChanged (const LicenseState& state) { + #if ! JUCER_ENABLE_GPL_MODE if (state.type != LicenseState::Type::notLoggedIn && state.type != LicenseState::Type::noLicenseChosenYet) + #else + ignoreUnused (state); + #endif { initialiseWindows (getCommandLineParameters()); } @@ -370,6 +380,7 @@ void ProjucerApplication::createFileMenu (PopupMenu& menu) #if ! JUCE_MAC menu.addCommandItem (commandManager, CommandIDs::showAboutWindow); + menu.addCommandItem (commandManager, CommandIDs::showAppUsageWindow); menu.addCommandItem (commandManager, CommandIDs::showGlobalPreferences); menu.addSeparator(); menu.addCommandItem (commandManager, StandardApplicationCommandIDs::quit); @@ -467,6 +478,7 @@ void ProjucerApplication::createToolsMenu (PopupMenu& menu) void ProjucerApplication::createExtraAppleMenuItems (PopupMenu& menu) { menu.addCommandItem (commandManager, CommandIDs::showAboutWindow); + menu.addCommandItem (commandManager, CommandIDs::showAppUsageWindow); menu.addSeparator(); menu.addCommandItem (commandManager, CommandIDs::showGlobalPreferences); } @@ -508,10 +520,11 @@ void ProjucerApplication::handleMainMenuCommand (int menuItemID) lookAndFeel.setupColours(); mainWindowList.sendLookAndFeelChange(); - if (utf8Window != nullptr) utf8Window->sendLookAndFeelChange(); - if (svgPathWindow != nullptr) svgPathWindow->sendLookAndFeelChange(); - if (globalPreferencesWindow != nullptr) globalPreferencesWindow->sendLookAndFeelChange(); - if (aboutWindow != nullptr) aboutWindow->sendLookAndFeelChange(); + if (utf8Window != nullptr) utf8Window->sendLookAndFeelChange(); + if (svgPathWindow != nullptr) svgPathWindow->sendLookAndFeelChange(); + if (globalPreferencesWindow != nullptr) globalPreferencesWindow->sendLookAndFeelChange(); + if (aboutWindow != nullptr) aboutWindow->sendLookAndFeelChange(); + if (applicationUsageDataWindow != nullptr) applicationUsageDataWindow->sendLookAndFeelChange(); } else { @@ -532,6 +545,7 @@ void ProjucerApplication::getAllCommands (Array & commands) CommandIDs::showUTF8Tool, CommandIDs::showSVGPathTool, CommandIDs::showAboutWindow, + CommandIDs::showAppUsageWindow, CommandIDs::loginLogout }; commands.addArray (ids, numElementsInArray (ids)); @@ -578,6 +592,10 @@ void ProjucerApplication::getCommandInfo (CommandID commandID, ApplicationComman result.setInfo ("About Projucer", "Shows the Projucer's 'About' page.", CommandCategories::general, 0); break; + case CommandIDs::showAppUsageWindow: + result.setInfo ("Application Usage Data", "Shows the application usage data agreement window", CommandCategories::general, 0); + break; + case CommandIDs::loginLogout: { bool isLoggedIn = false; @@ -615,6 +633,7 @@ bool ProjucerApplication::perform (const InvocationInfo& info) case CommandIDs::showSVGPathTool: showSVGPathDataToolWindow(); break; case CommandIDs::showGlobalPreferences: AppearanceSettings::showGlobalPreferences (globalPreferencesWindow); break; case CommandIDs::showAboutWindow: showAboutWindow(); break; + case CommandIDs::showAppUsageWindow: showApplicationUsageDataAgreementPopup(); break; case CommandIDs::loginLogout: doLogout(); break; default: return JUCEApplication::perform (info); } @@ -686,12 +705,28 @@ void ProjucerApplication::showAboutWindow() if (aboutWindow != nullptr) aboutWindow->toFront (true); else - new FloatingToolWindow ("", - "aboutWindowPos", - new AboutWindowComponent(), aboutWindow, false, + new FloatingToolWindow ({}, {}, new AboutWindowComponent(), + aboutWindow, false, 500, 300, 500, 300, 500, 300); } +void ProjucerApplication::showApplicationUsageDataAgreementPopup() +{ + if (applicationUsageDataWindow != nullptr) + applicationUsageDataWindow->toFront (true); + else + new FloatingToolWindow ("Application Usage Analytics", + {}, new ApplicationUsageDataWindowComponent (isPaidOrGPL()), + applicationUsageDataWindow, false, + 400, 300, 400, 300, 400, 300); +} + +void ProjucerApplication::dismissApplicationUsageDataAgreementPopup() +{ + if (applicationUsageDataWindow != nullptr) + applicationUsageDataWindow = nullptr; +} + //============================================================================== struct FileWithTime { diff --git a/extras/Projucer/Source/Application/jucer_Application.h b/extras/Projucer/Source/Application/jucer_Application.h index 1aea62e0d1..c019958448 100644 --- a/extras/Projucer/Source/Application/jucer_Application.h +++ b/extras/Projucer/Source/Application/jucer_Application.h @@ -99,8 +99,8 @@ public: void showSVGPathDataToolWindow(); void showAboutWindow(); - - void showLoginWindow(); + void showApplicationUsageDataAgreementPopup(); + void dismissApplicationUsageDataAgreementPopup(); void updateAllBuildTabs(); LatestVersionChecker* createVersionChecker() const; @@ -124,7 +124,9 @@ public: OpenDocumentManager openDocumentManager; ScopedPointer commandManager; - ScopedPointer appearanceEditorWindow, globalPreferencesWindow, utf8Window, svgPathWindow, aboutWindow; + ScopedPointer appearanceEditorWindow, globalPreferencesWindow, utf8Window, + svgPathWindow, aboutWindow, applicationUsageDataWindow; + ScopedPointer logger; bool isRunningCommandLine; diff --git a/extras/Projucer/Source/Application/jucer_CommandIDs.h b/extras/Projucer/Source/Application/jucer_CommandIDs.h index 850bb41412..3046977511 100644 --- a/extras/Projucer/Source/Application/jucer_CommandIDs.h +++ b/extras/Projucer/Source/Application/jucer_CommandIDs.h @@ -49,6 +49,7 @@ namespace CommandIDs showTranslationTool = 0x300022, showSVGPathTool = 0x300023, showAboutWindow = 0x300024, + showAppUsageWindow = 0x300025, showProjectSettings = 0x300030, showProjectTab = 0x300031, diff --git a/extras/Projucer/Source/Application/jucer_Main.cpp b/extras/Projucer/Source/Application/jucer_Main.cpp index 094564b1a0..1f68447cd2 100644 --- a/extras/Projucer/Source/Application/jucer_Main.cpp +++ b/extras/Projucer/Source/Application/jucer_Main.cpp @@ -34,6 +34,7 @@ #include "../Utility/jucer_UTF8Component.h" #include "../Utility/jucer_SVGPathDataComponent.h" #include "../Utility/jucer_AboutWindowComponent.h" +#include "../Utility/jucer_ApplicationUsageDataWindowComponent.h" #include "../Utility/jucer_FloatingToolWindow.h" #include "../LiveBuildEngine/projucer_MessageIDs.h" diff --git a/extras/Projucer/Source/Licenses/jucer_LicenseController.cpp b/extras/Projucer/Source/Licenses/jucer_LicenseController.cpp index c2e6085074..178facf086 100644 --- a/extras/Projucer/Source/Licenses/jucer_LicenseController.cpp +++ b/extras/Projucer/Source/Licenses/jucer_LicenseController.cpp @@ -71,6 +71,23 @@ static LicenseState::Type getLicenseTypeFromValue (const String& d) return LicenseState::Type::noLicenseChosenYet; } +static const char* getApplicationUsageDataStateValue (LicenseState::ApplicationUsageData type) +{ + switch (type) + { + case LicenseState::ApplicationUsageData::enabled: return "enabled"; + case LicenseState::ApplicationUsageData::disabled: return "disabled"; + default: return "notChosen"; + } +} + +static LicenseState::ApplicationUsageData getApplicationUsageDataTypeFromValue (const String& value) +{ + if (value == getApplicationUsageDataStateValue (LicenseState::ApplicationUsageData::enabled)) return LicenseState::ApplicationUsageData::enabled; + if (value == getApplicationUsageDataStateValue (LicenseState::ApplicationUsageData::disabled)) return LicenseState::ApplicationUsageData::disabled; + return LicenseState::ApplicationUsageData::notChosenYet; +} + //============================================================================== struct LicenseController::ModalCompletionCallback : ModalComponentManager::Callback { @@ -83,15 +100,11 @@ struct LicenseController::ModalCompletionCallback : ModalComponentManager::Callb //============================================================================== LicenseController::LicenseController() - #if (! JUCER_ENABLE_GPL_MODE) : state (licenseStateFromSettings (ProjucerApplication::getApp().settings->getGlobalProperties())) - #endif { #if JUCER_ENABLE_GPL_MODE state.type = LicenseState::Type::GPL; state.username = "GPL mode"; - #else - thread = new LicenseThread (*this, false); #endif } @@ -101,6 +114,37 @@ LicenseController::~LicenseController() closeWebview (-1); } +LicenseState LicenseController::getState() const noexcept +{ + LicenseState projucerState = state; + + // if the user has never logged in before and the user is running from command line + // then we have no way to ask the user to log in, so fallback to GPL mode + if (guiNotInitialisedYet + && (state.type == LicenseState::Type::notLoggedIn + || state.type == LicenseState::Type::noLicenseChosenYet)) + { + projucerState.type = LicenseState::Type::GPL; + projucerState.username = "GPL mode"; + } + + return projucerState; +} + +void LicenseController::startWebviewIfNeeded() +{ + if (guiNotInitialisedYet) + { + guiNotInitialisedYet = false; + listeners.call (&StateChangedCallback::licenseStateChanged, getState()); + } + + #if ! JUCER_ENABLE_GPL_MODE + if (thread == nullptr) + thread = new LicenseThread (*this, false); + #endif +} + void LicenseController::logout() { jassert (MessageManager::getInstance()->isThisTheMessageThread()); @@ -127,6 +171,15 @@ void LicenseController::chooseNewLicense() #endif } +void LicenseController::setApplicationUsageDataState (LicenseState::ApplicationUsageData newState) +{ + if (state.applicationUsageDataState != newState) + { + state.applicationUsageDataState = newState; + updateState (state); + } +} + //============================================================================== void LicenseController::closeWebview (int result) { @@ -198,7 +251,7 @@ void LicenseController::updateState (const LicenseState& newState) state = newState; licenseStateToSettings (state, props); - listeners.call (&StateChangedCallback::licenseStateChanged, state); + listeners.call (&StateChangedCallback::licenseStateChanged, getState()); } LicenseState LicenseController::licenseStateFromSettings (PropertiesFile& props) @@ -208,10 +261,11 @@ LicenseState LicenseController::licenseStateFromSettings (PropertiesFile& props) if (licenseXml != nullptr) { LicenseState result; - result.type = getLicenseTypeFromValue (licenseXml->getChildElementAllSubText ("type", {})); - result.username = licenseXml->getChildElementAllSubText ("username", {}); - result.email = licenseXml->getChildElementAllSubText ("email", {}); - result.authToken = licenseXml->getChildElementAllSubText ("authToken", {}); + result.type = getLicenseTypeFromValue (licenseXml->getChildElementAllSubText ("type", {})); + result.applicationUsageDataState = getApplicationUsageDataTypeFromValue (licenseXml->getChildElementAllSubText ("applicationUsageData", {})); + result.username = licenseXml->getChildElementAllSubText ("username", {}); + result.email = licenseXml->getChildElementAllSubText ("email", {}); + result.authToken = licenseXml->getChildElementAllSubText ("authToken", {}); MemoryOutputStream imageData; Base64::convertFromBase64 (imageData, licenseXml->getChildElementAllSubText ("avatar", {})); @@ -227,14 +281,15 @@ void LicenseController::licenseStateToSettings (const LicenseState& state, Prope { props.removeValue ("license"); - if (state.type != LicenseState::Type::notLoggedIn - && state.username.isNotEmpty() && state.authToken.isNotEmpty()) + if (state.type != LicenseState::Type::notLoggedIn && state.username.isNotEmpty()) { XmlElement licenseXml ("license"); if (auto* typeString = getLicenseStateValue (state.type)) licenseXml.createNewChildElement ("type")->addTextElement (typeString); + licenseXml.createNewChildElement ("applicationUsageData")->addTextElement (getApplicationUsageDataStateValue (state.applicationUsageDataState)); + licenseXml.createNewChildElement ("username")->addTextElement (state.username); licenseXml.createNewChildElement ("email") ->addTextElement (state.email); diff --git a/extras/Projucer/Source/Licenses/jucer_LicenseController.h b/extras/Projucer/Source/Licenses/jucer_LicenseController.h index ffa654c4a4..c5a03cd6dc 100644 --- a/extras/Projucer/Source/Licenses/jucer_LicenseController.h +++ b/extras/Projucer/Source/Licenses/jucer_LicenseController.h @@ -44,7 +44,16 @@ struct LicenseState pro }; + enum class ApplicationUsageData + { + notChosenYet, + + enabled, + disabled + }; + Type type = Type::notLoggedIn; + ApplicationUsageData applicationUsageDataState = ApplicationUsageData::notChosenYet; String username; String email; String authToken; @@ -71,10 +80,13 @@ public: LicenseController(); ~LicenseController(); + void startWebviewIfNeeded(); + //============================================================================== - const LicenseState& getState() const noexcept { return state; } + LicenseState getState() const noexcept; void logout(); void chooseNewLicense(); + void setApplicationUsageDataState (LicenseState::ApplicationUsageData newState); //============================================================================== void addLicenseStatusChangedCallback (StateChangedCallback* callback) { listeners.add (callback); } @@ -103,6 +115,7 @@ private: ScopedPointer thread; LicenseWebview* licenseWebview = nullptr; ListenerList listeners; + bool guiNotInitialisedYet = true; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LicenseController) }; diff --git a/extras/Projucer/Source/Project Saving/jucer_ProjectSaver.h b/extras/Projucer/Source/Project Saving/jucer_ProjectSaver.h index 06997a5e13..44781d44e7 100644 --- a/extras/Projucer/Source/Project Saving/jucer_ProjectSaver.h +++ b/extras/Projucer/Source/Project Saving/jucer_ProjectSaver.h @@ -374,7 +374,6 @@ private: << "// [END_USER_CODE_SECTION]" << newLine; out << newLine - << "//==============================================================================" << newLine << "/*" << newLine << " ==============================================================================" << newLine << newLine diff --git a/extras/Projucer/Source/Project/jucer_HeaderComponent.h b/extras/Projucer/Source/Project/jucer_HeaderComponent.h index cda7c5dbd3..833d1ba1f8 100644 --- a/extras/Projucer/Source/Project/jucer_HeaderComponent.h +++ b/extras/Projucer/Source/Project/jucer_HeaderComponent.h @@ -491,7 +491,7 @@ private: { if (LicenseController* controller = ProjucerApplication::getApp().licenseController) { - auto& state = controller->getState(); + auto state = controller->getState(); userSettingsButton->iconImage = state.avatar; userSettingsButton->repaint(); diff --git a/extras/Projucer/Source/Project/jucer_Project.cpp b/extras/Projucer/Source/Project/jucer_Project.cpp index 995a1029a4..4012db1c8e 100644 --- a/extras/Projucer/Source/Project/jucer_Project.cpp +++ b/extras/Projucer/Source/Project/jucer_Project.cpp @@ -113,13 +113,19 @@ void Project::setMissingDefaultValues() setTitle ("JUCE Project"); { - auto defaultSplashScreenAndReporting = ! ProjucerApplication::getApp().isPaidOrGPL(); + auto defaultSplashScreen = ! ProjucerApplication::getApp().isPaidOrGPL(); - if (shouldDisplaySplashScreen() == var() || defaultSplashScreenAndReporting) - shouldDisplaySplashScreen() = defaultSplashScreenAndReporting; + if (shouldDisplaySplashScreen() == var() || defaultSplashScreen) + shouldDisplaySplashScreen() = defaultSplashScreen; - if (shouldReportAppUsage() == var() || defaultSplashScreenAndReporting) - shouldReportAppUsage() = defaultSplashScreenAndReporting; + if (ProjucerApplication::getApp().isPaidOrGPL()) + { + if (shouldReportAppUsage() == var()) + shouldReportAppUsage() = ProjucerApplication::getApp().licenseController->getState().applicationUsageDataState + == LicenseState::ApplicationUsageData::enabled; + } + else + shouldReportAppUsage() = true; } if (splashScreenColour() == var()) diff --git a/extras/Projucer/Source/Utility/jucer_ApplicationUsageDataWindowComponent.h b/extras/Projucer/Source/Utility/jucer_ApplicationUsageDataWindowComponent.h new file mode 100644 index 0000000000..05b94bc263 --- /dev/null +++ b/extras/Projucer/Source/Utility/jucer_ApplicationUsageDataWindowComponent.h @@ -0,0 +1,167 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + 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 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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. + + ============================================================================== +*/ + +#pragma once + +class ApplicationUsageDataWindowComponent : public Component, + private Button::Listener +{ +public: + ApplicationUsageDataWindowComponent (bool showCheckbox) + { + addAndMakeVisible (headerLabel); + headerLabel.setText ("Application Usage Analytics", dontSendNotification); + headerLabel.setFont (Font (20.0f, Font::FontStyleFlags::bold)); + headerLabel.setJustificationType (Justification::centred); + + auto textToShow = String ("We use analytics services to understand how developers use our software in order for JUCE to improve its software and services. "); + + if (! showCheckbox) + textToShow += String (" Analytics can be disabled with an Indie or Pro license. "); + + textToShow += String ("For more information, please read the JUCE EULA and Privacy policy:"); + + addAndMakeVisible (bodyLabel); + bodyLabel.setText (textToShow, dontSendNotification); + bodyLabel.setFont (Font (14.0f)); + bodyLabel.setJustificationType (Justification::centredLeft); + + addAndMakeVisible (juceEULALink); + juceEULALink.setButtonText ("JUCE EULA"); + juceEULALink.setFont (Font (14.0f), false); + juceEULALink.setURL (URL ("https://juce.com/juce-5-license")); + + addAndMakeVisible (privacyPolicyLink); + privacyPolicyLink.setButtonText ("Privacy Policy"); + privacyPolicyLink.setFont (Font (14.0f), false); + privacyPolicyLink.setURL (URL ("https://juce.com/privacy-policy")); + + addAndMakeVisible (okButton); + okButton.setButtonText ("OK"); + okButton.addListener (this); + + if (showCheckbox) + { + addAndMakeVisible (shareApplicationUsageDataToggle = new ToggleButton()); + shareApplicationUsageDataToggle->setToggleState (true, dontSendNotification); + + addAndMakeVisible(shareApplicationUsageDataLabel = new Label ({}, "Help JUCE to improve its software and services by sharing my application usage data")); + shareApplicationUsageDataLabel->setFont (Font (14.0f)); + shareApplicationUsageDataLabel->setMinimumHorizontalScale (1.0f); + } + else + { + addAndMakeVisible (upgradeLicenseButton = new TextButton ("Upgrade License")); + upgradeLicenseButton->addListener (this); + upgradeLicenseButton->setColour (TextButton::buttonColourId, findColour (secondaryButtonBackgroundColourId)); + } + } + + ~ApplicationUsageDataWindowComponent() + { + if (LicenseController* controller = ProjucerApplication::getApp().licenseController) + { + auto newApplicationUsageDataState = LicenseState::ApplicationUsageData::enabled; + + if (shareApplicationUsageDataToggle != nullptr && ! shareApplicationUsageDataToggle->getToggleState()) + newApplicationUsageDataState = LicenseState::ApplicationUsageData::disabled; + + controller->setApplicationUsageDataState (newApplicationUsageDataState); + } + } + + void resized() override + { + auto bounds = getLocalBounds().reduced (20); + headerLabel.setBounds (bounds.removeFromTop (40)); + bodyLabel.setBounds (bounds.removeFromTop (75)); + + bounds.removeFromTop (10); + + auto linkBounds = bounds.removeFromTop (20); + juceEULALink.setBounds (linkBounds.removeFromLeft (linkBounds.getWidth() / 2).reduced (2)); + privacyPolicyLink.setBounds (linkBounds.reduced (2)); + + if (shareApplicationUsageDataToggle != nullptr) + { + bounds.removeFromTop (10); + + auto toggleBounds = bounds.removeFromTop (40); + shareApplicationUsageDataToggle->setBounds (toggleBounds.removeFromLeft (40).reduced (5)); + shareApplicationUsageDataLabel->setBounds (toggleBounds); + } + + bounds.removeFromTop (10); + + auto buttonW = 125; + auto buttonH = 40; + + if (upgradeLicenseButton != nullptr) + { + auto left = bounds.removeFromLeft (bounds.getWidth() / 2); + + upgradeLicenseButton->setSize (buttonW, buttonH); + upgradeLicenseButton->setCentrePosition (left.getCentreX(), left.getCentreY()); + } + + okButton.setSize (buttonW, buttonH); + okButton.setCentrePosition (bounds.getCentreX(), bounds.getCentreY()); + } + + void paint (Graphics& g) override + { + g.fillAll (findColour (backgroundColourId)); + } + +private: + Label headerLabel, bodyLabel; + HyperlinkButton juceEULALink, privacyPolicyLink; + ScopedPointer