migrating to the latest JUCE version

This commit is contained in:
2022-11-04 23:11:33 +01:00
committed by Nikolai Rodionov
parent 4257a0f8ba
commit faf8f18333
2796 changed files with 888518 additions and 784244 deletions

View File

@ -1,107 +1,103 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
ApplicationProperties::ApplicationProperties()
{
}
ApplicationProperties::~ApplicationProperties()
{
closeFiles();
}
//==============================================================================
void ApplicationProperties::setStorageParameters (const PropertiesFile::Options& newOptions)
{
options = newOptions;
}
//==============================================================================
void ApplicationProperties::openFiles()
{
// You need to call setStorageParameters() before trying to get hold of the properties!
jassert (options.applicationName.isNotEmpty());
if (options.applicationName.isNotEmpty())
{
PropertiesFile::Options o (options);
if (userProps == nullptr)
{
o.commonToAllUsers = false;
userProps.reset (new PropertiesFile (o));
}
if (commonProps == nullptr)
{
o.commonToAllUsers = true;
commonProps.reset (new PropertiesFile (o));
}
userProps->setFallbackPropertySet (commonProps.get());
}
}
PropertiesFile* ApplicationProperties::getUserSettings()
{
if (userProps == nullptr)
openFiles();
return userProps.get();
}
PropertiesFile* ApplicationProperties::getCommonSettings (const bool returnUserPropsIfReadOnly)
{
if (commonProps == nullptr)
openFiles();
if (returnUserPropsIfReadOnly)
{
if (commonSettingsAreReadOnly == 0)
commonSettingsAreReadOnly = commonProps->save() ? -1 : 1;
if (commonSettingsAreReadOnly > 0)
return userProps.get();
}
return commonProps.get();
}
bool ApplicationProperties::saveIfNeeded()
{
return (userProps == nullptr || userProps->saveIfNeeded())
&& (commonProps == nullptr || commonProps->saveIfNeeded());
}
void ApplicationProperties::closeFiles()
{
userProps.reset();
commonProps.reset();
}
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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.
==============================================================================
*/
namespace juce
{
ApplicationProperties::~ApplicationProperties()
{
closeFiles();
}
//==============================================================================
void ApplicationProperties::setStorageParameters (const PropertiesFile::Options& newOptions)
{
options = newOptions;
}
//==============================================================================
void ApplicationProperties::openFiles()
{
// You need to call setStorageParameters() before trying to get hold of the properties!
jassert (options.applicationName.isNotEmpty());
if (options.applicationName.isNotEmpty())
{
PropertiesFile::Options o (options);
if (userProps == nullptr)
{
o.commonToAllUsers = false;
userProps.reset (new PropertiesFile (o));
}
if (commonProps == nullptr)
{
o.commonToAllUsers = true;
commonProps.reset (new PropertiesFile (o));
}
userProps->setFallbackPropertySet (commonProps.get());
}
}
PropertiesFile* ApplicationProperties::getUserSettings()
{
if (userProps == nullptr)
openFiles();
return userProps.get();
}
PropertiesFile* ApplicationProperties::getCommonSettings (const bool returnUserPropsIfReadOnly)
{
if (commonProps == nullptr)
openFiles();
if (returnUserPropsIfReadOnly)
{
if (commonSettingsAreReadOnly == 0)
commonSettingsAreReadOnly = commonProps->save() ? -1 : 1;
if (commonSettingsAreReadOnly > 0)
return userProps.get();
}
return commonProps.get();
}
bool ApplicationProperties::saveIfNeeded()
{
return (userProps == nullptr || userProps->saveIfNeeded())
&& (commonProps == nullptr || commonProps->saveIfNeeded());
}
void ApplicationProperties::closeFiles()
{
userProps.reset();
commonProps.reset();
}
} // namespace juce

View File

@ -1,133 +1,133 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Manages a collection of properties.
This is a slightly higher-level wrapper for managing PropertiesFile objects.
It holds two different PropertiesFile objects internally, one for user-specific
settings (stored in your user directory), and one for settings that are common to
all users (stored in a folder accessible to all users).
The class manages the creation of these files on-demand, allowing access via the
getUserSettings() and getCommonSettings() methods.
After creating an instance of an ApplicationProperties object, you should first
of all call setStorageParameters() to tell it the parameters to use to create
its files.
@see PropertiesFile
@tags{DataStructures}
*/
class JUCE_API ApplicationProperties
{
public:
//==============================================================================
/**
Creates an ApplicationProperties object.
Before using it, you must call setStorageParameters() to give it the info
it needs to create the property files.
*/
ApplicationProperties();
/** Destructor. */
~ApplicationProperties();
//==============================================================================
/** Gives the object the information it needs to create the appropriate properties files.
See the PropertiesFile::Options class for details about what options you need to set.
*/
void setStorageParameters (const PropertiesFile::Options& options);
/** Returns the current storage parameters.
@see setStorageParameters
*/
const PropertiesFile::Options& getStorageParameters() const noexcept { return options; }
//==============================================================================
/** Returns the user settings file.
The first time this is called, it will create and load the properties file.
Note that when you search the user PropertiesFile for a value that it doesn't contain,
the common settings are used as a second-chance place to look. This is done via the
PropertySet::setFallbackPropertySet() method - by default the common settings are set
to the fallback for the user settings.
@see getCommonSettings
*/
PropertiesFile* getUserSettings();
/** Returns the common settings file.
The first time this is called, it will create and load the properties file.
@param returnUserPropsIfReadOnly if this is true, and the common properties file is
read-only (e.g. because the user doesn't have permission to write
to shared files), then this will return the user settings instead,
(like getUserSettings() would do). This is handy if you'd like to
write a value to the common settings, but if that's no possible,
then you'd rather write to the user settings than none at all.
If returnUserPropsIfReadOnly is false, this method will always return
the common settings, even if any changes to them can't be saved.
@see getUserSettings
*/
PropertiesFile* getCommonSettings (bool returnUserPropsIfReadOnly);
//==============================================================================
/** Saves both files if they need to be saved.
@see PropertiesFile::saveIfNeeded
*/
bool saveIfNeeded();
/** Flushes and closes both files if they are open.
This flushes any pending changes to disk with PropertiesFile::saveIfNeeded()
and closes both files. They will then be re-opened the next time getUserSettings()
or getCommonSettings() is called.
*/
void closeFiles();
private:
//==============================================================================
PropertiesFile::Options options;
std::unique_ptr<PropertiesFile> userProps, commonProps;
int commonSettingsAreReadOnly = 0;
void openFiles();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ApplicationProperties)
};
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Manages a collection of properties.
This is a slightly higher-level wrapper for managing PropertiesFile objects.
It holds two different PropertiesFile objects internally, one for user-specific
settings (stored in your user directory), and one for settings that are common to
all users (stored in a folder accessible to all users).
The class manages the creation of these files on-demand, allowing access via the
getUserSettings() and getCommonSettings() methods.
After creating an instance of an ApplicationProperties object, you should first
of all call setStorageParameters() to tell it the parameters to use to create
its files.
@see PropertiesFile
@tags{DataStructures}
*/
class JUCE_API ApplicationProperties
{
public:
//==============================================================================
/**
Creates an ApplicationProperties object.
Before using it, you must call setStorageParameters() to give it the info
it needs to create the property files.
*/
ApplicationProperties() = default;
/** Destructor. */
~ApplicationProperties();
//==============================================================================
/** Gives the object the information it needs to create the appropriate properties files.
See the PropertiesFile::Options class for details about what options you need to set.
*/
void setStorageParameters (const PropertiesFile::Options& options);
/** Returns the current storage parameters.
@see setStorageParameters
*/
const PropertiesFile::Options& getStorageParameters() const noexcept { return options; }
//==============================================================================
/** Returns the user settings file.
The first time this is called, it will create and load the properties file.
Note that when you search the user PropertiesFile for a value that it doesn't contain,
the common settings are used as a second-chance place to look. This is done via the
PropertySet::setFallbackPropertySet() method - by default the common settings are set
to the fallback for the user settings.
@see getCommonSettings
*/
PropertiesFile* getUserSettings();
/** Returns the common settings file.
The first time this is called, it will create and load the properties file.
@param returnUserPropsIfReadOnly if this is true, and the common properties file is
read-only (e.g. because the user doesn't have permission to write
to shared files), then this will return the user settings instead,
(like getUserSettings() would do). This is handy if you'd like to
write a value to the common settings, but if that's no possible,
then you'd rather write to the user settings than none at all.
If returnUserPropsIfReadOnly is false, this method will always return
the common settings, even if any changes to them can't be saved.
@see getUserSettings
*/
PropertiesFile* getCommonSettings (bool returnUserPropsIfReadOnly);
//==============================================================================
/** Saves both files if they need to be saved.
@see PropertiesFile::saveIfNeeded
*/
bool saveIfNeeded();
/** Flushes and closes both files if they are open.
This flushes any pending changes to disk with PropertiesFile::saveIfNeeded()
and closes both files. They will then be re-opened the next time getUserSettings()
or getCommonSettings() is called.
*/
void closeFiles();
private:
//==============================================================================
PropertiesFile::Options options;
std::unique_ptr<PropertiesFile> userProps, commonProps;
int commonSettingsAreReadOnly = 0;
void openFiles();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ApplicationProperties)
};
} // namespace juce

View File

@ -1,358 +1,358 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
namespace PropertyFileConstants
{
constexpr static const int magicNumber = (int) ByteOrder::makeInt ('P', 'R', 'O', 'P');
constexpr static const int magicNumberCompressed = (int) ByteOrder::makeInt ('C', 'P', 'R', 'P');
constexpr static const char* const fileTag = "PROPERTIES";
constexpr static const char* const valueTag = "VALUE";
constexpr static const char* const nameAttribute = "name";
constexpr static const char* const valueAttribute = "val";
}
//==============================================================================
PropertiesFile::Options::Options()
: commonToAllUsers (false),
ignoreCaseOfKeyNames (false),
doNotSave (false),
millisecondsBeforeSaving (3000),
storageFormat (PropertiesFile::storeAsXML),
processLock (nullptr)
{
}
File PropertiesFile::Options::getDefaultFile() const
{
// mustn't have illegal characters in this name..
jassert (applicationName == File::createLegalFileName (applicationName));
#if JUCE_MAC || JUCE_IOS
File dir (commonToAllUsers ? "/Library/"
: "~/Library/");
if (osxLibrarySubFolder != "Preferences"
&& ! osxLibrarySubFolder.startsWith ("Application Support")
&& ! osxLibrarySubFolder.startsWith ("Containers"))
{
/* The PropertiesFile class always used to put its settings files in "Library/Preferences", but Apple
have changed their advice, and now stipulate that settings should go in "Library/Application Support",
or Library/Containers/[app_bundle_id] for a sandboxed app.
Because older apps would be broken by a silent change in this class's behaviour, you must now
explicitly set the osxLibrarySubFolder value to indicate which path you want to use.
In newer apps, you should always set this to "Application Support"
or "Application Support/YourSubFolderName".
If your app needs to load settings files that were created by older versions of juce and
you want to maintain backwards-compatibility, then you can set this to "Preferences".
But.. for better Apple-compliance, the recommended approach would be to write some code that
finds your old settings files in ~/Library/Preferences, moves them to ~/Library/Application Support,
and then uses the new path.
*/
jassertfalse;
dir = dir.getChildFile ("Application Support");
}
else
{
dir = dir.getChildFile (osxLibrarySubFolder);
}
if (folderName.isNotEmpty())
dir = dir.getChildFile (folderName);
#elif JUCE_LINUX || JUCE_BSD || JUCE_ANDROID
auto dir = File (commonToAllUsers ? "/var" : "~")
.getChildFile (folderName.isNotEmpty() ? folderName
: ("." + applicationName));
#elif JUCE_WINDOWS
auto dir = File::getSpecialLocation (commonToAllUsers ? File::commonApplicationDataDirectory
: File::userApplicationDataDirectory);
if (dir == File())
return {};
dir = dir.getChildFile (folderName.isNotEmpty() ? folderName
: applicationName);
#endif
return (filenameSuffix.startsWithChar (L'.')
? dir.getChildFile (applicationName).withFileExtension (filenameSuffix)
: dir.getChildFile (applicationName + "." + filenameSuffix));
}
//==============================================================================
PropertiesFile::PropertiesFile (const File& f, const Options& o)
: PropertySet (o.ignoreCaseOfKeyNames),
file (f), options (o)
{
reload();
}
PropertiesFile::PropertiesFile (const Options& o)
: PropertySet (o.ignoreCaseOfKeyNames),
file (o.getDefaultFile()), options (o)
{
reload();
}
bool PropertiesFile::reload()
{
ProcessScopedLock pl (createProcessLock());
if (pl != nullptr && ! pl->isLocked())
return false; // locking failure..
loadedOk = (! file.exists()) || loadAsBinary() || loadAsXml();
return loadedOk;
}
PropertiesFile::~PropertiesFile()
{
saveIfNeeded();
}
InterProcessLock::ScopedLockType* PropertiesFile::createProcessLock() const
{
return options.processLock != nullptr ? new InterProcessLock::ScopedLockType (*options.processLock) : nullptr;
}
bool PropertiesFile::saveIfNeeded()
{
const ScopedLock sl (getLock());
return (! needsWriting) || save();
}
bool PropertiesFile::needsToBeSaved() const
{
const ScopedLock sl (getLock());
return needsWriting;
}
void PropertiesFile::setNeedsToBeSaved (const bool needsToBeSaved_)
{
const ScopedLock sl (getLock());
needsWriting = needsToBeSaved_;
}
bool PropertiesFile::save()
{
const ScopedLock sl (getLock());
stopTimer();
if (options.doNotSave
|| file == File()
|| file.isDirectory()
|| ! file.getParentDirectory().createDirectory())
return false;
if (options.storageFormat == storeAsXML)
return saveAsXml();
return saveAsBinary();
}
bool PropertiesFile::loadAsXml()
{
if (auto doc = parseXMLIfTagMatches (file, PropertyFileConstants::fileTag))
{
for (auto* e : doc->getChildWithTagNameIterator (PropertyFileConstants::valueTag))
{
auto name = e->getStringAttribute (PropertyFileConstants::nameAttribute);
if (name.isNotEmpty())
getAllProperties().set (name,
e->getFirstChildElement() != nullptr
? e->getFirstChildElement()->toString (XmlElement::TextFormat().singleLine().withoutHeader())
: e->getStringAttribute (PropertyFileConstants::valueAttribute));
}
return true;
}
return false;
}
bool PropertiesFile::saveAsXml()
{
XmlElement doc (PropertyFileConstants::fileTag);
auto& props = getAllProperties();
for (int i = 0; i < props.size(); ++i)
{
auto* e = doc.createNewChildElement (PropertyFileConstants::valueTag);
e->setAttribute (PropertyFileConstants::nameAttribute, props.getAllKeys() [i]);
// if the value seems to contain xml, store it as such..
if (auto childElement = parseXML (props.getAllValues() [i]))
e->addChildElement (childElement.release());
else
e->setAttribute (PropertyFileConstants::valueAttribute, props.getAllValues() [i]);
}
ProcessScopedLock pl (createProcessLock());
if (pl != nullptr && ! pl->isLocked())
return false; // locking failure..
if (doc.writeTo (file, {}))
{
needsWriting = false;
return true;
}
return false;
}
bool PropertiesFile::loadAsBinary()
{
FileInputStream fileStream (file);
if (fileStream.openedOk())
{
auto magicNumber = fileStream.readInt();
if (magicNumber == PropertyFileConstants::magicNumberCompressed)
{
SubregionStream subStream (&fileStream, 4, -1, false);
GZIPDecompressorInputStream gzip (subStream);
return loadAsBinary (gzip);
}
if (magicNumber == PropertyFileConstants::magicNumber)
return loadAsBinary (fileStream);
}
return false;
}
bool PropertiesFile::loadAsBinary (InputStream& input)
{
BufferedInputStream in (input, 2048);
int numValues = in.readInt();
while (--numValues >= 0 && ! in.isExhausted())
{
auto key = in.readString();
auto value = in.readString();
jassert (key.isNotEmpty());
if (key.isNotEmpty())
getAllProperties().set (key, value);
}
return true;
}
bool PropertiesFile::saveAsBinary()
{
ProcessScopedLock pl (createProcessLock());
if (pl != nullptr && ! pl->isLocked())
return false; // locking failure..
TemporaryFile tempFile (file);
{
FileOutputStream out (tempFile.getFile());
if (! out.openedOk())
return false;
if (options.storageFormat == storeAsCompressedBinary)
{
out.writeInt (PropertyFileConstants::magicNumberCompressed);
out.flush();
GZIPCompressorOutputStream zipped (out, 9);
if (! writeToStream (zipped))
return false;
}
else
{
// have you set up the storage option flags correctly?
jassert (options.storageFormat == storeAsBinary);
out.writeInt (PropertyFileConstants::magicNumber);
if (! writeToStream (out))
return false;
}
}
if (! tempFile.overwriteTargetFileWithTemporary())
return false;
needsWriting = false;
return true;
}
bool PropertiesFile::writeToStream (OutputStream& out)
{
auto& props = getAllProperties();
auto& keys = props.getAllKeys();
auto& values = props.getAllValues();
auto numProperties = props.size();
if (! out.writeInt (numProperties))
return false;
for (int i = 0; i < numProperties; ++i)
{
if (! out.writeString (keys[i])) return false;
if (! out.writeString (values[i])) return false;
}
return true;
}
void PropertiesFile::timerCallback()
{
saveIfNeeded();
}
void PropertiesFile::propertyChanged()
{
sendChangeMessage();
needsWriting = true;
if (options.millisecondsBeforeSaving > 0)
startTimer (options.millisecondsBeforeSaving);
else if (options.millisecondsBeforeSaving == 0)
saveIfNeeded();
}
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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.
==============================================================================
*/
namespace juce
{
namespace PropertyFileConstants
{
constexpr static const int magicNumber = (int) ByteOrder::makeInt ('P', 'R', 'O', 'P');
constexpr static const int magicNumberCompressed = (int) ByteOrder::makeInt ('C', 'P', 'R', 'P');
constexpr static const char* const fileTag = "PROPERTIES";
constexpr static const char* const valueTag = "VALUE";
constexpr static const char* const nameAttribute = "name";
constexpr static const char* const valueAttribute = "val";
}
//==============================================================================
PropertiesFile::Options::Options()
: commonToAllUsers (false),
ignoreCaseOfKeyNames (false),
doNotSave (false),
millisecondsBeforeSaving (3000),
storageFormat (PropertiesFile::storeAsXML),
processLock (nullptr)
{
}
File PropertiesFile::Options::getDefaultFile() const
{
// mustn't have illegal characters in this name..
jassert (applicationName == File::createLegalFileName (applicationName));
#if JUCE_MAC || JUCE_IOS
File dir (commonToAllUsers ? "/Library/"
: "~/Library/");
if (osxLibrarySubFolder != "Preferences"
&& ! osxLibrarySubFolder.startsWith ("Application Support")
&& ! osxLibrarySubFolder.startsWith ("Containers"))
{
/* The PropertiesFile class always used to put its settings files in "Library/Preferences", but Apple
have changed their advice, and now stipulate that settings should go in "Library/Application Support",
or Library/Containers/[app_bundle_id] for a sandboxed app.
Because older apps would be broken by a silent change in this class's behaviour, you must now
explicitly set the osxLibrarySubFolder value to indicate which path you want to use.
In newer apps, you should always set this to "Application Support"
or "Application Support/YourSubFolderName".
If your app needs to load settings files that were created by older versions of juce and
you want to maintain backwards-compatibility, then you can set this to "Preferences".
But.. for better Apple-compliance, the recommended approach would be to write some code that
finds your old settings files in ~/Library/Preferences, moves them to ~/Library/Application Support,
and then uses the new path.
*/
jassertfalse;
dir = dir.getChildFile ("Application Support");
}
else
{
dir = dir.getChildFile (osxLibrarySubFolder);
}
if (folderName.isNotEmpty())
dir = dir.getChildFile (folderName);
#elif JUCE_LINUX || JUCE_BSD || JUCE_ANDROID
auto dir = File (commonToAllUsers ? "/var" : "~")
.getChildFile (folderName.isNotEmpty() ? folderName
: ("." + applicationName));
#elif JUCE_WINDOWS
auto dir = File::getSpecialLocation (commonToAllUsers ? File::commonApplicationDataDirectory
: File::userApplicationDataDirectory);
if (dir == File())
return {};
dir = dir.getChildFile (folderName.isNotEmpty() ? folderName
: applicationName);
#endif
return (filenameSuffix.startsWithChar (L'.')
? dir.getChildFile (applicationName).withFileExtension (filenameSuffix)
: dir.getChildFile (applicationName + "." + filenameSuffix));
}
//==============================================================================
PropertiesFile::PropertiesFile (const File& f, const Options& o)
: PropertySet (o.ignoreCaseOfKeyNames),
file (f), options (o)
{
reload();
}
PropertiesFile::PropertiesFile (const Options& o)
: PropertySet (o.ignoreCaseOfKeyNames),
file (o.getDefaultFile()), options (o)
{
reload();
}
bool PropertiesFile::reload()
{
ProcessScopedLock pl (createProcessLock());
if (pl != nullptr && ! pl->isLocked())
return false; // locking failure..
loadedOk = (! file.exists()) || loadAsBinary() || loadAsXml();
return loadedOk;
}
PropertiesFile::~PropertiesFile()
{
saveIfNeeded();
}
InterProcessLock::ScopedLockType* PropertiesFile::createProcessLock() const
{
return options.processLock != nullptr ? new InterProcessLock::ScopedLockType (*options.processLock) : nullptr;
}
bool PropertiesFile::saveIfNeeded()
{
const ScopedLock sl (getLock());
return (! needsWriting) || save();
}
bool PropertiesFile::needsToBeSaved() const
{
const ScopedLock sl (getLock());
return needsWriting;
}
void PropertiesFile::setNeedsToBeSaved (const bool needsToBeSaved_)
{
const ScopedLock sl (getLock());
needsWriting = needsToBeSaved_;
}
bool PropertiesFile::save()
{
const ScopedLock sl (getLock());
stopTimer();
if (options.doNotSave
|| file == File()
|| file.isDirectory()
|| ! file.getParentDirectory().createDirectory())
return false;
if (options.storageFormat == storeAsXML)
return saveAsXml();
return saveAsBinary();
}
bool PropertiesFile::loadAsXml()
{
if (auto doc = parseXMLIfTagMatches (file, PropertyFileConstants::fileTag))
{
for (auto* e : doc->getChildWithTagNameIterator (PropertyFileConstants::valueTag))
{
auto name = e->getStringAttribute (PropertyFileConstants::nameAttribute);
if (name.isNotEmpty())
getAllProperties().set (name,
e->getFirstChildElement() != nullptr
? e->getFirstChildElement()->toString (XmlElement::TextFormat().singleLine().withoutHeader())
: e->getStringAttribute (PropertyFileConstants::valueAttribute));
}
return true;
}
return false;
}
bool PropertiesFile::saveAsXml()
{
XmlElement doc (PropertyFileConstants::fileTag);
auto& props = getAllProperties();
for (int i = 0; i < props.size(); ++i)
{
auto* e = doc.createNewChildElement (PropertyFileConstants::valueTag);
e->setAttribute (PropertyFileConstants::nameAttribute, props.getAllKeys() [i]);
// if the value seems to contain xml, store it as such..
if (auto childElement = parseXML (props.getAllValues() [i]))
e->addChildElement (childElement.release());
else
e->setAttribute (PropertyFileConstants::valueAttribute, props.getAllValues() [i]);
}
ProcessScopedLock pl (createProcessLock());
if (pl != nullptr && ! pl->isLocked())
return false; // locking failure..
if (doc.writeTo (file, {}))
{
needsWriting = false;
return true;
}
return false;
}
bool PropertiesFile::loadAsBinary()
{
FileInputStream fileStream (file);
if (fileStream.openedOk())
{
auto magicNumber = fileStream.readInt();
if (magicNumber == PropertyFileConstants::magicNumberCompressed)
{
SubregionStream subStream (&fileStream, 4, -1, false);
GZIPDecompressorInputStream gzip (subStream);
return loadAsBinary (gzip);
}
if (magicNumber == PropertyFileConstants::magicNumber)
return loadAsBinary (fileStream);
}
return false;
}
bool PropertiesFile::loadAsBinary (InputStream& input)
{
BufferedInputStream in (input, 2048);
int numValues = in.readInt();
while (--numValues >= 0 && ! in.isExhausted())
{
auto key = in.readString();
auto value = in.readString();
jassert (key.isNotEmpty());
if (key.isNotEmpty())
getAllProperties().set (key, value);
}
return true;
}
bool PropertiesFile::saveAsBinary()
{
ProcessScopedLock pl (createProcessLock());
if (pl != nullptr && ! pl->isLocked())
return false; // locking failure..
TemporaryFile tempFile (file);
{
FileOutputStream out (tempFile.getFile());
if (! out.openedOk())
return false;
if (options.storageFormat == storeAsCompressedBinary)
{
out.writeInt (PropertyFileConstants::magicNumberCompressed);
out.flush();
GZIPCompressorOutputStream zipped (out, 9);
if (! writeToStream (zipped))
return false;
}
else
{
// have you set up the storage option flags correctly?
jassert (options.storageFormat == storeAsBinary);
out.writeInt (PropertyFileConstants::magicNumber);
if (! writeToStream (out))
return false;
}
}
if (! tempFile.overwriteTargetFileWithTemporary())
return false;
needsWriting = false;
return true;
}
bool PropertiesFile::writeToStream (OutputStream& out)
{
auto& props = getAllProperties();
auto& keys = props.getAllKeys();
auto& values = props.getAllValues();
auto numProperties = props.size();
if (! out.writeInt (numProperties))
return false;
for (int i = 0; i < numProperties; ++i)
{
if (! out.writeString (keys[i])) return false;
if (! out.writeString (values[i])) return false;
}
return true;
}
void PropertiesFile::timerCallback()
{
saveIfNeeded();
}
void PropertiesFile::propertyChanged()
{
sendChangeMessage();
needsWriting = true;
if (options.millisecondsBeforeSaving > 0)
startTimer (options.millisecondsBeforeSaving);
else if (options.millisecondsBeforeSaving == 0)
saveIfNeeded();
}
} // namespace juce

View File

@ -1,255 +1,255 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
//==============================================================================
/** Wrapper on a file that stores a list of key/value data pairs.
Useful for storing application settings, etc. See the PropertySet class for
the interfaces that read and write values.
Not designed for very large amounts of data, as it keeps all the values in
memory and writes them out to disk lazily when they are changed.
Because this class derives from ChangeBroadcaster, ChangeListeners can be registered
with it, and these will be signalled when a value changes.
@see PropertySet
@tags{DataStructures}
*/
class JUCE_API PropertiesFile : public PropertySet,
public ChangeBroadcaster,
private Timer
{
public:
//==============================================================================
enum StorageFormat
{
storeAsBinary,
storeAsCompressedBinary,
storeAsXML
};
//==============================================================================
/** Structure describing properties file options */
struct JUCE_API Options
{
/** Creates an empty Options structure.
You'll need to fill-in the data members appropriately before using this structure.
*/
Options();
/** The name of your application - this is used to help generate the path and filename
at which the properties file will be stored. */
String applicationName;
/** The suffix to use for your properties file.
It doesn't really matter what this is - you may want to use ".settings" or
".properties" or something. If the suffix includes the prefixing dot (for example
".settings") then the suffix of applicationName will be replaced with your suffix
("MyApp.exe" -> "MyApp.settings"). If your filenameSuffix does NOT include the dot,
then the suffix will be appended to the applicationName ("MyApp.exe" ->
"MyApp.exe.settings").
*/
String filenameSuffix;
/** The name of a subfolder in which you'd like your properties file to live.
See the getDefaultFile() method for more details about how this is used.
*/
String folderName;
/** If you're using properties files on a Mac, you must set this value - failure to
do so will cause a runtime assertion.
The PropertiesFile class always used to put its settings files in "Library/Preferences", but Apple
have changed their advice, and now stipulate that settings should go in "Library/Application Support".
Because older apps would be broken by a silent change in this class's behaviour, you must now
explicitly set the osxLibrarySubFolder value to indicate which path you want to use.
In newer apps, you should always set this to "Application Support" or
"Application Support/YourSubFolderName".
If your app needs to load settings files that were created by older versions of juce and
you want to maintain backwards-compatibility, then you can set this to "Preferences".
But.. for better Apple-compliance, the recommended approach would be to write some code that
finds your old settings files in ~/Library/Preferences, moves them to ~/Library/Application Support,
and then uses the new path.
*/
String osxLibrarySubFolder;
/** If true, the file will be created in a location that's shared between users.
The default constructor initialises this value to false.
*/
bool commonToAllUsers;
/** If true, this means that property names are matched in a case-insensitive manner.
See the PropertySet constructor for more info.
The default constructor initialises this value to false.
*/
bool ignoreCaseOfKeyNames;
/** If set to true, this prevents the file from being written to disk. */
bool doNotSave;
/** If this is zero or greater, then after a value is changed, the object will wait
for this amount of time and then save the file. If this zero, the file will be
written to disk immediately on being changed (which might be slow, as it'll re-write
synchronously each time a value-change method is called). If it is less than zero,
the file won't be saved until save() or saveIfNeeded() are explicitly called.
The default constructor sets this to a reasonable value of a few seconds, so you
only need to change it if you need a special case.
*/
int millisecondsBeforeSaving;
/** Specifies whether the file should be written as XML, binary, etc.
The default constructor sets this to storeAsXML, so you only need to set it explicitly
if you want to use a different format.
*/
StorageFormat storageFormat;
/** An optional InterprocessLock object that will be used to prevent multiple threads or
processes from writing to the file at the same time. The PropertiesFile will keep a
pointer to this object but will not take ownership of it - the caller is responsible for
making sure that the lock doesn't get deleted before the PropertiesFile has been deleted.
The default constructor initialises this value to nullptr, so you don't need to touch it
unless you want to use a lock.
*/
InterProcessLock* processLock;
/** This can be called to suggest a file that should be used, based on the values
in this structure.
So on a Mac, this will return a file called:
~/Library/[osxLibrarySubFolder]/[folderName]/[applicationName].[filenameSuffix]
On Windows it'll return something like:
C:\\Documents and Settings\\username\\Application Data\\[folderName]\\[applicationName].[filenameSuffix]
On Linux it'll return
~/[folderName]/[applicationName].[filenameSuffix]
If the folderName variable is empty, it'll use the app name for this (or omit the
folder name on the Mac).
The paths will also vary depending on whether commonToAllUsers is true.
*/
File getDefaultFile() const;
};
//==============================================================================
/** Creates a PropertiesFile object.
The file used will be chosen by calling PropertiesFile::Options::getDefaultFile()
for the options provided. To set the file explicitly, use the other constructor.
*/
explicit PropertiesFile (const Options& options);
/** Creates a PropertiesFile object.
Unlike the other constructor, this one allows you to explicitly set the file that you
want to be used, rather than using the default one.
*/
PropertiesFile (const File& file,
const Options& options);
/** Destructor.
When deleted, the file will first call saveIfNeeded() to flush any changes to disk.
*/
~PropertiesFile() override;
//==============================================================================
/** Returns true if this file was created from a valid (or non-existent) file.
If the file failed to load correctly because it was corrupt or had insufficient
access, this will be false.
*/
bool isValidFile() const noexcept { return loadedOk; }
//==============================================================================
/** This will flush all the values to disk if they've changed since the last
time they were saved.
Returns false if it fails to write to the file for some reason (maybe because
it's read-only or the directory doesn't exist or something).
@see save
*/
bool saveIfNeeded();
/** This will force a write-to-disk of the current values, regardless of whether
anything has changed since the last save.
Returns false if it fails to write to the file for some reason (maybe because
it's read-only or the directory doesn't exist or something).
@see saveIfNeeded
*/
bool save();
/** Returns true if the properties have been altered since the last time they were saved.
The file is flagged as needing to be saved when you change a value, but you can
explicitly set this flag with setNeedsToBeSaved().
*/
bool needsToBeSaved() const;
/** Explicitly sets the flag to indicate whether the file needs saving or not.
@see needsToBeSaved
*/
void setNeedsToBeSaved (bool needsToBeSaved);
/** Attempts to reload the settings from the file. */
bool reload();
//==============================================================================
/** Returns the file that's being used. */
const File& getFile() const noexcept { return file; }
protected:
/** @internal */
void propertyChanged() override;
private:
//==============================================================================
File file;
Options options;
bool loadedOk = false, needsWriting = false;
using ProcessScopedLock = const std::unique_ptr<InterProcessLock::ScopedLockType>;
InterProcessLock::ScopedLockType* createProcessLock() const;
void timerCallback() override;
bool saveAsXml();
bool saveAsBinary();
bool loadAsXml();
bool loadAsBinary();
bool loadAsBinary (InputStream&);
bool writeToStream (OutputStream&);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PropertiesFile)
};
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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.
==============================================================================
*/
namespace juce
{
//==============================================================================
/** Wrapper on a file that stores a list of key/value data pairs.
Useful for storing application settings, etc. See the PropertySet class for
the interfaces that read and write values.
Not designed for very large amounts of data, as it keeps all the values in
memory and writes them out to disk lazily when they are changed.
Because this class derives from ChangeBroadcaster, ChangeListeners can be registered
with it, and these will be signalled when a value changes.
@see PropertySet
@tags{DataStructures}
*/
class JUCE_API PropertiesFile : public PropertySet,
public ChangeBroadcaster,
private Timer
{
public:
//==============================================================================
enum StorageFormat
{
storeAsBinary,
storeAsCompressedBinary,
storeAsXML
};
//==============================================================================
/** Structure describing properties file options */
struct JUCE_API Options
{
/** Creates an empty Options structure.
You'll need to fill-in the data members appropriately before using this structure.
*/
Options();
/** The name of your application - this is used to help generate the path and filename
at which the properties file will be stored. */
String applicationName;
/** The suffix to use for your properties file.
It doesn't really matter what this is - you may want to use ".settings" or
".properties" or something. If the suffix includes the prefixing dot (for example
".settings") then the suffix of applicationName will be replaced with your suffix
("MyApp.exe" -> "MyApp.settings"). If your filenameSuffix does NOT include the dot,
then the suffix will be appended to the applicationName ("MyApp.exe" ->
"MyApp.exe.settings").
*/
String filenameSuffix;
/** The name of a subfolder in which you'd like your properties file to live.
See the getDefaultFile() method for more details about how this is used.
*/
String folderName;
/** If you're using properties files on a Mac, you must set this value - failure to
do so will cause a runtime assertion.
The PropertiesFile class always used to put its settings files in "Library/Preferences", but Apple
have changed their advice, and now stipulate that settings should go in "Library/Application Support".
Because older apps would be broken by a silent change in this class's behaviour, you must now
explicitly set the osxLibrarySubFolder value to indicate which path you want to use.
In newer apps, you should always set this to "Application Support" or
"Application Support/YourSubFolderName".
If your app needs to load settings files that were created by older versions of juce and
you want to maintain backwards-compatibility, then you can set this to "Preferences".
But.. for better Apple-compliance, the recommended approach would be to write some code that
finds your old settings files in ~/Library/Preferences, moves them to ~/Library/Application Support,
and then uses the new path.
*/
String osxLibrarySubFolder;
/** If true, the file will be created in a location that's shared between users.
The default constructor initialises this value to false.
*/
bool commonToAllUsers;
/** If true, this means that property names are matched in a case-insensitive manner.
See the PropertySet constructor for more info.
The default constructor initialises this value to false.
*/
bool ignoreCaseOfKeyNames;
/** If set to true, this prevents the file from being written to disk. */
bool doNotSave;
/** If this is zero or greater, then after a value is changed, the object will wait
for this amount of time and then save the file. If this zero, the file will be
written to disk immediately on being changed (which might be slow, as it'll re-write
synchronously each time a value-change method is called). If it is less than zero,
the file won't be saved until save() or saveIfNeeded() are explicitly called.
The default constructor sets this to a reasonable value of a few seconds, so you
only need to change it if you need a special case.
*/
int millisecondsBeforeSaving;
/** Specifies whether the file should be written as XML, binary, etc.
The default constructor sets this to storeAsXML, so you only need to set it explicitly
if you want to use a different format.
*/
StorageFormat storageFormat;
/** An optional InterprocessLock object that will be used to prevent multiple threads or
processes from writing to the file at the same time. The PropertiesFile will keep a
pointer to this object but will not take ownership of it - the caller is responsible for
making sure that the lock doesn't get deleted before the PropertiesFile has been deleted.
The default constructor initialises this value to nullptr, so you don't need to touch it
unless you want to use a lock.
*/
InterProcessLock* processLock;
/** This can be called to suggest a file that should be used, based on the values
in this structure.
So on a Mac, this will return a file called:
~/Library/[osxLibrarySubFolder]/[folderName]/[applicationName].[filenameSuffix]
On Windows it'll return something like:
C:\\Documents and Settings\\username\\Application Data\\[folderName]\\[applicationName].[filenameSuffix]
On Linux it'll return
~/[folderName]/[applicationName].[filenameSuffix]
If the folderName variable is empty, it'll use the app name for this (or omit the
folder name on the Mac).
The paths will also vary depending on whether commonToAllUsers is true.
*/
File getDefaultFile() const;
};
//==============================================================================
/** Creates a PropertiesFile object.
The file used will be chosen by calling PropertiesFile::Options::getDefaultFile()
for the options provided. To set the file explicitly, use the other constructor.
*/
explicit PropertiesFile (const Options& options);
/** Creates a PropertiesFile object.
Unlike the other constructor, this one allows you to explicitly set the file that you
want to be used, rather than using the default one.
*/
PropertiesFile (const File& file,
const Options& options);
/** Destructor.
When deleted, the file will first call saveIfNeeded() to flush any changes to disk.
*/
~PropertiesFile() override;
//==============================================================================
/** Returns true if this file was created from a valid (or non-existent) file.
If the file failed to load correctly because it was corrupt or had insufficient
access, this will be false.
*/
bool isValidFile() const noexcept { return loadedOk; }
//==============================================================================
/** This will flush all the values to disk if they've changed since the last
time they were saved.
Returns false if it fails to write to the file for some reason (maybe because
it's read-only or the directory doesn't exist or something).
@see save
*/
bool saveIfNeeded();
/** This will force a write-to-disk of the current values, regardless of whether
anything has changed since the last save.
Returns false if it fails to write to the file for some reason (maybe because
it's read-only or the directory doesn't exist or something).
@see saveIfNeeded
*/
bool save();
/** Returns true if the properties have been altered since the last time they were saved.
The file is flagged as needing to be saved when you change a value, but you can
explicitly set this flag with setNeedsToBeSaved().
*/
bool needsToBeSaved() const;
/** Explicitly sets the flag to indicate whether the file needs saving or not.
@see needsToBeSaved
*/
void setNeedsToBeSaved (bool needsToBeSaved);
/** Attempts to reload the settings from the file. */
bool reload();
//==============================================================================
/** Returns the file that's being used. */
const File& getFile() const noexcept { return file; }
protected:
/** @internal */
void propertyChanged() override;
private:
//==============================================================================
File file;
Options options;
bool loadedOk = false, needsWriting = false;
using ProcessScopedLock = const std::unique_ptr<InterProcessLock::ScopedLockType>;
InterProcessLock::ScopedLockType* createProcessLock() const;
void timerCallback() override;
bool saveAsXml();
bool saveAsBinary();
bool loadAsXml();
bool loadAsBinary();
bool loadAsBinary (InputStream&);
bool writeToStream (OutputStream&);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PropertiesFile)
};
} // namespace juce

View File

@ -1,44 +1,47 @@
/*
==============================================================================
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.
==============================================================================
*/
#ifdef JUCE_DATA_STRUCTURES_H_INCLUDED
/* When you add this cpp file to your project, you mustn't include it in a file where you've
already included any other headers - just put it inside a file on its own, possibly with your config
flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix
header files that the compiler may be using.
*/
#error "Incorrect use of JUCE cpp file"
#endif
#include "juce_data_structures.h"
#include "values/juce_Value.cpp"
#include "values/juce_ValueTree.cpp"
#include "values/juce_ValueTreeSynchroniser.cpp"
#include "values/juce_CachedValue.cpp"
#include "values/juce_ValueWithDefault.cpp"
#include "undomanager/juce_UndoManager.cpp"
#include "app_properties/juce_ApplicationProperties.cpp"
#include "app_properties/juce_PropertiesFile.cpp"
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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.
==============================================================================
*/
#ifdef JUCE_DATA_STRUCTURES_H_INCLUDED
/* When you add this cpp file to your project, you mustn't include it in a file where you've
already included any other headers - just put it inside a file on its own, possibly with your config
flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix
header files that the compiler may be using.
*/
#error "Incorrect use of JUCE cpp file"
#endif
#include "juce_data_structures.h"
#include "values/juce_Value.cpp"
#include "values/juce_ValueTree.cpp"
#include "values/juce_ValueTreeSynchroniser.cpp"
#include "values/juce_CachedValue.cpp"
#include "undomanager/juce_UndoManager.cpp"
#include "app_properties/juce_ApplicationProperties.cpp"
#include "app_properties/juce_PropertiesFile.cpp"
#if JUCE_UNIT_TESTS
#include "values/juce_ValueTreePropertyWithDefault_test.cpp"
#endif

View File

@ -1,66 +1,66 @@
/*
==============================================================================
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.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this module, and is read by
the Projucer to automatically generate project code that uses it.
For details about the syntax and how to create or use a module, see the
JUCE Module Format.md file.
BEGIN_JUCE_MODULE_DECLARATION
ID: juce_data_structures
vendor: juce
version: 6.1.2
name: JUCE data model helper classes
description: Classes for undo/redo management, and smart data structures.
website: http://www.juce.com/juce
license: GPL/Commercial
minimumCppStandard: 14
dependencies: juce_events
END_JUCE_MODULE_DECLARATION
*******************************************************************************/
#pragma once
#define JUCE_DATA_STRUCTURES_H_INCLUDED
//==============================================================================
#include <juce_events/juce_events.h>
#include "undomanager/juce_UndoableAction.h"
#include "undomanager/juce_UndoManager.h"
#include "values/juce_Value.h"
#include "values/juce_ValueTree.h"
#include "values/juce_ValueTreeSynchroniser.h"
#include "values/juce_CachedValue.h"
#include "values/juce_ValueWithDefault.h"
#include "app_properties/juce_PropertiesFile.h"
#include "app_properties/juce_ApplicationProperties.h"
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this module, and is read by
the Projucer to automatically generate project code that uses it.
For details about the syntax and how to create or use a module, see the
JUCE Module Format.md file.
BEGIN_JUCE_MODULE_DECLARATION
ID: juce_data_structures
vendor: juce
version: 7.0.2
name: JUCE data model helper classes
description: Classes for undo/redo management, and smart data structures.
website: http://www.juce.com/juce
license: GPL/Commercial
minimumCppStandard: 14
dependencies: juce_events
END_JUCE_MODULE_DECLARATION
*******************************************************************************/
#pragma once
#define JUCE_DATA_STRUCTURES_H_INCLUDED
//==============================================================================
#include <juce_events/juce_events.h>
#include "undomanager/juce_UndoableAction.h"
#include "undomanager/juce_UndoManager.h"
#include "values/juce_Value.h"
#include "values/juce_ValueTree.h"
#include "values/juce_ValueTreeSynchroniser.h"
#include "values/juce_CachedValue.h"
#include "values/juce_ValueTreePropertyWithDefault.h"
#include "app_properties/juce_PropertiesFile.h"
#include "app_properties/juce_ApplicationProperties.h"

View File

@ -2,15 +2,15 @@
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
Copyright (c) 2022 - 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).
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-6-licence
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see

View File

@ -1,369 +1,369 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
struct UndoManager::ActionSet
{
ActionSet (const String& transactionName) : name (transactionName)
{}
bool perform() const
{
for (auto* a : actions)
if (! a->perform())
return false;
return true;
}
bool undo() const
{
for (int i = actions.size(); --i >= 0;)
if (! actions.getUnchecked(i)->undo())
return false;
return true;
}
int getTotalSize() const
{
int total = 0;
for (auto* a : actions)
total += a->getSizeInUnits();
return total;
}
OwnedArray<UndoableAction> actions;
String name;
Time time { Time::getCurrentTime() };
};
//==============================================================================
UndoManager::UndoManager (int maxNumberOfUnitsToKeep, int minimumTransactions)
{
setMaxNumberOfStoredUnits (maxNumberOfUnitsToKeep, minimumTransactions);
}
UndoManager::~UndoManager()
{
}
//==============================================================================
void UndoManager::clearUndoHistory()
{
transactions.clear();
totalUnitsStored = 0;
nextIndex = 0;
sendChangeMessage();
}
int UndoManager::getNumberOfUnitsTakenUpByStoredCommands() const
{
return totalUnitsStored;
}
void UndoManager::setMaxNumberOfStoredUnits (int maxUnits, int minTransactions)
{
maxNumUnitsToKeep = jmax (1, maxUnits);
minimumTransactionsToKeep = jmax (1, minTransactions);
}
//==============================================================================
bool UndoManager::perform (UndoableAction* newAction, const String& actionName)
{
if (perform (newAction))
{
if (actionName.isNotEmpty())
setCurrentTransactionName (actionName);
return true;
}
return false;
}
bool UndoManager::perform (UndoableAction* newAction)
{
if (newAction != nullptr)
{
std::unique_ptr<UndoableAction> action (newAction);
if (isPerformingUndoRedo())
{
jassertfalse; // Don't call perform() recursively from the UndoableAction::perform()
// or undo() methods, or else these actions will be discarded!
return false;
}
if (action->perform())
{
auto* actionSet = getCurrentSet();
if (actionSet != nullptr && ! newTransaction)
{
if (auto* lastAction = actionSet->actions.getLast())
{
if (auto coalescedAction = lastAction->createCoalescedAction (action.get()))
{
action.reset (coalescedAction);
totalUnitsStored -= lastAction->getSizeInUnits();
actionSet->actions.removeLast();
}
}
}
else
{
actionSet = new ActionSet (newTransactionName);
transactions.insert (nextIndex, actionSet);
++nextIndex;
}
totalUnitsStored += action->getSizeInUnits();
actionSet->actions.add (std::move (action));
newTransaction = false;
moveFutureTransactionsToStash();
dropOldTransactionsIfTooLarge();
sendChangeMessage();
return true;
}
}
return false;
}
void UndoManager::moveFutureTransactionsToStash()
{
if (nextIndex < transactions.size())
{
stashedFutureTransactions.clear();
while (nextIndex < transactions.size())
{
auto* removed = transactions.removeAndReturn (nextIndex);
stashedFutureTransactions.add (removed);
totalUnitsStored -= removed->getTotalSize();
}
}
}
void UndoManager::restoreStashedFutureTransactions()
{
while (nextIndex < transactions.size())
{
totalUnitsStored -= transactions.getUnchecked (nextIndex)->getTotalSize();
transactions.remove (nextIndex);
}
for (auto* stashed : stashedFutureTransactions)
{
transactions.add (stashed);
totalUnitsStored += stashed->getTotalSize();
}
stashedFutureTransactions.clearQuick (false);
}
void UndoManager::dropOldTransactionsIfTooLarge()
{
while (nextIndex > 0
&& totalUnitsStored > maxNumUnitsToKeep
&& transactions.size() > minimumTransactionsToKeep)
{
totalUnitsStored -= transactions.getFirst()->getTotalSize();
transactions.remove (0);
--nextIndex;
// if this fails, then some actions may not be returning
// consistent results from their getSizeInUnits() method
jassert (totalUnitsStored >= 0);
}
}
void UndoManager::beginNewTransaction()
{
beginNewTransaction ({});
}
void UndoManager::beginNewTransaction (const String& actionName)
{
newTransaction = true;
newTransactionName = actionName;
}
void UndoManager::setCurrentTransactionName (const String& newName)
{
if (newTransaction)
newTransactionName = newName;
else if (auto* action = getCurrentSet())
action->name = newName;
}
String UndoManager::getCurrentTransactionName() const
{
if (auto* action = getCurrentSet())
return action->name;
return newTransactionName;
}
//==============================================================================
UndoManager::ActionSet* UndoManager::getCurrentSet() const { return transactions[nextIndex - 1]; }
UndoManager::ActionSet* UndoManager::getNextSet() const { return transactions[nextIndex]; }
bool UndoManager::isPerformingUndoRedo() const { return isInsideUndoRedoCall; }
bool UndoManager::canUndo() const { return getCurrentSet() != nullptr; }
bool UndoManager::canRedo() const { return getNextSet() != nullptr; }
bool UndoManager::undo()
{
if (auto* s = getCurrentSet())
{
const ScopedValueSetter<bool> setter (isInsideUndoRedoCall, true);
if (s->undo())
--nextIndex;
else
clearUndoHistory();
beginNewTransaction();
sendChangeMessage();
return true;
}
return false;
}
bool UndoManager::redo()
{
if (auto* s = getNextSet())
{
const ScopedValueSetter<bool> setter (isInsideUndoRedoCall, true);
if (s->perform())
++nextIndex;
else
clearUndoHistory();
beginNewTransaction();
sendChangeMessage();
return true;
}
return false;
}
String UndoManager::getUndoDescription() const
{
if (auto* s = getCurrentSet())
return s->name;
return {};
}
String UndoManager::getRedoDescription() const
{
if (auto* s = getNextSet())
return s->name;
return {};
}
StringArray UndoManager::getUndoDescriptions() const
{
StringArray descriptions;
for (int i = nextIndex;;)
{
if (auto* t = transactions[--i])
descriptions.add (t->name);
else
return descriptions;
}
}
StringArray UndoManager::getRedoDescriptions() const
{
StringArray descriptions;
for (int i = nextIndex;;)
{
if (auto* t = transactions[i++])
descriptions.add (t->name);
else
return descriptions;
}
}
Time UndoManager::getTimeOfUndoTransaction() const
{
if (auto* s = getCurrentSet())
return s->time;
return {};
}
Time UndoManager::getTimeOfRedoTransaction() const
{
if (auto* s = getNextSet())
return s->time;
return Time::getCurrentTime();
}
bool UndoManager::undoCurrentTransactionOnly()
{
if ((! newTransaction) && undo())
{
restoreStashedFutureTransactions();
return true;
}
return false;
}
void UndoManager::getActionsInCurrentTransaction (Array<const UndoableAction*>& actionsFound) const
{
if (! newTransaction)
if (auto* s = getCurrentSet())
for (auto* a : s->actions)
actionsFound.add (a);
}
int UndoManager::getNumActionsInCurrentTransaction() const
{
if (! newTransaction)
if (auto* s = getCurrentSet())
return s->actions.size();
return 0;
}
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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.
==============================================================================
*/
namespace juce
{
struct UndoManager::ActionSet
{
ActionSet (const String& transactionName) : name (transactionName)
{}
bool perform() const
{
for (auto* a : actions)
if (! a->perform())
return false;
return true;
}
bool undo() const
{
for (int i = actions.size(); --i >= 0;)
if (! actions.getUnchecked(i)->undo())
return false;
return true;
}
int getTotalSize() const
{
int total = 0;
for (auto* a : actions)
total += a->getSizeInUnits();
return total;
}
OwnedArray<UndoableAction> actions;
String name;
Time time { Time::getCurrentTime() };
};
//==============================================================================
UndoManager::UndoManager (int maxNumberOfUnitsToKeep, int minimumTransactions)
{
setMaxNumberOfStoredUnits (maxNumberOfUnitsToKeep, minimumTransactions);
}
UndoManager::~UndoManager()
{
}
//==============================================================================
void UndoManager::clearUndoHistory()
{
transactions.clear();
totalUnitsStored = 0;
nextIndex = 0;
sendChangeMessage();
}
int UndoManager::getNumberOfUnitsTakenUpByStoredCommands() const
{
return totalUnitsStored;
}
void UndoManager::setMaxNumberOfStoredUnits (int maxUnits, int minTransactions)
{
maxNumUnitsToKeep = jmax (1, maxUnits);
minimumTransactionsToKeep = jmax (1, minTransactions);
}
//==============================================================================
bool UndoManager::perform (UndoableAction* newAction, const String& actionName)
{
if (perform (newAction))
{
if (actionName.isNotEmpty())
setCurrentTransactionName (actionName);
return true;
}
return false;
}
bool UndoManager::perform (UndoableAction* newAction)
{
if (newAction != nullptr)
{
std::unique_ptr<UndoableAction> action (newAction);
if (isPerformingUndoRedo())
{
jassertfalse; // Don't call perform() recursively from the UndoableAction::perform()
// or undo() methods, or else these actions will be discarded!
return false;
}
if (action->perform())
{
auto* actionSet = getCurrentSet();
if (actionSet != nullptr && ! newTransaction)
{
if (auto* lastAction = actionSet->actions.getLast())
{
if (auto coalescedAction = lastAction->createCoalescedAction (action.get()))
{
action.reset (coalescedAction);
totalUnitsStored -= lastAction->getSizeInUnits();
actionSet->actions.removeLast();
}
}
}
else
{
actionSet = new ActionSet (newTransactionName);
transactions.insert (nextIndex, actionSet);
++nextIndex;
}
totalUnitsStored += action->getSizeInUnits();
actionSet->actions.add (std::move (action));
newTransaction = false;
moveFutureTransactionsToStash();
dropOldTransactionsIfTooLarge();
sendChangeMessage();
return true;
}
}
return false;
}
void UndoManager::moveFutureTransactionsToStash()
{
if (nextIndex < transactions.size())
{
stashedFutureTransactions.clear();
while (nextIndex < transactions.size())
{
auto* removed = transactions.removeAndReturn (nextIndex);
stashedFutureTransactions.add (removed);
totalUnitsStored -= removed->getTotalSize();
}
}
}
void UndoManager::restoreStashedFutureTransactions()
{
while (nextIndex < transactions.size())
{
totalUnitsStored -= transactions.getUnchecked (nextIndex)->getTotalSize();
transactions.remove (nextIndex);
}
for (auto* stashed : stashedFutureTransactions)
{
transactions.add (stashed);
totalUnitsStored += stashed->getTotalSize();
}
stashedFutureTransactions.clearQuick (false);
}
void UndoManager::dropOldTransactionsIfTooLarge()
{
while (nextIndex > 0
&& totalUnitsStored > maxNumUnitsToKeep
&& transactions.size() > minimumTransactionsToKeep)
{
totalUnitsStored -= transactions.getFirst()->getTotalSize();
transactions.remove (0);
--nextIndex;
// if this fails, then some actions may not be returning
// consistent results from their getSizeInUnits() method
jassert (totalUnitsStored >= 0);
}
}
void UndoManager::beginNewTransaction()
{
beginNewTransaction ({});
}
void UndoManager::beginNewTransaction (const String& actionName)
{
newTransaction = true;
newTransactionName = actionName;
}
void UndoManager::setCurrentTransactionName (const String& newName)
{
if (newTransaction)
newTransactionName = newName;
else if (auto* action = getCurrentSet())
action->name = newName;
}
String UndoManager::getCurrentTransactionName() const
{
if (auto* action = getCurrentSet())
return action->name;
return newTransactionName;
}
//==============================================================================
UndoManager::ActionSet* UndoManager::getCurrentSet() const { return transactions[nextIndex - 1]; }
UndoManager::ActionSet* UndoManager::getNextSet() const { return transactions[nextIndex]; }
bool UndoManager::isPerformingUndoRedo() const { return isInsideUndoRedoCall; }
bool UndoManager::canUndo() const { return getCurrentSet() != nullptr; }
bool UndoManager::canRedo() const { return getNextSet() != nullptr; }
bool UndoManager::undo()
{
if (auto* s = getCurrentSet())
{
const ScopedValueSetter<bool> setter (isInsideUndoRedoCall, true);
if (s->undo())
--nextIndex;
else
clearUndoHistory();
beginNewTransaction();
sendChangeMessage();
return true;
}
return false;
}
bool UndoManager::redo()
{
if (auto* s = getNextSet())
{
const ScopedValueSetter<bool> setter (isInsideUndoRedoCall, true);
if (s->perform())
++nextIndex;
else
clearUndoHistory();
beginNewTransaction();
sendChangeMessage();
return true;
}
return false;
}
String UndoManager::getUndoDescription() const
{
if (auto* s = getCurrentSet())
return s->name;
return {};
}
String UndoManager::getRedoDescription() const
{
if (auto* s = getNextSet())
return s->name;
return {};
}
StringArray UndoManager::getUndoDescriptions() const
{
StringArray descriptions;
for (int i = nextIndex;;)
{
if (auto* t = transactions[--i])
descriptions.add (t->name);
else
return descriptions;
}
}
StringArray UndoManager::getRedoDescriptions() const
{
StringArray descriptions;
for (int i = nextIndex;;)
{
if (auto* t = transactions[i++])
descriptions.add (t->name);
else
return descriptions;
}
}
Time UndoManager::getTimeOfUndoTransaction() const
{
if (auto* s = getCurrentSet())
return s->time;
return {};
}
Time UndoManager::getTimeOfRedoTransaction() const
{
if (auto* s = getNextSet())
return s->time;
return Time::getCurrentTime();
}
bool UndoManager::undoCurrentTransactionOnly()
{
if ((! newTransaction) && undo())
{
restoreStashedFutureTransactions();
return true;
}
return false;
}
void UndoManager::getActionsInCurrentTransaction (Array<const UndoableAction*>& actionsFound) const
{
if (! newTransaction)
if (auto* s = getCurrentSet())
for (auto* a : s->actions)
actionsFound.add (a);
}
int UndoManager::getNumActionsInCurrentTransaction() const
{
if (! newTransaction)
if (auto* s = getCurrentSet())
return s->actions.size();
return 0;
}
} // namespace juce

View File

@ -1,264 +1,264 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Manages a list of undo/redo commands.
An UndoManager object keeps a list of past actions and can use these actions
to move backwards and forwards through an undo history.
To use it, create subclasses of UndoableAction which perform all the
actions you need, then when you need to actually perform an action, create one
and pass it to the UndoManager's perform() method.
The manager also uses the concept of 'transactions' to group the actions
together - all actions performed between calls to beginNewTransaction() are
grouped together and are all undone/redone as a group.
The UndoManager is a ChangeBroadcaster, so listeners can register to be told
when actions are performed or undone.
@see UndoableAction
@tags{DataStructures}
*/
class JUCE_API UndoManager : public ChangeBroadcaster
{
public:
//==============================================================================
/** Creates an UndoManager.
@param maxNumberOfUnitsToKeep each UndoableAction object returns a value
to indicate how much storage it takes up
(UndoableAction::getSizeInUnits()), so this
lets you specify the maximum total number of
units that the undomanager is allowed to
keep in memory before letting the older actions
drop off the end of the list.
@param minimumTransactionsToKeep this specifies the minimum number of transactions
that will be kept, even if this involves exceeding
the amount of space specified in maxNumberOfUnitsToKeep
*/
UndoManager (int maxNumberOfUnitsToKeep = 30000,
int minimumTransactionsToKeep = 30);
/** Destructor. */
~UndoManager() override;
//==============================================================================
/** Deletes all stored actions in the list. */
void clearUndoHistory();
/** Returns the current amount of space to use for storing UndoableAction objects.
@see setMaxNumberOfStoredUnits
*/
int getNumberOfUnitsTakenUpByStoredCommands() const;
/** Sets the amount of space that can be used for storing UndoableAction objects.
@param maxNumberOfUnitsToKeep each UndoableAction object returns a value
to indicate how much storage it takes up
(UndoableAction::getSizeInUnits()), so this
lets you specify the maximum total number of
units that the undomanager is allowed to
keep in memory before letting the older actions
drop off the end of the list.
@param minimumTransactionsToKeep this specifies the minimum number of transactions
that will be kept, even if this involves exceeding
the amount of space specified in maxNumberOfUnitsToKeep
@see getNumberOfUnitsTakenUpByStoredCommands
*/
void setMaxNumberOfStoredUnits (int maxNumberOfUnitsToKeep,
int minimumTransactionsToKeep);
//==============================================================================
/** Performs an action and adds it to the undo history list.
@param action the action to perform - this object will be deleted by
the UndoManager when no longer needed
@returns true if the command succeeds - see UndoableAction::perform
@see beginNewTransaction
*/
bool perform (UndoableAction* action);
/** Performs an action and also gives it a name.
@param action the action to perform - this object will be deleted by
the UndoManager when no longer needed
@param actionName if this string is non-empty, the current transaction will be
given this name; if it's empty, the current transaction name will
be left unchanged. See setCurrentTransactionName()
@returns true if the command succeeds - see UndoableAction::perform
@see beginNewTransaction
*/
bool perform (UndoableAction* action, const String& actionName);
/** Starts a new group of actions that together will be treated as a single transaction.
All actions that are passed to the perform() method between calls to this
method are grouped together and undone/redone together by a single call to
undo() or redo().
*/
void beginNewTransaction();
/** Starts a new group of actions that together will be treated as a single transaction.
All actions that are passed to the perform() method between calls to this
method are grouped together and undone/redone together by a single call to
undo() or redo().
@param actionName a description of the transaction that is about to be
performed
*/
void beginNewTransaction (const String& actionName);
/** Changes the name stored for the current transaction.
Each transaction is given a name when the beginNewTransaction() method is
called, but this can be used to change that name without starting a new
transaction.
*/
void setCurrentTransactionName (const String& newName);
/** Returns the name of the current transaction.
@see setCurrentTransactionName
*/
String getCurrentTransactionName() const;
//==============================================================================
/** Returns true if there's at least one action in the list to undo.
@see getUndoDescription, undo, canRedo
*/
bool canUndo() const;
/** Tries to roll-back the last transaction.
@returns true if the transaction can be undone, and false if it fails, or
if there aren't any transactions to undo
@see undoCurrentTransactionOnly
*/
bool undo();
/** Tries to roll-back any actions that were added to the current transaction.
This will perform an undo() only if there are some actions in the undo list
that were added after the last call to beginNewTransaction().
This is useful because it lets you call beginNewTransaction(), then
perform an operation which may or may not actually perform some actions, and
then call this method to get rid of any actions that might have been done
without it rolling back the previous transaction if nothing was actually
done.
@returns true if any actions were undone.
*/
bool undoCurrentTransactionOnly();
/** Returns the name of the transaction that will be rolled-back when undo() is called.
@see undo, canUndo, getUndoDescriptions
*/
String getUndoDescription() const;
/** Returns the names of the sequence of transactions that will be performed if undo()
is repeatedly called. Note that for transactions where no name was provided, the
corresponding string will be empty.
@see undo, canUndo, getUndoDescription
*/
StringArray getUndoDescriptions() const;
/** Returns the time to which the state would be restored if undo() was to be called.
If an undo isn't currently possible, it'll return Time().
*/
Time getTimeOfUndoTransaction() const;
/** Returns a list of the UndoableAction objects that have been performed during the
transaction that is currently open.
Effectively, this is the list of actions that would be undone if undoCurrentTransactionOnly()
were to be called now.
The first item in the list is the earliest action performed.
*/
void getActionsInCurrentTransaction (Array<const UndoableAction*>& actionsFound) const;
/** Returns the number of UndoableAction objects that have been performed during the
transaction that is currently open.
@see getActionsInCurrentTransaction
*/
int getNumActionsInCurrentTransaction() const;
//==============================================================================
/** Returns true if there's at least one action in the list to redo.
@see getRedoDescription, redo, canUndo
*/
bool canRedo() const;
/** Tries to redo the last transaction that was undone.
@returns true if the transaction can be redone, and false if it fails, or
if there aren't any transactions to redo
*/
bool redo();
/** Returns the name of the transaction that will be redone when redo() is called.
@see redo, canRedo, getRedoDescriptions
*/
String getRedoDescription() const;
/** Returns the names of the sequence of transactions that will be performed if redo()
is repeatedly called. Note that for transactions where no name was provided, the
corresponding string will be empty.
@see redo, canRedo, getRedoDescription
*/
StringArray getRedoDescriptions() const;
/** Returns the time to which the state would be restored if redo() was to be called.
If a redo isn't currently possible, it'll return Time::getCurrentTime().
@see redo, canRedo
*/
Time getTimeOfRedoTransaction() const;
/** Returns true if the caller code is in the middle of an undo or redo action. */
bool isPerformingUndoRedo() const;
private:
//==============================================================================
struct ActionSet;
OwnedArray<ActionSet> transactions, stashedFutureTransactions;
String newTransactionName;
int totalUnitsStored = 0, maxNumUnitsToKeep = 0, minimumTransactionsToKeep = 0, nextIndex = 0;
bool newTransaction = true, isInsideUndoRedoCall = false;
ActionSet* getCurrentSet() const;
ActionSet* getNextSet() const;
void moveFutureTransactionsToStash();
void restoreStashedFutureTransactions();
void dropOldTransactionsIfTooLarge();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UndoManager)
};
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Manages a list of undo/redo commands.
An UndoManager object keeps a list of past actions and can use these actions
to move backwards and forwards through an undo history.
To use it, create subclasses of UndoableAction which perform all the
actions you need, then when you need to actually perform an action, create one
and pass it to the UndoManager's perform() method.
The manager also uses the concept of 'transactions' to group the actions
together - all actions performed between calls to beginNewTransaction() are
grouped together and are all undone/redone as a group.
The UndoManager is a ChangeBroadcaster, so listeners can register to be told
when actions are performed or undone.
@see UndoableAction
@tags{DataStructures}
*/
class JUCE_API UndoManager : public ChangeBroadcaster
{
public:
//==============================================================================
/** Creates an UndoManager.
@param maxNumberOfUnitsToKeep each UndoableAction object returns a value
to indicate how much storage it takes up
(UndoableAction::getSizeInUnits()), so this
lets you specify the maximum total number of
units that the undomanager is allowed to
keep in memory before letting the older actions
drop off the end of the list.
@param minimumTransactionsToKeep this specifies the minimum number of transactions
that will be kept, even if this involves exceeding
the amount of space specified in maxNumberOfUnitsToKeep
*/
UndoManager (int maxNumberOfUnitsToKeep = 30000,
int minimumTransactionsToKeep = 30);
/** Destructor. */
~UndoManager() override;
//==============================================================================
/** Deletes all stored actions in the list. */
void clearUndoHistory();
/** Returns the current amount of space to use for storing UndoableAction objects.
@see setMaxNumberOfStoredUnits
*/
int getNumberOfUnitsTakenUpByStoredCommands() const;
/** Sets the amount of space that can be used for storing UndoableAction objects.
@param maxNumberOfUnitsToKeep each UndoableAction object returns a value
to indicate how much storage it takes up
(UndoableAction::getSizeInUnits()), so this
lets you specify the maximum total number of
units that the undomanager is allowed to
keep in memory before letting the older actions
drop off the end of the list.
@param minimumTransactionsToKeep this specifies the minimum number of transactions
that will be kept, even if this involves exceeding
the amount of space specified in maxNumberOfUnitsToKeep
@see getNumberOfUnitsTakenUpByStoredCommands
*/
void setMaxNumberOfStoredUnits (int maxNumberOfUnitsToKeep,
int minimumTransactionsToKeep);
//==============================================================================
/** Performs an action and adds it to the undo history list.
@param action the action to perform - this object will be deleted by
the UndoManager when no longer needed
@returns true if the command succeeds - see UndoableAction::perform
@see beginNewTransaction
*/
bool perform (UndoableAction* action);
/** Performs an action and also gives it a name.
@param action the action to perform - this object will be deleted by
the UndoManager when no longer needed
@param actionName if this string is non-empty, the current transaction will be
given this name; if it's empty, the current transaction name will
be left unchanged. See setCurrentTransactionName()
@returns true if the command succeeds - see UndoableAction::perform
@see beginNewTransaction
*/
bool perform (UndoableAction* action, const String& actionName);
/** Starts a new group of actions that together will be treated as a single transaction.
All actions that are passed to the perform() method between calls to this
method are grouped together and undone/redone together by a single call to
undo() or redo().
*/
void beginNewTransaction();
/** Starts a new group of actions that together will be treated as a single transaction.
All actions that are passed to the perform() method between calls to this
method are grouped together and undone/redone together by a single call to
undo() or redo().
@param actionName a description of the transaction that is about to be
performed
*/
void beginNewTransaction (const String& actionName);
/** Changes the name stored for the current transaction.
Each transaction is given a name when the beginNewTransaction() method is
called, but this can be used to change that name without starting a new
transaction.
*/
void setCurrentTransactionName (const String& newName);
/** Returns the name of the current transaction.
@see setCurrentTransactionName
*/
String getCurrentTransactionName() const;
//==============================================================================
/** Returns true if there's at least one action in the list to undo.
@see getUndoDescription, undo, canRedo
*/
bool canUndo() const;
/** Tries to roll-back the last transaction.
@returns true if the transaction can be undone, and false if it fails, or
if there aren't any transactions to undo
@see undoCurrentTransactionOnly
*/
bool undo();
/** Tries to roll-back any actions that were added to the current transaction.
This will perform an undo() only if there are some actions in the undo list
that were added after the last call to beginNewTransaction().
This is useful because it lets you call beginNewTransaction(), then
perform an operation which may or may not actually perform some actions, and
then call this method to get rid of any actions that might have been done
without it rolling back the previous transaction if nothing was actually
done.
@returns true if any actions were undone.
*/
bool undoCurrentTransactionOnly();
/** Returns the name of the transaction that will be rolled-back when undo() is called.
@see undo, canUndo, getUndoDescriptions
*/
String getUndoDescription() const;
/** Returns the names of the sequence of transactions that will be performed if undo()
is repeatedly called. Note that for transactions where no name was provided, the
corresponding string will be empty.
@see undo, canUndo, getUndoDescription
*/
StringArray getUndoDescriptions() const;
/** Returns the time to which the state would be restored if undo() was to be called.
If an undo isn't currently possible, it'll return Time().
*/
Time getTimeOfUndoTransaction() const;
/** Returns a list of the UndoableAction objects that have been performed during the
transaction that is currently open.
Effectively, this is the list of actions that would be undone if undoCurrentTransactionOnly()
were to be called now.
The first item in the list is the earliest action performed.
*/
void getActionsInCurrentTransaction (Array<const UndoableAction*>& actionsFound) const;
/** Returns the number of UndoableAction objects that have been performed during the
transaction that is currently open.
@see getActionsInCurrentTransaction
*/
int getNumActionsInCurrentTransaction() const;
//==============================================================================
/** Returns true if there's at least one action in the list to redo.
@see getRedoDescription, redo, canUndo
*/
bool canRedo() const;
/** Tries to redo the last transaction that was undone.
@returns true if the transaction can be redone, and false if it fails, or
if there aren't any transactions to redo
*/
bool redo();
/** Returns the name of the transaction that will be redone when redo() is called.
@see redo, canRedo, getRedoDescriptions
*/
String getRedoDescription() const;
/** Returns the names of the sequence of transactions that will be performed if redo()
is repeatedly called. Note that for transactions where no name was provided, the
corresponding string will be empty.
@see redo, canRedo, getRedoDescription
*/
StringArray getRedoDescriptions() const;
/** Returns the time to which the state would be restored if redo() was to be called.
If a redo isn't currently possible, it'll return Time::getCurrentTime().
@see redo, canRedo
*/
Time getTimeOfRedoTransaction() const;
/** Returns true if the caller code is in the middle of an undo or redo action. */
bool isPerformingUndoRedo() const;
private:
//==============================================================================
struct ActionSet;
OwnedArray<ActionSet> transactions, stashedFutureTransactions;
String newTransactionName;
int totalUnitsStored = 0, maxNumUnitsToKeep = 0, minimumTransactionsToKeep = 0, nextIndex = 0;
bool newTransaction = true, isInsideUndoRedoCall = false;
ActionSet* getCurrentSet() const;
ActionSet* getNextSet() const;
void moveFutureTransactionsToStash();
void restoreStashedFutureTransactions();
void dropOldTransactionsIfTooLarge();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UndoManager)
};
} // namespace juce

View File

@ -1,100 +1,100 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Used by the UndoManager class to store an action which can be done
and undone.
@see UndoManager
@tags{DataStructures}
*/
class JUCE_API UndoableAction
{
protected:
/** Creates an action. */
UndoableAction() = default;
public:
/** Destructor. */
virtual ~UndoableAction() = default;
//==============================================================================
/** Overridden by a subclass to perform the action.
This method is called by the UndoManager, and shouldn't be used directly by
applications.
Be careful not to make any calls in a perform() method that could call
recursively back into the UndoManager::perform() method
@returns true if the action could be performed.
@see UndoManager::perform
*/
virtual bool perform() = 0;
/** Overridden by a subclass to undo the action.
This method is called by the UndoManager, and shouldn't be used directly by
applications.
Be careful not to make any calls in an undo() method that could call
recursively back into the UndoManager::perform() method
@returns true if the action could be undone without any errors.
@see UndoManager::perform
*/
virtual bool undo() = 0;
//==============================================================================
/** Returns a value to indicate how much memory this object takes up.
Because the UndoManager keeps a list of UndoableActions, this is used
to work out how much space each one will take up, so that the UndoManager
can work out how many to keep.
The default value returned here is 10 - units are arbitrary and
don't have to be accurate.
@see UndoManager::getNumberOfUnitsTakenUpByStoredCommands,
UndoManager::setMaxNumberOfStoredUnits
*/
virtual int getSizeInUnits() { return 10; }
/** Allows multiple actions to be coalesced into a single action object, to reduce storage space.
If possible, this method should create and return a single action that does the same job as
this one followed by the supplied action.
If it's not possible to merge the two actions, the method should return a nullptr.
*/
virtual UndoableAction* createCoalescedAction (UndoableAction* nextAction) { ignoreUnused (nextAction); return nullptr; }
};
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Used by the UndoManager class to store an action which can be done
and undone.
@see UndoManager
@tags{DataStructures}
*/
class JUCE_API UndoableAction
{
protected:
/** Creates an action. */
UndoableAction() = default;
public:
/** Destructor. */
virtual ~UndoableAction() = default;
//==============================================================================
/** Overridden by a subclass to perform the action.
This method is called by the UndoManager, and shouldn't be used directly by
applications.
Be careful not to make any calls in a perform() method that could call
recursively back into the UndoManager::perform() method
@returns true if the action could be performed.
@see UndoManager::perform
*/
virtual bool perform() = 0;
/** Overridden by a subclass to undo the action.
This method is called by the UndoManager, and shouldn't be used directly by
applications.
Be careful not to make any calls in an undo() method that could call
recursively back into the UndoManager::perform() method
@returns true if the action could be undone without any errors.
@see UndoManager::perform
*/
virtual bool undo() = 0;
//==============================================================================
/** Returns a value to indicate how much memory this object takes up.
Because the UndoManager keeps a list of UndoableActions, this is used
to work out how much space each one will take up, so that the UndoManager
can work out how many to keep.
The default value returned here is 10 - units are arbitrary and
don't have to be accurate.
@see UndoManager::getNumberOfUnitsTakenUpByStoredCommands,
UndoManager::setMaxNumberOfStoredUnits
*/
virtual int getSizeInUnits() { return 10; }
/** Allows multiple actions to be coalesced into a single action object, to reduce storage space.
If possible, this method should create and return a single action that does the same job as
this one followed by the supplied action.
If it's not possible to merge the two actions, the method should return a nullptr.
*/
virtual UndoableAction* createCoalescedAction (UndoableAction* nextAction) { ignoreUnused (nextAction); return nullptr; }
};
} // namespace juce

View File

@ -1,155 +1,155 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
#if JUCE_UNIT_TESTS
class CachedValueTests : public UnitTest
{
public:
CachedValueTests()
: UnitTest ("CachedValues", UnitTestCategories::values)
{}
void runTest() override
{
beginTest ("default constructor");
{
CachedValue<String> cv;
expect (cv.isUsingDefault());
expect (cv.get() == String());
}
beginTest ("without default value");
{
ValueTree t ("root");
t.setProperty ("testkey", "testvalue", nullptr);
CachedValue<String> cv (t, "testkey", nullptr);
expect (! cv.isUsingDefault());
expect (cv.get() == "testvalue");
cv.resetToDefault();
expect (cv.isUsingDefault());
expect (cv.get() == String());
}
beginTest ("with default value");
{
ValueTree t ("root");
t.setProperty ("testkey", "testvalue", nullptr);
CachedValue<String> cv (t, "testkey", nullptr, "defaultvalue");
expect (! cv.isUsingDefault());
expect (cv.get() == "testvalue");
cv.resetToDefault();
expect (cv.isUsingDefault());
expect (cv.get() == "defaultvalue");
}
beginTest ("with default value (int)");
{
ValueTree t ("root");
t.setProperty ("testkey", 23, nullptr);
CachedValue<int> cv (t, "testkey", nullptr, 34);
expect (! cv.isUsingDefault());
expect (cv == 23);
expectEquals (cv.get(), 23);
cv.resetToDefault();
expect (cv.isUsingDefault());
expect (cv == 34);
}
beginTest ("with void value");
{
ValueTree t ("root");
t.setProperty ("testkey", var(), nullptr);
CachedValue<String> cv (t, "testkey", nullptr, "defaultvalue");
expect (! cv.isUsingDefault());
expect (cv == "");
expectEquals (cv.get(), String());
}
beginTest ("with non-existent value");
{
ValueTree t ("root");
CachedValue<String> cv (t, "testkey", nullptr, "defaultvalue");
expect (cv.isUsingDefault());
expect (cv == "defaultvalue");
expect (cv.get() == "defaultvalue");
}
beginTest ("with value changing");
{
ValueTree t ("root");
t.setProperty ("testkey", "oldvalue", nullptr);
CachedValue<String> cv (t, "testkey", nullptr, "defaultvalue");
expect (cv == "oldvalue");
t.setProperty ("testkey", "newvalue", nullptr);
expect (cv != "oldvalue");
expect (cv == "newvalue");
}
beginTest ("set value");
{
ValueTree t ("root");
t.setProperty ("testkey", 23, nullptr);
CachedValue<int> cv (t, "testkey", nullptr, 45);
cv = 34;
expectEquals ((int) t["testkey"], 34);
cv.resetToDefault();
expect (cv == 45);
expectEquals (cv.get(), 45);
expect (t["testkey"] == var());
}
}
};
static CachedValueTests cachedValueTests;
#endif
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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.
==============================================================================
*/
namespace juce
{
#if JUCE_UNIT_TESTS
class CachedValueTests : public UnitTest
{
public:
CachedValueTests()
: UnitTest ("CachedValues", UnitTestCategories::values)
{}
void runTest() override
{
beginTest ("default constructor");
{
CachedValue<String> cv;
expect (cv.isUsingDefault());
expect (cv.get() == String());
}
beginTest ("without default value");
{
ValueTree t ("root");
t.setProperty ("testkey", "testvalue", nullptr);
CachedValue<String> cv (t, "testkey", nullptr);
expect (! cv.isUsingDefault());
expect (cv.get() == "testvalue");
cv.resetToDefault();
expect (cv.isUsingDefault());
expect (cv.get() == String());
}
beginTest ("with default value");
{
ValueTree t ("root");
t.setProperty ("testkey", "testvalue", nullptr);
CachedValue<String> cv (t, "testkey", nullptr, "defaultvalue");
expect (! cv.isUsingDefault());
expect (cv.get() == "testvalue");
cv.resetToDefault();
expect (cv.isUsingDefault());
expect (cv.get() == "defaultvalue");
}
beginTest ("with default value (int)");
{
ValueTree t ("root");
t.setProperty ("testkey", 23, nullptr);
CachedValue<int> cv (t, "testkey", nullptr, 34);
expect (! cv.isUsingDefault());
expect (cv == 23);
expectEquals (cv.get(), 23);
cv.resetToDefault();
expect (cv.isUsingDefault());
expect (cv == 34);
}
beginTest ("with void value");
{
ValueTree t ("root");
t.setProperty ("testkey", var(), nullptr);
CachedValue<String> cv (t, "testkey", nullptr, "defaultvalue");
expect (! cv.isUsingDefault());
expect (cv == "");
expectEquals (cv.get(), String());
}
beginTest ("with non-existent value");
{
ValueTree t ("root");
CachedValue<String> cv (t, "testkey", nullptr, "defaultvalue");
expect (cv.isUsingDefault());
expect (cv == "defaultvalue");
expect (cv.get() == "defaultvalue");
}
beginTest ("with value changing");
{
ValueTree t ("root");
t.setProperty ("testkey", "oldvalue", nullptr);
CachedValue<String> cv (t, "testkey", nullptr, "defaultvalue");
expect (cv == "oldvalue");
t.setProperty ("testkey", "newvalue", nullptr);
expect (cv != "oldvalue");
expect (cv == "newvalue");
}
beginTest ("set value");
{
ValueTree t ("root");
t.setProperty ("testkey", 23, nullptr);
CachedValue<int> cv (t, "testkey", nullptr, 45);
cv = 34;
expectEquals ((int) t["testkey"], 34);
cv.resetToDefault();
expect (cv == 45);
expectEquals (cv.get(), 45);
expect (t["testkey"] == var());
}
}
};
static CachedValueTests cachedValueTests;
#endif
} // namespace juce

View File

@ -1,314 +1,314 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
This class acts as a typed wrapper around a property inside a ValueTree.
A CachedValue provides an easy way to read and write a ValueTree property with
a chosen type. So for example a CachedValue<int> allows you to read or write the
property as an int, and a CachedValue<String> lets you work with it as a String.
It also allows efficient access to the value, by caching a copy of it in the
type that is being used.
You can give the CachedValue an optional UndoManager which it will use when writing
to the underlying ValueTree.
If the property inside the ValueTree is missing, the CachedValue will automatically
return an optional default value, which can be specified when initialising the CachedValue.
To create one, you can either use the constructor to attach the CachedValue to a
ValueTree, or can create an uninitialised CachedValue with its default constructor and
then attach it later with the referTo() methods.
Common types like String, int, double which can be easily converted to a var should work
out-of-the-box, but if you want to use more complex custom types, you may need to implement
some template specialisations of VariantConverter which this class uses to convert between
the type and the ValueTree's internal var.
@tags{DataStructures}
*/
template <typename Type>
class CachedValue : private ValueTree::Listener
{
public:
//==============================================================================
/** Default constructor.
Creates a default CachedValue not referring to any property. To initialise the
object, call one of the referTo() methods.
*/
CachedValue();
/** Constructor.
Creates a CachedValue referring to a Value property inside a ValueTree.
If you use this constructor, the fallback value will be a default-constructed
instance of Type.
@param tree The ValueTree containing the property
@param propertyID The identifier of the property
@param undoManager The UndoManager to use when writing to the property
*/
CachedValue (ValueTree& tree, const Identifier& propertyID,
UndoManager* undoManager);
/** Constructor.
Creates a default Cached Value referring to a Value property inside a ValueTree,
and specifies a fallback value to use if the property does not exist.
@param tree The ValueTree containing the property
@param propertyID The identifier of the property
@param undoManager The UndoManager to use when writing to the property
@param defaultToUse The fallback default value to use.
*/
CachedValue (ValueTree& tree, const Identifier& propertyID,
UndoManager* undoManager, const Type& defaultToUse);
//==============================================================================
/** Returns the current value of the property. If the property does not exist,
returns the fallback default value.
This is the same as calling get().
*/
operator Type() const noexcept { return cachedValue; }
/** Returns the current value of the property. If the property does not exist,
returns the fallback default value.
*/
Type get() const noexcept { return cachedValue; }
/** Dereference operator. Provides direct access to the property. */
Type& operator*() noexcept { return cachedValue; }
/** Dereference operator. Provides direct access to members of the property
if it is of object type.
*/
Type* operator->() noexcept { return &cachedValue; }
/** Returns true if the current value of the property (or the fallback value)
is equal to other.
*/
template <typename OtherType>
bool operator== (const OtherType& other) const { return cachedValue == other; }
/** Returns true if the current value of the property (or the fallback value)
is not equal to other.
*/
template <typename OtherType>
bool operator!= (const OtherType& other) const { return cachedValue != other; }
//==============================================================================
/** Returns the current property as a Value object. */
Value getPropertyAsValue();
/** Returns true if the current property does not exist and the CachedValue is using
the fallback default value instead.
*/
bool isUsingDefault() const;
/** Returns the current fallback default value. */
Type getDefault() const { return defaultValue; }
//==============================================================================
/** Sets the property. This will actually modify the property in the referenced ValueTree. */
CachedValue& operator= (const Type& newValue);
/** Sets the property. This will actually modify the property in the referenced ValueTree. */
void setValue (const Type& newValue, UndoManager* undoManagerToUse);
/** Removes the property from the referenced ValueTree and makes the CachedValue
return the fallback default value instead.
*/
void resetToDefault();
/** Removes the property from the referenced ValueTree and makes the CachedValue
return the fallback default value instead.
*/
void resetToDefault (UndoManager* undoManagerToUse);
/** Resets the fallback default value. */
void setDefault (const Type& value) { defaultValue = value; }
//==============================================================================
/** Makes the CachedValue refer to the specified property inside the given ValueTree. */
void referTo (ValueTree& tree, const Identifier& property, UndoManager* um);
/** Makes the CachedValue refer to the specified property inside the given ValueTree,
and specifies a fallback value to use if the property does not exist.
*/
void referTo (ValueTree& tree, const Identifier& property, UndoManager* um, const Type& defaultVal);
/** Force an update in case the referenced property has been changed from elsewhere.
Note: The CachedValue is a ValueTree::Listener and therefore will be informed of
changes of the referenced property anyway (and update itself). But this may happen
asynchronously. forceUpdateOfCachedValue() forces an update immediately.
*/
void forceUpdateOfCachedValue();
//==============================================================================
/** Returns a reference to the ValueTree containing the referenced property. */
ValueTree& getValueTree() noexcept { return targetTree; }
/** Returns the property ID of the referenced property. */
const Identifier& getPropertyID() const noexcept { return targetProperty; }
/** Returns the UndoManager that is being used. */
UndoManager* getUndoManager() noexcept { return undoManager; }
private:
//==============================================================================
ValueTree targetTree;
Identifier targetProperty;
UndoManager* undoManager = nullptr;
Type defaultValue;
Type cachedValue;
//==============================================================================
void referToWithDefault (ValueTree&, const Identifier&, UndoManager*, const Type&);
Type getTypedValue() const;
void valueTreePropertyChanged (ValueTree& changedTree, const Identifier& changedProperty) override;
//==============================================================================
JUCE_DECLARE_WEAK_REFERENCEABLE (CachedValue)
JUCE_DECLARE_NON_COPYABLE (CachedValue)
};
//==============================================================================
template <typename Type>
inline CachedValue<Type>::CachedValue() = default;
template <typename Type>
inline CachedValue<Type>::CachedValue (ValueTree& v, const Identifier& i, UndoManager* um)
: targetTree (v), targetProperty (i), undoManager (um),
defaultValue(), cachedValue (getTypedValue())
{
targetTree.addListener (this);
}
template <typename Type>
inline CachedValue<Type>::CachedValue (ValueTree& v, const Identifier& i, UndoManager* um, const Type& defaultToUse)
: targetTree (v), targetProperty (i), undoManager (um),
defaultValue (defaultToUse), cachedValue (getTypedValue())
{
targetTree.addListener (this);
}
template <typename Type>
inline Value CachedValue<Type>::getPropertyAsValue()
{
return targetTree.getPropertyAsValue (targetProperty, undoManager);
}
template <typename Type>
inline bool CachedValue<Type>::isUsingDefault() const
{
return ! targetTree.hasProperty (targetProperty);
}
template <typename Type>
inline CachedValue<Type>& CachedValue<Type>::operator= (const Type& newValue)
{
setValue (newValue, undoManager);
return *this;
}
template <typename Type>
inline void CachedValue<Type>::setValue (const Type& newValue, UndoManager* undoManagerToUse)
{
if (cachedValue != newValue || isUsingDefault())
{
cachedValue = newValue;
targetTree.setProperty (targetProperty, VariantConverter<Type>::toVar (newValue), undoManagerToUse);
}
}
template <typename Type>
inline void CachedValue<Type>::resetToDefault()
{
resetToDefault (undoManager);
}
template <typename Type>
inline void CachedValue<Type>::resetToDefault (UndoManager* undoManagerToUse)
{
targetTree.removeProperty (targetProperty, undoManagerToUse);
forceUpdateOfCachedValue();
}
template <typename Type>
inline void CachedValue<Type>::referTo (ValueTree& v, const Identifier& i, UndoManager* um)
{
referToWithDefault (v, i, um, Type());
}
template <typename Type>
inline void CachedValue<Type>::referTo (ValueTree& v, const Identifier& i, UndoManager* um, const Type& defaultVal)
{
referToWithDefault (v, i, um, defaultVal);
}
template <typename Type>
inline void CachedValue<Type>::forceUpdateOfCachedValue()
{
cachedValue = getTypedValue();
}
template <typename Type>
inline void CachedValue<Type>::referToWithDefault (ValueTree& v, const Identifier& i, UndoManager* um, const Type& defaultVal)
{
targetTree.removeListener (this);
targetTree = v;
targetProperty = i;
undoManager = um;
defaultValue = defaultVal;
cachedValue = getTypedValue();
targetTree.addListener (this);
}
template <typename Type>
inline Type CachedValue<Type>::getTypedValue() const
{
if (const var* property = targetTree.getPropertyPointer (targetProperty))
return VariantConverter<Type>::fromVar (*property);
return defaultValue;
}
template <typename Type>
inline void CachedValue<Type>::valueTreePropertyChanged (ValueTree& changedTree, const Identifier& changedProperty)
{
if (changedProperty == targetProperty && targetTree == changedTree)
forceUpdateOfCachedValue();
}
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
This class acts as a typed wrapper around a property inside a ValueTree.
A CachedValue provides an easy way to read and write a ValueTree property with
a chosen type. So for example a CachedValue<int> allows you to read or write the
property as an int, and a CachedValue<String> lets you work with it as a String.
It also allows efficient access to the value, by caching a copy of it in the
type that is being used.
You can give the CachedValue an optional UndoManager which it will use when writing
to the underlying ValueTree.
If the property inside the ValueTree is missing, the CachedValue will automatically
return an optional default value, which can be specified when initialising the CachedValue.
To create one, you can either use the constructor to attach the CachedValue to a
ValueTree, or can create an uninitialised CachedValue with its default constructor and
then attach it later with the referTo() methods.
Common types like String, int, double which can be easily converted to a var should work
out-of-the-box, but if you want to use more complex custom types, you may need to implement
some template specialisations of VariantConverter which this class uses to convert between
the type and the ValueTree's internal var.
@tags{DataStructures}
*/
template <typename Type>
class CachedValue : private ValueTree::Listener
{
public:
//==============================================================================
/** Default constructor.
Creates a default CachedValue not referring to any property. To initialise the
object, call one of the referTo() methods.
*/
CachedValue();
/** Constructor.
Creates a CachedValue referring to a Value property inside a ValueTree.
If you use this constructor, the fallback value will be a default-constructed
instance of Type.
@param tree The ValueTree containing the property
@param propertyID The identifier of the property
@param undoManager The UndoManager to use when writing to the property
*/
CachedValue (ValueTree& tree, const Identifier& propertyID,
UndoManager* undoManager);
/** Constructor.
Creates a default Cached Value referring to a Value property inside a ValueTree,
and specifies a fallback value to use if the property does not exist.
@param tree The ValueTree containing the property
@param propertyID The identifier of the property
@param undoManager The UndoManager to use when writing to the property
@param defaultToUse The fallback default value to use.
*/
CachedValue (ValueTree& tree, const Identifier& propertyID,
UndoManager* undoManager, const Type& defaultToUse);
//==============================================================================
/** Returns the current value of the property. If the property does not exist,
returns the fallback default value.
This is the same as calling get().
*/
operator Type() const noexcept { return cachedValue; }
/** Returns the current value of the property. If the property does not exist,
returns the fallback default value.
*/
Type get() const noexcept { return cachedValue; }
/** Dereference operator. Provides direct access to the property. */
Type& operator*() noexcept { return cachedValue; }
/** Dereference operator. Provides direct access to members of the property
if it is of object type.
*/
Type* operator->() noexcept { return &cachedValue; }
/** Returns true if the current value of the property (or the fallback value)
is equal to other.
*/
template <typename OtherType>
bool operator== (const OtherType& other) const { return cachedValue == other; }
/** Returns true if the current value of the property (or the fallback value)
is not equal to other.
*/
template <typename OtherType>
bool operator!= (const OtherType& other) const { return cachedValue != other; }
//==============================================================================
/** Returns the current property as a Value object. */
Value getPropertyAsValue();
/** Returns true if the current property does not exist and the CachedValue is using
the fallback default value instead.
*/
bool isUsingDefault() const;
/** Returns the current fallback default value. */
Type getDefault() const { return defaultValue; }
//==============================================================================
/** Sets the property. This will actually modify the property in the referenced ValueTree. */
CachedValue& operator= (const Type& newValue);
/** Sets the property. This will actually modify the property in the referenced ValueTree. */
void setValue (const Type& newValue, UndoManager* undoManagerToUse);
/** Removes the property from the referenced ValueTree and makes the CachedValue
return the fallback default value instead.
*/
void resetToDefault();
/** Removes the property from the referenced ValueTree and makes the CachedValue
return the fallback default value instead.
*/
void resetToDefault (UndoManager* undoManagerToUse);
/** Resets the fallback default value. */
void setDefault (const Type& value) { defaultValue = value; }
//==============================================================================
/** Makes the CachedValue refer to the specified property inside the given ValueTree. */
void referTo (ValueTree& tree, const Identifier& property, UndoManager* um);
/** Makes the CachedValue refer to the specified property inside the given ValueTree,
and specifies a fallback value to use if the property does not exist.
*/
void referTo (ValueTree& tree, const Identifier& property, UndoManager* um, const Type& defaultVal);
/** Force an update in case the referenced property has been changed from elsewhere.
Note: The CachedValue is a ValueTree::Listener and therefore will be informed of
changes of the referenced property anyway (and update itself). But this may happen
asynchronously. forceUpdateOfCachedValue() forces an update immediately.
*/
void forceUpdateOfCachedValue();
//==============================================================================
/** Returns a reference to the ValueTree containing the referenced property. */
ValueTree& getValueTree() noexcept { return targetTree; }
/** Returns the property ID of the referenced property. */
const Identifier& getPropertyID() const noexcept { return targetProperty; }
/** Returns the UndoManager that is being used. */
UndoManager* getUndoManager() noexcept { return undoManager; }
private:
//==============================================================================
ValueTree targetTree;
Identifier targetProperty;
UndoManager* undoManager = nullptr;
Type defaultValue;
Type cachedValue;
//==============================================================================
void referToWithDefault (ValueTree&, const Identifier&, UndoManager*, const Type&);
Type getTypedValue() const;
void valueTreePropertyChanged (ValueTree& changedTree, const Identifier& changedProperty) override;
//==============================================================================
JUCE_DECLARE_WEAK_REFERENCEABLE (CachedValue)
JUCE_DECLARE_NON_COPYABLE (CachedValue)
};
//==============================================================================
template <typename Type>
inline CachedValue<Type>::CachedValue() = default;
template <typename Type>
inline CachedValue<Type>::CachedValue (ValueTree& v, const Identifier& i, UndoManager* um)
: targetTree (v), targetProperty (i), undoManager (um),
defaultValue(), cachedValue (getTypedValue())
{
targetTree.addListener (this);
}
template <typename Type>
inline CachedValue<Type>::CachedValue (ValueTree& v, const Identifier& i, UndoManager* um, const Type& defaultToUse)
: targetTree (v), targetProperty (i), undoManager (um),
defaultValue (defaultToUse), cachedValue (getTypedValue())
{
targetTree.addListener (this);
}
template <typename Type>
inline Value CachedValue<Type>::getPropertyAsValue()
{
return targetTree.getPropertyAsValue (targetProperty, undoManager);
}
template <typename Type>
inline bool CachedValue<Type>::isUsingDefault() const
{
return ! targetTree.hasProperty (targetProperty);
}
template <typename Type>
inline CachedValue<Type>& CachedValue<Type>::operator= (const Type& newValue)
{
setValue (newValue, undoManager);
return *this;
}
template <typename Type>
inline void CachedValue<Type>::setValue (const Type& newValue, UndoManager* undoManagerToUse)
{
if (cachedValue != newValue || isUsingDefault())
{
cachedValue = newValue;
targetTree.setProperty (targetProperty, VariantConverter<Type>::toVar (newValue), undoManagerToUse);
}
}
template <typename Type>
inline void CachedValue<Type>::resetToDefault()
{
resetToDefault (undoManager);
}
template <typename Type>
inline void CachedValue<Type>::resetToDefault (UndoManager* undoManagerToUse)
{
targetTree.removeProperty (targetProperty, undoManagerToUse);
forceUpdateOfCachedValue();
}
template <typename Type>
inline void CachedValue<Type>::referTo (ValueTree& v, const Identifier& i, UndoManager* um)
{
referToWithDefault (v, i, um, Type());
}
template <typename Type>
inline void CachedValue<Type>::referTo (ValueTree& v, const Identifier& i, UndoManager* um, const Type& defaultVal)
{
referToWithDefault (v, i, um, defaultVal);
}
template <typename Type>
inline void CachedValue<Type>::forceUpdateOfCachedValue()
{
cachedValue = getTypedValue();
}
template <typename Type>
inline void CachedValue<Type>::referToWithDefault (ValueTree& v, const Identifier& i, UndoManager* um, const Type& defaultVal)
{
targetTree.removeListener (this);
targetTree = v;
targetProperty = i;
undoManager = um;
defaultValue = defaultVal;
cachedValue = getTypedValue();
targetTree.addListener (this);
}
template <typename Type>
inline Type CachedValue<Type>::getTypedValue() const
{
if (const var* property = targetTree.getPropertyPointer (targetProperty))
return VariantConverter<Type>::fromVar (*property);
return defaultValue;
}
template <typename Type>
inline void CachedValue<Type>::valueTreePropertyChanged (ValueTree& changedTree, const Identifier& changedProperty)
{
if (changedProperty == targetProperty && targetTree == changedTree)
forceUpdateOfCachedValue();
}
} // namespace juce

View File

@ -1,241 +1,241 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
Value::ValueSource::ValueSource()
{
}
Value::ValueSource::~ValueSource()
{
cancelPendingUpdate();
}
void Value::ValueSource::handleAsyncUpdate()
{
sendChangeMessage (true);
}
void Value::ValueSource::sendChangeMessage (const bool synchronous)
{
const int numListeners = valuesWithListeners.size();
if (numListeners > 0)
{
if (synchronous)
{
const ReferenceCountedObjectPtr<ValueSource> localRef (this);
cancelPendingUpdate();
for (int i = numListeners; --i >= 0;)
if (Value* const v = valuesWithListeners[i])
v->callListeners();
}
else
{
triggerAsyncUpdate();
}
}
}
//==============================================================================
class SimpleValueSource : public Value::ValueSource
{
public:
SimpleValueSource()
{
}
SimpleValueSource (const var& initialValue)
: value (initialValue)
{
}
var getValue() const override
{
return value;
}
void setValue (const var& newValue) override
{
if (! newValue.equalsWithSameType (value))
{
value = newValue;
sendChangeMessage (false);
}
}
private:
var value;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SimpleValueSource)
};
//==============================================================================
Value::Value() : value (new SimpleValueSource())
{
}
Value::Value (ValueSource* const v) : value (v)
{
jassert (v != nullptr);
}
Value::Value (const var& initialValue) : value (new SimpleValueSource (initialValue))
{
}
Value::Value (const Value& other) : value (other.value)
{
}
Value::Value (Value&& other) noexcept
{
// moving a Value with listeners will lose those listeners, which
// probably isn't what you wanted to happen!
jassert (other.listeners.size() == 0);
other.removeFromListenerList();
value = std::move (other.value);
}
Value& Value::operator= (Value&& other) noexcept
{
// moving a Value with listeners will lose those listeners, which
// probably isn't what you wanted to happen!
jassert (other.listeners.size() == 0);
other.removeFromListenerList();
value = std::move (other.value);
return *this;
}
Value::~Value()
{
removeFromListenerList();
}
void Value::removeFromListenerList()
{
if (listeners.size() > 0 && value != nullptr) // may be nullptr after a move operation
value->valuesWithListeners.removeValue (this);
}
//==============================================================================
var Value::getValue() const
{
return value->getValue();
}
Value::operator var() const
{
return value->getValue();
}
void Value::setValue (const var& newValue)
{
value->setValue (newValue);
}
String Value::toString() const
{
return value->getValue().toString();
}
Value& Value::operator= (const var& newValue)
{
value->setValue (newValue);
return *this;
}
void Value::referTo (const Value& valueToReferTo)
{
if (valueToReferTo.value != value)
{
if (listeners.size() > 0)
{
value->valuesWithListeners.removeValue (this);
valueToReferTo.value->valuesWithListeners.add (this);
}
value = valueToReferTo.value;
callListeners();
}
}
bool Value::refersToSameSourceAs (const Value& other) const
{
return value == other.value;
}
bool Value::operator== (const Value& other) const
{
return value == other.value || value->getValue() == other.getValue();
}
bool Value::operator!= (const Value& other) const
{
return value != other.value && value->getValue() != other.getValue();
}
//==============================================================================
void Value::addListener (Value::Listener* listener)
{
if (listener != nullptr)
{
if (listeners.size() == 0)
value->valuesWithListeners.add (this);
listeners.add (listener);
}
}
void Value::removeListener (Value::Listener* listener)
{
listeners.remove (listener);
if (listeners.size() == 0)
value->valuesWithListeners.removeValue (this);
}
void Value::callListeners()
{
if (listeners.size() > 0)
{
Value v (*this); // (create a copy in case this gets deleted by a callback)
listeners.call ([&] (Value::Listener& l) { l.valueChanged (v); });
}
}
OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const Value& value)
{
return stream << value.toString();
}
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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.
==============================================================================
*/
namespace juce
{
Value::ValueSource::ValueSource()
{
}
Value::ValueSource::~ValueSource()
{
cancelPendingUpdate();
}
void Value::ValueSource::handleAsyncUpdate()
{
sendChangeMessage (true);
}
void Value::ValueSource::sendChangeMessage (const bool synchronous)
{
const int numListeners = valuesWithListeners.size();
if (numListeners > 0)
{
if (synchronous)
{
const ReferenceCountedObjectPtr<ValueSource> localRef (this);
cancelPendingUpdate();
for (int i = numListeners; --i >= 0;)
if (Value* const v = valuesWithListeners[i])
v->callListeners();
}
else
{
triggerAsyncUpdate();
}
}
}
//==============================================================================
class SimpleValueSource : public Value::ValueSource
{
public:
SimpleValueSource()
{
}
SimpleValueSource (const var& initialValue)
: value (initialValue)
{
}
var getValue() const override
{
return value;
}
void setValue (const var& newValue) override
{
if (! newValue.equalsWithSameType (value))
{
value = newValue;
sendChangeMessage (false);
}
}
private:
var value;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SimpleValueSource)
};
//==============================================================================
Value::Value() : value (new SimpleValueSource())
{
}
Value::Value (ValueSource* const v) : value (v)
{
jassert (v != nullptr);
}
Value::Value (const var& initialValue) : value (new SimpleValueSource (initialValue))
{
}
Value::Value (const Value& other) : value (other.value)
{
}
Value::Value (Value&& other) noexcept
{
// moving a Value with listeners will lose those listeners, which
// probably isn't what you wanted to happen!
jassert (other.listeners.size() == 0);
other.removeFromListenerList();
value = std::move (other.value);
}
Value& Value::operator= (Value&& other) noexcept
{
// moving a Value with listeners will lose those listeners, which
// probably isn't what you wanted to happen!
jassert (other.listeners.size() == 0);
other.removeFromListenerList();
value = std::move (other.value);
return *this;
}
Value::~Value()
{
removeFromListenerList();
}
void Value::removeFromListenerList()
{
if (listeners.size() > 0 && value != nullptr) // may be nullptr after a move operation
value->valuesWithListeners.removeValue (this);
}
//==============================================================================
var Value::getValue() const
{
return value->getValue();
}
Value::operator var() const
{
return value->getValue();
}
void Value::setValue (const var& newValue)
{
value->setValue (newValue);
}
String Value::toString() const
{
return value->getValue().toString();
}
Value& Value::operator= (const var& newValue)
{
value->setValue (newValue);
return *this;
}
void Value::referTo (const Value& valueToReferTo)
{
if (valueToReferTo.value != value)
{
if (listeners.size() > 0)
{
value->valuesWithListeners.removeValue (this);
valueToReferTo.value->valuesWithListeners.add (this);
}
value = valueToReferTo.value;
callListeners();
}
}
bool Value::refersToSameSourceAs (const Value& other) const
{
return value == other.value;
}
bool Value::operator== (const Value& other) const
{
return value == other.value || value->getValue() == other.getValue();
}
bool Value::operator!= (const Value& other) const
{
return value != other.value && value->getValue() != other.getValue();
}
//==============================================================================
void Value::addListener (Value::Listener* listener)
{
if (listener != nullptr)
{
if (listeners.size() == 0)
value->valuesWithListeners.add (this);
listeners.add (listener);
}
}
void Value::removeListener (Value::Listener* listener)
{
listeners.remove (listener);
if (listeners.size() == 0)
value->valuesWithListeners.removeValue (this);
}
void Value::callListeners()
{
if (listeners.size() > 0)
{
Value v (*this); // (create a copy in case this gets deleted by a callback)
listeners.call ([&] (Value::Listener& l) { l.valueChanged (v); });
}
}
OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const Value& value)
{
return stream << value.toString();
}
} // namespace juce

View File

@ -1,242 +1,243 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Represents a shared variant value.
A Value object contains a reference to a var object, and can get and set its value.
Listeners can be attached to be told when the value is changed.
The Value class is a wrapper around a shared, reference-counted underlying data
object - this means that multiple Value objects can all refer to the same piece of
data, allowing all of them to be notified when any of them changes it.
When you create a Value with its default constructor, it acts as a wrapper around a
simple var object, but by creating a Value that refers to a custom subclass of ValueSource,
you can map the Value onto any kind of underlying data.
Important note! The Value class is not thread-safe! If you're accessing one from
multiple threads, then you'll need to use your own synchronisation around any code
that accesses it.
@tags{DataStructures}
*/
class JUCE_API Value final
{
public:
//==============================================================================
/** Creates an empty Value, containing a void var. */
Value();
/** Creates a Value that refers to the same value as another one.
Note that this doesn't make a copy of the other value - both this and the other
Value will share the same underlying value, so that when either one alters it, both
will see it change.
*/
Value (const Value& other);
/** Creates a Value that is set to the specified value. */
explicit Value (const var& initialValue);
/** Move constructor */
Value (Value&&) noexcept;
/** Destructor. */
~Value();
//==============================================================================
/** Returns the current value. */
var getValue() const;
/** Returns the current value. */
operator var() const;
/** Returns the value as a string.
This is a shortcut for "myValue.getValue().toString()".
*/
String toString() const;
/** Sets the current value.
You can also use operator= to set the value.
If there are any listeners registered, they will be notified of the
change asynchronously.
*/
void setValue (const var& newValue);
/** Sets the current value.
This is the same as calling setValue().
If there are any listeners registered, they will be notified of the
change asynchronously.
*/
Value& operator= (const var& newValue);
/** Move assignment operator */
Value& operator= (Value&&) noexcept;
/** Makes this object refer to the same underlying ValueSource as another one.
Once this object has been connected to another one, changing either one
will update the other.
Existing listeners will still be registered after you call this method, and
they'll continue to receive messages when the new value changes.
*/
void referTo (const Value& valueToReferTo);
/** Returns true if this value and the other one are references to the same value.
*/
bool refersToSameSourceAs (const Value& other) const;
/** Compares two values.
This is a compare-by-value comparison, so is effectively the same as
saying (this->getValue() == other.getValue()).
*/
bool operator== (const Value& other) const;
/** Compares two values.
This is a compare-by-value comparison, so is effectively the same as
saying (this->getValue() != other.getValue()).
*/
bool operator!= (const Value& other) const;
//==============================================================================
/** Receives callbacks when a Value object changes.
@see Value::addListener
*/
class JUCE_API Listener
{
public:
Listener() = default;
virtual ~Listener() = default;
/** Called when a Value object is changed.
Note that the Value object passed as a parameter may not be exactly the same
object that you registered the listener with - it might be a copy that refers
to the same underlying ValueSource. To find out, you can call Value::refersToSameSourceAs().
*/
virtual void valueChanged (Value& value) = 0;
};
/** Adds a listener to receive callbacks when the value changes.
The listener is added to this specific Value object, and not to the shared
object that it refers to. When this object is deleted, all the listeners will
be lost, even if other references to the same Value still exist. So when you're
adding a listener, make sure that you add it to a Value instance that will last
for as long as you need the listener. In general, you'd never want to add a listener
to a local stack-based Value, but more likely to one that's a member variable.
@see removeListener
*/
void addListener (Listener* listener);
/** Removes a listener that was previously added with addListener(). */
void removeListener (Listener* listener);
//==============================================================================
/**
Used internally by the Value class as the base class for its shared value objects.
The Value class is essentially a reference-counted pointer to a shared instance
of a ValueSource object. If you're feeling adventurous, you can create your own custom
ValueSource classes to allow Value objects to represent your own custom data items.
*/
class JUCE_API ValueSource : public ReferenceCountedObject,
private AsyncUpdater
{
public:
ValueSource();
~ValueSource() override;
/** Returns the current value of this object. */
virtual var getValue() const = 0;
/** Changes the current value.
This must also trigger a change message if the value actually changes.
*/
virtual void setValue (const var& newValue) = 0;
/** Delivers a change message to all the listeners that are registered with
this value.
If dispatchSynchronously is true, the method will call all the listeners
before returning; otherwise it'll dispatch a message and make the call later.
*/
void sendChangeMessage (bool dispatchSynchronously);
protected:
//==============================================================================
friend class Value;
SortedSet<Value*> valuesWithListeners;
private:
void handleAsyncUpdate() override;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueSource)
};
//==============================================================================
/** Creates a Value object that uses this valueSource object as its underlying data. */
explicit Value (ValueSource* valueSource);
/** Returns the ValueSource that this value is referring to. */
ValueSource& getValueSource() noexcept { return *value; }
private:
//==============================================================================
friend class ValueSource;
ReferenceCountedObjectPtr<ValueSource> value;
ListenerList<Listener> listeners;
void callListeners();
void removeFromListenerList();
// This is disallowed to avoid confusion about whether it should
// do a by-value or by-reference copy.
Value& operator= (const Value&) = delete;
// This declaration prevents accidental construction from an integer of 0,
// which is possible in some compilers via an implicit cast to a pointer.
explicit Value (void*) = delete;
};
/** Writes a Value to an OutputStream as a UTF8 string. */
OutputStream& JUCE_CALLTYPE operator<< (OutputStream&, const Value&);
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Represents a shared variant value.
A Value object contains a reference to a var object, and can get and set its value.
Listeners can be attached to be told when the value is changed.
The Value class is a wrapper around a shared, reference-counted underlying data
object - this means that multiple Value objects can all refer to the same piece of
data, allowing all of them to be notified when any of them changes it.
When you create a Value with its default constructor, it acts as a wrapper around a
simple var object, but by creating a Value that refers to a custom subclass of ValueSource,
you can map the Value onto any kind of underlying data.
Important note! The Value class is not thread-safe! If you're accessing one from
multiple threads, then you'll need to use your own synchronisation around any code
that accesses it.
@tags{DataStructures}
*/
class JUCE_API Value final
{
public:
//==============================================================================
/** Creates an empty Value, containing a void var. */
Value();
/** Creates a Value that refers to the same value as another one.
Note that this doesn't make a copy of the other value - both this and the other
Value will share the same underlying value, so that when either one alters it, both
will see it change.
*/
Value (const Value& other);
/** Creates a Value that is set to the specified value. */
explicit Value (const var& initialValue);
/** Move constructor */
Value (Value&&) noexcept;
/** Destructor. */
~Value();
//==============================================================================
/** Returns the current value. */
var getValue() const;
/** Returns the current value. */
operator var() const;
/** Returns the value as a string.
This is a shortcut for "myValue.getValue().toString()".
*/
String toString() const;
/** Sets the current value.
You can also use operator= to set the value.
If there are any listeners registered, they will be notified of the
change asynchronously.
*/
void setValue (const var& newValue);
/** Sets the current value.
This is the same as calling setValue().
If there are any listeners registered, they will be notified of the
change asynchronously.
*/
Value& operator= (const var& newValue);
/** Move assignment operator */
Value& operator= (Value&&) noexcept;
/** Makes this object refer to the same underlying ValueSource as another one.
Once this object has been connected to another one, changing either one
will update the other.
Existing listeners will still be registered after you call this method, and
they'll continue to receive messages when the new value changes.
*/
void referTo (const Value& valueToReferTo);
/** Returns true if this object and the other one use the same underlying
ValueSource object.
*/
bool refersToSameSourceAs (const Value& other) const;
/** Compares two values.
This is a compare-by-value comparison, so is effectively the same as
saying (this->getValue() == other.getValue()).
*/
bool operator== (const Value& other) const;
/** Compares two values.
This is a compare-by-value comparison, so is effectively the same as
saying (this->getValue() != other.getValue()).
*/
bool operator!= (const Value& other) const;
//==============================================================================
/** Receives callbacks when a Value object changes.
@see Value::addListener
*/
class JUCE_API Listener
{
public:
Listener() = default;
virtual ~Listener() = default;
/** Called when a Value object is changed.
Note that the Value object passed as a parameter may not be exactly the same
object that you registered the listener with - it might be a copy that refers
to the same underlying ValueSource. To find out, you can call Value::refersToSameSourceAs().
*/
virtual void valueChanged (Value& value) = 0;
};
/** Adds a listener to receive callbacks when the value changes.
The listener is added to this specific Value object, and not to the shared
object that it refers to. When this object is deleted, all the listeners will
be lost, even if other references to the same Value still exist. So when you're
adding a listener, make sure that you add it to a Value instance that will last
for as long as you need the listener. In general, you'd never want to add a listener
to a local stack-based Value, but more likely to one that's a member variable.
@see removeListener
*/
void addListener (Listener* listener);
/** Removes a listener that was previously added with addListener(). */
void removeListener (Listener* listener);
//==============================================================================
/**
Used internally by the Value class as the base class for its shared value objects.
The Value class is essentially a reference-counted pointer to a shared instance
of a ValueSource object. If you're feeling adventurous, you can create your own custom
ValueSource classes to allow Value objects to represent your own custom data items.
*/
class JUCE_API ValueSource : public ReferenceCountedObject,
private AsyncUpdater
{
public:
ValueSource();
~ValueSource() override;
/** Returns the current value of this object. */
virtual var getValue() const = 0;
/** Changes the current value.
This must also trigger a change message if the value actually changes.
*/
virtual void setValue (const var& newValue) = 0;
/** Delivers a change message to all the listeners that are registered with
this value.
If dispatchSynchronously is true, the method will call all the listeners
before returning; otherwise it'll dispatch a message and make the call later.
*/
void sendChangeMessage (bool dispatchSynchronously);
protected:
//==============================================================================
friend class Value;
SortedSet<Value*> valuesWithListeners;
private:
void handleAsyncUpdate() override;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueSource)
};
//==============================================================================
/** Creates a Value object that uses this valueSource object as its underlying data. */
explicit Value (ValueSource* valueSource);
/** Returns the ValueSource that this value is referring to. */
ValueSource& getValueSource() noexcept { return *value; }
private:
//==============================================================================
friend class ValueSource;
ReferenceCountedObjectPtr<ValueSource> value;
ListenerList<Listener> listeners;
void callListeners();
void removeFromListenerList();
// This is disallowed to avoid confusion about whether it should
// do a by-value or by-reference copy.
Value& operator= (const Value&) = delete;
// This declaration prevents accidental construction from an integer of 0,
// which is possible in some compilers via an implicit cast to a pointer.
explicit Value (void*) = delete;
};
/** Writes a Value to an OutputStream as a UTF8 string. */
OutputStream& JUCE_CALLTYPE operator<< (OutputStream&, const Value&);
} // namespace juce

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,334 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
This class acts as a wrapper around a property inside a ValueTree.
If the property inside the ValueTree is missing it will return a default value,
which can be specified in the constructor or by calling setDefault().
@tags{DataStructures}
*/
class JUCE_API ValueTreePropertyWithDefault : private Value::Listener
{
public:
//==============================================================================
/** Creates an uninitialised ValueTreePropertyWithDefault object.
Initialise it using one of the referTo() methods.
*/
ValueTreePropertyWithDefault() = default;
/** Creates a ValueTreePropertyWithDefault object for the specified property.
The default value will be an empty var.
*/
ValueTreePropertyWithDefault (ValueTree& tree,
const Identifier& propertyID,
UndoManager* um)
{
referTo (tree, propertyID, um);
}
/** Creates an ValueTreePropertyWithDefault object for the specified property.
The default value will be defaultToUse.
*/
ValueTreePropertyWithDefault (ValueTree& tree,
const Identifier& propertyID,
UndoManager* um,
var defaultToUse)
{
referTo (tree, propertyID, um, defaultToUse);
}
/** Creates a ValueTreePropertyWithDefault object for the specified property.
The default value will be defaultToUse.
Use this constructor if the underlying var object being controlled is an array and
it will handle the conversion to/from a delimited String that can be written to
XML format.
*/
ValueTreePropertyWithDefault (ValueTree& tree,
const Identifier& propertyID,
UndoManager* um,
var defaultToUse,
StringRef arrayDelimiter)
{
referTo (tree, propertyID, um, defaultToUse, arrayDelimiter);
}
/** Creates a ValueTreePropertyWithDefault object from another ValueTreePropertyWithDefault object. */
ValueTreePropertyWithDefault (const ValueTreePropertyWithDefault& other)
{
referToWithDefault (other.targetTree,
other.targetProperty,
other.undoManager,
other.defaultValue,
other.delimiter);
}
/** Destructor. */
~ValueTreePropertyWithDefault() override
{
defaultValue.removeListener (this);
}
//==============================================================================
/** Returns the current value of the property.
If the property does not exist this returns the default value.
*/
var get() const noexcept
{
if (isUsingDefault())
return defaultValue;
if (delimiter.isNotEmpty())
return delimitedStringToVarArray (targetTree[targetProperty].toString(), delimiter);
return targetTree[targetProperty];
}
/** Returns the current property as a Value object. */
Value getPropertyAsValue() { return targetTree.getPropertyAsValue (targetProperty, undoManager); }
/** Returns the current default value. */
var getDefault() const { return defaultValue; }
/** Sets the default value to a new var. */
void setDefault (const var& newDefault) { defaultValue = newDefault; }
/** Returns true if the property does not exist in the referenced ValueTree. */
bool isUsingDefault() const { return ! targetTree.hasProperty (targetProperty); }
/** Removes the property from the referenced ValueTree. */
void resetToDefault() noexcept { targetTree.removeProperty (targetProperty, nullptr); }
/** You can assign a lambda to this callback and it will called when the default
value is changed.
@see setDefault
*/
std::function<void()> onDefaultChange;
//==============================================================================
/** Sets the property and returns the new ValueTreePropertyWithDefault.
This will modify the property in the referenced ValueTree.
*/
ValueTreePropertyWithDefault& operator= (const var& newValue)
{
setValue (newValue, undoManager);
return *this;
}
/** Sets the property.
This will modify the property in the referenced ValueTree.
*/
void setValue (const var& newValue, UndoManager* undoManagerToUse)
{
if (auto* array = newValue.getArray())
targetTree.setProperty (targetProperty, varArrayToDelimitedString (*array, delimiter), undoManagerToUse);
else
targetTree.setProperty (targetProperty, newValue, undoManagerToUse);
}
//==============================================================================
/** Makes the ValueTreePropertyWithDefault refer to the specified property inside
the given ValueTree.
The default value will be an empty var.
*/
void referTo (ValueTree tree,
const Identifier& property,
UndoManager* um)
{
referToWithDefault (tree,
property,
um,
Value (new SynchronousValueSource (var())),
{});
}
/** Makes the ValueTreePropertyWithDefault refer to the specified property inside
the given ValueTree.
The default value will be defaultVal.
*/
void referTo (ValueTree tree,
const Identifier& property,
UndoManager* um,
var defaultVal)
{
referToWithDefault (tree,
property,
um,
Value (new SynchronousValueSource (defaultVal)),
{});
}
/** Makes the ValueTreePropertyWithDefault refer to the specified property inside
the given ValueTree.
The default value will be defaultVal.
*/
void referTo (ValueTree tree,
const Identifier& property,
UndoManager* um,
var defaultVal,
StringRef arrayDelimiter)
{
referToWithDefault (tree,
property,
um,
Value (new SynchronousValueSource (defaultVal)),
arrayDelimiter);
}
//==============================================================================
/** Returns a reference to the ValueTree containing the referenced property. */
ValueTree& getValueTree() noexcept { return targetTree; }
/** Returns the property ID of the referenced property. */
Identifier& getPropertyID() noexcept { return targetProperty; }
/** Returns the UndoManager that is being used. */
UndoManager* getUndoManager() noexcept { return undoManager; }
//==============================================================================
ValueTreePropertyWithDefault& operator= (const ValueTreePropertyWithDefault& other)
{
referToWithDefault (other.targetTree,
other.targetProperty,
other.undoManager,
other.defaultValue,
other.delimiter);
return *this;
}
private:
//==============================================================================
class SynchronousValueSource : public Value::ValueSource
{
public:
explicit SynchronousValueSource (const var& initialValue)
: value (initialValue)
{
}
var getValue() const override
{
return value;
}
void setValue (const var& newValue) override
{
if (! newValue.equalsWithSameType (value))
{
value = newValue;
sendChangeMessage (true);
}
}
private:
var value;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SynchronousValueSource)
};
//==============================================================================
static String varArrayToDelimitedString (const Array<var>& input, StringRef delim)
{
// if you are trying to control a var that is an array then you need to
// set a delimiter string that will be used when writing to XML!
jassert (delim.isNotEmpty());
StringArray elements;
for (auto& v : input)
elements.add (v.toString());
return elements.joinIntoString (delim);
}
static Array<var> delimitedStringToVarArray (StringRef input, StringRef delim)
{
Array<var> arr;
for (auto t : StringArray::fromTokens (input, delim, {}))
arr.add (t);
return arr;
}
void valueChanged (Value&) override
{
if (onDefaultChange != nullptr)
onDefaultChange();
}
void referToWithDefault (ValueTree v,
const Identifier& i,
UndoManager* um,
const Value& defaultVal,
StringRef del)
{
targetTree = v;
targetProperty = i;
undoManager = um;
defaultValue.referTo (defaultVal);
delimiter = del;
defaultValue.addListener (this);
}
//==============================================================================
ValueTree targetTree;
Identifier targetProperty;
UndoManager* undoManager = nullptr;
Value defaultValue;
String delimiter;
//==============================================================================
JUCE_LEAK_DETECTOR (ValueTreePropertyWithDefault)
};
//==============================================================================
#ifndef DOXYGEN
using ValueWithDefault [[deprecated ("This class has been renamed to better describe what is does. "
"This declaration is here for backwards compatibility and new "
"code should use the new class name.")]]
= ValueTreePropertyWithDefault;
#endif
} // namespace juce

View File

@ -0,0 +1,97 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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.
==============================================================================
*/
namespace juce
{
class ValueTreePropertyWithDefaultTests : public UnitTest
{
public:
ValueTreePropertyWithDefaultTests()
: UnitTest ("ValueTreePropertyWithDefault", UnitTestCategories::values)
{}
void runTest() override
{
beginTest ("default constructor");
{
ValueTreePropertyWithDefault value;
expect (value.isUsingDefault());
expect (value.get() == var());
}
beginTest ("missing property");
{
ValueTree t ("root");
ValueTreePropertyWithDefault value (t, "testKey", nullptr, "default");
expect (value.isUsingDefault());
expectEquals (value.get().toString(), String ("default"));
}
beginTest ("non-empty property");
{
ValueTree t ("root");
t.setProperty ("testKey", "non-default", nullptr);
ValueTreePropertyWithDefault value (t, "testKey", nullptr, "default");
expect (! value.isUsingDefault());
expectEquals (value.get().toString(), String ("non-default"));
}
beginTest ("set default");
{
ValueTree t ("root");
ValueTreePropertyWithDefault value (t, "testkey", nullptr);
value.setDefault ("default");
expect (value.isUsingDefault());
expectEquals (value.get().toString(), String ("default"));
}
beginTest ("set value");
{
ValueTree t ("root");
t.setProperty ("testkey", "testvalue", nullptr);
ValueTreePropertyWithDefault value (t, "testkey", nullptr, "default");
value = "newvalue";
expect (! value.isUsingDefault());
expectEquals (t["testkey"].toString(), String ("newvalue"));
value.resetToDefault();
expect (value.isUsingDefault());
expect (t["testkey"] == var());
}
}
};
static ValueTreePropertyWithDefaultTests valueTreePropertyWithDefaultTests;
} // namespace juce

View File

@ -1,242 +1,242 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
namespace ValueTreeSynchroniserHelpers
{
enum ChangeType
{
propertyChanged = 1,
fullSync = 2,
childAdded = 3,
childRemoved = 4,
childMoved = 5,
propertyRemoved = 6
};
static void getValueTreePath (ValueTree v, const ValueTree& topLevelTree, Array<int>& path)
{
while (v != topLevelTree)
{
ValueTree parent (v.getParent());
if (! parent.isValid())
break;
path.add (parent.indexOf (v));
v = parent;
}
}
static void writeHeader (MemoryOutputStream& stream, ChangeType type)
{
stream.writeByte ((char) type);
}
static void writeHeader (ValueTreeSynchroniser& target, MemoryOutputStream& stream,
ChangeType type, ValueTree v)
{
writeHeader (stream, type);
Array<int> path;
getValueTreePath (v, target.getRoot(), path);
stream.writeCompressedInt (path.size());
for (int i = path.size(); --i >= 0;)
stream.writeCompressedInt (path.getUnchecked(i));
}
static ValueTree readSubTreeLocation (MemoryInputStream& input, ValueTree v)
{
const int numLevels = input.readCompressedInt();
if (! isPositiveAndBelow (numLevels, 65536)) // sanity-check
return {};
for (int i = numLevels; --i >= 0;)
{
const int index = input.readCompressedInt();
if (! isPositiveAndBelow (index, v.getNumChildren()))
return {};
v = v.getChild (index);
}
return v;
}
}
ValueTreeSynchroniser::ValueTreeSynchroniser (const ValueTree& tree) : valueTree (tree)
{
valueTree.addListener (this);
}
ValueTreeSynchroniser::~ValueTreeSynchroniser()
{
valueTree.removeListener (this);
}
void ValueTreeSynchroniser::sendFullSyncCallback()
{
MemoryOutputStream m;
writeHeader (m, ValueTreeSynchroniserHelpers::fullSync);
valueTree.writeToStream (m);
stateChanged (m.getData(), m.getDataSize());
}
void ValueTreeSynchroniser::valueTreePropertyChanged (ValueTree& vt, const Identifier& property)
{
MemoryOutputStream m;
if (auto* value = vt.getPropertyPointer (property))
{
ValueTreeSynchroniserHelpers::writeHeader (*this, m, ValueTreeSynchroniserHelpers::propertyChanged, vt);
m.writeString (property.toString());
value->writeToStream (m);
}
else
{
ValueTreeSynchroniserHelpers::writeHeader (*this, m, ValueTreeSynchroniserHelpers::propertyRemoved, vt);
m.writeString (property.toString());
}
stateChanged (m.getData(), m.getDataSize());
}
void ValueTreeSynchroniser::valueTreeChildAdded (ValueTree& parentTree, ValueTree& childTree)
{
const int index = parentTree.indexOf (childTree);
jassert (index >= 0);
MemoryOutputStream m;
ValueTreeSynchroniserHelpers::writeHeader (*this, m, ValueTreeSynchroniserHelpers::childAdded, parentTree);
m.writeCompressedInt (index);
childTree.writeToStream (m);
stateChanged (m.getData(), m.getDataSize());
}
void ValueTreeSynchroniser::valueTreeChildRemoved (ValueTree& parentTree, ValueTree&, int oldIndex)
{
MemoryOutputStream m;
ValueTreeSynchroniserHelpers::writeHeader (*this, m, ValueTreeSynchroniserHelpers::childRemoved, parentTree);
m.writeCompressedInt (oldIndex);
stateChanged (m.getData(), m.getDataSize());
}
void ValueTreeSynchroniser::valueTreeChildOrderChanged (ValueTree& parent, int oldIndex, int newIndex)
{
MemoryOutputStream m;
ValueTreeSynchroniserHelpers::writeHeader (*this, m, ValueTreeSynchroniserHelpers::childMoved, parent);
m.writeCompressedInt (oldIndex);
m.writeCompressedInt (newIndex);
stateChanged (m.getData(), m.getDataSize());
}
bool ValueTreeSynchroniser::applyChange (ValueTree& root, const void* data, size_t dataSize, UndoManager* undoManager)
{
MemoryInputStream input (data, dataSize, false);
const ValueTreeSynchroniserHelpers::ChangeType type = (ValueTreeSynchroniserHelpers::ChangeType) input.readByte();
if (type == ValueTreeSynchroniserHelpers::fullSync)
{
root = ValueTree::readFromStream (input);
return true;
}
ValueTree v (ValueTreeSynchroniserHelpers::readSubTreeLocation (input, root));
if (! v.isValid())
return false;
switch (type)
{
case ValueTreeSynchroniserHelpers::propertyChanged:
{
Identifier property (input.readString());
v.setProperty (property, var::readFromStream (input), undoManager);
return true;
}
case ValueTreeSynchroniserHelpers::propertyRemoved:
{
Identifier property (input.readString());
v.removeProperty (property, undoManager);
return true;
}
case ValueTreeSynchroniserHelpers::childAdded:
{
const int index = input.readCompressedInt();
v.addChild (ValueTree::readFromStream (input), index, undoManager);
return true;
}
case ValueTreeSynchroniserHelpers::childRemoved:
{
const int index = input.readCompressedInt();
if (isPositiveAndBelow (index, v.getNumChildren()))
{
v.removeChild (index, undoManager);
return true;
}
jassertfalse; // Either received some corrupt data, or the trees have drifted out of sync
break;
}
case ValueTreeSynchroniserHelpers::childMoved:
{
const int oldIndex = input.readCompressedInt();
const int newIndex = input.readCompressedInt();
if (isPositiveAndBelow (oldIndex, v.getNumChildren())
&& isPositiveAndBelow (newIndex, v.getNumChildren()))
{
v.moveChild (oldIndex, newIndex, undoManager);
return true;
}
jassertfalse; // Either received some corrupt data, or the trees have drifted out of sync
break;
}
case ValueTreeSynchroniserHelpers::fullSync:
break;
default:
jassertfalse; // Seem to have received some corrupt data?
break;
}
return false;
}
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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.
==============================================================================
*/
namespace juce
{
namespace ValueTreeSynchroniserHelpers
{
enum ChangeType
{
propertyChanged = 1,
fullSync = 2,
childAdded = 3,
childRemoved = 4,
childMoved = 5,
propertyRemoved = 6
};
static void getValueTreePath (ValueTree v, const ValueTree& topLevelTree, Array<int>& path)
{
while (v != topLevelTree)
{
ValueTree parent (v.getParent());
if (! parent.isValid())
break;
path.add (parent.indexOf (v));
v = parent;
}
}
static void writeHeader (MemoryOutputStream& stream, ChangeType type)
{
stream.writeByte ((char) type);
}
static void writeHeader (ValueTreeSynchroniser& target, MemoryOutputStream& stream,
ChangeType type, ValueTree v)
{
writeHeader (stream, type);
Array<int> path;
getValueTreePath (v, target.getRoot(), path);
stream.writeCompressedInt (path.size());
for (int i = path.size(); --i >= 0;)
stream.writeCompressedInt (path.getUnchecked(i));
}
static ValueTree readSubTreeLocation (MemoryInputStream& input, ValueTree v)
{
const int numLevels = input.readCompressedInt();
if (! isPositiveAndBelow (numLevels, 65536)) // sanity-check
return {};
for (int i = numLevels; --i >= 0;)
{
const int index = input.readCompressedInt();
if (! isPositiveAndBelow (index, v.getNumChildren()))
return {};
v = v.getChild (index);
}
return v;
}
}
ValueTreeSynchroniser::ValueTreeSynchroniser (const ValueTree& tree) : valueTree (tree)
{
valueTree.addListener (this);
}
ValueTreeSynchroniser::~ValueTreeSynchroniser()
{
valueTree.removeListener (this);
}
void ValueTreeSynchroniser::sendFullSyncCallback()
{
MemoryOutputStream m;
writeHeader (m, ValueTreeSynchroniserHelpers::fullSync);
valueTree.writeToStream (m);
stateChanged (m.getData(), m.getDataSize());
}
void ValueTreeSynchroniser::valueTreePropertyChanged (ValueTree& vt, const Identifier& property)
{
MemoryOutputStream m;
if (auto* value = vt.getPropertyPointer (property))
{
ValueTreeSynchroniserHelpers::writeHeader (*this, m, ValueTreeSynchroniserHelpers::propertyChanged, vt);
m.writeString (property.toString());
value->writeToStream (m);
}
else
{
ValueTreeSynchroniserHelpers::writeHeader (*this, m, ValueTreeSynchroniserHelpers::propertyRemoved, vt);
m.writeString (property.toString());
}
stateChanged (m.getData(), m.getDataSize());
}
void ValueTreeSynchroniser::valueTreeChildAdded (ValueTree& parentTree, ValueTree& childTree)
{
const int index = parentTree.indexOf (childTree);
jassert (index >= 0);
MemoryOutputStream m;
ValueTreeSynchroniserHelpers::writeHeader (*this, m, ValueTreeSynchroniserHelpers::childAdded, parentTree);
m.writeCompressedInt (index);
childTree.writeToStream (m);
stateChanged (m.getData(), m.getDataSize());
}
void ValueTreeSynchroniser::valueTreeChildRemoved (ValueTree& parentTree, ValueTree&, int oldIndex)
{
MemoryOutputStream m;
ValueTreeSynchroniserHelpers::writeHeader (*this, m, ValueTreeSynchroniserHelpers::childRemoved, parentTree);
m.writeCompressedInt (oldIndex);
stateChanged (m.getData(), m.getDataSize());
}
void ValueTreeSynchroniser::valueTreeChildOrderChanged (ValueTree& parent, int oldIndex, int newIndex)
{
MemoryOutputStream m;
ValueTreeSynchroniserHelpers::writeHeader (*this, m, ValueTreeSynchroniserHelpers::childMoved, parent);
m.writeCompressedInt (oldIndex);
m.writeCompressedInt (newIndex);
stateChanged (m.getData(), m.getDataSize());
}
bool ValueTreeSynchroniser::applyChange (ValueTree& root, const void* data, size_t dataSize, UndoManager* undoManager)
{
MemoryInputStream input (data, dataSize, false);
const ValueTreeSynchroniserHelpers::ChangeType type = (ValueTreeSynchroniserHelpers::ChangeType) input.readByte();
if (type == ValueTreeSynchroniserHelpers::fullSync)
{
root = ValueTree::readFromStream (input);
return true;
}
ValueTree v (ValueTreeSynchroniserHelpers::readSubTreeLocation (input, root));
if (! v.isValid())
return false;
switch (type)
{
case ValueTreeSynchroniserHelpers::propertyChanged:
{
Identifier property (input.readString());
v.setProperty (property, var::readFromStream (input), undoManager);
return true;
}
case ValueTreeSynchroniserHelpers::propertyRemoved:
{
Identifier property (input.readString());
v.removeProperty (property, undoManager);
return true;
}
case ValueTreeSynchroniserHelpers::childAdded:
{
const int index = input.readCompressedInt();
v.addChild (ValueTree::readFromStream (input), index, undoManager);
return true;
}
case ValueTreeSynchroniserHelpers::childRemoved:
{
const int index = input.readCompressedInt();
if (isPositiveAndBelow (index, v.getNumChildren()))
{
v.removeChild (index, undoManager);
return true;
}
jassertfalse; // Either received some corrupt data, or the trees have drifted out of sync
break;
}
case ValueTreeSynchroniserHelpers::childMoved:
{
const int oldIndex = input.readCompressedInt();
const int newIndex = input.readCompressedInt();
if (isPositiveAndBelow (oldIndex, v.getNumChildren())
&& isPositiveAndBelow (newIndex, v.getNumChildren()))
{
v.moveChild (oldIndex, newIndex, undoManager);
return true;
}
jassertfalse; // Either received some corrupt data, or the trees have drifted out of sync
break;
}
case ValueTreeSynchroniserHelpers::fullSync:
break;
default:
jassertfalse; // Seem to have received some corrupt data?
break;
}
return false;
}
} // namespace juce

View File

@ -1,97 +1,97 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
This class can be used to watch for all changes to the state of a ValueTree,
and to convert them to a transmittable binary encoding.
The purpose of this class is to allow two or more ValueTrees to be remotely
synchronised by transmitting encoded changes over some kind of transport
mechanism.
To use it, you'll need to implement a subclass of ValueTreeSynchroniser
and implement the stateChanged() method to transmit the encoded change (maybe
via a network or other means) to a remote destination, where it can be
applied to a target tree.
@tags{DataStructures}
*/
class JUCE_API ValueTreeSynchroniser : private ValueTree::Listener
{
public:
/** Creates a ValueTreeSynchroniser that watches the given tree.
After creating an instance of this class and somehow attaching it to
a target tree, you probably want to call sendFullSyncCallback() to
get them into a common starting state.
*/
ValueTreeSynchroniser (const ValueTree& tree);
/** Destructor. */
~ValueTreeSynchroniser() override;
/** This callback happens when the ValueTree changes and the given state-change message
needs to be applied to any other trees that need to stay in sync with it.
The data is an opaque blob of binary that you should transmit to wherever your
target tree lives, and use applyChange() to apply this to the target tree.
*/
virtual void stateChanged (const void* encodedChange, size_t encodedChangeSize) = 0;
/** Forces the sending of a full state message, which may be large, as it
encodes the entire ValueTree.
This will internally invoke stateChanged() with the encoded version of the state.
*/
void sendFullSyncCallback();
/** Applies an encoded change to the given destination tree.
When you implement a receiver for changes that were sent by the stateChanged()
message, this is the function that you'll need to call to apply them to the
target tree that you want to be synced.
*/
static bool applyChange (ValueTree& target,
const void* encodedChangeData, size_t encodedChangeDataSize,
UndoManager* undoManager);
/** Returns the root ValueTree that is being observed. */
const ValueTree& getRoot() noexcept { return valueTree; }
private:
ValueTree valueTree;
void valueTreePropertyChanged (ValueTree&, const Identifier&) override;
void valueTreeChildAdded (ValueTree&, ValueTree&) override;
void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override;
void valueTreeChildOrderChanged (ValueTree&, int, int) override;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueTreeSynchroniser)
};
} // namespace juce
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
This class can be used to watch for all changes to the state of a ValueTree,
and to convert them to a transmittable binary encoding.
The purpose of this class is to allow two or more ValueTrees to be remotely
synchronised by transmitting encoded changes over some kind of transport
mechanism.
To use it, you'll need to implement a subclass of ValueTreeSynchroniser
and implement the stateChanged() method to transmit the encoded change (maybe
via a network or other means) to a remote destination, where it can be
applied to a target tree.
@tags{DataStructures}
*/
class JUCE_API ValueTreeSynchroniser : private ValueTree::Listener
{
public:
/** Creates a ValueTreeSynchroniser that watches the given tree.
After creating an instance of this class and somehow attaching it to
a target tree, you probably want to call sendFullSyncCallback() to
get them into a common starting state.
*/
ValueTreeSynchroniser (const ValueTree& tree);
/** Destructor. */
~ValueTreeSynchroniser() override;
/** This callback happens when the ValueTree changes and the given state-change message
needs to be applied to any other trees that need to stay in sync with it.
The data is an opaque blob of binary that you should transmit to wherever your
target tree lives, and use applyChange() to apply this to the target tree.
*/
virtual void stateChanged (const void* encodedChange, size_t encodedChangeSize) = 0;
/** Forces the sending of a full state message, which may be large, as it
encodes the entire ValueTree.
This will internally invoke stateChanged() with the encoded version of the state.
*/
void sendFullSyncCallback();
/** Applies an encoded change to the given destination tree.
When you implement a receiver for changes that were sent by the stateChanged()
message, this is the function that you'll need to call to apply them to the
target tree that you want to be synced.
*/
static bool applyChange (ValueTree& target,
const void* encodedChangeData, size_t encodedChangeDataSize,
UndoManager* undoManager);
/** Returns the root ValueTree that is being observed. */
const ValueTree& getRoot() noexcept { return valueTree; }
private:
ValueTree valueTree;
void valueTreePropertyChanged (ValueTree&, const Identifier&) override;
void valueTreeChildAdded (ValueTree&, ValueTree&) override;
void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override;
void valueTreeChildOrderChanged (ValueTree&, int, int) override;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueTreeSynchroniser)
};
} // namespace juce

View File

@ -1,101 +0,0 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
#if JUCE_UNIT_TESTS
class ValueWithDefaultTests : public UnitTest
{
public:
ValueWithDefaultTests()
: UnitTest ("ValueWithDefault", UnitTestCategories::values)
{}
void runTest() override
{
beginTest ("default constructor");
{
ValueWithDefault vwd;
expect (vwd.isUsingDefault());
expect (vwd.get() == var());
}
beginTest ("missing property");
{
ValueTree t ("root");
ValueWithDefault vwd (t, "testKey", nullptr, "default");
expect (vwd.isUsingDefault());
expectEquals (vwd.get().toString(), String ("default"));
}
beginTest ("non-empty property");
{
ValueTree t ("root");
t.setProperty ("testKey", "non-default", nullptr);
ValueWithDefault vwd (t, "testKey", nullptr, "default");
expect (! vwd.isUsingDefault());
expectEquals (vwd.get().toString(), String ("non-default"));
}
beginTest ("set default");
{
ValueTree t ("root");
ValueWithDefault vwd (t, "testkey", nullptr);
vwd.setDefault ("default");
expect (vwd.isUsingDefault());
expectEquals (vwd.get().toString(), String ("default"));
}
beginTest ("set value");
{
ValueTree t ("root");
t.setProperty ("testkey", "testvalue", nullptr);
ValueWithDefault vwd (t, "testkey", nullptr, "default");
vwd = "newvalue";
expect (! vwd.isUsingDefault());
expectEquals (t["testkey"].toString(), String ("newvalue"));
vwd.resetToDefault();
expect (vwd.isUsingDefault());
expect (t["testkey"] == var());
}
}
};
static ValueWithDefaultTests valueWithDefaultTests;
#endif
} // namespace juce

View File

@ -1,244 +0,0 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
This class acts as a wrapper around a property inside a ValueTree.
If the property inside the ValueTree is missing the ValueWithDefault will automatically
return a default value, which can be specified when initialising the ValueWithDefault.
@tags{DataStructures}
*/
class ValueWithDefault
{
public:
//==============================================================================
/** Creates an unitialised ValueWithDefault. Initialise it using one of the referTo() methods. */
ValueWithDefault() = default;
/** Creates an ValueWithDefault object. The default value will be an empty var. */
ValueWithDefault (ValueTree& tree, const Identifier& propertyID, UndoManager* um)
: targetTree (tree),
targetProperty (propertyID),
undoManager (um),
defaultValue()
{
}
/** Creates an ValueWithDefault object. The default value will be defaultToUse. */
ValueWithDefault (ValueTree& tree, const Identifier& propertyID, UndoManager* um,
const var& defaultToUse)
: targetTree (tree),
targetProperty (propertyID),
undoManager (um),
defaultValue (defaultToUse)
{
}
/** Creates an ValueWithDefault object. The default value will be defaultToUse.
Use this constructor if the underlying var object being controlled is an array and
it will handle the conversion to/from a delimited String that can be written to
XML format.
*/
ValueWithDefault (ValueTree& tree, const Identifier& propertyID, UndoManager* um,
const var& defaultToUse, StringRef arrayDelimiter)
: targetTree (tree),
targetProperty (propertyID),
undoManager (um),
defaultValue (defaultToUse),
delimiter (arrayDelimiter)
{
}
/** Creates a ValueWithDefault object from another ValueWithDefault object. */
ValueWithDefault (const ValueWithDefault& other)
: targetTree (other.targetTree),
targetProperty (other.targetProperty),
undoManager (other.undoManager),
defaultValue (other.defaultValue),
delimiter (other.delimiter)
{
}
//==============================================================================
/** Returns the current value of the property. If the property does not exist this
returns the default value.
*/
var get() const noexcept
{
if (isUsingDefault())
return defaultValue;
if (delimiter.isNotEmpty())
return delimitedStringToVarArray (targetTree[targetProperty].toString());
return targetTree[targetProperty];
}
/** Returns the current property as a Value object. */
Value getPropertyAsValue() { return targetTree.getPropertyAsValue (targetProperty, undoManager); }
/** Returns the current default value. */
var getDefault() const { return defaultValue; }
/** Sets the default value to a new var. */
void setDefault (const var& newDefault)
{
if (defaultValue != newDefault)
{
defaultValue = newDefault;
if (onDefaultChange != nullptr)
onDefaultChange();
}
}
/** Returns true if the property does not exist in the referenced ValueTree. */
bool isUsingDefault() const
{
return ! targetTree.hasProperty (targetProperty);
}
/** Removes the property from the referenced ValueTree. */
void resetToDefault() noexcept
{
targetTree.removeProperty (targetProperty, nullptr);
}
/** You can assign a lambda to this callback object to have it called when the default value is changed. */
std::function<void()> onDefaultChange;
//==============================================================================
/** Sets the property and returns the new ValueWithDefault. This will modify the property in the referenced ValueTree. */
ValueWithDefault& operator= (const var& newValue)
{
setValue (newValue, undoManager);
return *this;
}
/** Sets the property. This will actually modify the property in the referenced ValueTree. */
void setValue (const var& newValue, UndoManager* undoManagerToUse)
{
if (auto* array = newValue.getArray())
targetTree.setProperty (targetProperty, varArrayToDelimitedString (*array), undoManagerToUse);
else
targetTree.setProperty (targetProperty, newValue, undoManagerToUse);
}
//==============================================================================
/** Makes the ValueWithDefault refer to the specified property inside the given ValueTree. */
void referTo (ValueTree& tree, const Identifier& property, UndoManager* um)
{
referToWithDefault (tree, property, um, var(), {});
}
/** Makes the ValueWithDefault refer to the specified property inside the given ValueTree,
and specifies a default value to use.
*/
void referTo (ValueTree& tree, const Identifier& property, UndoManager* um, const var& defaultVal)
{
referToWithDefault (tree, property, um, defaultVal, {});
}
void referTo (ValueTree& tree, const Identifier& property, UndoManager* um,
const var& defaultVal, StringRef arrayDelimiter)
{
referToWithDefault (tree, property, um, defaultVal, arrayDelimiter);
}
//==============================================================================
/** Returns a reference to the ValueTree containing the referenced property. */
ValueTree& getValueTree() noexcept { return targetTree; }
/** Returns the property ID of the referenced property. */
Identifier& getPropertyID() noexcept { return targetProperty; }
/** Returns the UndoManager that is being used. */
UndoManager* getUndoManager() noexcept { return undoManager; }
//==============================================================================
ValueWithDefault& operator= (const ValueWithDefault& other)
{
referToWithDefault (other.targetTree, other.targetProperty, other.undoManager,
other.defaultValue, other.delimiter);
return *this;
}
private:
//==============================================================================
ValueTree targetTree;
Identifier targetProperty;
UndoManager* undoManager = nullptr;
var defaultValue;
String delimiter;
//==============================================================================
void referToWithDefault (const ValueTree& v, const Identifier& i, UndoManager* um,
const var& defaultVal, StringRef del)
{
targetTree = v;
targetProperty = i;
undoManager = um;
defaultValue = defaultVal;
delimiter = del;
}
//==============================================================================
String varArrayToDelimitedString (const Array<var>& input) const noexcept
{
// if you are trying to control a var that is an array then you need to
// set a delimiter string that will be used when writing to XML!
jassert (delimiter.isNotEmpty());
StringArray elements;
for (auto& v : input)
elements.add (v.toString());
return elements.joinIntoString (delimiter);
}
Array<var> delimitedStringToVarArray (StringRef input) const noexcept
{
Array<var> arr;
for (auto t : StringArray::fromTokens (input, delimiter, {}))
arr.add (t);
return arr;
}
//==============================================================================
JUCE_DECLARE_WEAK_REFERENCEABLE (ValueWithDefault)
};
} // namespace juce