- clean working copy for leaving SVN

This commit is contained in:
2022-06-28 19:04:46 +02:00
parent 62a2d47cb3
commit 762b8f0e7d
1966 changed files with 699872 additions and 126 deletions
@@ -0,0 +1,18 @@
{
"id": "juce_tracktion_marketplace",
"name": "JUCE Tracktion marketplace support",
"version": "3.1.1",
"description": "JUCE classes for online product authentication via the Tracktion marketplace.",
"website": "http://www.juce.com/juce",
"license": "GPL/Commercial",
"dependencies": [ { "id": "juce_data_structures", "version": "matching" },
{ "id": "juce_cryptography", "version": "matching" } ],
"include": "juce_tracktion_marketplace.h",
"compile": [ { "file": "juce_tracktion_marketplace.cpp" } ],
"browse": [ "marketplace/*" ]
}
@@ -0,0 +1,47 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#if defined (JUCE_TRACKTION_MARKETPLACE_H_INCLUDED) && ! JUCE_AMALGAMATED_INCLUDE
/* When you add this cpp file to your project, you mustn't include it in a file where you've
already included any other headers - just put it inside a file on its own, possibly with your config
flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix
header files that the compiler may be using.
*/
#error "Incorrect use of JUCE cpp file"
#endif
// Your project must contain an AppConfig.h file with your project-specific settings in it,
// and your header search path must make it accessible to the module's files.
#include "AppConfig.h"
#include "juce_tracktion_marketplace.h"
namespace juce
{
#include "marketplace/juce_TracktionMarketplaceStatus.cpp"
#if JUCE_MODULE_AVAILABLE_juce_gui_extra
#include "marketplace/juce_TracktionMarketplaceUnlockForm.cpp"
#endif
}
@@ -0,0 +1,58 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_TRACKTION_MARKETPLACE_H_INCLUDED
#define JUCE_TRACKTION_MARKETPLACE_H_INCLUDED
/**
The Tracktion Marketplace module is a simple user-registration system for
allowing you to build apps/plugins with features that are unlocked by a
user having a suitable account on a webserver.
Although originally designed for use with products that are sold on the
Tracktion Marketplace web-store, the module itself is fully open, and can
be used to connect to your own web-store instead, if you implement your
own compatible web-server back-end.
*/
//=============================================================================
#include "../juce_cryptography/juce_cryptography.h"
#include "../juce_data_structures/juce_data_structures.h"
#if JUCE_MODULE_AVAILABLE_juce_gui_extra
#include "../juce_gui_extra/juce_gui_extra.h"
#endif
namespace juce
{
#include "marketplace/juce_TracktionMarketplaceStatus.h"
#include "marketplace/juce_TracktionMarketplaceServer.h"
#if JUCE_MODULE_AVAILABLE_juce_gui_extra
#include "marketplace/juce_TracktionMarketplaceUnlockForm.h"
#endif
}
#endif // JUCE_TRACKTION_MARKETPLACE_H_INCLUDED
@@ -0,0 +1,91 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
/**
Contains static utilities for generating key-files that can be unlocked by
the TracktionMarketplaceStatus class.
*/
class JUCE_API TracktionMarketplaceKeyGeneration
{
public:
/**
Generates the content of a key-file which can be sent to a user's machine to
unlock a product.
The returned value is a block of text containing an RSA-encoded block, followed
by some human-readable details. If you pass this block of text to
TracktionMarketplaceStatus::applyKeyFile(), it will decrpyt it, and if the
key matches and the machine numbers match, it will unlock that machine.
Typically the way you'd use this on a server would be to build a small executable
that simply calls this method and prints the result, so that the webserver can
use this as a reply to the product's auto-registration mechanism. The
keyGenerationAppMain() function is an example of how to build such a function.
@see TracktionMarketplaceStatus
*/
static String JUCE_CALLTYPE generateKeyFile (const String& appName,
const String& userEmail,
const String& userName,
const String& machineNumbers,
const RSAKey& privateKey);
//==============================================================================
/** This is a simple implementation of a key-generator that you could easily wrap in
a command-line main() function for use on your server.
So for example you might use this in a command line app called "unlocker" and
then call it like this:
unlocker MyGreatApp Joe_Bloggs joebloggs@foobar.com 1234abcd,95432ff 22d9aec92d986dd1,923ad49e9e7ff294c
*/
static inline int keyGenerationAppMain (int argc, char* argv[])
{
StringArray args;
for (int i = 1; i < argc; ++i)
args.add (argv[i]);
if (args.size() != 5)
{
std::cout << "Requires 5 arguments: app-name username user-email machine-numbers private-key" << std::endl
<< " app-name: name of the product being unlocked" << std::endl
<< " user-email: user's email address" << std::endl
<< " username: name of the user. Careful not to allow any spaces!" << std::endl
<< " machine-numbers: a comma- or semicolon-separated list of all machine ID strings this user can run this product on (no whitespace between items!)" << std::endl
<< " private-key: the RSA private key corresponding to the public key you've used in the app" << std::endl
<< std::endl;
return 1;
}
if (! args[4].containsChar (','))
{
std::cout << "Not a valid RSA key!" << std::endl;
return 1;
}
std::cout << generateKeyFile (args[0], args[1], args[2], args[3], RSAKey (args[4])) << std::endl;
return 0;
}
};
@@ -0,0 +1,397 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
/* Note: there's a bit of light obfuscation in this code, just to make things
a bit more annoying for crackers who try to reverse-engineer your binaries, but
nothing particularly foolproof.
*/
struct KeyFileUtils
{
static String encryptXML (const XmlElement& xml, RSAKey privateKey)
{
MemoryOutputStream text;
text << xml.createDocument (String::empty, true);
BigInteger val;
val.loadFromMemoryBlock (text.getMemoryBlock());
privateKey.applyToValue (val);
return val.toString (16);
}
static String createKeyFile (String comment,
const XmlElement& xml,
RSAKey rsaPrivateKey)
{
String asHex ("#" + encryptXML (xml, rsaPrivateKey));
StringArray lines;
lines.add (comment);
lines.add (String::empty);
const int charsPerLine = 70;
while (asHex.length() > 0)
{
lines.add (asHex.substring (0, charsPerLine));
asHex = asHex.substring (charsPerLine);
}
lines.add (String::empty);
return lines.joinIntoString ("\r\n");
}
//==============================================================================
static XmlElement decryptXML (String hexData, RSAKey rsaPublicKey)
{
BigInteger val;
val.parseString (hexData, 16);
RSAKey key (rsaPublicKey);
key.applyToValue (val);
ScopedPointer<XmlElement> xml (XmlDocument::parse (val.toMemoryBlock().toString()));
return xml != nullptr ? *xml : XmlElement("key");
}
static XmlElement getXmlFromKeyFile (String keyFileText, RSAKey rsaPublicKey)
{
return decryptXML (keyFileText.fromLastOccurrenceOf ("#", false, false).trim(), rsaPublicKey);
}
static StringArray getMachineNumbers (XmlElement xml)
{
StringArray numbers;
numbers.addTokens (xml.getStringAttribute ("mach"), ",; ", String::empty);
numbers.trim();
numbers.removeEmptyStrings();
return numbers;
}
static String getLicensee (const XmlElement& xml) { return xml.getStringAttribute ("user"); }
static String getEmail (const XmlElement& xml) { return xml.getStringAttribute ("email"); }
static String getAppID (const XmlElement& xml) { return xml.getStringAttribute ("app"); }
struct KeyFileData
{
String licensee, email, appID;
StringArray machineNumbers;
};
static KeyFileData getDataFromKeyFile (XmlElement xml)
{
KeyFileData data;
data.licensee = getLicensee (xml);
data.email = getEmail (xml);
data.appID = getAppID (xml);
data.machineNumbers.addArray (getMachineNumbers (xml));
return data;
}
};
//==============================================================================
const char* TracktionMarketplaceStatus::unlockedProp = "u";
static const char* stateTagName = "REG";
static const char* userNameProp = "user";
static const char* passwordProp = "pw";
static var machineNumberAllowed (StringArray numbersFromKeyFile,
StringArray localMachineNumbers)
{
var result;
for (int i = 0; i < localMachineNumbers.size(); ++i)
{
String localNumber (localMachineNumbers[i].trim());
if (localNumber.isNotEmpty())
{
for (int j = numbersFromKeyFile.size(); --j >= 0;)
{
var ok (localNumber.trim().equalsIgnoreCase (numbersFromKeyFile[j].trim()));
result.swapWith (ok);
if (result)
break;
}
}
}
return result;
}
//==============================================================================
TracktionMarketplaceStatus::TracktionMarketplaceStatus() : status (stateTagName)
{
}
TracktionMarketplaceStatus::~TracktionMarketplaceStatus()
{
}
void TracktionMarketplaceStatus::load()
{
MemoryBlock mb;
mb.fromBase64Encoding (getState());
if (mb.getSize() > 0)
status = ValueTree::readFromGZIPData (mb.getData(), mb.getSize());
else
status = ValueTree (stateTagName);
if (machineNumberAllowed (StringArray ("1234"), getLocalMachineIDs()))
status.removeProperty (unlockedProp, nullptr);
}
void TracktionMarketplaceStatus::save()
{
MemoryOutputStream mo;
{
GZIPCompressorOutputStream gzipStream (&mo, 9);
status.writeToStream (gzipStream);
}
saveState (mo.getMemoryBlock().toBase64Encoding());
}
static String getEncodedIDString (const String& input)
{
static const char* const platform =
#if JUCE_MAC
"M";
#elif JUCE_WINDOWS
"W";
#elif JUCE_LINUX
"L";
#elif JUCE_IOS
"I";
#elif JUCE_ANDROID
"A";
#endif
return platform + MD5 ((input + "salt_1" + platform).toUTF8())
.toHexString().substring (0, 9).toUpperCase();
}
StringArray TracktionMarketplaceStatus::getLocalMachineIDs()
{
StringArray nums;
// First choice for an ID number is a filesystem ID for the user's home
// folder or windows directory.
#if JUCE_WINDOWS
uint64 num = File::getSpecialLocation (File::windowsSystemDirectory).getFileIdentifier();
#else
uint64 num = File ("~").getFileIdentifier();
#endif
if (num != 0)
{
nums.add (getEncodedIDString (String::toHexString ((int64) num)));
return nums;
}
// ..if that fails, use the MAC addresses..
Array<MACAddress> addresses;
MACAddress::findAllAddresses (addresses);
for (int i = 0; i < addresses.size(); ++i)
nums.add (getEncodedIDString (addresses[i].toString()));
jassert (nums.size() > 0); // failed to create any IDs!
return nums;
}
URL TracktionMarketplaceStatus::getServerAuthenticationURL()
{
return URL ("https://www.tracktion.com/marketplace/authenticate.php");
}
String TracktionMarketplaceStatus::getWebsiteName()
{
return "tracktion.com";
}
void TracktionMarketplaceStatus::setUserEmail (const String& usernameOrEmail)
{
status.setProperty (userNameProp, usernameOrEmail, nullptr);
}
String TracktionMarketplaceStatus::getUserEmail() const
{
return status[userNameProp].toString();
}
bool TracktionMarketplaceStatus::applyKeyFile (String keyFileContent)
{
KeyFileUtils::KeyFileData data;
data = KeyFileUtils::getDataFromKeyFile (KeyFileUtils::getXmlFromKeyFile (keyFileContent, getPublicKey()));
if (data.licensee.isNotEmpty() && data.email.isNotEmpty() && data.appID == getMarketplaceProductID())
{
setUserEmail (data.email);
if (! isUnlocked())
{
var actualResult (0), dummyResult (1.0);
var v (machineNumberAllowed (data.machineNumbers, getLocalMachineIDs()));
actualResult.swapWith (v);
v = machineNumberAllowed (StringArray ("01"), getLocalMachineIDs());
dummyResult.swapWith (v);
jassert (! dummyResult);
if ((! dummyResult) && actualResult)
status.setProperty (unlockedProp, actualResult, nullptr);
}
return true;
}
return false;
}
static bool canConnectToWebsite (const URL& url)
{
ScopedPointer<InputStream> in (url.createInputStream (false, nullptr, nullptr, String(), 2000, nullptr));
return in != nullptr;
}
static bool areMajorWebsitesAvailable()
{
const char* urlsToTry[] = { "http://google.com", "http://bing.com", "http://amazon.com", nullptr};
for (const char** url = urlsToTry; *url != nullptr; ++url)
if (canConnectToWebsite (URL (*url)))
return true;
return false;
}
TracktionMarketplaceStatus::UnlockResult TracktionMarketplaceStatus::handleXmlReply (XmlElement xml)
{
UnlockResult r;
if (const XmlElement* keyNode = xml.getChildByName ("KEY"))
{
const String keyText (keyNode->getAllSubText().trim());
r.succeeded = keyText.length() > 10 && applyKeyFile (keyText);
}
if (xml.hasTagName ("MESSAGE"))
r.informativeMessage = xml.getStringAttribute ("message").trim();
if (xml.hasTagName ("ERROR"))
r.errorMessage = xml.getStringAttribute ("error").trim();
if (xml.getStringAttribute ("url").isNotEmpty())
r.urlToLaunch = xml.getStringAttribute ("url").trim();
if (r.errorMessage.isEmpty() && r.informativeMessage.isEmpty() && r.urlToLaunch.isEmpty() && ! r.succeeded)
r.errorMessage = TRANS ("Unexpected or corrupted reply from XYZ").replace ("XYZ", getWebsiteName()) + "...\n\n"
+ TRANS("Please try again in a few minutes, and contact us for support if this message appears again.");
return r;
}
TracktionMarketplaceStatus::UnlockResult TracktionMarketplaceStatus::handleFailedConnection()
{
UnlockResult r;
r.errorMessage = TRANS("Couldn't connect to XYZ").replace ("XYZ", getWebsiteName()) + "...\n\n";
if (areMajorWebsitesAvailable())
r.errorMessage << TRANS("Your internet connection seems to be OK, but our webserver "
"didn't respond... This is most likely a temporary problem, so try "
"again in a few minutes, but if it persists, please contact us for support!");
else
r.errorMessage << TRANS("No internet sites seem to be accessible from your computer.. Before trying again, "
"please check that your network is working correctly, and make sure "
"that any firewall/security software installed on your machine isn't "
"blocking your web connection.");
return r;
}
TracktionMarketplaceStatus::UnlockResult TracktionMarketplaceStatus::attemptWebserverUnlock (const String& email,
const String& password)
{
// This method will block while it contacts the server, so you must run it on a background thread!
jassert (! MessageManager::getInstance()->isThisTheMessageThread());
URL url (getServerAuthenticationURL()
.withParameter ("product", getMarketplaceProductID())
.withParameter ("email", email)
.withParameter ("pw", password)
.withParameter ("os", SystemStats::getOperatingSystemName())
.withParameter ("mach", getLocalMachineIDs()[0]));
DBG ("Trying to unlock via URL: " << url.toString (true));
const String reply (url.readEntireTextStream());
DBG ("Reply from server: " << reply);
ScopedPointer<XmlElement> xml (XmlDocument::parse (reply));
if (xml != nullptr)
return handleXmlReply (*xml);
return handleFailedConnection();
}
//==============================================================================
String TracktionMarketplaceKeyGeneration::generateKeyFile (const String& appName,
const String& userEmail,
const String& userName,
const String& machineNumbers,
const RSAKey& privateKey)
{
XmlElement xml ("key");
xml.setAttribute ("user", userName);
xml.setAttribute ("email", userEmail);
xml.setAttribute ("mach", machineNumbers);
xml.setAttribute ("app", appName);
xml.setAttribute ("date", String::toHexString (Time::getCurrentTime().toMilliseconds()));
String comment;
comment << "Keyfile for " << appName << newLine;
if (userName.isNotEmpty())
comment << "User: " << userName << newLine;
comment << "Email: " << userEmail << newLine
<< "Machine numbers: " << machineNumbers << newLine
<< "Created: " << Time::getCurrentTime().toString (true, true);
return KeyFileUtils::createKeyFile (comment, xml, privateKey);
}
@@ -0,0 +1,199 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
/**
Contains information about whether your app has been unlocked for the current machine,
and handles communication with the web-store to perform the unlock procedure.
To use this class, create a subclass of it, and implement its pure virtual methods
(see their comments to find out what you'll need to make them do).
Then you can create an instance of your subclass which will hold the registration
state. Typically, you'll want to just keep a single instance of the class around for
the duration of your app. You can then call its methods to handle the various
registration tasks.
Areas of your code that need to know whether the user is registered (e.g. to decide
whether a particular feature is available) should call isUnlocked() to find out.
If you want to create a GUI that allows your users to enter their details and
register, see the TracktionMarketplaceUnlockForm class.
@see TracktionMarketplaceUnlockForm, TracktionMarketplaceKeyGeneration
*/
class JUCE_API TracktionMarketplaceStatus
{
public:
TracktionMarketplaceStatus();
/** Destructor. */
virtual ~TracktionMarketplaceStatus();
//==============================================================================
/** This must return your product's ID, as allocated by the store. */
virtual String getMarketplaceProductID() = 0;
/** This must return the RSA public key for authenticating responses from
the server for this app. You can get this key from your marketplace
account page.
*/
virtual RSAKey getPublicKey() = 0;
/** This method must store the given string somewhere in your app's
persistent properties, so it can be retrieved later by getState().
*/
virtual void saveState (const String&) = 0;
/** This method must retrieve the last state that was provided by the
saveState method.
On first-run, it should just return an empty string.
*/
virtual String getState() = 0;
/** Returns a list of strings, any of which should be unique to this
physical computer.
When testing whether the user is allowed to use the product on this
machine, this list of tokens is compared to the ones that were stored
on the Tracktion marketplace webserver.
The default implementation of this method will calculate some
machine IDs based on things like network MAC addresses, hard-disk
IDs, etc, but if you want, you can overload it to generate your
own list of IDs.
The IDs that are returned should be short alphanumeric strings
without any punctuation characters. Since users may need to type
them, case is ignored when comparing them.
Note that the first item in the list is considered to be the
"main" ID, and this will be the one that is displayed to the user
and registered with the marketplace webserver. Subsequent IDs are
just used as fallback to avoid false negatives when checking for
registration on machines which have had hardware added/removed
since the product was first registered.
*/
virtual StringArray getLocalMachineIDs();
/** Can be overridden if necessary, but by default returns the tracktion.com
marketplace server.
*/
virtual URL getServerAuthenticationURL();
/** Can be overridden if necessary, but by default returns "tracktion.com". */
virtual String getWebsiteName();
//==============================================================================
// The following methods can be called by your app:
/** Returns true if the product has been successfully authorised for this machine.
The reason it returns a variant rather than a bool is just to make it marginally
more tedious for crackers to work around. Hopefully if this method gets inlined
they'll need to hack all the places where you call it, rather than just the
function itself.
Bear in mind that each place where you check this return value will need to be
changed by a cracker in order to unlock your app, so the more places you call this
method, the more hassle it will be for them to find and crack them all.
*/
inline var isUnlocked() const { return status[unlockedProp]; }
/** Optionally allows the app to provide the user's email address if
it is known.
You don't need to call this, but if you do it may save the user
typing it in.
*/
void setUserEmail (const String& usernameOrEmail);
/** Returns the user's email address if known. */
String getUserEmail() const;
/** Attempts to perform an unlock using a block of key-file data provided.
You may wish to use this as a way of allowing a user to unlock your app
by drag-and-dropping a file containing the key data, or by letting them
select such a file. This is often needed for allowing registration on
machines without internet access.
*/
bool applyKeyFile (String keyFileContent);
/** This provides some details about the reply that the server gave in a call
to attemptWebserverUnlock().
*/
struct UnlockResult
{
/** If an unlock operation fails, this is the error message that the webserver
supplied (or a message saying that the server couldn't be contacted)
*/
String errorMessage;
/** This is a message that the webserver returned, and which the user should
be shown.
It's not necessarily an error message, e.g. it might say that there's a
new version of the app available or some other status update.
*/
String informativeMessage;
/** If the webserver wants the user to be directed to a web-page for further
information, this is the URL that it would like them to go to.
*/
String urlToLaunch;
/** If the unlock operation succeeded, this will be set to true. */
bool succeeded;
};
/** Contacts the webserver and attempts to perform a registration with the
given user details.
The return value will either be a success, or a failure with an error message
from the server, so you should show this message to your user.
Note that this is designed for the Tracktion marketplace server - if you're
building your own server, this function probably won't do the right thing for you.
*/
UnlockResult attemptWebserverUnlock (const String& email, const String& password);
/** Attempts to load the status from the state retrieved by getState().
Call this somewhere in your app's startup code.
*/
void load();
/** Triggers a call to saveState which you can use to store the current unlock status
in your app's settings.
*/
void save();
private:
ValueTree status;
UnlockResult handleXmlReply (XmlElement);
UnlockResult handleFailedConnection();
static const char* unlockedProp;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TracktionMarketplaceStatus)
};
@@ -0,0 +1,258 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
struct Spinner : public Component, private Timer
{
Spinner() { startTimer (1000 / 50); }
void timerCallback() override { repaint(); }
void paint (Graphics& g) override
{
getLookAndFeel().drawSpinningWaitAnimation (g, Colours::darkgrey, 0, 0, getWidth(), getHeight());
}
};
struct TracktionMarketplaceUnlockForm::OverlayComp : public Component,
private Thread,
private Timer
{
OverlayComp (TracktionMarketplaceUnlockForm& f) : Thread (String()), form (f)
{
email = form.emailBox.getText();
password = form.passwordBox.getText();
addAndMakeVisible (spinner);
startThread (4);
}
~OverlayComp()
{
stopThread (10000);
}
void paint (Graphics& g) override
{
g.fillAll (Colours::white.withAlpha (0.97f));
g.setColour (Colours::black);
g.setFont (15.0f);
g.drawFittedText (TRANS("Contacting XYZ...").replace ("XYZ", form.status.getWebsiteName()),
getLocalBounds().reduced (20, 0).removeFromTop (proportionOfHeight (0.6f)),
Justification::centred, 5);
}
void resized() override
{
const int spinnerSize = 40;
spinner.setBounds ((getWidth() - spinnerSize) / 2, proportionOfHeight (0.6f), spinnerSize, spinnerSize);
}
void run() override
{
result = form.status.attemptWebserverUnlock (email, password);
startTimer (100);
}
void timerCallback() override
{
spinner.setVisible (false);
stopTimer();
if (result.errorMessage.isNotEmpty())
{
AlertWindow::showMessageBox (AlertWindow::WarningIcon,
TRANS("Registration Failed"),
result.errorMessage);
}
else if (result.informativeMessage.isNotEmpty())
{
AlertWindow::showMessageBox (AlertWindow::InfoIcon,
TRANS("Registration Complete!"),
result.informativeMessage);
}
else if (result.urlToLaunch.isNotEmpty())
{
URL url (result.urlToLaunch);
url.launchInDefaultBrowser();
}
if (result.succeeded)
form.cancel();
else
delete this;
}
TracktionMarketplaceUnlockForm& form;
Spinner spinner;
TracktionMarketplaceStatus::UnlockResult result;
String email, password;
};
static juce_wchar getDefaultPasswordChar() noexcept
{
#if JUCE_LINUX
return 0x2022;
#else
return 0x25cf;
#endif
}
TracktionMarketplaceUnlockForm::TracktionMarketplaceUnlockForm (TracktionMarketplaceStatus& s,
const String& userInstructions)
: status (s),
message (String(), userInstructions),
passwordBox (String(), getDefaultPasswordChar()),
registerButton (TRANS("Register")),
cancelButton (TRANS ("Cancel"))
{
// Please supply a message to tell your users what to do!
jassert (userInstructions.isNotEmpty());
setOpaque (true);
emailBox.setText (status.getUserEmail());
message.setJustificationType (Justification::centred);
addAndMakeVisible (message);
addAndMakeVisible (emailBox);
addAndMakeVisible (passwordBox);
addAndMakeVisible (registerButton);
addAndMakeVisible (cancelButton);
registerButton.addListener (this);
cancelButton.addListener (this);
lookAndFeelChanged();
setSize (500, 250);
}
TracktionMarketplaceUnlockForm::~TracktionMarketplaceUnlockForm()
{
unlockingOverlay.deleteAndZero();
}
void TracktionMarketplaceUnlockForm::paint (Graphics& g)
{
g.fillAll (Colours::lightgrey);
}
void TracktionMarketplaceUnlockForm::resized()
{
/* If you're writing a plugin, then DO NOT USE A POP-UP A DIALOG WINDOW!
Plugins that create external windows are incredibly annoying for users, and
cause all sorts of headaches for hosts. Don't be the person who writes that
plugin that irritates everyone with a nagging dialog box every time they scan!
*/
jassert (JUCEApplicationBase::isStandaloneApp() || findParentComponentOfClass<DialogWindow>() == nullptr);
const int buttonHeight = 22;
Rectangle<int> r (getLocalBounds().reduced (10, 20));
Rectangle<int> buttonArea (r.removeFromBottom (buttonHeight));
registerButton.changeWidthToFitText (buttonHeight);
cancelButton.changeWidthToFitText (buttonHeight);
const int gap = 20;
buttonArea = buttonArea.withSizeKeepingCentre (registerButton.getWidth() + gap + cancelButton.getWidth(), buttonHeight);
registerButton.setBounds (buttonArea.removeFromLeft (registerButton.getWidth()));
buttonArea.removeFromLeft (gap);
cancelButton.setBounds (buttonArea);
r.removeFromBottom (20);
const int boxHeight = 24;
passwordBox.setBounds (r.removeFromBottom (boxHeight));
passwordBox.setInputRestrictions (64);
r.removeFromBottom (30);
emailBox.setBounds (r.removeFromBottom (boxHeight));
passwordBox.setInputRestrictions (256);
r.removeFromBottom (30);
message.setBounds (r);
if (unlockingOverlay != nullptr)
unlockingOverlay->setBounds (getLocalBounds());
}
void TracktionMarketplaceUnlockForm::lookAndFeelChanged()
{
Colour labelCol (findColour (TextEditor::backgroundColourId).contrasting (0.5f));
emailBox.setTextToShowWhenEmpty (TRANS("Email Address"), labelCol);
passwordBox.setTextToShowWhenEmpty (TRANS("Password"), labelCol);
}
void TracktionMarketplaceUnlockForm::showBubbleMessage (const String& text, Component& target)
{
bubble = new BubbleMessageComponent (500);
addChildComponent (bubble);
AttributedString attString;
attString.append (text, Font (15.0f));
bubble->showAt (getLocalArea (&target, target.getLocalBounds()),
attString, 500, // numMillisecondsBeforeRemoving
true, // removeWhenMouseClicked
false); // deleteSelfAfterUse
}
void TracktionMarketplaceUnlockForm::buttonClicked (Button* b)
{
if (b == &registerButton)
attemptRegistration();
else if (b == &cancelButton)
cancel();
}
void TracktionMarketplaceUnlockForm::attemptRegistration()
{
if (unlockingOverlay == nullptr)
{
if (emailBox.getText().trim().length() < 3)
{
showBubbleMessage (TRANS ("Please enter a valid email address!"), emailBox);
return;
}
if (passwordBox.getText().trim().length() < 3)
{
showBubbleMessage (TRANS ("Please enter a valid password!"), passwordBox);
return;
}
status.setUserEmail (emailBox.getText());
addAndMakeVisible (unlockingOverlay = new OverlayComp (*this));
resized();
unlockingOverlay->enterModalState();
}
}
void TracktionMarketplaceUnlockForm::cancel()
{
delete this;
}
@@ -0,0 +1,82 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
/** Acts as a GUI which asks the user for their details, and calls the approriate
methods on your TracktionMarketplaceStatus object to attempt to register the app.
You should create one of these components and add it to your parent window,
or use a DialogWindow to display it as a pop-up. But if you're writing a plugin,
then DO NOT USE A DIALOG WINDOW! Add it as a child component of your plugin's editor
component instead. Plugins that pop up external registration windows are incredibly
annoying, and cause all sorts of headaches for hosts. Don't be the person who
writes that plugin that irritates everyone with a dialog box every time they
try to scan for new plugins!
Note that after adding it, you should put the component into a modal state,
and it will automatically delete itself when it has completed.
Although it deletes itself, it's also OK to delete it manually yourself
if you need to get rid of it sooner.
@see TracktionMarketplaceStatus
*/
class JUCE_API TracktionMarketplaceUnlockForm : public Component,
private ButtonListener
{
public:
/** Creates an unlock form that will work with the given status object.
The userInstructions will be displayed above the email and password boxes.
*/
TracktionMarketplaceUnlockForm (TracktionMarketplaceStatus&,
const String& userInstructions);
/** Destructor. */
~TracktionMarketplaceUnlockForm();
/** @internal */
void paint (Graphics&) override;
/** @internal */
void resized() override;
/** @internal */
void lookAndFeelChanged() override;
private:
TracktionMarketplaceStatus& status;
Label message;
TextEditor emailBox, passwordBox;
TextButton registerButton, cancelButton;
ScopedPointer<BubbleMessageComponent> bubble;
struct OverlayComp;
friend struct OverlayComp;
Component::SafePointer<Component> unlockingOverlay;
void buttonClicked (Button*) override;
void attemptRegistration();
void cancel();
void showBubbleMessage (const String&, Component&);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TracktionMarketplaceUnlockForm)
};