git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce

subrepo:
  subdir:   "deps/juce"
  merged:   "b13f9084e"
upstream:
  origin:   "https://github.com/essej/JUCE.git"
  branch:   "sono6good"
  commit:   "b13f9084e"
git-subrepo:
  version:  "0.4.3"
  origin:   "https://github.com/ingydotnet/git-subrepo.git"
  commit:   "2f68596"
This commit is contained in:
essej
2022-04-18 17:51:22 -04:00
parent 63e175fee6
commit 25bd5d8adb
3210 changed files with 1045392 additions and 0 deletions

View File

@ -0,0 +1,224 @@
/*
==============================================================================
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.
==============================================================================
*/
#pragma once
#include "jucer_LicenseState.h"
#include "jucer_LicenseQueryThread.h"
//==============================================================================
class LicenseController : private Timer
{
public:
LicenseController()
{
checkLicense();
}
//==============================================================================
static LicenseState getGPLState()
{
return { LicenseState::Type::gpl, projucerMajorVersion, {}, {} };
}
LicenseState getCurrentState() const noexcept
{
return state;
}
void setState (const LicenseState& newState)
{
if (state != newState)
{
state = newState;
licenseStateToSettings (state, getGlobalProperties());
stateListeners.call ([] (LicenseStateListener& l) { l.licenseStateChanged(); });
}
}
void resetState()
{
setState ({});
}
void signIn (const String& email, const String& password,
std::function<void (const String&)> completionCallback)
{
licenseQueryThread.doSignIn (email, password,
[this, completionCallback] (LicenseQueryThread::ErrorMessageAndType error,
LicenseState newState)
{
completionCallback (error.first);
setState (newState);
});
}
void cancelSignIn()
{
licenseQueryThread.cancelRunningJobs();
}
//==============================================================================
struct LicenseStateListener
{
virtual ~LicenseStateListener() = default;
virtual void licenseStateChanged() = 0;
};
void addListener (LicenseStateListener* listenerToAdd)
{
stateListeners.add (listenerToAdd);
}
void removeListener (LicenseStateListener* listenerToRemove)
{
stateListeners.remove (listenerToRemove);
}
private:
//==============================================================================
static const char* getLicenseStateValue (LicenseState::Type type)
{
switch (type)
{
case LicenseState::Type::gpl: return "GPL";
case LicenseState::Type::personal: return "personal";
case LicenseState::Type::educational: return "edu";
case LicenseState::Type::indie: return "indie";
case LicenseState::Type::pro: return "pro";
case LicenseState::Type::none:
default: break;
}
return nullptr;
}
static LicenseState::Type getLicenseTypeFromValue (const String& d)
{
if (d == getLicenseStateValue (LicenseState::Type::gpl)) return LicenseState::Type::gpl;
if (d == getLicenseStateValue (LicenseState::Type::personal)) return LicenseState::Type::personal;
if (d == getLicenseStateValue (LicenseState::Type::educational)) return LicenseState::Type::educational;
if (d == getLicenseStateValue (LicenseState::Type::indie)) return LicenseState::Type::indie;
if (d == getLicenseStateValue (LicenseState::Type::pro)) return LicenseState::Type::pro;
return LicenseState::Type::none;
}
static LicenseState licenseStateFromSettings (PropertiesFile& props)
{
if (auto licenseXml = props.getXmlValue ("license"))
{
// this is here for backwards compatibility with old-style settings files using XML text elements
if (licenseXml->getChildElementAllSubText ("type", {}).isNotEmpty())
{
auto stateFromOldSettings = [&licenseXml]() -> LicenseState
{
return { getLicenseTypeFromValue (licenseXml->getChildElementAllSubText ("type", {})),
licenseXml->getChildElementAllSubText ("version", "-1").getIntValue(),
licenseXml->getChildElementAllSubText ("username", {}),
licenseXml->getChildElementAllSubText ("authToken", {}) };
}();
licenseStateToSettings (stateFromOldSettings, props);
return stateFromOldSettings;
}
return { getLicenseTypeFromValue (licenseXml->getStringAttribute ("type", {})),
licenseXml->getIntAttribute ("version", -1),
licenseXml->getStringAttribute ("username", {}),
licenseXml->getStringAttribute ("authToken", {}) };
}
return {};
}
static void licenseStateToSettings (const LicenseState& state, PropertiesFile& props)
{
props.removeValue ("license");
if (state.isSignedIn())
{
XmlElement licenseXml ("license");
if (auto* typeString = getLicenseStateValue (state.type))
licenseXml.setAttribute ("type", typeString);
licenseXml.setAttribute ("version", state.version);
licenseXml.setAttribute ("username", state.username);
licenseXml.setAttribute ("authToken", state.authToken);
props.setValue ("license", &licenseXml);
}
props.saveIfNeeded();
}
//==============================================================================
void checkLicense()
{
if (state.authToken.isNotEmpty() && ! state.isGPL())
{
auto completionCallback = [this] (LicenseQueryThread::ErrorMessageAndType error,
LicenseState updatedState)
{
if (error == LicenseQueryThread::ErrorMessageAndType())
{
setState (updatedState);
}
else if ((error.second == LicenseQueryThread::ErrorType::busy
|| error.second == LicenseQueryThread::ErrorType::cancelled
|| error.second == LicenseQueryThread::ErrorType::connectionError)
&& ! hasRetriedLicenseCheck)
{
hasRetriedLicenseCheck = true;
startTimer (10000);
}
};
licenseQueryThread.checkLicenseValidity (state, std::move (completionCallback));
}
}
void timerCallback() override
{
stopTimer();
checkLicense();
}
//==============================================================================
#if JUCER_ENABLE_GPL_MODE
LicenseState state = getGPLState();
#else
LicenseState state = licenseStateFromSettings (getGlobalProperties());
#endif
ListenerList<LicenseStateListener> stateListeners;
LicenseQueryThread licenseQueryThread;
bool hasRetriedLicenseCheck = false;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LicenseController)
};

View File

@ -0,0 +1,371 @@
/*
==============================================================================
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.
==============================================================================
*/
#pragma once
//==============================================================================
namespace LicenseHelpers
{
inline LicenseState::Type licenseTypeForString (const String& licenseString)
{
if (licenseString == "juce-pro") return LicenseState::Type::pro;
if (licenseString == "juce-indie") return LicenseState::Type::indie;
if (licenseString == "juce-edu") return LicenseState::Type::educational;
if (licenseString == "juce-personal") return LicenseState::Type::personal;
jassertfalse; // unknown type
return LicenseState::Type::none;
}
using LicenseVersionAndType = std::pair<int, LicenseState::Type>;
inline LicenseVersionAndType findBestLicense (std::vector<LicenseVersionAndType>&& licenses)
{
if (licenses.size() == 1)
return licenses[0];
auto getValueForLicenceType = [] (LicenseState::Type type)
{
switch (type)
{
case LicenseState::Type::pro: return 4;
case LicenseState::Type::indie: return 3;
case LicenseState::Type::educational: return 2;
case LicenseState::Type::personal: return 1;
case LicenseState::Type::gpl:
case LicenseState::Type::none:
default: return -1;
}
};
std::sort (licenses.begin(), licenses.end(),
[getValueForLicenceType] (const LicenseVersionAndType& l1, const LicenseVersionAndType& l2)
{
if (l1.first > l2.first)
return true;
if (l1.first == l2.first)
return getValueForLicenceType (l1.second) > getValueForLicenceType (l2.second);
return false;
});
auto findFirstLicense = [&licenses] (bool isPaid)
{
auto iter = std::find_if (licenses.begin(), licenses.end(),
[isPaid] (const LicenseVersionAndType& l)
{
auto proOrIndie = (l.second == LicenseState::Type::pro || l.second == LicenseState::Type::indie);
return isPaid ? proOrIndie : ! proOrIndie;
});
return iter != licenses.end() ? *iter
: LicenseVersionAndType();
};
auto newestPaid = findFirstLicense (true);
auto newestFree = findFirstLicense (false);
if (newestPaid.first >= projucerMajorVersion || newestPaid.first >= newestFree.first)
return newestPaid;
return newestFree;
}
}
//==============================================================================
class LicenseQueryThread
{
public:
enum class ErrorType
{
busy,
cancelled,
connectionError,
webResponseError
};
using ErrorMessageAndType = std::pair<String, ErrorType>;
using LicenseQueryCallback = std::function<void (ErrorMessageAndType, LicenseState)>;
//==============================================================================
LicenseQueryThread() = default;
void checkLicenseValidity (const LicenseState& state, LicenseQueryCallback completionCallback)
{
if (jobPool.getNumJobs() > 0)
{
completionCallback ({ {}, ErrorType::busy }, {});
return;
}
jobPool.addJob ([this, state, completionCallback]
{
auto updatedState = state;
auto result = runTask (std::make_unique<UserLicenseQuery> (state.authToken), updatedState);
WeakReference<LicenseQueryThread> weakThis (this);
MessageManager::callAsync ([weakThis, result, updatedState, completionCallback]
{
if (weakThis != nullptr)
completionCallback (result, updatedState);
});
});
}
void doSignIn (const String& email, const String& password, LicenseQueryCallback completionCallback)
{
cancelRunningJobs();
jobPool.addJob ([this, email, password, completionCallback]
{
LicenseState state;
auto result = runTask (std::make_unique<UserLogin> (email, password), state);
if (result == ErrorMessageAndType())
result = runTask (std::make_unique<UserLicenseQuery> (state.authToken), state);
if (result != ErrorMessageAndType())
state = {};
WeakReference<LicenseQueryThread> weakThis (this);
MessageManager::callAsync ([weakThis, result, state, completionCallback]
{
if (weakThis != nullptr)
completionCallback (result, state);
});
});
}
void cancelRunningJobs()
{
jobPool.removeAllJobs (true, 500);
}
private:
//==============================================================================
struct AccountEnquiryBase
{
virtual ~AccountEnquiryBase() = default;
virtual bool isPOSTLikeRequest() const = 0;
virtual String getEndpointURLSuffix() const = 0;
virtual StringPairArray getParameterNamesAndValues() const = 0;
virtual String getExtraHeaders() const = 0;
virtual int getSuccessCode() const = 0;
virtual String errorCodeToString (int) const = 0;
virtual bool parseServerResponse (const String&, LicenseState&) = 0;
};
struct UserLogin : public AccountEnquiryBase
{
UserLogin (const String& e, const String& p)
: userEmail (e), userPassword (p)
{
}
bool isPOSTLikeRequest() const override { return true; }
String getEndpointURLSuffix() const override { return "/authenticate/projucer"; }
int getSuccessCode() const override { return 200; }
StringPairArray getParameterNamesAndValues() const override
{
StringPairArray namesAndValues;
namesAndValues.set ("email", userEmail);
namesAndValues.set ("password", userPassword);
return namesAndValues;
}
String getExtraHeaders() const override
{
return "Content-Type: application/json";
}
String errorCodeToString (int errorCode) const override
{
switch (errorCode)
{
case 400: return "Please enter your email and password to sign in.";
case 401: return "Your email and password are incorrect.";
case 451: return "Access denied.";
default: return "Something went wrong, please try again.";
}
}
bool parseServerResponse (const String& serverResponse, LicenseState& licenseState) override
{
auto json = JSON::parse (serverResponse);
licenseState.authToken = json.getProperty ("token", {}).toString();
licenseState.username = json.getProperty ("user", {}).getProperty ("username", {}).toString();
return (licenseState.authToken.isNotEmpty() && licenseState.username.isNotEmpty());
}
String userEmail, userPassword;
};
struct UserLicenseQuery : public AccountEnquiryBase
{
UserLicenseQuery (const String& authToken)
: userAuthToken (authToken)
{
}
bool isPOSTLikeRequest() const override { return false; }
String getEndpointURLSuffix() const override { return "/user/licences/projucer"; }
int getSuccessCode() const override { return 200; }
StringPairArray getParameterNamesAndValues() const override
{
return {};
}
String getExtraHeaders() const override
{
return "x-access-token: " + userAuthToken;
}
String errorCodeToString (int errorCode) const override
{
switch (errorCode)
{
case 401: return "User not found or could not be verified.";
default: return "User licenses info fetch failed (unknown error).";
}
}
bool parseServerResponse (const String& serverResponse, LicenseState& licenseState) override
{
auto json = JSON::parse (serverResponse);
if (auto* licensesJson = json.getArray())
{
std::vector<LicenseHelpers::LicenseVersionAndType> licenses;
for (auto& license : *licensesJson)
{
auto version = license.getProperty ("product_version", {}).toString().trim();
auto type = license.getProperty ("licence_type", {}).toString();
auto status = license.getProperty ("status", {}).toString();
if (status == "active" && type.isNotEmpty() && version.isNotEmpty())
licenses.push_back ({ version.getIntValue(), LicenseHelpers::licenseTypeForString (type) });
}
if (! licenses.empty())
{
auto bestLicense = LicenseHelpers::findBestLicense (std::move (licenses));
licenseState.version = bestLicense.first;
licenseState.type = bestLicense.second;
}
return true;
}
return false;
}
String userAuthToken;
};
//==============================================================================
static String postDataStringAsJSON (const StringPairArray& parameters)
{
DynamicObject::Ptr d (new DynamicObject());
for (auto& key : parameters.getAllKeys())
d->setProperty (key, parameters[key]);
return JSON::toString (var (d.get()));
}
static ErrorMessageAndType runTask (std::unique_ptr<AccountEnquiryBase> accountEnquiryTask, LicenseState& state)
{
const ErrorMessageAndType cancelledError ("Cancelled.", ErrorType::cancelled);
const String endpointURL ("https://api.juce.com/api/v1");
URL url (endpointURL + accountEnquiryTask->getEndpointURLSuffix());
auto isPOST = accountEnquiryTask->isPOSTLikeRequest();
if (isPOST)
url = url.withPOSTData (postDataStringAsJSON (accountEnquiryTask->getParameterNamesAndValues()));
if (ThreadPoolJob::getCurrentThreadPoolJob()->shouldExit())
return cancelledError;
int statusCode = 0;
auto urlStream = url.createInputStream (URL::InputStreamOptions (isPOST ? URL::ParameterHandling::inPostData
: URL::ParameterHandling::inAddress)
.withExtraHeaders (accountEnquiryTask->getExtraHeaders())
.withConnectionTimeoutMs (5000)
.withStatusCode (&statusCode));
if (urlStream == nullptr)
return { "Failed to connect to the web server.", ErrorType::connectionError };
if (statusCode != accountEnquiryTask->getSuccessCode())
return { accountEnquiryTask->errorCodeToString (statusCode), ErrorType::webResponseError };
if (ThreadPoolJob::getCurrentThreadPoolJob()->shouldExit())
return cancelledError;
String response;
for (;;)
{
char buffer [8192] = "";
auto num = urlStream->read (buffer, sizeof (buffer));
if (ThreadPoolJob::getCurrentThreadPoolJob()->shouldExit())
return cancelledError;
if (num <= 0)
break;
response += buffer;
}
if (ThreadPoolJob::getCurrentThreadPoolJob()->shouldExit())
return cancelledError;
if (! accountEnquiryTask->parseServerResponse (response, state))
return { "Failed to parse server response.", ErrorType::webResponseError };
return {};
}
//==============================================================================
ThreadPool jobPool { 1 };
//==============================================================================
JUCE_DECLARE_WEAK_REFERENCEABLE (LicenseQueryThread)
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LicenseQueryThread)
};

View File

@ -0,0 +1,91 @@
/*
==============================================================================
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.
==============================================================================
*/
#pragma once
//==============================================================================
struct LicenseState
{
enum class Type
{
none,
gpl,
personal,
educational,
indie,
pro
};
LicenseState() = default;
LicenseState (Type t, int v, String user, String token)
: type (t), version (v), username (user), authToken (token)
{
}
bool operator== (const LicenseState& other) const noexcept
{
return type == other.type
&& version == other.version
&& username == other.username
&& authToken == other.authToken;
}
bool operator != (const LicenseState& other) const noexcept
{
return ! operator== (other);
}
bool isSignedIn() const noexcept { return isGPL() || (version > 0 && username.isNotEmpty()); }
bool isOldLicense() const noexcept { return isSignedIn() && version < projucerMajorVersion; }
bool isGPL() const noexcept { return type == Type::gpl; }
bool canUnlockFullFeatures() const noexcept
{
return isGPL() || (isSignedIn() && ! isOldLicense() && (type == Type::indie || type == Type::pro));
}
String getLicenseTypeString() const
{
switch (type)
{
case Type::none: return "No license";
case Type::gpl: return "GPL";
case Type::personal: return "Personal";
case Type::educational: return "Educational";
case Type::indie: return "Indie";
case Type::pro: return "Pro";
default: break;
};
jassertfalse;
return {};
}
Type type = Type::none;
int version = -1;
String username, authToken;
};

View File

@ -0,0 +1,283 @@
/*
==============================================================================
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.
==============================================================================
*/
#pragma once
#include "../../Project/UI/jucer_UserAvatarComponent.h"
//==============================================================================
class LoginFormComponent : public Component
{
public:
LoginFormComponent (MainWindow& window)
: mainWindow (window)
{
setTitle ("Login");
setFocusContainerType (FocusContainerType::focusContainer);
addAndMakeVisible (emailBox);
emailBox.setTextToShowWhenEmpty ("Email", Colours::black.withAlpha (0.2f));
emailBox.setJustification (Justification::centredLeft);
emailBox.onReturnKey = [this] { submitDetails(); };
emailBox.setTitle ("Email");
addAndMakeVisible (passwordBox);
passwordBox.setTextToShowWhenEmpty ("Password", Colours::black.withAlpha (0.2f));
passwordBox.setPasswordCharacter ((juce_wchar) 0x2022);
passwordBox.setJustification (Justification::centredLeft);
passwordBox.onReturnKey = [this] { submitDetails(); };
passwordBox.setTitle ("Password");
addAndMakeVisible (logInButton);
logInButton.onClick = [this] { submitDetails(); };
addAndMakeVisible (enableGPLButton);
enableGPLButton.onClick = [this]
{
ProjucerApplication::getApp().getLicenseController().setState (LicenseController::getGPLState());
mainWindow.hideLoginFormOverlay();
};
addAndMakeVisible (userAvatar);
addAndMakeVisible (createAccountLabel);
createAccountLabel.setFont (Font (14.0f, Font::underlined));
createAccountLabel.addMouseListener (this, false);
createAccountLabel.setMouseCursor (MouseCursor::PointingHandCursor);
addAndMakeVisible (errorMessageLabel);
errorMessageLabel.setMinimumHorizontalScale (1.0f);
errorMessageLabel.setFont (12.0f);
errorMessageLabel.setColour (Label::textColourId, Colours::red);
errorMessageLabel.setVisible (false);
dismissButton.setShape (getLookAndFeel().getCrossShape (1.0f), false, true, false);
addAndMakeVisible (dismissButton);
dismissButton.onClick = [this] { mainWindow.hideLoginFormOverlay(); };
dismissButton.setTitle ("Dismiss");
setWantsKeyboardFocus (true);
setOpaque (true);
lookAndFeelChanged();
setSize (300, 350);
}
~LoginFormComponent() override
{
ProjucerApplication::getApp().getLicenseController().cancelSignIn();
}
void resized() override
{
auto bounds = getLocalBounds().reduced (20);
auto spacing = bounds.getHeight() / 20;
userAvatar.setBounds (bounds.removeFromTop (iconHeight).reduced ((bounds.getWidth() / 2) - (iconHeight / 2), 0));
errorMessageLabel.setBounds (bounds.removeFromTop (spacing));
bounds.removeFromTop (spacing / 2);
auto textEditorHeight = bounds.getHeight() / 5;
emailBox.setBounds (bounds.removeFromTop (textEditorHeight));
bounds.removeFromTop (spacing);
passwordBox.setBounds (bounds.removeFromTop (textEditorHeight));
bounds.removeFromTop (spacing * 2);
emailBox.setFont (Font ((float) textEditorHeight / 2.5f));
passwordBox.setFont (Font ((float) textEditorHeight / 2.5f));
logInButton.setBounds (bounds.removeFromTop (textEditorHeight));
auto slice = bounds.removeFromTop (textEditorHeight);
createAccountLabel.setBounds (slice.removeFromLeft (createAccountLabel.getFont().getStringWidth (createAccountLabel.getText()) + 5));
slice.removeFromLeft (15);
enableGPLButton.setBounds (slice.reduced (0, 5));
dismissButton.setBounds (getLocalBounds().reduced (10).removeFromTop (20).removeFromRight (20));
}
void paint (Graphics& g) override
{
g.fillAll (findColour (secondaryBackgroundColourId).contrasting (0.1f));
}
void mouseUp (const MouseEvent& event) override
{
if (event.eventComponent == &createAccountLabel)
URL ("https://juce.com/verification/register").launchInDefaultBrowser();
}
void lookAndFeelChanged() override
{
enableGPLButton.setColour (TextButton::buttonColourId, findColour (secondaryButtonBackgroundColourId));
}
private:
class ProgressButton : public TextButton,
private Timer
{
public:
ProgressButton (const String& buttonName)
: TextButton (buttonName), text (buttonName)
{
}
void setBusy (bool shouldBeBusy)
{
isInProgress = shouldBeBusy;
if (isInProgress)
{
setEnabled (false);
setButtonText ({});
startTimerHz (30);
}
else
{
setEnabled (true);
setButtonText (text);
stopTimer();
}
}
void paint (Graphics& g) override
{
TextButton::paint (g);
if (isInProgress)
{
auto size = getHeight() - 10;
auto halfSize = size / 2;
getLookAndFeel().drawSpinningWaitAnimation (g, Colours::white,
(getWidth() / 2) - halfSize, (getHeight() / 2) - halfSize,
size, size);
}
}
private:
void timerCallback() override
{
repaint();
}
String text;
bool isInProgress = false;
};
//==============================================================================
void updateLoginButtonStates (bool isLoggingIn)
{
logInButton.setBusy (isLoggingIn);
emailBox.setEnabled (! isLoggingIn);
passwordBox.setEnabled (! isLoggingIn);
}
void submitDetails()
{
auto loginFormError = checkLoginFormsAreValid();
if (loginFormError.isNotEmpty())
{
showErrorMessage (loginFormError);
return;
}
updateLoginButtonStates (true);
auto completionCallback = [weakThis = SafePointer<LoginFormComponent> { this }] (const String& errorMessage)
{
if (weakThis == nullptr)
return;
weakThis->updateLoginButtonStates (false);
if (errorMessage.isNotEmpty())
{
weakThis->showErrorMessage (errorMessage);
}
else
{
weakThis->hideErrorMessage();
weakThis->mainWindow.hideLoginFormOverlay();
ProjucerApplication::getApp().getCommandManager().commandStatusChanged();
}
};
ProjucerApplication::getApp().getLicenseController().signIn (emailBox.getText(), passwordBox.getText(),
std::move (completionCallback));
}
String checkLoginFormsAreValid() const
{
auto email = emailBox.getText();
if (email.isEmpty() || email.indexOfChar ('@') < 0)
return "Please enter a valid email.";
auto password = passwordBox.getText();
if (password.isEmpty() || password.length() < 8)
return "Please enter a valid password.";
return {};
}
void showErrorMessage (const String& errorMessage)
{
errorMessageLabel.setText (errorMessage, dontSendNotification);
errorMessageLabel.setVisible (true);
}
void hideErrorMessage()
{
errorMessageLabel.setText ({}, dontSendNotification);
errorMessageLabel.setVisible (false);
}
//==============================================================================
static constexpr int iconHeight = 50;
MainWindow& mainWindow;
TextEditor emailBox, passwordBox;
ProgressButton logInButton { "Sign In" };
TextButton enableGPLButton { "Enable GPL Mode" };
ShapeButton dismissButton { {},
findColour (treeIconColourId),
findColour (treeIconColourId).overlaidWith (findColour (defaultHighlightedTextColourId).withAlpha (0.2f)),
findColour (treeIconColourId).overlaidWith (findColour (defaultHighlightedTextColourId).withAlpha (0.4f)) };
UserAvatarComponent userAvatar { false };
Label createAccountLabel { {}, "Create an account" },
errorMessageLabel { {}, {} };
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LoginFormComponent)
};