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,369 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: AnalyticsCollectionDemo
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Collects analytics data.
dependencies: juce_analytics, juce_core, juce_data_structures, juce_events,
juce_graphics, juce_gui_basics
exporters: xcode_mac, vs2019, linux_make, xcode_iphone, androidstudio
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: Component
mainClass: AnalyticsCollectionDemo
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
//==============================================================================
enum DemoAnalyticsEventTypes
{
event,
sessionStart,
sessionEnd,
screenView,
exception
};
//==============================================================================
class GoogleAnalyticsDestination : public ThreadedAnalyticsDestination
{
public:
GoogleAnalyticsDestination()
: ThreadedAnalyticsDestination ("GoogleAnalyticsThread")
{
{
// Choose where to save any unsent events.
auto appDataDir = File::getSpecialLocation (File::userApplicationDataDirectory)
.getChildFile (JUCEApplication::getInstance()->getApplicationName());
if (! appDataDir.exists())
appDataDir.createDirectory();
savedEventsFile = appDataDir.getChildFile ("analytics_events.xml");
}
{
// It's often a good idea to construct any analytics service API keys
// at runtime, so they're not searchable in the binary distribution of
// your application (but we've not done this here). You should replace
// the following key with your own to get this example application
// fully working.
apiKey = "UA-XXXXXXXXX-1";
}
startAnalyticsThread (initialPeriodMs);
}
~GoogleAnalyticsDestination() override
{
// Here we sleep so that our background thread has a chance to send the
// last lot of batched events. Be careful - if your app takes too long to
// shut down then some operating systems will kill it forcibly!
Thread::sleep (initialPeriodMs);
stopAnalyticsThread (1000);
}
int getMaximumBatchSize() override { return 20; }
bool logBatchedEvents (const Array<AnalyticsEvent>& events) override
{
// Send events to Google Analytics.
String appData ("v=1&aip=1&tid=" + apiKey);
StringArray postData;
for (auto& event : events)
{
StringPairArray data;
switch (event.eventType)
{
case (DemoAnalyticsEventTypes::event):
{
data.set ("t", "event");
if (event.name == "startup")
{
data.set ("ec", "info");
data.set ("ea", "appStarted");
}
else if (event.name == "shutdown")
{
data.set ("ec", "info");
data.set ("ea", "appStopped");
}
else if (event.name == "button_press")
{
data.set ("ec", "button_press");
data.set ("ea", event.parameters["id"]);
}
else if (event.name == "crash")
{
data.set ("ec", "crash");
data.set ("ea", "crash");
}
else
{
jassertfalse;
continue;
}
break;
}
default:
{
// Unknown event type! In this demo app we're just using a
// single event type, but in a real app you probably want to
// handle multiple ones.
jassertfalse;
break;
}
}
data.set ("cid", event.userID);
StringArray eventData;
for (auto& key : data.getAllKeys())
eventData.add (key + "=" + URL::addEscapeChars (data[key], true));
postData.add (appData + "&" + eventData.joinIntoString ("&"));
}
auto url = URL ("https://www.google-analytics.com/batch")
.withPOSTData (postData.joinIntoString ("\n"));
{
const ScopedLock lock (webStreamCreation);
if (shouldExit)
return false;
webStream.reset (new WebInputStream (url, true));
}
auto success = webStream->connect (nullptr);
// Do an exponential backoff if we failed to connect.
if (success)
periodMs = initialPeriodMs;
else
periodMs *= 2;
setBatchPeriod (periodMs);
return success;
}
void stopLoggingEvents() override
{
const ScopedLock lock (webStreamCreation);
shouldExit = true;
if (webStream.get() != nullptr)
webStream->cancel();
}
private:
void saveUnloggedEvents (const std::deque<AnalyticsEvent>& eventsToSave) override
{
// Save unsent events to disk. Here we use XML as a serialisation format, but
// you can use anything else as long as the restoreUnloggedEvents method can
// restore events from disk. If you're saving very large numbers of events then
// a binary format may be more suitable if it is faster - remember that this
// method is called on app shutdown so it needs to complete quickly!
auto xml = parseXMLIfTagMatches (savedEventsFile, "events");
if (xml == nullptr)
xml = std::make_unique<XmlElement> ("events");
for (auto& event : eventsToSave)
{
auto* xmlEvent = new XmlElement ("google_analytics_event");
xmlEvent->setAttribute ("name", event.name);
xmlEvent->setAttribute ("type", event.eventType);
xmlEvent->setAttribute ("timestamp", (int) event.timestamp);
xmlEvent->setAttribute ("user_id", event.userID);
auto* parameters = new XmlElement ("parameters");
for (auto& key : event.parameters.getAllKeys())
parameters->setAttribute (key, event.parameters[key]);
xmlEvent->addChildElement (parameters);
auto* userProperties = new XmlElement ("user_properties");
for (auto& key : event.userProperties.getAllKeys())
userProperties->setAttribute (key, event.userProperties[key]);
xmlEvent->addChildElement (userProperties);
xml->addChildElement (xmlEvent);
}
xml->writeTo (savedEventsFile, {});
}
void restoreUnloggedEvents (std::deque<AnalyticsEvent>& restoredEventQueue) override
{
if (auto xml = parseXMLIfTagMatches (savedEventsFile, "events"))
{
auto numEvents = xml->getNumChildElements();
for (auto iEvent = 0; iEvent < numEvents; ++iEvent)
{
auto* xmlEvent = xml->getChildElement (iEvent);
StringPairArray parameters;
auto* xmlParameters = xmlEvent->getChildByName ("parameters");
auto numParameters = xmlParameters->getNumAttributes();
for (auto iParam = 0; iParam < numParameters; ++iParam)
parameters.set (xmlParameters->getAttributeName (iParam),
xmlParameters->getAttributeValue (iParam));
StringPairArray userProperties;
auto* xmlUserProperties = xmlEvent->getChildByName ("user_properties");
auto numUserProperties = xmlUserProperties->getNumAttributes();
for (auto iProp = 0; iProp < numUserProperties; ++iProp)
userProperties.set (xmlUserProperties->getAttributeName (iProp),
xmlUserProperties->getAttributeValue (iProp));
restoredEventQueue.push_back ({
xmlEvent->getStringAttribute ("name"),
xmlEvent->getIntAttribute ("type"),
static_cast<uint32> (xmlEvent->getIntAttribute ("timestamp")),
parameters,
xmlEvent->getStringAttribute ("user_id"),
userProperties
});
}
savedEventsFile.deleteFile();
}
}
const int initialPeriodMs = 1000;
int periodMs = initialPeriodMs;
CriticalSection webStreamCreation;
bool shouldExit = false;
std::unique_ptr<WebInputStream> webStream;
String apiKey;
File savedEventsFile;
};
//==============================================================================
class AnalyticsCollectionDemo : public Component
{
public:
//==============================================================================
AnalyticsCollectionDemo()
{
// Add an analytics identifier for the user. Make sure you don't accidentally
// collect identifiable information if you haven't asked for permission!
Analytics::getInstance()->setUserId ("AnonUser1234");
// Add any other constant user information.
StringPairArray userData;
userData.set ("group", "beta");
Analytics::getInstance()->setUserProperties (userData);
// Add any analytics destinations we want to use to the Analytics singleton.
Analytics::getInstance()->addDestination (new GoogleAnalyticsDestination());
// The event type here should probably be DemoAnalyticsEventTypes::sessionStart
// in a more advanced app.
Analytics::getInstance()->logEvent ("startup", {}, DemoAnalyticsEventTypes::event);
crashButton.onClick = [this] { sendCrash(); };
addAndMakeVisible (eventButton);
addAndMakeVisible (crashButton);
setSize (300, 200);
StringPairArray logButtonPressParameters;
logButtonPressParameters.set ("id", "a");
logEventButtonPress.reset (new ButtonTracker (eventButton, "button_press", logButtonPressParameters));
}
~AnalyticsCollectionDemo() override
{
// The event type here should probably be DemoAnalyticsEventTypes::sessionEnd
// in a more advanced app.
Analytics::getInstance()->logEvent ("shutdown", {}, DemoAnalyticsEventTypes::event);
}
void paint (Graphics& g) override
{
g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
}
void resized() override
{
eventButton.centreWithSize (100, 40);
eventButton.setBounds (eventButton.getBounds().translated (0, 25));
crashButton.setBounds (eventButton.getBounds().translated (0, -50));
}
private:
//==============================================================================
void sendCrash()
{
// In a more advanced application you would probably use a different event
// type here.
Analytics::getInstance()->logEvent ("crash", {}, DemoAnalyticsEventTypes::event);
Analytics::getInstance()->getDestinations().clear();
JUCEApplication::getInstance()->shutdown();
}
TextButton eventButton { "Press me!" }, crashButton { "Simulate crash!" };
std::unique_ptr<ButtonTracker> logEventButtonPress;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnalyticsCollectionDemo)
};

333
deps/juce/examples/Utilities/Box2DDemo.h vendored Normal file
View File

@ -0,0 +1,333 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: Box2DDemo
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Showcases 2D graphics features.
dependencies: juce_box2d, juce_core, juce_data_structures, juce_events,
juce_graphics, juce_gui_basics
exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: Component
mainClass: Box2DDemo
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
#include "../Assets/DemoUtilities.h"
//==============================================================================
// (These classes and random functions are used inside the 3rd-party Box2D demo code)
inline float32 RandomFloat() { return Random::getSystemRandom().nextFloat() * 2.0f - 1.0f; }
inline float32 RandomFloat (float32 lo, float32 hi) { return Random::getSystemRandom().nextFloat() * (hi - lo) + lo; }
struct Settings
{
b2Vec2 viewCenter { 0.0f, 20.0f };
float32 hz = 60.0f;
int velocityIterations = 8;
int positionIterations = 3;
int drawShapes = 1;
int drawJoints = 1;
int drawAABBs = 0;
int drawPairs = 0;
int drawContactPoints = 0;
int drawContactNormals = 0;
int drawContactForces = 0;
int drawFrictionForces = 0;
int drawCOMs = 0;
int drawStats = 0;
int drawProfile = 0;
int enableWarmStarting = 1;
int enableContinuous = 1;
int enableSubStepping = 0;
int pause = 0;
int singleStep = 0;
};
struct Test
{
Test() {}
virtual ~Test() {}
virtual void Keyboard (unsigned char /*key*/) {}
virtual void KeyboardUp (unsigned char /*key*/) {}
std::unique_ptr<b2World> m_world { new b2World (b2Vec2 (0.0f, -10.0f)) };
};
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wimplicit-int-float-conversion")
#include "../Assets/Box2DTests/AddPair.h"
#include "../Assets/Box2DTests/ApplyForce.h"
#include "../Assets/Box2DTests/Dominos.h"
#include "../Assets/Box2DTests/Chain.h"
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
//==============================================================================
/** This list box just displays a StringArray and broadcasts a change message when the
selected row changes.
*/
class Box2DTestList : public ListBoxModel,
public ChangeBroadcaster
{
public:
Box2DTestList (const StringArray& testList)
: tests (testList)
{}
int getNumRows() override { return tests.size(); }
void selectedRowsChanged (int /*lastRowSelected*/) override { sendChangeMessage(); }
void paintListBoxItem (int row, Graphics& g, int w, int h, bool rowIsSelected) override
{
auto& lf = LookAndFeel::getDefaultLookAndFeel();
if (rowIsSelected)
g.fillAll (Colour::contrasting (lf.findColour (ListBox::textColourId),
lf.findColour (ListBox::backgroundColourId)));
g.setColour (lf.findColour (ListBox::textColourId));
g.setFont ((float) h * 0.7f);
g.drawText (tests[row], Rectangle<int> (0, 0, w, h).reduced (2),
Justification::centredLeft, true);
}
private:
StringArray tests;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Box2DTestList)
};
//==============================================================================
struct Box2DRenderComponent : public Component
{
Box2DRenderComponent()
{
setOpaque (true);
}
void paint (Graphics& g) override
{
g.fillAll (Colours::white);
if (currentTest.get() != nullptr)
{
Box2DRenderer renderer;
renderer.render (g, *currentTest->m_world,
-16.0f, 30.0f, 16.0f, -1.0f,
getLocalBounds().toFloat().reduced (8.0f));
}
}
std::unique_ptr<Test> currentTest;
};
//==============================================================================
class Box2DDemo : public Component,
private Timer,
private ChangeListener
{
public:
enum Demos
{
addPair = 0,
applyForce,
dominoes,
chain,
numTests
};
Box2DDemo()
: testsList (getTestsList())
{
setOpaque (true);
setWantsKeyboardFocus (true);
testsListModel.addChangeListener (this);
addAndMakeVisible (renderComponent);
addAndMakeVisible (testsListBox);
testsListBox.setModel (&testsListModel);
testsListBox.selectRow (dominoes);
addAndMakeVisible (instructions);
instructions.setMultiLine (true);
instructions.setReadOnly (true);
startTimerHz (60);
setSize (500, 500);
}
~Box2DDemo() override
{
testsListModel.removeChangeListener (this);
}
void paint (Graphics& g) override
{
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
}
void resized() override
{
auto r = getLocalBounds().reduced (4);
auto area = r.removeFromBottom (150);
testsListBox.setBounds (area.removeFromLeft (150));
area.removeFromLeft (4);
instructions.setBounds (area);
r.removeFromBottom (6);
renderComponent.setBounds (r);
}
bool keyPressed (const KeyPress& key) override
{
if (renderComponent.currentTest.get() != nullptr)
{
// We override this to avoid the system beeping for an unused keypress
switch (key.getTextCharacter())
{
case 'a':
case 'w':
case 'd':
return true;
default:
break;
}
}
return false;
}
private:
StringArray testsList;
Box2DTestList testsListModel { testsList };
Box2DRenderComponent renderComponent;
ListBox testsListBox;
TextEditor instructions;
static Test* createTest (int index)
{
switch (index)
{
case addPair: return new AddPair();
case applyForce: return new ApplyForce();
case dominoes: return new Dominos();
case chain: return new Chain();
default: break;
}
return nullptr;
}
static String getInstructions (int index)
{
switch (index)
{
case applyForce:
return String ("Keys:") + newLine + "Left: \'a\'" + newLine
+ "Right: \'d\'" + newLine + "Forward: \'w\'";
default:
break;
}
return {};
}
void checkKeys()
{
if (renderComponent.currentTest.get() == nullptr)
return;
checkKeyCode ('a');
checkKeyCode ('w');
checkKeyCode ('d');
}
void checkKeyCode (const int keyCode)
{
if (KeyPress::isKeyCurrentlyDown (keyCode))
renderComponent.currentTest->Keyboard ((unsigned char) keyCode);
}
void timerCallback() override
{
if (renderComponent.currentTest.get() == nullptr)
return;
if (isShowing())
grabKeyboardFocus();
checkKeys();
renderComponent.currentTest->m_world->Step (1.0f / 60.0f, 6, 2);
repaint();
}
void changeListenerCallback (ChangeBroadcaster* source) override
{
if (source == &testsListModel)
{
auto index = testsListBox.getSelectedRow();
renderComponent.currentTest.reset (createTest (index));
instructions.setText (getInstructions (index));
repaint();
}
}
void lookAndFeelChanged() override
{
instructions.applyFontToAllText (instructions.getFont());
}
static StringArray getTestsList()
{
return { "Add Pair Stress Test", "Apply Force", "Dominoes", "Chain" };
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Box2DDemo)
};

View File

@ -0,0 +1,24 @@
# ==============================================================================
#
# 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.
#
# ==============================================================================
_juce_add_pips()

View File

@ -0,0 +1,355 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: ChildProcessDemo
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Launches applications as child processes.
dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
juce_gui_basics
exporters: xcode_mac, vs2019, linux_make
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: Console
mainClass: ChildProcessDemo
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
#include "../Assets/DemoUtilities.h"
//==============================================================================
// This is a token that's used at both ends of our parent-child processes, to
// act as a unique token in the command line arguments.
static const char* demoCommandLineUID = "demoUID";
// A few quick utility functions to convert between raw data and ValueTrees
static ValueTree memoryBlockToValueTree (const MemoryBlock& mb)
{
return ValueTree::readFromData (mb.getData(), mb.getSize());
}
static MemoryBlock valueTreeToMemoryBlock (const ValueTree& v)
{
MemoryOutputStream mo;
v.writeToStream (mo);
return mo.getMemoryBlock();
}
static String valueTreeToString (const ValueTree& v)
{
if (auto xml = v.createXml())
return xml->toString (XmlElement::TextFormat().singleLine().withoutHeader());
return {};
}
//==============================================================================
class ChildProcessDemo : public Component,
private MessageListener
{
public:
ChildProcessDemo()
{
setOpaque (true);
addAndMakeVisible (launchButton);
launchButton.onClick = [this] { launchChildProcess(); };
addAndMakeVisible (pingButton);
pingButton.onClick = [this] { pingChildProcess(); };
addAndMakeVisible (killButton);
killButton.onClick = [this] { killChildProcess(); };
addAndMakeVisible (testResultsBox);
testResultsBox.setMultiLine (true);
testResultsBox.setFont ({ Font::getDefaultMonospacedFontName(), 12.0f, Font::plain });
logMessage (String ("This demo uses the ChildProcessMaster and ChildProcessSlave classes to launch and communicate "
"with a child process, sending messages in the form of serialised ValueTree objects.") + newLine);
setSize (500, 500);
}
~ChildProcessDemo() override
{
masterProcess.reset();
}
void paint (Graphics& g) override
{
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
}
void resized() override
{
auto area = getLocalBounds();
auto top = area.removeFromTop (40);
launchButton.setBounds (top.removeFromLeft (180).reduced (8));
pingButton .setBounds (top.removeFromLeft (180).reduced (8));
killButton .setBounds (top.removeFromLeft (180).reduced (8));
testResultsBox.setBounds (area.reduced (8));
}
// Appends a message to the textbox that's shown in the demo as the console
void logMessage (const String& message)
{
postMessage (new LogMessage (message));
}
// invoked by the 'launch' button.
void launchChildProcess()
{
if (masterProcess.get() == nullptr)
{
masterProcess.reset (new DemoMasterProcess (*this));
if (masterProcess->launchSlaveProcess (File::getSpecialLocation (File::currentExecutableFile), demoCommandLineUID))
logMessage ("Child process started");
}
}
// invoked by the 'ping' button.
void pingChildProcess()
{
if (masterProcess.get() != nullptr)
masterProcess->sendPingMessageToSlave();
else
logMessage ("Child process is not running!");
}
// invoked by the 'kill' button.
void killChildProcess()
{
if (masterProcess.get() != nullptr)
{
masterProcess.reset();
logMessage ("Child process killed");
}
}
//==============================================================================
// This class is used by the main process, acting as the master and receiving messages
// from the slave process.
class DemoMasterProcess : public ChildProcessMaster,
private DeletedAtShutdown
{
public:
DemoMasterProcess (ChildProcessDemo& d) : demo (d) {}
// This gets called when a message arrives from the slave process..
void handleMessageFromSlave (const MemoryBlock& mb) override
{
auto incomingMessage = memoryBlockToValueTree (mb);
demo.logMessage ("Received: " + valueTreeToString (incomingMessage));
}
// This gets called if the slave process dies.
void handleConnectionLost() override
{
demo.logMessage ("Connection lost to child process!");
demo.killChildProcess();
}
void sendPingMessageToSlave()
{
ValueTree message ("MESSAGE");
message.setProperty ("count", count++, nullptr);
demo.logMessage ("Sending: " + valueTreeToString (message));
sendMessageToSlave (valueTreeToMemoryBlock (message));
}
ChildProcessDemo& demo;
int count = 0;
};
//==============================================================================
std::unique_ptr<DemoMasterProcess> masterProcess;
private:
TextButton launchButton { "Launch Child Process" };
TextButton pingButton { "Send Ping" };
TextButton killButton { "Kill Child Process" };
TextEditor testResultsBox;
struct LogMessage : public Message
{
LogMessage (const String& m) : message (m) {}
String message;
};
void handleMessage (const Message& message) override
{
testResultsBox.moveCaretToEnd();
testResultsBox.insertTextAtCaret (static_cast<const LogMessage&> (message).message + newLine);
testResultsBox.moveCaretToEnd();
}
void lookAndFeelChanged() override
{
testResultsBox.applyFontToAllText (testResultsBox.getFont());
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessDemo)
};
//==============================================================================
/* This class gets instantiated in the child process, and receives messages from
the master process.
*/
class DemoSlaveProcess : public ChildProcessSlave,
private DeletedAtShutdown
{
public:
DemoSlaveProcess() {}
void handleMessageFromMaster (const MemoryBlock& mb) override
{
ValueTree incomingMessage (memoryBlockToValueTree (mb));
/* In this demo we're only expecting one type of message, which will contain a 'count' parameter -
we'll just increment that number and send back a new message containing the new number.
Obviously in a real app you'll probably want to look at the type of the message, and do
some more interesting behaviour.
*/
ValueTree reply ("REPLY");
reply.setProperty ("countPlusOne", static_cast<int> (incomingMessage["count"]) + 1, nullptr);
sendMessageToMaster (valueTreeToMemoryBlock (reply));
}
void handleConnectionMade() override
{
// This method is called when the connection is established, and in response, we'll just
// send off a message to say hello.
ValueTree reply ("HelloWorld");
sendMessageToMaster (valueTreeToMemoryBlock (reply));
}
/* If no pings are received from the master process for a number of seconds, then this will get invoked.
Typically you'll want to use this as a signal to kill the process as quickly as possible, as you
don't want to leave it hanging around as a zombie..
*/
void handleConnectionLost() override
{
JUCEApplication::quit();
}
};
//==============================================================================
/* The JUCEApplication::initialise method calls this function to allow the
child process to launch when the command line parameters indicate that we're
being asked to run as a child process..
*/
bool invokeChildProcessDemo (const String& commandLine)
{
std::unique_ptr<DemoSlaveProcess> slave (new DemoSlaveProcess());
if (slave->initialiseFromCommandLine (commandLine, demoCommandLineUID))
{
slave.release(); // allow the slave object to stay alive - it'll handle its own deletion.
return true;
}
return false;
}
#ifndef JUCE_DEMO_RUNNER
//==============================================================================
// As we need to modify the JUCEApplication::initialise method to launch the child process
// based on the command line parameters, we can't just use the normal auto-generated Main.cpp.
// Instead, we don't do anything in Main.cpp and create a JUCEApplication subclass here with
// the necessary modifications.
class Application : public JUCEApplication
{
public:
//==============================================================================
Application() {}
const String getApplicationName() override { return "ChildProcessDemo"; }
const String getApplicationVersion() override { return "1.0.0"; }
void initialise (const String& commandLine) override
{
// launches the child process if the command line parameters contain the demo UID
if (invokeChildProcessDemo (commandLine))
return;
mainWindow.reset (new MainWindow ("ChildProcessDemo", new ChildProcessDemo()));
}
void shutdown() override { mainWindow = nullptr; }
private:
class MainWindow : public DocumentWindow
{
public:
MainWindow (const String& name, Component* c) : DocumentWindow (name,
Desktop::getInstance().getDefaultLookAndFeel()
.findColour (ResizableWindow::backgroundColourId),
DocumentWindow::allButtons)
{
setUsingNativeTitleBar (true);
setContentOwned (c, true);
centreWithSize (getWidth(), getHeight());
setVisible (true);
}
void closeButtonPressed() override
{
JUCEApplication::getInstance()->systemRequestedQuit();
}
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow)
};
std::unique_ptr<MainWindow> mainWindow;
};
//==============================================================================
START_JUCE_APPLICATION (Application)
#endif

View File

@ -0,0 +1,271 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: CryptographyDemo
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Encrypts and decrypts data.
dependencies: juce_core, juce_cryptography, juce_data_structures, juce_events,
juce_graphics, juce_gui_basics
exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: Component
mainClass: CryptographyDemo
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
#include "../Assets/DemoUtilities.h"
//==============================================================================
class RSAComponent : public Component
{
public:
RSAComponent()
{
addAndMakeVisible (rsaGroup);
addAndMakeVisible (bitSize);
bitSize.setText (String (256));
bitSizeLabel.attachToComponent (&bitSize, true);
addAndMakeVisible (generateRSAButton);
generateRSAButton.onClick = [this] { createRSAKey(); };
addAndMakeVisible (rsaResultBox);
rsaResultBox.setReadOnly (true);
rsaResultBox.setMultiLine (true);
}
void resized() override
{
auto area = getLocalBounds();
rsaGroup.setBounds (area);
area.removeFromTop (10);
area.reduce (5, 5);
auto topArea = area.removeFromTop (34);
topArea.removeFromLeft (110);
bitSize.setBounds (topArea.removeFromLeft (topArea.getWidth() / 2).reduced (5));
generateRSAButton.setBounds (topArea.reduced (5));
rsaResultBox.setBounds (area.reduced (5));
}
private:
void createRSAKey()
{
auto bits = jlimit (32, 1024, bitSize.getText().getIntValue());
bitSize.setText (String (bits), dontSendNotification);
// Create a key-pair...
RSAKey publicKey, privateKey;
RSAKey::createKeyPair (publicKey, privateKey, bits);
// Test the new key on a piece of data...
BigInteger testValue;
testValue.parseString ("1234567890abcdef", 16);
auto encodedValue = testValue;
publicKey.applyToValue (encodedValue);
auto decodedValue = encodedValue;
privateKey.applyToValue (decodedValue);
// ..and show the results..
String message;
message << "Number of bits: " << bits << newLine
<< "Public Key: " << publicKey .toString() << newLine
<< "Private Key: " << privateKey.toString() << newLine
<< newLine
<< "Test input: " << testValue.toString (16) << newLine
<< "Encoded: " << encodedValue.toString (16) << newLine
<< "Decoded: " << decodedValue.toString (16) << newLine;
rsaResultBox.setText (message, false);
}
GroupComponent rsaGroup { {}, "RSA Encryption" };
TextButton generateRSAButton { "Generate RSA" };
Label bitSizeLabel { {}, "Num Bits to Use:" };
TextEditor bitSize, rsaResultBox;
void lookAndFeelChanged() override
{
rsaGroup.setColour (GroupComponent::outlineColourId,
getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::outline,
Colours::grey));
rsaGroup.setColour (GroupComponent::textColourId,
getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::defaultText,
Colours::white));
rsaResultBox.setColour (TextEditor::backgroundColourId,
getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::widgetBackground,
Colours::white.withAlpha (0.5f)));
bitSize.applyFontToAllText (bitSize.getFont());
rsaResultBox.applyFontToAllText (rsaResultBox.getFont());
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RSAComponent)
};
//==============================================================================
class HashesComponent : public Component
{
public:
HashesComponent()
{
addAndMakeVisible (hashGroup);
addAndMakeVisible (hashEntryBox);
hashEntryBox.setMultiLine (true);
hashEntryBox.setReturnKeyStartsNewLine (true);
hashEntryBox.setText ("Type some text in this box and the resulting MD5, SHA and Whirlpool hashes will update below");
auto updateHashes = [this]
{
auto text = hashEntryBox.getText();
updateMD5Result (text.toUTF8());
updateSHA256Result (text.toUTF8());
updateWhirlpoolResult (text.toUTF8());
};
hashEntryBox.onTextChange = updateHashes;
hashEntryBox.onReturnKey = updateHashes;
hashLabel1.attachToComponent (&hashEntryBox, true);
hashLabel2.attachToComponent (&md5Result, true);
hashLabel3.attachToComponent (&shaResult, true);
hashLabel4.attachToComponent (&whirlpoolResult, true);
addAndMakeVisible (md5Result);
addAndMakeVisible (shaResult);
addAndMakeVisible (whirlpoolResult);
updateHashes();
}
void updateMD5Result (CharPointer_UTF8 text)
{
md5Result.setText (MD5 (text).toHexString(), dontSendNotification);
}
void updateSHA256Result (CharPointer_UTF8 text)
{
shaResult.setText (SHA256 (text).toHexString(), dontSendNotification);
}
void updateWhirlpoolResult (CharPointer_UTF8 text)
{
whirlpoolResult.setText (Whirlpool (text).toHexString(), dontSendNotification);
}
void resized() override
{
auto area = getLocalBounds();
hashGroup.setBounds (area);
area.removeFromLeft (120);
area.removeFromTop (10);
area.reduce (5, 5);
whirlpoolResult.setBounds (area.removeFromBottom (34));
shaResult .setBounds (area.removeFromBottom (34));
md5Result .setBounds (area.removeFromBottom (34));
hashEntryBox .setBounds (area.reduced (5));
}
private:
GroupComponent hashGroup { {}, "Hashes" };
TextEditor hashEntryBox;
Label md5Result, shaResult, whirlpoolResult;
Label hashLabel1 { {}, "Text to Hash:" };
Label hashLabel2 { {}, "MD5 Result:" };
Label hashLabel3 { {}, "SHA Result:" };
Label hashLabel4 { {}, "Whirlpool Result:" };
void lookAndFeelChanged() override
{
hashGroup.setColour (GroupComponent::outlineColourId,
getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::outline,
Colours::grey));
hashGroup.setColour (GroupComponent::textColourId,
getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::defaultText,
Colours::white));
hashEntryBox.setColour (TextEditor::backgroundColourId,
getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::widgetBackground,
Colours::white.withAlpha (0.5f)));
hashEntryBox.applyFontToAllText (hashEntryBox.getFont());
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HashesComponent)
};
//==============================================================================
class CryptographyDemo : public Component
{
public:
CryptographyDemo()
{
addAndMakeVisible (rsaDemo);
addAndMakeVisible (hashDemo);
setSize (750, 750);
}
void paint (Graphics& g) override
{
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground,
Colour::greyLevel (0.4f)));
}
void resized() override
{
auto area = getLocalBounds();
rsaDemo .setBounds (area.removeFromTop (getHeight() / 2).reduced (5));
hashDemo.setBounds (area.reduced (5));
}
private:
RSAComponent rsaDemo;
HashesComponent hashDemo;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CryptographyDemo)
};

View File

@ -0,0 +1,599 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: InAppPurchasesDemo
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Showcases in-app purchases features. To run this demo you must enable the
"In-App Purchases Capability" option in the Projucer exporter.
dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
juce_audio_processors, juce_audio_utils, juce_core,
juce_cryptography, juce_data_structures, juce_events,
juce_graphics, juce_gui_basics, juce_gui_extra,
juce_product_unlocking
exporters: xcode_mac, xcode_iphone, androidstudio
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
JUCE_IN_APP_PURCHASES=1
type: Component
mainClass: InAppPurchasesDemo
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
#include "../Assets/DemoUtilities.h"
/*
To finish the setup of this demo, do the following in the Projucer project:
1. In the project settings, set the "Bundle Identifier" to com.rmsl.juceInAppPurchaseSample
2. In the Android exporter settings, change the following settings:
- "In-App Billing" - Enabled
- "Key Signing: key.store" - path to InAppPurchase.keystore file in examples/Assets/Signing
- "Key Signing: key.store.password" - amazingvoices
- "Key Signing: key-alias" - InAppPurchase
- "Key Signing: key.alias.password" - amazingvoices
3. Re-save the project
*/
//==============================================================================
class VoicePurchases : private InAppPurchases::Listener
{
public:
//==============================================================================
struct VoiceProduct
{
const char* identifier;
const char* humanReadable;
bool isPurchased, priceIsKnown, purchaseInProgress;
String purchasePrice;
};
//==============================================================================
VoicePurchases (AsyncUpdater& asyncUpdater)
: guiUpdater (asyncUpdater)
{
voiceProducts = Array<VoiceProduct>(
{ VoiceProduct {"robot", "Robot", true, true, false, "Free" },
VoiceProduct {"jules", "Jules", false, false, false, "Retrieving price..." },
VoiceProduct {"fabian", "Fabian", false, false, false, "Retrieving price..." },
VoiceProduct {"ed", "Ed", false, false, false, "Retrieving price..." },
VoiceProduct {"lukasz", "Lukasz", false, false, false, "Retrieving price..." },
VoiceProduct {"jb", "JB", false, false, false, "Retrieving price..." } });
}
~VoicePurchases() override
{
InAppPurchases::getInstance()->removeListener (this);
}
//==============================================================================
VoiceProduct getPurchase (int voiceIndex)
{
if (! havePurchasesBeenRestored)
{
havePurchasesBeenRestored = true;
InAppPurchases::getInstance()->addListener (this);
InAppPurchases::getInstance()->restoreProductsBoughtList (true);
}
return voiceProducts[voiceIndex];
}
void purchaseVoice (int voiceIndex)
{
if (havePricesBeenFetched && isPositiveAndBelow (voiceIndex, voiceProducts.size()))
{
auto& product = voiceProducts.getReference (voiceIndex);
if (! product.isPurchased)
{
purchaseInProgress = true;
product.purchaseInProgress = true;
InAppPurchases::getInstance()->purchaseProduct (product.identifier);
guiUpdater.triggerAsyncUpdate();
}
}
}
StringArray getVoiceNames() const
{
StringArray names;
for (auto& voiceProduct : voiceProducts)
names.add (voiceProduct.humanReadable);
return names;
}
bool isPurchaseInProgress() const noexcept { return purchaseInProgress; }
private:
//==============================================================================
void productsInfoReturned (const Array<InAppPurchases::Product>& products) override
{
if (! InAppPurchases::getInstance()->isInAppPurchasesSupported())
{
for (auto idx = 1; idx < voiceProducts.size(); ++idx)
{
auto& voiceProduct = voiceProducts.getReference (idx);
voiceProduct.isPurchased = false;
voiceProduct.priceIsKnown = false;
voiceProduct.purchasePrice = "In-App purchases unavailable";
}
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
"In-app purchase is unavailable!",
"In-App purchases are not available. This either means you are trying "
"to use IAP on a platform that does not support IAP or you haven't setup "
"your app correctly to work with IAP.",
"OK");
}
else
{
for (auto product : products)
{
auto idx = findVoiceIndexFromIdentifier (product.identifier);
if (isPositiveAndBelow (idx, voiceProducts.size()))
{
auto& voiceProduct = voiceProducts.getReference (idx);
voiceProduct.priceIsKnown = true;
voiceProduct.purchasePrice = product.price;
}
}
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
"Your credit card will be charged!",
"You are running the sample code for JUCE In-App purchases. "
"Although this is only sample code, it will still CHARGE YOUR CREDIT CARD!",
"Understood!");
}
guiUpdater.triggerAsyncUpdate();
}
void productPurchaseFinished (const PurchaseInfo& info, bool success, const String&) override
{
purchaseInProgress = false;
auto idx = findVoiceIndexFromIdentifier (info.purchase.productId);
if (isPositiveAndBelow (idx, voiceProducts.size()))
{
auto& voiceProduct = voiceProducts.getReference (idx);
voiceProduct.isPurchased = success;
voiceProduct.purchaseInProgress = false;
}
else
{
// On failure Play Store will not tell us which purchase failed
for (auto& voiceProduct : voiceProducts)
voiceProduct.purchaseInProgress = false;
}
guiUpdater.triggerAsyncUpdate();
}
void purchasesListRestored (const Array<PurchaseInfo>& infos, bool success, const String&) override
{
if (success)
{
for (auto& info : infos)
{
auto idx = findVoiceIndexFromIdentifier (info.purchase.productId);
if (isPositiveAndBelow (idx, voiceProducts.size()))
{
auto& voiceProduct = voiceProducts.getReference (idx);
voiceProduct.isPurchased = true;
}
}
guiUpdater.triggerAsyncUpdate();
}
if (! havePricesBeenFetched)
{
havePricesBeenFetched = true;
StringArray identifiers;
for (auto& voiceProduct : voiceProducts)
identifiers.add (voiceProduct.identifier);
InAppPurchases::getInstance()->getProductsInformation (identifiers);
}
}
//==============================================================================
int findVoiceIndexFromIdentifier (String identifier) const
{
identifier = identifier.toLowerCase();
for (auto i = 0; i < voiceProducts.size(); ++i)
if (String (voiceProducts.getReference (i).identifier) == identifier)
return i;
return -1;
}
//==============================================================================
AsyncUpdater& guiUpdater;
bool havePurchasesBeenRestored = false, havePricesBeenFetched = false, purchaseInProgress = false;
Array<VoiceProduct> voiceProducts;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VoicePurchases)
};
//==============================================================================
class PhraseModel : public ListBoxModel
{
public:
PhraseModel() {}
int getNumRows() override { return phrases.size(); }
void paintListBoxItem (int row, Graphics& g, int w, int h, bool isSelected) override
{
Rectangle<int> r (0, 0, w, h);
auto& lf = Desktop::getInstance().getDefaultLookAndFeel();
g.setColour (lf.findColour (isSelected ? (int) TextEditor::highlightColourId : (int) ListBox::backgroundColourId));
g.fillRect (r);
g.setColour (lf.findColour (ListBox::textColourId));
g.setFont (18);
String phrase = (isPositiveAndBelow (row, phrases.size()) ? phrases[row] : String{});
g.drawText (phrase, 10, 0, w, h, Justification::centredLeft);
}
private:
StringArray phrases {"I love JUCE!", "The five dimensions of touch", "Make it fast!"};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PhraseModel)
};
//==============================================================================
class VoiceModel : public ListBoxModel
{
public:
//==============================================================================
class VoiceRow : public Component,
private Timer
{
public:
VoiceRow (VoicePurchases& voicePurchases) : purchases (voicePurchases)
{
addAndMakeVisible (nameLabel);
addAndMakeVisible (purchaseButton);
addAndMakeVisible (priceLabel);
purchaseButton.onClick = [this] { clickPurchase(); };
voices = purchases.getVoiceNames();
setSize (600, 33);
}
void paint (Graphics& g) override
{
auto r = getLocalBounds().reduced (4);
{
auto voiceIconBounds = r.removeFromLeft (r.getHeight());
g.setColour (Colours::black);
g.drawRect (voiceIconBounds);
voiceIconBounds.reduce (1, 1);
g.setColour (hasBeenPurchased ? Colours::white : Colours::grey);
g.fillRect (voiceIconBounds);
g.drawImage (avatar, voiceIconBounds.toFloat());
if (! hasBeenPurchased)
{
g.setColour (Colours::white.withAlpha (0.8f));
g.fillRect (voiceIconBounds);
if (purchaseInProgress)
getLookAndFeel().drawSpinningWaitAnimation (g, Colours::darkgrey,
voiceIconBounds.getX(),
voiceIconBounds.getY(),
voiceIconBounds.getWidth(),
voiceIconBounds.getHeight());
}
}
}
void resized() override
{
auto r = getLocalBounds().reduced (4 + 8, 4);
auto h = r.getHeight();
auto w = static_cast<int> (h * 1.5);
r.removeFromLeft (h);
purchaseButton.setBounds (r.removeFromRight (w).withSizeKeepingCentre (w, h / 2));
nameLabel.setBounds (r.removeFromTop (18));
priceLabel.setBounds (r.removeFromTop (18));
}
void update (int rowNumber, bool rowIsSelected)
{
isSelected = rowIsSelected;
rowSelected = rowNumber;
if (isPositiveAndBelow (rowNumber, voices.size()))
{
auto imageResourceName = voices[rowNumber] + ".png";
nameLabel.setText (voices[rowNumber], NotificationType::dontSendNotification);
auto purchase = purchases.getPurchase (rowNumber);
hasBeenPurchased = purchase.isPurchased;
purchaseInProgress = purchase.purchaseInProgress;
if (purchaseInProgress)
startTimer (1000 / 50);
else
stopTimer();
nameLabel.setFont (Font (16).withStyle (Font::bold | (hasBeenPurchased ? 0 : Font::italic)));
nameLabel.setColour (Label::textColourId, hasBeenPurchased ? Colours::white : Colours::grey);
priceLabel.setFont (Font (10).withStyle (purchase.priceIsKnown ? 0 : Font::italic));
priceLabel.setColour (Label::textColourId, hasBeenPurchased ? Colours::white : Colours::grey);
priceLabel.setText (purchase.purchasePrice, NotificationType::dontSendNotification);
if (rowNumber == 0)
{
purchaseButton.setButtonText ("Internal");
purchaseButton.setEnabled (false);
}
else
{
purchaseButton.setButtonText (hasBeenPurchased ? "Purchased" : "Purchase");
purchaseButton.setEnabled (! hasBeenPurchased && purchase.priceIsKnown);
}
setInterceptsMouseClicks (! hasBeenPurchased, ! hasBeenPurchased);
if (auto fileStream = createAssetInputStream (String ("Purchases/" + String (imageResourceName)).toRawUTF8()))
avatar = PNGImageFormat().decodeImage (*fileStream);
}
}
private:
//==============================================================================
void clickPurchase()
{
if (rowSelected >= 0)
{
if (! hasBeenPurchased)
{
purchases.purchaseVoice (rowSelected);
purchaseInProgress = true;
startTimer (1000 / 50);
}
}
}
void timerCallback() override { repaint(); }
//==============================================================================
bool isSelected = false, hasBeenPurchased = false, purchaseInProgress = false;
int rowSelected = -1;
Image avatar;
StringArray voices;
VoicePurchases& purchases;
Label nameLabel, priceLabel;
TextButton purchaseButton {"Purchase"};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VoiceRow)
};
//==============================================================================
VoiceModel (VoicePurchases& voicePurchases) : purchases (voicePurchases)
{
voiceProducts = purchases.getVoiceNames();
}
int getNumRows() override { return voiceProducts.size(); }
Component* refreshComponentForRow (int row, bool selected, Component* existing) override
{
if (isPositiveAndBelow (row, voiceProducts.size()))
{
if (existing == nullptr)
existing = new VoiceRow (purchases);
if (auto* voiceRow = dynamic_cast<VoiceRow*> (existing))
voiceRow->update (row, selected);
return existing;
}
return nullptr;
}
void paintListBoxItem (int, Graphics& g, int w, int h, bool isSelected) override
{
auto r = Rectangle<int> (0, 0, w, h).reduced (4);
auto& lf = Desktop::getInstance().getDefaultLookAndFeel();
g.setColour (lf.findColour (isSelected ? (int) TextEditor::highlightColourId : (int) ListBox::backgroundColourId));
g.fillRect (r);
}
private:
StringArray voiceProducts;
VoicePurchases& purchases;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VoiceModel)
};
//==============================================================================
class InAppPurchasesDemo : public Component,
private AsyncUpdater
{
public:
InAppPurchasesDemo()
{
Desktop::getInstance().getDefaultLookAndFeel().setUsingNativeAlertWindows (true);
dm.addAudioCallback (&player);
dm.initialiseWithDefaultDevices (0, 2);
setOpaque (true);
phraseListBox.setModel (phraseModel.get());
voiceListBox .setModel (voiceModel.get());
phraseListBox.setRowHeight (33);
phraseListBox.selectRow (0);
phraseListBox.updateContent();
voiceListBox.setRowHeight (66);
voiceListBox.selectRow (0);
voiceListBox.updateContent();
voiceListBox.getViewport()->setScrollOnDragEnabled (true);
addAndMakeVisible (phraseLabel);
addAndMakeVisible (phraseListBox);
addAndMakeVisible (playStopButton);
addAndMakeVisible (voiceLabel);
addAndMakeVisible (voiceListBox);
playStopButton.onClick = [this] { playStopPhrase(); };
soundNames = purchases.getVoiceNames();
#if JUCE_ANDROID || JUCE_IOS
auto screenBounds = Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea;
setSize (screenBounds.getWidth(), screenBounds.getHeight());
#else
setSize (800, 600);
#endif
}
~InAppPurchasesDemo() override
{
dm.closeAudioDevice();
dm.removeAudioCallback (&player);
}
private:
//==============================================================================
void handleAsyncUpdate() override
{
voiceListBox.updateContent();
voiceListBox.setEnabled (! purchases.isPurchaseInProgress());
voiceListBox.repaint();
}
//==============================================================================
void resized() override
{
auto r = getLocalBounds().reduced (20);
{
auto phraseArea = r.removeFromTop (r.getHeight() / 2);
phraseLabel .setBounds (phraseArea.removeFromTop (36).reduced (0, 10));
playStopButton.setBounds (phraseArea.removeFromBottom (50).reduced (0, 10));
phraseListBox .setBounds (phraseArea);
}
{
auto voiceArea = r;
voiceLabel .setBounds (voiceArea.removeFromTop (36).reduced (0, 10));
voiceListBox.setBounds (voiceArea);
}
}
void paint (Graphics& g) override
{
g.fillAll (Desktop::getInstance().getDefaultLookAndFeel()
.findColour (ResizableWindow::backgroundColourId));
}
//==============================================================================
void playStopPhrase()
{
auto idx = voiceListBox.getSelectedRow();
if (isPositiveAndBelow (idx, soundNames.size()))
{
auto assetName = "Purchases/" + soundNames[idx] + String (phraseListBox.getSelectedRow()) + ".ogg";
if (auto fileStream = createAssetInputStream (assetName.toRawUTF8()))
{
currentPhraseData.reset();
fileStream->readIntoMemoryBlock (currentPhraseData);
player.play (currentPhraseData.getData(), currentPhraseData.getSize());
}
}
}
//==============================================================================
StringArray soundNames;
Label phraseLabel { "phraseLabel", NEEDS_TRANS ("Phrases:") };
ListBox phraseListBox { "phraseListBox" };
std::unique_ptr<ListBoxModel> phraseModel { new PhraseModel() };
TextButton playStopButton { "Play" };
SoundPlayer player;
VoicePurchases purchases { *this };
AudioDeviceManager dm;
Label voiceLabel { "voiceLabel", NEEDS_TRANS ("Voices:") };
ListBox voiceListBox { "voiceListBox" };
std::unique_ptr<VoiceModel> voiceModel { new VoiceModel (purchases) };
MemoryBlock currentPhraseData;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InAppPurchasesDemo)
};

View File

@ -0,0 +1,185 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: JavaScriptDemo
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Showcases JavaScript features.
dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
juce_gui_basics, juce_gui_extra
exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: Component
mainClass: JavaScriptDemo
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
#include "../Assets/DemoUtilities.h"
//==============================================================================
class JavaScriptDemo : public Component,
private CodeDocument::Listener,
private Timer
{
public:
JavaScriptDemo()
{
setOpaque (true);
editor.reset (new CodeEditorComponent (codeDocument, nullptr));
addAndMakeVisible (editor.get());
editor->setFont ({ Font::getDefaultMonospacedFontName(), 14.0f, Font::plain });
editor->setTabSize (4, true);
outputDisplay.setMultiLine (true);
outputDisplay.setReadOnly (true);
outputDisplay.setCaretVisible (false);
outputDisplay.setFont ({ Font::getDefaultMonospacedFontName(), 14.0f, Font::plain });
addAndMakeVisible (outputDisplay);
codeDocument.addListener (this);
editor->loadContent (
"/*\n"
" Javascript! In this simple demo, the native\n"
" code provides an object called \'Demo\' which\n"
" has a method \'print\' that writes to the\n"
" console below...\n"
"*/\n"
"\n"
"Demo.print (\"Hello World in JUCE + Javascript!\");\n"
"Demo.print (\"\");\n"
"\n"
"function factorial (n)\n"
"{\n"
" var total = 1;\n"
" while (n > 0)\n"
" total = total * n--;\n"
" return total;\n"
"}\n"
"\n"
"for (var i = 1; i < 10; ++i)\n"
" Demo.print (\"Factorial of \" + i \n"
" + \" = \" + factorial (i));\n");
setSize (600, 750);
}
void runScript()
{
outputDisplay.clear();
JavascriptEngine engine;
engine.maximumExecutionTime = RelativeTime::seconds (5);
engine.registerNativeObject ("Demo", new DemoClass (*this));
auto startTime = Time::getMillisecondCounterHiRes();
auto result = engine.execute (codeDocument.getAllContent());
auto elapsedMs = Time::getMillisecondCounterHiRes() - startTime;
if (result.failed())
outputDisplay.setText (result.getErrorMessage());
else
outputDisplay.insertTextAtCaret ("\n(Execution time: " + String (elapsedMs, 2) + " milliseconds)");
}
void consoleOutput (const String& message)
{
outputDisplay.moveCaretToEnd();
outputDisplay.insertTextAtCaret (message + newLine);
}
//==============================================================================
// This class is used by the script, and provides methods that the JS can call.
struct DemoClass : public DynamicObject
{
DemoClass (JavaScriptDemo& demo) : owner (demo)
{
setMethod ("print", print);
}
static Identifier getClassName() { return "Demo"; }
static var print (const var::NativeFunctionArgs& args)
{
if (args.numArguments > 0)
if (auto* thisObject = dynamic_cast<DemoClass*> (args.thisObject.getObject()))
thisObject->owner.consoleOutput (args.arguments[0].toString());
return var::undefined();
}
JavaScriptDemo& owner;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DemoClass)
};
void paint (Graphics& g) override
{
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
}
private:
CodeDocument codeDocument;
std::unique_ptr<CodeEditorComponent> editor;
TextEditor outputDisplay;
void codeDocumentTextInserted (const String&, int) override { startTimer (300); }
void codeDocumentTextDeleted (int, int) override { startTimer (300); }
void timerCallback() override
{
stopTimer();
runScript();
}
void resized() override
{
auto r = getLocalBounds().reduced (8);
editor->setBounds (r.removeFromTop (proportionOfHeight (0.6f)));
outputDisplay.setBounds (r.withTrimmedTop (8));
}
void lookAndFeelChanged() override
{
outputDisplay.applyFontToAllText (outputDisplay.getFont());
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JavaScriptDemo)
};

View File

@ -0,0 +1,132 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: LiveConstantDemo
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Demonstrates the live constant macro.
dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
juce_gui_basics, juce_gui_extra
exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: Component
mainClass: LiveConstantDemo
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
#include "../Assets/DemoUtilities.h"
//==============================================================================
struct LiveConstantDemoComponent : public Component
{
LiveConstantDemoComponent() {}
void paint (Graphics& g) override
{
g.fillAll (JUCE_LIVE_CONSTANT (Colour (0xffe5e7a7)));
g.setColour (JUCE_LIVE_CONSTANT (Colours::red.withAlpha (0.2f)));
auto blockWidth = JUCE_LIVE_CONSTANT (0x120);
auto blockHeight = JUCE_LIVE_CONSTANT (200);
g.fillRect ((getWidth() - blockWidth) / 2, (getHeight() - blockHeight) / 2, blockWidth, blockHeight);
auto fontColour = JUCE_LIVE_CONSTANT (Colour (0xff000a55));
auto fontSize = JUCE_LIVE_CONSTANT (30.0f);
g.setColour (fontColour);
g.setFont (fontSize);
g.drawFittedText (getDemoText(), getLocalBounds(), Justification::centred, 2);
}
static String getDemoText()
{
return JUCE_LIVE_CONSTANT ("Hello world!");
}
};
//==============================================================================
class LiveConstantDemo : public Component
{
public:
LiveConstantDemo()
{
descriptionLabel.setMinimumHorizontalScale (1.0f);
descriptionLabel.setText ("This demonstrates the JUCE_LIVE_CONSTANT macro, which allows you to quickly "
"adjust primitive values at runtime by just wrapping them in a macro.\n\n"
"To understand what's going on in this demo, you should have a look at the "
"LiveConstantDemoComponent class, where you can see the code that's invoking the demo below...",
dontSendNotification);
addAndMakeVisible (descriptionLabel);
addAndMakeVisible (startButton);
addChildComponent (demoComp);
startButton.onClick = [this] { start(); };
setSize (500, 500);
}
void paint (Graphics& g) override
{
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
}
void resized() override
{
auto r = getLocalBounds().reduced (10);
descriptionLabel.setBounds (r.removeFromTop (200));
startButton .setBounds (r.removeFromTop (22).removeFromLeft (250));
demoComp .setBounds (r.withTrimmedTop (10));
}
void start()
{
startButton.setVisible (false);
demoComp .setVisible (true);
descriptionLabel.setText ("Tweak some of the colours and values in the pop-up window to see what "
"the effect of your changes would be on the component below...",
dontSendNotification);
}
private:
Label descriptionLabel;
TextButton startButton { "Begin Demo" };
LiveConstantDemoComponent demoComp;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LiveConstantDemo)
};

View File

@ -0,0 +1,329 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: MultithreadingDemo
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Demonstrates multi-threading.
dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
juce_gui_basics
exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: Component
mainClass: MultithreadingDemo
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
#include "../Assets/DemoUtilities.h"
//==============================================================================
class BouncingBall : private ComponentListener
{
public:
BouncingBall (Component& comp)
: containerComponent (comp)
{
containerComponent.addComponentListener (this);
auto speed = 5.0f; // give each ball a fixed speed so we can
// see the effects of thread priority on how fast
// they actually go.
auto angle = Random::getSystemRandom().nextFloat() * MathConstants<float>::twoPi;
dx = std::sin (angle) * speed;
dy = std::cos (angle) * speed;
colour = Colour ((juce::uint32) Random::getSystemRandom().nextInt())
.withAlpha (0.5f)
.withBrightness (0.7f);
componentMovedOrResized (containerComponent, true, true);
x = Random::getSystemRandom().nextFloat() * parentWidth;
y = Random::getSystemRandom().nextFloat() * parentHeight;
}
~BouncingBall() override
{
containerComponent.removeComponentListener (this);
}
// This will be called from the message thread
void draw (Graphics& g)
{
const ScopedLock lock (drawing);
g.setColour (colour);
g.fillEllipse (x, y, size, size);
g.setColour (Colours::black);
g.setFont (10.0f);
g.drawText (String::toHexString ((int64) threadId), Rectangle<float> (x, y, size, size), Justification::centred, false);
}
void moveBall()
{
const ScopedLock lock (drawing);
threadId = Thread::getCurrentThreadId(); // this is so the component can print the thread ID inside the ball
x += dx;
y += dy;
if (x < 0)
dx = std::abs (dx);
if (x > parentWidth)
dx = -std::abs (dx);
if (y < 0)
dy = std::abs (dy);
if (y > parentHeight)
dy = -std::abs (dy);
}
private:
void componentMovedOrResized (Component& comp, bool, bool) override
{
const ScopedLock lock (drawing);
parentWidth = (float) comp.getWidth() - size;
parentHeight = (float) comp.getHeight() - size;
}
float x = 0.0f, y = 0.0f,
size = Random::getSystemRandom().nextFloat() * 30.0f + 30.0f,
dx = 0.0f, dy = 0.0f,
parentWidth = 50.0f, parentHeight = 50.0f;
Colour colour;
Thread::ThreadID threadId = {};
CriticalSection drawing;
Component& containerComponent;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BouncingBall)
};
//==============================================================================
class DemoThread : public BouncingBall,
public Thread
{
public:
DemoThread (Component& containerComp)
: BouncingBall (containerComp),
Thread ("JUCE Demo Thread")
{
// give the threads a random priority, so some will move more
// smoothly than others..
startThread (Random::getSystemRandom().nextInt (3) + 3);
}
~DemoThread() override
{
// allow the thread 2 seconds to stop cleanly - should be plenty of time.
stopThread (2000);
}
void run() override
{
// this is the code that runs this thread - we'll loop continuously,
// updating the coordinates of our blob.
// threadShouldExit() returns true when the stopThread() method has been
// called, so we should check it often, and exit as soon as it gets flagged.
while (! threadShouldExit())
{
// sleep a bit so the threads don't all grind the CPU to a halt..
wait (interval);
// because this is a background thread, we mustn't do any UI work without
// first grabbing a MessageManagerLock..
const MessageManagerLock mml (Thread::getCurrentThread());
if (! mml.lockWasGained()) // if something is trying to kill this job, the lock
return; // will fail, in which case we'd better return..
// now we've got the UI thread locked, we can mess about with the components
moveBall();
}
}
private:
int interval = Random::getSystemRandom().nextInt (50) + 6;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DemoThread)
};
//==============================================================================
class DemoThreadPoolJob : public BouncingBall,
public ThreadPoolJob
{
public:
DemoThreadPoolJob (Component& containerComp)
: BouncingBall (containerComp),
ThreadPoolJob ("Demo Threadpool Job")
{}
JobStatus runJob() override
{
// this is the code that runs this job. It'll be repeatedly called until we return
// jobHasFinished instead of jobNeedsRunningAgain.
Thread::sleep (30);
// because this is a background thread, we mustn't do any UI work without
// first grabbing a MessageManagerLock..
const MessageManagerLock mml (this);
// before moving the ball, we need to check whether the lock was actually gained, because
// if something is trying to stop this job, it will have failed..
if (mml.lockWasGained())
moveBall();
return jobNeedsRunningAgain;
}
void removedFromQueue()
{
// This is called to tell us that our job has been removed from the pool.
// In this case there's no need to do anything here.
}
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DemoThreadPoolJob)
};
//==============================================================================
class MultithreadingDemo : public Component,
private Timer
{
public:
//==============================================================================
MultithreadingDemo()
{
setOpaque (true);
addAndMakeVisible (controlButton);
controlButton.changeWidthToFitText (24);
controlButton.setTopLeftPosition (20, 20);
controlButton.setTriggeredOnMouseDown (true);
controlButton.setAlwaysOnTop (true);
controlButton.onClick = [this] { showMenu(); };
setSize (500, 500);
resetAllBalls();
startTimerHz (60);
}
~MultithreadingDemo() override
{
pool.removeAllJobs (true, 2000);
}
void resetAllBalls()
{
pool.removeAllJobs (true, 4000);
balls.clear();
for (int i = 0; i < 5; ++i)
addABall();
}
void paint (Graphics& g) override
{
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
for (auto* ball : balls)
ball->draw (g);
}
private:
//==============================================================================
void setUsingPool (bool usePool)
{
isUsingPool = usePool;
resetAllBalls();
}
void addABall()
{
if (isUsingPool)
{
auto newBall = std::make_unique<DemoThreadPoolJob> (*this);
pool.addJob (newBall.get(), false);
balls.add (newBall.release());
}
else
{
balls.add (new DemoThread (*this));
}
}
void timerCallback() override
{
repaint();
}
void showMenu()
{
PopupMenu m;
m.addItem (1, "Use one thread per ball", true, ! isUsingPool);
m.addItem (2, "Use a thread pool", true, isUsingPool);
m.showMenuAsync (PopupMenu::Options().withTargetComponent (controlButton),
ModalCallbackFunction::forComponent (menuItemChosenCallback, this));
}
static void menuItemChosenCallback (int result, MultithreadingDemo* demoComponent)
{
if (result != 0 && demoComponent != nullptr)
demoComponent->setUsingPool (result == 2);
}
//==============================================================================
ThreadPool pool { 3 };
TextButton controlButton { "Thread type" };
bool isUsingPool = false;
OwnedArray<BouncingBall> balls;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultithreadingDemo)
};

View File

@ -0,0 +1,137 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: NetworkingDemo
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Showcases networking features.
dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
juce_gui_basics, juce_gui_extra
exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: Component
mainClass: NetworkingDemo
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
#include "../Assets/DemoUtilities.h"
//==============================================================================
class NetworkingDemo : public Component,
private Thread
{
public:
NetworkingDemo()
: Thread ("Network Demo")
{
setOpaque (true);
addAndMakeVisible (urlBox);
urlBox.setText ("https://www.google.com");
urlBox.onReturnKey = [this] { fetchButton.triggerClick(); };
addAndMakeVisible (fetchButton);
fetchButton.onClick = [this] { startThread(); };
addAndMakeVisible (resultsBox);
setSize (500, 500);
}
void paint (Graphics& g) override
{
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
}
void resized() override
{
auto area = getLocalBounds();
{
auto topArea = area.removeFromTop (40);
fetchButton.setBounds (topArea.removeFromRight (180).reduced (8));
urlBox .setBounds (topArea.reduced (8));
}
resultsBox.setBounds (area.reduced (8));
}
void run() override
{
auto result = getResultText (urlBox.getText());
MessageManagerLock mml (this);
if (mml.lockWasGained())
resultsBox.loadContent (result);
}
String getResultText (const URL& url)
{
StringPairArray responseHeaders;
int statusCode = 0;
if (auto stream = url.createInputStream (URL::InputStreamOptions (URL::ParameterHandling::inAddress)
.withConnectionTimeoutMs(10000)
.withResponseHeaders (&responseHeaders)
.withStatusCode (&statusCode)))
{
return (statusCode != 0 ? "Status code: " + String (statusCode) + newLine : String())
+ "Response headers: " + newLine
+ responseHeaders.getDescription() + newLine
+ "----------------------------------------------------" + newLine
+ stream->readEntireStreamAsString();
}
if (statusCode != 0)
return "Failed to connect, status code = " + String (statusCode);
return "Failed to connect!";
}
private:
TextEditor urlBox;
TextButton fetchButton { "Download URL Contents" };
CodeDocument resultsDocument;
CodeEditorComponent resultsBox { resultsDocument, nullptr };
void lookAndFeelChanged() override
{
urlBox.applyFontToAllText (urlBox.getFont());
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NetworkingDemo)
};

494
deps/juce/examples/Utilities/OSCDemo.h vendored Normal file
View File

@ -0,0 +1,494 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: OSCDemo
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Application using the OSC protocol.
dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
juce_gui_basics, juce_osc
exporters: xcode_mac, vs2019, linux_make
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: Component
mainClass: OSCDemo
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
//==============================================================================
class OSCLogListBox : public ListBox,
private ListBoxModel,
private AsyncUpdater
{
public:
OSCLogListBox()
{
setModel (this);
}
~OSCLogListBox() override = default;
//==============================================================================
int getNumRows() override
{
return oscLogList.size();
}
//==============================================================================
void paintListBoxItem (int row, Graphics& g, int width, int height, bool rowIsSelected) override
{
ignoreUnused (rowIsSelected);
if (isPositiveAndBelow (row, oscLogList.size()))
{
g.setColour (Colours::white);
g.drawText (oscLogList[row],
Rectangle<int> (width, height).reduced (4, 0),
Justification::centredLeft, true);
}
}
//==============================================================================
void addOSCMessage (const OSCMessage& message, int level = 0)
{
oscLogList.add (getIndentationString (level)
+ "- osc message, address = '"
+ message.getAddressPattern().toString()
+ "', "
+ String (message.size())
+ " argument(s)");
if (! message.isEmpty())
{
for (auto& arg : message)
addOSCMessageArgument (arg, level + 1);
}
triggerAsyncUpdate();
}
//==============================================================================
void addOSCBundle (const OSCBundle& bundle, int level = 0)
{
OSCTimeTag timeTag = bundle.getTimeTag();
oscLogList.add (getIndentationString (level)
+ "- osc bundle, time tag = "
+ timeTag.toTime().toString (true, true, true, true));
for (auto& element : bundle)
{
if (element.isMessage())
addOSCMessage (element.getMessage(), level + 1);
else if (element.isBundle())
addOSCBundle (element.getBundle(), level + 1);
}
triggerAsyncUpdate();
}
//==============================================================================
void addOSCMessageArgument (const OSCArgument& arg, int level)
{
String typeAsString;
String valueAsString;
if (arg.isFloat32())
{
typeAsString = "float32";
valueAsString = String (arg.getFloat32());
}
else if (arg.isInt32())
{
typeAsString = "int32";
valueAsString = String (arg.getInt32());
}
else if (arg.isString())
{
typeAsString = "string";
valueAsString = arg.getString();
}
else if (arg.isBlob())
{
typeAsString = "blob";
auto& blob = arg.getBlob();
valueAsString = String::fromUTF8 ((const char*) blob.getData(), (int) blob.getSize());
}
else
{
typeAsString = "(unknown)";
}
oscLogList.add (getIndentationString (level + 1) + "- " + typeAsString.paddedRight(' ', 12) + valueAsString);
}
//==============================================================================
void addInvalidOSCPacket (const char* /* data */, int dataSize)
{
oscLogList.add ("- (" + String(dataSize) + "bytes with invalid format)");
}
//==============================================================================
void clear()
{
oscLogList.clear();
triggerAsyncUpdate();
}
//==============================================================================
void handleAsyncUpdate() override
{
updateContent();
scrollToEnsureRowIsOnscreen (oscLogList.size() - 1);
repaint();
}
private:
static String getIndentationString (int level)
{
return String().paddedRight (' ', 2 * level);
}
//==============================================================================
StringArray oscLogList;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSCLogListBox)
};
//==============================================================================
class OSCSenderDemo : public Component
{
public:
OSCSenderDemo()
{
addAndMakeVisible (senderLabel);
senderLabel.attachToComponent (&rotaryKnob, false);
rotaryKnob.setRange (0.0, 1.0);
rotaryKnob.setSliderStyle (Slider::RotaryVerticalDrag);
rotaryKnob.setTextBoxStyle (Slider::TextBoxBelow, true, 150, 25);
rotaryKnob.setBounds (50, 50, 180, 180);
addAndMakeVisible (rotaryKnob);
rotaryKnob.onValueChange = [this]
{
// create and send an OSC message with an address and a float value:
if (! sender1.send ("/juce/rotaryknob", (float) rotaryKnob.getValue()))
showConnectionErrorMessage ("Error: could not send OSC message.");
if (! sender2.send ("/juce/rotaryknob", (float) rotaryKnob.getValue()))
showConnectionErrorMessage ("Error: could not send OSC message.");
};
// specify here where to send OSC messages to: host URL and UDP port number
if (! sender1.connect ("127.0.0.1", 9001))
showConnectionErrorMessage ("Error: could not connect to UDP port 9001.");
if (! sender2.connect ("127.0.0.1", 9002))
showConnectionErrorMessage ("Error: could not connect to UDP port 9002.");
}
private:
//==============================================================================
void showConnectionErrorMessage (const String& messageText)
{
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
"Connection error",
messageText,
"OK");
}
//==============================================================================
Slider rotaryKnob;
OSCSender sender1, sender2;
Label senderLabel { {}, "Sender" };
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSCSenderDemo)
};
//==============================================================================
class OSCReceiverDemo : public Component,
private OSCReceiver,
private OSCReceiver::ListenerWithOSCAddress<OSCReceiver::MessageLoopCallback>
{
public:
//==============================================================================
OSCReceiverDemo()
{
addAndMakeVisible (receiverLabel);
receiverLabel.attachToComponent (&rotaryKnob, false);
rotaryKnob.setRange (0.0, 1.0);
rotaryKnob.setSliderStyle (Slider::RotaryVerticalDrag);
rotaryKnob.setTextBoxStyle (Slider::TextBoxBelow, true, 150, 25);
rotaryKnob.setBounds (50, 50, 180, 180);
rotaryKnob.setInterceptsMouseClicks (false, false);
addAndMakeVisible (rotaryKnob);
// specify here on which UDP port number to receive incoming OSC messages
if (! connect (9001))
showConnectionErrorMessage ("Error: could not connect to UDP port 9001.");
// tell the component to listen for OSC messages matching this address:
addListener (this, "/juce/rotaryknob");
}
private:
//==============================================================================
void oscMessageReceived (const OSCMessage& message) override
{
if (message.size() == 1 && message[0].isFloat32())
rotaryKnob.setValue (jlimit (0.0f, 10.0f, message[0].getFloat32()));
}
void showConnectionErrorMessage (const String& messageText)
{
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
"Connection error",
messageText,
"OK");
}
//==============================================================================
Slider rotaryKnob;
Label receiverLabel { {}, "Receiver" };
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSCReceiverDemo)
};
//==============================================================================
class OSCMonitorDemo : public Component,
private OSCReceiver::Listener<OSCReceiver::MessageLoopCallback>
{
public:
//==============================================================================
OSCMonitorDemo()
{
portNumberLabel.setBounds (10, 18, 130, 25);
addAndMakeVisible (portNumberLabel);
portNumberField.setEditable (true, true, true);
portNumberField.setBounds (140, 18, 50, 25);
addAndMakeVisible (portNumberField);
connectButton.setBounds (210, 18, 100, 25);
addAndMakeVisible (connectButton);
connectButton.onClick = [this] { connectButtonClicked(); };
clearButton.setBounds (320, 18, 60, 25);
addAndMakeVisible (clearButton);
clearButton.onClick = [this] { clearButtonClicked(); };
connectionStatusLabel.setBounds (450, 18, 240, 25);
updateConnectionStatusLabel();
addAndMakeVisible (connectionStatusLabel);
oscLogListBox.setBounds (0, 60, 700, 340);
addAndMakeVisible (oscLogListBox);
oscReceiver.addListener (this);
oscReceiver.registerFormatErrorHandler ([this] (const char* data, int dataSize)
{
oscLogListBox.addInvalidOSCPacket (data, dataSize);
});
}
private:
//==============================================================================
Label portNumberLabel { {}, "UDP Port Number: " };
Label portNumberField { {}, "9002" };
TextButton connectButton { "Connect" };
TextButton clearButton { "Clear" };
Label connectionStatusLabel;
OSCLogListBox oscLogListBox;
OSCReceiver oscReceiver;
int currentPortNumber = -1;
//==============================================================================
void connectButtonClicked()
{
if (! isConnected())
connect();
else
disconnect();
updateConnectionStatusLabel();
}
//==============================================================================
void clearButtonClicked()
{
oscLogListBox.clear();
}
//==============================================================================
void oscMessageReceived (const OSCMessage& message) override
{
oscLogListBox.addOSCMessage (message);
}
void oscBundleReceived (const OSCBundle& bundle) override
{
oscLogListBox.addOSCBundle (bundle);
}
//==============================================================================
void connect()
{
auto portToConnect = portNumberField.getText().getIntValue();
if (! isValidOscPort (portToConnect))
{
handleInvalidPortNumberEntered();
return;
}
if (oscReceiver.connect (portToConnect))
{
currentPortNumber = portToConnect;
connectButton.setButtonText ("Disconnect");
}
else
{
handleConnectError (portToConnect);
}
}
//==============================================================================
void disconnect()
{
if (oscReceiver.disconnect())
{
currentPortNumber = -1;
connectButton.setButtonText ("Connect");
}
else
{
handleDisconnectError();
}
}
//==============================================================================
void handleConnectError (int failedPort)
{
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
"OSC Connection error",
"Error: could not connect to port " + String (failedPort),
"OK");
}
//==============================================================================
void handleDisconnectError()
{
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
"Unknown error",
"An unknown error occurred while trying to disconnect from UDP port.",
"OK");
}
//==============================================================================
void handleInvalidPortNumberEntered()
{
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
"Invalid port number",
"Error: you have entered an invalid UDP port number.",
"OK");
}
//==============================================================================
bool isConnected() const
{
return currentPortNumber != -1;
}
//==============================================================================
bool isValidOscPort (int port) const
{
return port > 0 && port < 65536;
}
//==============================================================================
void updateConnectionStatusLabel()
{
String text = "Status: ";
if (isConnected())
text += "Connected to UDP port " + String (currentPortNumber);
else
text += "Disconnected";
auto textColour = isConnected() ? Colours::green : Colours::red;
connectionStatusLabel.setText (text, dontSendNotification);
connectionStatusLabel.setFont (Font (15.00f, Font::bold));
connectionStatusLabel.setColour (Label::textColourId, textColour);
connectionStatusLabel.setJustificationType (Justification::centredRight);
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSCMonitorDemo)
};
//==============================================================================
class OSCDemo : public Component
{
public:
OSCDemo()
{
addAndMakeVisible (monitor);
addAndMakeVisible (receiver);
addAndMakeVisible (sender);
setSize (700, 400);
}
void resized() override
{
auto bounds = getLocalBounds();
auto lowerBounds = bounds.removeFromBottom (getHeight() / 2);
auto halfBounds = bounds.removeFromRight (getWidth() / 2);
sender .setBounds (bounds);
receiver.setBounds (halfBounds);
monitor .setBounds (lowerBounds.removeFromTop (getHeight() / 2));
}
private:
OSCMonitorDemo monitor;
OSCReceiverDemo receiver;
OSCSenderDemo sender;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSCDemo)
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,245 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: SystemInfoDemo
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Displays system information.
dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
juce_gui_basics
exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: Component
mainClass: SystemInfoDemo
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
#include "../Assets/DemoUtilities.h"
//==============================================================================
static String getMacAddressList()
{
String addressList;
for (auto& addr : MACAddress::getAllAddresses())
addressList << addr.toString() << newLine;
return addressList;
}
static String getFileSystemRoots()
{
Array<File> roots;
File::findFileSystemRoots (roots);
StringArray rootList;
for (auto& r : roots)
rootList.add (r.getFullPathName());
return rootList.joinIntoString (", ");
}
static String getIPAddressList()
{
String addressList;
for (auto& addr : IPAddress::getAllAddresses())
addressList << " " << addr.toString() << newLine;
return addressList;
}
static const char* getDisplayOrientation()
{
switch (Desktop::getInstance().getCurrentOrientation())
{
case Desktop::upright: return "Upright";
case Desktop::upsideDown: return "Upside-down";
case Desktop::rotatedClockwise: return "Rotated Clockwise";
case Desktop::rotatedAntiClockwise: return "Rotated Anti-clockwise";
case Desktop::allOrientations: return "All";
default: jassertfalse; break;
}
return nullptr;
}
static String getDisplayInfo()
{
auto& displays = Desktop::getInstance().getDisplays();
String displayDesc;
for (int i = 0; i < displays.displays.size(); ++i)
{
auto display = displays.displays.getReference (i);
displayDesc << "Display " << (i + 1) << (display.isMain ? " (main)" : "") << ":" << newLine
<< " Total area: " << display.totalArea.toString() << newLine
<< " User area: " << display.userArea .toString() << newLine
<< " DPI: " << display.dpi << newLine
<< " Scale: " << display.scale << newLine
<< newLine;
}
displayDesc << "Orientation: " << getDisplayOrientation() << newLine;
return displayDesc;
}
static String getAllSystemInfo()
{
String systemInfo;
systemInfo
<< "Here are a few system statistics..." << newLine
<< newLine
<< "Time and date: " << Time::getCurrentTime().toString (true, true) << newLine
<< "System up-time: " << RelativeTime::milliseconds ((int64) Time::getMillisecondCounterHiRes()).getDescription() << newLine
<< "Compilation date: " << Time::getCompilationDate().toString (true, false) << newLine
<< newLine
<< "Operating system: " << SystemStats::getOperatingSystemName() << newLine
<< "Host name: " << SystemStats::getComputerName() << newLine
<< "Device type: " << SystemStats::getDeviceDescription() << newLine
<< "Manufacturer: " << SystemStats::getDeviceManufacturer() << newLine
<< "User logon name: " << SystemStats::getLogonName() << newLine
<< "Full user name: " << SystemStats::getFullUserName() << newLine
<< "User region: " << SystemStats::getUserRegion() << newLine
<< "User language: " << SystemStats::getUserLanguage() << newLine
<< "Display language: " << SystemStats::getDisplayLanguage() << newLine
<< newLine;
systemInfo
<< "Number of logical CPUs: " << SystemStats::getNumCpus() << newLine
<< "Number of physical CPUs: " << SystemStats::getNumPhysicalCpus() << newLine
<< "Memory size: " << SystemStats::getMemorySizeInMegabytes() << " MB" << newLine
<< "CPU vendor: " << SystemStats::getCpuVendor() << newLine
<< "CPU model: " << SystemStats::getCpuModel() << newLine
<< "CPU speed: " << SystemStats::getCpuSpeedInMegahertz() << " MHz" << newLine
<< "CPU has MMX: " << (SystemStats::hasMMX() ? "yes" : "no") << newLine
<< "CPU has FMA3: " << (SystemStats::hasFMA3() ? "yes" : "no") << newLine
<< "CPU has FMA4: " << (SystemStats::hasFMA4() ? "yes" : "no") << newLine
<< "CPU has SSE: " << (SystemStats::hasSSE() ? "yes" : "no") << newLine
<< "CPU has SSE2: " << (SystemStats::hasSSE2() ? "yes" : "no") << newLine
<< "CPU has SSE3: " << (SystemStats::hasSSE3() ? "yes" : "no") << newLine
<< "CPU has SSSE3: " << (SystemStats::hasSSSE3() ? "yes" : "no") << newLine
<< "CPU has SSE4.1: " << (SystemStats::hasSSE41() ? "yes" : "no") << newLine
<< "CPU has SSE4.2: " << (SystemStats::hasSSE42() ? "yes" : "no") << newLine
<< "CPU has 3DNOW: " << (SystemStats::has3DNow() ? "yes" : "no") << newLine
<< "CPU has AVX: " << (SystemStats::hasAVX() ? "yes" : "no") << newLine
<< "CPU has AVX2: " << (SystemStats::hasAVX2() ? "yes" : "no") << newLine
<< "CPU has AVX512F: " << (SystemStats::hasAVX512F() ? "yes" : "no") << newLine
<< "CPU has AVX512BW: " << (SystemStats::hasAVX512BW() ? "yes" : "no") << newLine
<< "CPU has AVX512CD: " << (SystemStats::hasAVX512CD() ? "yes" : "no") << newLine
<< "CPU has AVX512DQ: " << (SystemStats::hasAVX512DQ() ? "yes" : "no") << newLine
<< "CPU has AVX512ER: " << (SystemStats::hasAVX512ER() ? "yes" : "no") << newLine
<< "CPU has AVX512IFMA: " << (SystemStats::hasAVX512IFMA() ? "yes" : "no") << newLine
<< "CPU has AVX512PF: " << (SystemStats::hasAVX512PF() ? "yes" : "no") << newLine
<< "CPU has AVX512VBMI: " << (SystemStats::hasAVX512VBMI() ? "yes" : "no") << newLine
<< "CPU has AVX512VL: " << (SystemStats::hasAVX512VL() ? "yes" : "no") << newLine
<< "CPU has AVX512VPOPCNTDQ: " << (SystemStats::hasAVX512VPOPCNTDQ() ? "yes" : "no") << newLine
<< "CPU has Neon: " << (SystemStats::hasNeon() ? "yes" : "no") << newLine
<< newLine;
systemInfo
<< "Current working directory: " << File::getCurrentWorkingDirectory().getFullPathName() << newLine
<< "Current application file: " << File::getSpecialLocation (File::currentApplicationFile).getFullPathName() << newLine
<< "Current executable file: " << File::getSpecialLocation (File::currentExecutableFile) .getFullPathName() << newLine
<< "Invoked executable file: " << File::getSpecialLocation (File::invokedExecutableFile) .getFullPathName() << newLine
<< newLine;
systemInfo
<< "User home folder: " << File::getSpecialLocation (File::userHomeDirectory) .getFullPathName() << newLine
<< "User desktop folder: " << File::getSpecialLocation (File::userDesktopDirectory) .getFullPathName() << newLine
<< "User documents folder: " << File::getSpecialLocation (File::userDocumentsDirectory) .getFullPathName() << newLine
<< "User application data folder: " << File::getSpecialLocation (File::userApplicationDataDirectory) .getFullPathName() << newLine
<< "User music folder: " << File::getSpecialLocation (File::userMusicDirectory) .getFullPathName() << newLine
<< "User movies folder: " << File::getSpecialLocation (File::userMoviesDirectory) .getFullPathName() << newLine
<< "User pictures folder: " << File::getSpecialLocation (File::userPicturesDirectory) .getFullPathName() << newLine
<< "Common application data folder: " << File::getSpecialLocation (File::commonApplicationDataDirectory).getFullPathName() << newLine
<< "Common documents folder: " << File::getSpecialLocation (File::commonDocumentsDirectory) .getFullPathName() << newLine
<< "Local temp folder: " << File::getSpecialLocation (File::tempDirectory) .getFullPathName() << newLine
<< newLine;
systemInfo
<< "File System roots: " << getFileSystemRoots() << newLine
<< "Free space in home folder: " << File::descriptionOfSizeInBytes (File::getSpecialLocation (File::userHomeDirectory)
.getBytesFreeOnVolume()) << newLine
<< newLine
<< getDisplayInfo() << newLine
<< "Network IP addresses: " << newLine << getIPAddressList() << newLine
<< "Network card MAC addresses: " << newLine << getMacAddressList() << newLine;
DBG (systemInfo);
return systemInfo;
}
class SystemInfoDemo : public Component
{
public:
SystemInfoDemo()
{
addAndMakeVisible (resultsBox);
resultsBox.setReadOnly (true);
resultsBox.setMultiLine (true);
resultsBox.setColour (TextEditor::backgroundColourId, Colours::transparentBlack);
resultsBox.setFont ({ Font::getDefaultMonospacedFontName(), 12.0f, Font::plain });
resultsBox.setText (getAllSystemInfo());
setSize (500, 500);
}
void paint (Graphics& g) override
{
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground,
Colour::greyLevel (0.93f)));
}
void resized() override
{
resultsBox.setBounds (getLocalBounds().reduced (8));
}
private:
TextEditor resultsBox;
void lookAndFeelChanged() override
{
resultsBox.applyFontToAllText (resultsBox.getFont());
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SystemInfoDemo)
};

View File

@ -0,0 +1,261 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: TimersAndEventsDemo
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Application using timers and events.
dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
juce_gui_basics
exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: Component
mainClass: TimersAndEventsDemo
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
#include "../Assets/DemoUtilities.h"
//==============================================================================
/** Simple message that holds a Colour. */
struct ColourMessage : public Message
{
ColourMessage (Colour col) : colour (col) {}
/** Returns the colour of a ColourMessage of white if the message is not a ColourMessage. */
static Colour getColour (const Message& message)
{
if (auto* cm = dynamic_cast<const ColourMessage*> (&message))
return cm->colour;
return Colours::white;
}
Colour colour;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ColourMessage)
};
//==============================================================================
/** Simple component that can be triggered to flash.
The flash will then fade using a Timer to repaint itself and will send a change
message once it is finished.
*/
class FlashingComponent : public Component,
public MessageListener,
public ChangeBroadcaster,
private Timer
{
public:
FlashingComponent() {}
void startFlashing()
{
flashAlpha = 1.0f;
startTimerHz (25);
}
/** Stops this component flashing without sending a change message. */
void stopFlashing()
{
flashAlpha = 0.0f;
stopTimer();
repaint();
}
/** Sets the colour of the component. */
void setFlashColour (const Colour newColour)
{
colour = newColour;
repaint();
}
/** Draws our component. */
void paint (Graphics& g) override
{
g.setColour (colour.overlaidWith (Colours::white.withAlpha (flashAlpha)));
g.fillEllipse (getLocalBounds().toFloat());
}
/** Custom mouse handler to trigger a flash. */
void mouseDown (const MouseEvent&) override
{
startFlashing();
}
/** Message listener callback used to change our colour */
void handleMessage (const Message& message) override
{
setFlashColour (ColourMessage::getColour (message));
}
private:
float flashAlpha = 0.0f;
Colour colour { Colours::red };
void timerCallback() override
{
// Reduce the alpha level of the flash slightly so it fades out
flashAlpha -= 0.075f;
if (flashAlpha < 0.05f)
{
stopFlashing();
sendChangeMessage();
// Once we've finished flashing send a change message to trigger the next component to flash
}
repaint();
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlashingComponent)
};
//==============================================================================
class TimersAndEventsDemo : public Component,
private ChangeListener
{
public:
TimersAndEventsDemo()
{
setOpaque (true);
// Create and add our FlashingComponents with some random colours and sizes
for (int i = 0; i < numFlashingComponents; ++i)
{
auto* newFlasher = new FlashingComponent();
flashingComponents.add (newFlasher);
newFlasher->setFlashColour (getRandomBrightColour());
newFlasher->addChangeListener (this);
auto diameter = 25 + random.nextInt (75);
newFlasher->setSize (diameter, diameter);
addAndMakeVisible (newFlasher);
}
addAndMakeVisible (stopButton);
stopButton.onClick = [this] { stopButtonClicked(); };
addAndMakeVisible (randomColourButton);
randomColourButton.onClick = [this] { randomColourButtonClicked(); };
// lay out our components in a pseudo random grid
Rectangle<int> area (0, 100, 150, 150);
for (auto* comp : flashingComponents)
{
auto buttonArea = area.withSize (comp->getWidth(), comp->getHeight());
buttonArea.translate (random.nextInt (area.getWidth() - comp->getWidth()),
random.nextInt (area.getHeight() - comp->getHeight()));
comp->setBounds (buttonArea);
area.translate (area.getWidth(), 0);
// if we go off the right start a new row
if (area.getRight() > (800 - area.getWidth()))
{
area.translate (0, area.getWidth());
area.setX (0);
}
}
setSize (600, 600);
}
~TimersAndEventsDemo() override
{
for (auto* fc : flashingComponents)
fc->removeChangeListener (this);
}
void paint (Graphics& g) override
{
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground,
Colours::darkgrey));
}
void paintOverChildren (Graphics& g) override
{
auto explanationArea = getLocalBounds().removeFromTop (100);
AttributedString s;
s.append ("Click on a circle to make it flash. When it has finished flashing it will send a message which causes the next circle to flash");
s.append (newLine);
s.append ("Click the \"Set Random Colour\" button to change the colour of one of the circles.");
s.append (newLine);
s.setFont (16.0f);
s.setColour (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::defaultText, Colours::lightgrey));
s.draw (g, explanationArea.reduced (10).toFloat());
}
void resized() override
{
auto area = getLocalBounds().removeFromBottom (40);
randomColourButton.setBounds (area.removeFromLeft (166) .reduced (8));
stopButton .setBounds (area.removeFromRight (166).reduced (8));
}
private:
enum { numFlashingComponents = 9 };
OwnedArray<FlashingComponent> flashingComponents;
TextButton randomColourButton { "Set Random Colour" },
stopButton { "Stop" };
Random random;
void changeListenerCallback (ChangeBroadcaster* source) override
{
for (int i = 0; i < flashingComponents.size(); ++i)
if (source == flashingComponents.getUnchecked (i))
flashingComponents.getUnchecked ((i + 1) % flashingComponents.size())->startFlashing();
}
void randomColourButtonClicked()
{
// Here we post a new ColourMessage with a random colour to a random flashing component.
// This will send a message to the component asynchronously and trigger its handleMessage callback
flashingComponents.getUnchecked (random.nextInt (flashingComponents.size()))->postMessage (new ColourMessage (getRandomBrightColour()));
}
void stopButtonClicked()
{
for (auto* fc : flashingComponents)
fc->stopFlashing();
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TimersAndEventsDemo)
};

View File

@ -0,0 +1,232 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: UnitTestsDemo
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Performs unit tests.
dependencies: juce_analytics, juce_audio_basics, juce_audio_devices,
juce_audio_formats, juce_audio_processors, juce_audio_utils,
juce_core, juce_cryptography, juce_data_structures, juce_dsp,
juce_events, juce_graphics, juce_gui_basics, juce_gui_extra,
juce_opengl, juce_osc, juce_product_unlocking, juce_video
exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
defines: JUCE_UNIT_TESTS=1
type: Component
mainClass: UnitTestsDemo
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
#include "../Assets/DemoUtilities.h"
//==============================================================================
class UnitTestsDemo : public Component
{
public:
UnitTestsDemo()
{
setOpaque (true);
addAndMakeVisible (startTestButton);
startTestButton.onClick = [this] { start(); };
addAndMakeVisible (testResultsBox);
testResultsBox.setMultiLine (true);
testResultsBox.setFont (Font (Font::getDefaultMonospacedFontName(), 12.0f, Font::plain));
addAndMakeVisible (categoriesBox);
categoriesBox.addItem ("All Tests", 1);
auto categories = UnitTest::getAllCategories();
categories.sort (true);
categoriesBox.addItemList (categories, 2);
categoriesBox.setSelectedId (1);
logMessage ("This panel runs the built-in JUCE unit-tests from the selected category.\n");
logMessage ("To add your own unit-tests, see the JUCE_UNIT_TESTS macro.");
setSize (500, 500);
}
~UnitTestsDemo() override
{
stopTest();
}
//==============================================================================
void paint (Graphics& g) override
{
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground,
Colours::grey));
}
void resized() override
{
auto bounds = getLocalBounds().reduced (6);
auto topSlice = bounds.removeFromTop (25);
startTestButton.setBounds (topSlice.removeFromLeft (200));
topSlice.removeFromLeft (10);
categoriesBox .setBounds (topSlice.removeFromLeft (250));
bounds.removeFromTop (5);
testResultsBox.setBounds (bounds);
}
void start()
{
startTest (categoriesBox.getText());
}
void startTest (const String& category)
{
testResultsBox.clear();
startTestButton.setEnabled (false);
currentTestThread.reset (new TestRunnerThread (*this, category));
currentTestThread->startThread();
}
void stopTest()
{
if (currentTestThread.get() != nullptr)
{
currentTestThread->stopThread (15000);
currentTestThread.reset();
}
}
void logMessage (const String& message)
{
testResultsBox.moveCaretToEnd();
testResultsBox.insertTextAtCaret (message + newLine);
testResultsBox.moveCaretToEnd();
}
void testFinished()
{
stopTest();
startTestButton.setEnabled (true);
logMessage (newLine + "*** Tests finished ***");
}
private:
//==============================================================================
class TestRunnerThread : public Thread,
private Timer
{
public:
TestRunnerThread (UnitTestsDemo& utd, const String& ctg)
: Thread ("Unit Tests"),
owner (utd),
category (ctg)
{}
void run() override
{
CustomTestRunner runner (*this);
if (category == "All Tests")
runner.runAllTests();
else
runner.runTestsInCategory (category);
startTimer (50); // when finished, start the timer which will
// wait for the thread to end, then tell our component.
}
void logMessage (const String& message)
{
WeakReference<UnitTestsDemo> safeOwner (&owner);
MessageManager::callAsync ([=]
{
if (auto* o = safeOwner.get())
o->logMessage (message);
});
}
void timerCallback() override
{
if (! isThreadRunning())
owner.testFinished(); // inform the demo page when done, so it can delete this thread.
}
private:
//==============================================================================
// This subclass of UnitTestRunner is used to redirect the test output to our
// TextBox, and to interrupt the running tests when our thread is asked to stop..
class CustomTestRunner : public UnitTestRunner
{
public:
CustomTestRunner (TestRunnerThread& trt) : owner (trt) {}
void logMessage (const String& message) override
{
owner.logMessage (message);
}
bool shouldAbortTests() override
{
return owner.threadShouldExit();
}
private:
TestRunnerThread& owner;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomTestRunner)
};
UnitTestsDemo& owner;
const String category;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TestRunnerThread)
};
std::unique_ptr<TestRunnerThread> currentTestThread;
TextButton startTestButton { "Run Unit Tests..." };
ComboBox categoriesBox;
TextEditor testResultsBox;
void lookAndFeelChanged() override
{
testResultsBox.applyFontToAllText (testResultsBox.getFont());
}
JUCE_DECLARE_WEAK_REFERENCEABLE (UnitTestsDemo)
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UnitTestsDemo)
};

View File

@ -0,0 +1,310 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: ValueTreesDemo
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Showcases value tree features.
dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
juce_gui_basics
exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: Component
mainClass: ValueTreesDemo
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
#include "../Assets/DemoUtilities.h"
//==============================================================================
class ValueTreeItem : public TreeViewItem,
private ValueTree::Listener
{
public:
ValueTreeItem (const ValueTree& v, UndoManager& um)
: tree (v), undoManager (um)
{
tree.addListener (this);
}
String getUniqueName() const override
{
return tree["name"].toString();
}
bool mightContainSubItems() override
{
return tree.getNumChildren() > 0;
}
void paintItem (Graphics& g, int width, int height) override
{
if (isSelected())
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::highlightedFill,
Colours::teal));
g.setColour (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::defaultText,
Colours::black));
g.setFont (15.0f);
g.drawText (tree["name"].toString(),
4, 0, width - 4, height,
Justification::centredLeft, true);
}
void itemOpennessChanged (bool isNowOpen) override
{
if (isNowOpen && getNumSubItems() == 0)
refreshSubItems();
else
clearSubItems();
}
var getDragSourceDescription() override
{
return "Drag Demo";
}
bool isInterestedInDragSource (const DragAndDropTarget::SourceDetails& dragSourceDetails) override
{
return dragSourceDetails.description == "Drag Demo";
}
void itemDropped (const DragAndDropTarget::SourceDetails&, int insertIndex) override
{
OwnedArray<ValueTree> selectedTrees;
getSelectedTreeViewItems (*getOwnerView(), selectedTrees);
moveItems (*getOwnerView(), selectedTrees, tree, insertIndex, undoManager);
}
static void moveItems (TreeView& treeView, const OwnedArray<ValueTree>& items,
ValueTree newParent, int insertIndex, UndoManager& undoManager)
{
if (items.size() > 0)
{
std::unique_ptr<XmlElement> oldOpenness (treeView.getOpennessState (false));
for (auto* v : items)
{
if (v->getParent().isValid() && newParent != *v && ! newParent.isAChildOf (*v))
{
if (v->getParent() == newParent && newParent.indexOf (*v) < insertIndex)
--insertIndex;
v->getParent().removeChild (*v, &undoManager);
newParent.addChild (*v, insertIndex, &undoManager);
}
}
if (oldOpenness.get() != nullptr)
treeView.restoreOpennessState (*oldOpenness, false);
}
}
static void getSelectedTreeViewItems (TreeView& treeView, OwnedArray<ValueTree>& items)
{
auto numSelected = treeView.getNumSelectedItems();
for (int i = 0; i < numSelected; ++i)
if (auto* vti = dynamic_cast<ValueTreeItem*> (treeView.getSelectedItem (i)))
items.add (new ValueTree (vti->tree));
}
private:
ValueTree tree;
UndoManager& undoManager;
void refreshSubItems()
{
clearSubItems();
for (int i = 0; i < tree.getNumChildren(); ++i)
addSubItem (new ValueTreeItem (tree.getChild (i), undoManager));
}
void valueTreePropertyChanged (ValueTree&, const Identifier&) override
{
repaintItem();
}
void valueTreeChildAdded (ValueTree& parentTree, ValueTree&) override { treeChildrenChanged (parentTree); }
void valueTreeChildRemoved (ValueTree& parentTree, ValueTree&, int) override { treeChildrenChanged (parentTree); }
void valueTreeChildOrderChanged (ValueTree& parentTree, int, int) override { treeChildrenChanged (parentTree); }
void valueTreeParentChanged (ValueTree&) override {}
void treeChildrenChanged (const ValueTree& parentTree)
{
if (parentTree == tree)
{
refreshSubItems();
treeHasChanged();
setOpen (true);
}
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueTreeItem)
};
//==============================================================================
class ValueTreesDemo : public Component,
public DragAndDropContainer,
private Timer
{
public:
ValueTreesDemo()
{
addAndMakeVisible (tree);
tree.setTitle ("ValueTree");
tree.setDefaultOpenness (true);
tree.setMultiSelectEnabled (true);
rootItem.reset (new ValueTreeItem (createRootValueTree(), undoManager));
tree.setRootItem (rootItem.get());
addAndMakeVisible (undoButton);
addAndMakeVisible (redoButton);
undoButton.onClick = [this] { undoManager.undo(); };
redoButton.onClick = [this] { undoManager.redo(); };
startTimer (500);
setSize (500, 500);
}
~ValueTreesDemo() override
{
tree.setRootItem (nullptr);
}
void paint (Graphics& g) override
{
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
}
void resized() override
{
auto r = getLocalBounds().reduced (8);
auto buttons = r.removeFromBottom (22);
undoButton.setBounds (buttons.removeFromLeft (100));
buttons.removeFromLeft (6);
redoButton.setBounds (buttons.removeFromLeft (100));
r.removeFromBottom (4);
tree.setBounds (r);
}
static ValueTree createTree (const String& desc)
{
ValueTree t ("Item");
t.setProperty ("name", desc, nullptr);
return t;
}
static ValueTree createRootValueTree()
{
auto vt = createTree ("This demo displays a ValueTree as a treeview.");
vt.appendChild (createTree ("You can drag around the nodes to rearrange them"), nullptr);
vt.appendChild (createTree ("..and press 'delete' or 'backspace' to delete them"), nullptr);
vt.appendChild (createTree ("Then, you can use the undo/redo buttons to undo these changes"), nullptr);
int n = 1;
vt.appendChild (createRandomTree (n, 0), nullptr);
return vt;
}
static ValueTree createRandomTree (int& counter, int depth)
{
auto t = createTree ("Item " + String (counter++));
if (depth < 3)
for (int i = 1 + Random::getSystemRandom().nextInt (7); --i >= 0;)
t.appendChild (createRandomTree (counter, depth + 1), nullptr);
return t;
}
void deleteSelectedItems()
{
OwnedArray<ValueTree> selectedItems;
ValueTreeItem::getSelectedTreeViewItems (tree, selectedItems);
for (auto* v : selectedItems)
{
if (v->getParent().isValid())
v->getParent().removeChild (*v, &undoManager);
}
}
bool keyPressed (const KeyPress& key) override
{
if (key == KeyPress::deleteKey || key == KeyPress::backspaceKey)
{
deleteSelectedItems();
return true;
}
if (key == KeyPress ('z', ModifierKeys::commandModifier, 0))
{
undoManager.undo();
return true;
}
if (key == KeyPress ('z', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0))
{
undoManager.redo();
return true;
}
return Component::keyPressed (key);
}
private:
TreeView tree;
TextButton undoButton { "Undo" },
redoButton { "Redo" };
std::unique_ptr<ValueTreeItem> rootItem;
UndoManager undoManager;
void timerCallback() override
{
undoManager.beginNewTransaction();
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueTreesDemo)
};

View File

@ -0,0 +1,400 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: XMLandJSONDemo
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Reads XML and JSON files.
dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
juce_gui_basics, juce_gui_extra
exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: Component
mainClass: XMLandJSONDemo
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
#include "../Assets/DemoUtilities.h"
//==============================================================================
class XmlTreeItem : public TreeViewItem
{
public:
XmlTreeItem (XmlElement& x) : xml (x) {}
String getUniqueName() const override
{
if (xml.getTagName().isEmpty())
return "unknown";
return xml.getTagName();
}
bool mightContainSubItems() override
{
return xml.getFirstChildElement() != nullptr;
}
void paintItem (Graphics& g, int width, int height) override
{
// if this item is selected, fill it with a background colour..
if (isSelected())
g.fillAll (Colours::blue.withAlpha (0.3f));
// use a "colour" attribute in the xml tag for this node to set the text colour..
g.setColour (Colour::fromString (xml.getStringAttribute ("colour", "ff000000")));
g.setFont ((float) height * 0.7f);
// draw the xml element's tag name..
g.drawText (xml.getTagName(),
4, 0, width - 4, height,
Justification::centredLeft, true);
}
void itemOpennessChanged (bool isNowOpen) override
{
if (isNowOpen)
{
// if we've not already done so, we'll now add the tree's sub-items. You could
// also choose to delete the existing ones and refresh them if that's more suitable
// in your app.
if (getNumSubItems() == 0)
{
// create and add sub-items to this node of the tree, corresponding to
// each sub-element in the XML..
for (auto* child : xml.getChildIterator())
if (child != nullptr)
addSubItem (new XmlTreeItem (*child));
}
}
else
{
// in this case, we'll leave any sub-items in the tree when the node gets closed,
// though you could choose to delete them if that's more appropriate for
// your application.
}
}
private:
XmlElement& xml;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XmlTreeItem)
};
//==============================================================================
class JsonTreeItem : public TreeViewItem
{
public:
JsonTreeItem (Identifier i, var value)
: identifier (i),
json (value)
{}
String getUniqueName() const override
{
return identifier.toString() + "_id";
}
bool mightContainSubItems() override
{
if (auto* obj = json.getDynamicObject())
return obj->getProperties().size() > 0;
return json.isArray();
}
void paintItem (Graphics& g, int width, int height) override
{
// if this item is selected, fill it with a background colour..
if (isSelected())
g.fillAll (Colours::blue.withAlpha (0.3f));
g.setColour (Colours::black);
g.setFont ((float) height * 0.7f);
// draw the element's tag name..
g.drawText (getText(),
4, 0, width - 4, height,
Justification::centredLeft, true);
}
void itemOpennessChanged (bool isNowOpen) override
{
if (isNowOpen)
{
// if we've not already done so, we'll now add the tree's sub-items. You could
// also choose to delete the existing ones and refresh them if that's more suitable
// in your app.
if (getNumSubItems() == 0)
{
// create and add sub-items to this node of the tree, corresponding to
// the type of object this var represents
if (json.isArray())
{
for (int i = 0; i < json.size(); ++i)
{
auto& child = json[i];
jassert (! child.isVoid());
addSubItem (new JsonTreeItem ({}, child));
}
}
else if (auto* obj = json.getDynamicObject())
{
auto& props = obj->getProperties();
for (int i = 0; i < props.size(); ++i)
{
auto id = props.getName (i);
auto child = props[id];
jassert (! child.isVoid());
addSubItem (new JsonTreeItem (id, child));
}
}
}
}
else
{
// in this case, we'll leave any sub-items in the tree when the node gets closed,
// though you could choose to delete them if that's more appropriate for
// your application.
}
}
private:
Identifier identifier;
var json;
/** Returns the text to display in the tree.
This is a little more complex for JSON than XML as nodes can be strings, objects or arrays.
*/
String getText() const
{
String text;
if (identifier.isValid())
text << identifier.toString();
if (! json.isVoid())
{
if (text.isNotEmpty() && (! json.isArray()))
text << ": ";
if (json.isObject() && (! identifier.isValid()))
text << "[Array]";
else if (! json.isArray())
text << json.toString();
}
return text;
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JsonTreeItem)
};
//==============================================================================
class XMLandJSONDemo : public Component,
private CodeDocument::Listener
{
public:
/** The type of database to parse. */
enum Type
{
xml,
json
};
XMLandJSONDemo()
{
setOpaque (true);
addAndMakeVisible (typeBox);
typeBox.addItem ("XML", 1);
typeBox.addItem ("JSON", 2);
typeBox.onChange = [this]
{
if (typeBox.getSelectedId() == 1)
reset (xml);
else
reset (json);
};
comboBoxLabel.attachToComponent (&typeBox, true);
addAndMakeVisible (codeDocumentComponent);
codeDocument.addListener (this);
resultsTree.setTitle ("Results");
addAndMakeVisible (resultsTree);
resultsTree.setColour (TreeView::backgroundColourId, Colours::white);
resultsTree.setDefaultOpenness (true);
addAndMakeVisible (errorMessage);
errorMessage.setReadOnly (true);
errorMessage.setMultiLine (true);
errorMessage.setCaretVisible (false);
errorMessage.setColour (TextEditor::outlineColourId, Colours::transparentWhite);
errorMessage.setColour (TextEditor::shadowColourId, Colours::transparentWhite);
typeBox.setSelectedId (1);
setSize (500, 500);
}
~XMLandJSONDemo() override
{
resultsTree.setRootItem (nullptr);
}
void paint (Graphics& g) override
{
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
}
void resized() override
{
auto area = getLocalBounds();
typeBox.setBounds (area.removeFromTop (36).removeFromRight (150).reduced (8));
codeDocumentComponent.setBounds (area.removeFromTop(area.getHeight() / 2).reduced (8));
resultsTree .setBounds (area.reduced (8));
errorMessage .setBounds (resultsTree.getBounds());
}
private:
ComboBox typeBox;
Label comboBoxLabel { {}, "Database Type:" };
CodeDocument codeDocument;
CodeEditorComponent codeDocumentComponent { codeDocument, nullptr };
TreeView resultsTree;
std::unique_ptr<TreeViewItem> rootItem;
std::unique_ptr<XmlElement> parsedXml;
TextEditor errorMessage;
void rebuildTree()
{
std::unique_ptr<XmlElement> openness;
if (rootItem.get() != nullptr)
openness = rootItem->getOpennessState();
createNewRootNode();
if (openness.get() != nullptr && rootItem.get() != nullptr)
rootItem->restoreOpennessState (*openness);
}
void createNewRootNode()
{
// clear the current tree
resultsTree.setRootItem (nullptr);
rootItem.reset();
// try and parse the editor's contents
switch (typeBox.getSelectedItemIndex())
{
case xml: rootItem.reset (rebuildXml()); break;
case json: rootItem.reset (rebuildJson()); break;
default: rootItem.reset(); break;
}
// if we have a valid TreeViewItem hide any old error messages and set our TreeView to use it
if (rootItem.get() != nullptr)
errorMessage.clear();
errorMessage.setVisible (! errorMessage.isEmpty());
resultsTree.setRootItem (rootItem.get());
}
/** Parses the editor's contents as XML. */
TreeViewItem* rebuildXml()
{
parsedXml.reset();
XmlDocument doc (codeDocument.getAllContent());
parsedXml = doc.getDocumentElement();
if (parsedXml.get() == nullptr)
{
auto error = doc.getLastParseError();
if (error.isEmpty())
error = "Unknown error";
errorMessage.setText ("Error parsing XML: " + error, dontSendNotification);
return nullptr;
}
return new XmlTreeItem (*parsedXml);
}
/** Parses the editor's contents as JSON. */
TreeViewItem* rebuildJson()
{
var parsedJson;
auto result = JSON::parse (codeDocument.getAllContent(), parsedJson);
if (! result.wasOk())
{
errorMessage.setText ("Error parsing JSON: " + result.getErrorMessage());
return nullptr;
}
return new JsonTreeItem (Identifier(), parsedJson);
}
/** Clears the editor and loads some default text. */
void reset (Type type)
{
switch (type)
{
case xml: codeDocument.replaceAllContent (loadEntireAssetIntoString ("treedemo.xml")); break;
case json: codeDocument.replaceAllContent (loadEntireAssetIntoString ("juce_module_info")); break;
default: codeDocument.replaceAllContent ({}); break;
}
}
void codeDocumentTextInserted (const String&, int) override { rebuildTree(); }
void codeDocumentTextDeleted (int, int) override { rebuildTree(); }
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XMLandJSONDemo)
};