fix macOS build (following Projucer changes made in Windows, which removed /Applications/JUCE/modules from its headers). move JUCE headers under source control, so that Windows and macOS can both build against same version of JUCE. remove AUv3 target (I think it's an iOS thing, so it will never work with this macOS fluidsynth dylib).
This commit is contained in:
159
modules/juce_data_structures/values/juce_CachedValue.cpp
Normal file
159
modules/juce_data_structures/values/juce_CachedValue.cpp
Normal file
@ -0,0 +1,159 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
class CachedValueTests : public UnitTest
|
||||
{
|
||||
public:
|
||||
CachedValueTests() : UnitTest ("CachedValues", "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());
|
||||
}
|
||||
|
||||
beginTest ("reset value");
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static CachedValueTests cachedValueTests;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
314
modules/juce_data_structures/values/juce_CachedValue.h
Normal file
314
modules/juce_data_structures/values/juce_CachedValue.h
Normal file
@ -0,0 +1,314 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
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; }
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
ValueTree targetTree;
|
||||
Identifier targetProperty;
|
||||
UndoManager* undoManager;
|
||||
Type defaultValue;
|
||||
Type cachedValue;
|
||||
|
||||
//==============================================================================
|
||||
void referToWithDefault (ValueTree&, const Identifier&, UndoManager*, const Type&);
|
||||
Type getTypedValue() const;
|
||||
|
||||
void valueTreePropertyChanged (ValueTree& changedTree, const Identifier& changedProperty) override;
|
||||
void valueTreeChildAdded (ValueTree&, ValueTree&) override {}
|
||||
void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override {}
|
||||
void valueTreeChildOrderChanged (ValueTree&, int, int) override {}
|
||||
void valueTreeParentChanged (ValueTree&) override {}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (CachedValue)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
template <typename Type>
|
||||
inline CachedValue<Type>::CachedValue() : undoManager (nullptr) {}
|
||||
|
||||
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
|
242
modules/juce_data_structures/values/juce_Value.cpp
Normal file
242
modules/juce_data_structures/values/juce_Value.cpp
Normal file
@ -0,0 +1,242 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
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 = static_cast<ReferenceCountedObjectPtr<ValueSource>&&> (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 = static_cast<ReferenceCountedObjectPtr<ValueSource>&&> (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
|
243
modules/juce_data_structures/values/juce_Value.h
Normal file
243
modules/juce_data_structures/values/juce_Value.h
Normal file
@ -0,0 +1,243 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
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() {}
|
||||
virtual ~Listener() {}
|
||||
|
||||
/** 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();
|
||||
virtual ~ValueSource();
|
||||
|
||||
/** 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
|
1170
modules/juce_data_structures/values/juce_ValueTree.cpp
Normal file
1170
modules/juce_data_structures/values/juce_ValueTree.cpp
Normal file
File diff suppressed because it is too large
Load Diff
626
modules/juce_data_structures/values/juce_ValueTree.h
Normal file
626
modules/juce_data_structures/values/juce_ValueTree.h
Normal file
@ -0,0 +1,626 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A powerful tree structure that can be used to hold free-form data, and which can
|
||||
handle its own undo and redo behaviour.
|
||||
|
||||
A ValueTree contains a list of named properties as var objects, and also holds
|
||||
any number of sub-trees.
|
||||
|
||||
Create ValueTree objects on the stack, and don't be afraid to copy them around, as
|
||||
they're simply a lightweight reference to a shared data container. Creating a copy
|
||||
of another ValueTree simply creates a new reference to the same underlying object - to
|
||||
make a separate, deep copy of a tree you should explicitly call createCopy().
|
||||
|
||||
Each ValueTree has a type name, in much the same way as an XmlElement has a tag name,
|
||||
and much of the structure of a ValueTree is similar to an XmlElement tree.
|
||||
You can convert a ValueTree to and from an XmlElement, and as long as the XML doesn't
|
||||
contain text elements, the conversion works well and makes a good serialisation
|
||||
format. They can also be serialised to a binary format, which is very fast and compact.
|
||||
|
||||
All the methods that change data take an optional UndoManager, which will be used
|
||||
to track any changes to the object. For this to work, you have to be careful to
|
||||
consistently always use the same UndoManager for all operations to any sub-tree inside
|
||||
the tree.
|
||||
|
||||
A ValueTree can only be a child of one parent at a time, so if you're moving one from
|
||||
one tree to another, be careful to always remove it first, before adding it. This
|
||||
could also mess up your undo/redo chain, so be wary! In a debug build you should hit
|
||||
assertions if you try to do anything dangerous, but there are still plenty of ways it
|
||||
could go wrong.
|
||||
|
||||
Note that although the children in a tree have a fixed order, the properties are not
|
||||
guaranteed to be stored in any particular order, so don't expect that a property's index
|
||||
will correspond to the order in which the property was added, or that it will remain
|
||||
constant when other properties are added or removed.
|
||||
|
||||
Listeners can be added to a ValueTree to be told when properies change and when
|
||||
sub-trees are added or removed.
|
||||
|
||||
@see var, XmlElement
|
||||
|
||||
@tags{DataStructures}
|
||||
*/
|
||||
class JUCE_API ValueTree final
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty, invalid ValueTree.
|
||||
|
||||
A ValueTree that is created with this constructor can't actually be used for anything,
|
||||
it's just a default 'null' ValueTree that can be returned to indicate some sort of failure.
|
||||
To create a real one, use the constructor that takes a string.
|
||||
*/
|
||||
ValueTree() noexcept;
|
||||
|
||||
/** Creates an empty ValueTree with the given type name.
|
||||
|
||||
Like an XmlElement, each ValueTree has a type, which you can access with
|
||||
getType() and hasType().
|
||||
*/
|
||||
explicit ValueTree (const Identifier& type);
|
||||
|
||||
/** Creates a value tree from nested lists of properties and ValueTrees.
|
||||
|
||||
This code,
|
||||
|
||||
@code
|
||||
ValueTree groups
|
||||
{ "ParameterGroups", {},
|
||||
{
|
||||
{ "Group", {{ "name", "Tone Controls" }},
|
||||
{
|
||||
{ "Parameter", {{ "id", "distortion" }, { "value", 0.5 }}},
|
||||
{ "Parameter", {{ "id", "reverb" }, { "value", 0.5 }}}
|
||||
}
|
||||
},
|
||||
{ "Group", {{ "name", "Other Controls" }},
|
||||
{
|
||||
{ "Parameter", {{ "id", "drywet" }, { "value", 0.5 }}},
|
||||
{ "Parameter", {{ "id", "gain" }, { "value", 0.5 }}}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@endcode
|
||||
|
||||
produces this tree:
|
||||
|
||||
@verbatim
|
||||
<ParameterGroups>
|
||||
<Group name="Tone Controls">
|
||||
<Parameter id="distortion" value="0.5"/>
|
||||
<Parameter id="reverb" value="0.5"/>
|
||||
</Group>
|
||||
<Group name="Other Controls">
|
||||
<Parameter id="drywet" value="0.5"/>
|
||||
<Parameter id="gain" value="0.5"/>
|
||||
</Group>
|
||||
</ParameterGroups>
|
||||
@endverbatim
|
||||
*/
|
||||
ValueTree (const Identifier& type,
|
||||
std::initializer_list<std::pair<Identifier, var>> properties,
|
||||
std::initializer_list<ValueTree> subTrees = {});
|
||||
|
||||
/** Creates a reference to another ValueTree. */
|
||||
ValueTree (const ValueTree&) noexcept;
|
||||
|
||||
/** Changes this object to be a reference to the given tree. */
|
||||
ValueTree& operator= (const ValueTree&);
|
||||
|
||||
/** Move constructor */
|
||||
ValueTree (ValueTree&&) noexcept;
|
||||
|
||||
/** Destructor. */
|
||||
~ValueTree();
|
||||
|
||||
/** Returns true if both this and the other tree refer to the same underlying structure.
|
||||
Note that this isn't a value comparison - two independently-created trees which
|
||||
contain identical data are NOT considered equal.
|
||||
*/
|
||||
bool operator== (const ValueTree&) const noexcept;
|
||||
|
||||
/** Returns true if this and the other tree refer to different underlying structures.
|
||||
Note that this isn't a value comparison - two independently-created trees which
|
||||
contain identical data are not considered equal.
|
||||
*/
|
||||
bool operator!= (const ValueTree&) const noexcept;
|
||||
|
||||
/** Performs a deep comparison between the properties and children of two trees.
|
||||
If all the properties and children of the two trees are the same (recursively), this
|
||||
returns true.
|
||||
The normal operator==() only checks whether two trees refer to the same shared data
|
||||
structure, so use this method if you need to do a proper value comparison.
|
||||
*/
|
||||
bool isEquivalentTo (const ValueTree&) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if this tree refers to some valid data.
|
||||
An invalid tree is one that was created with the default constructor.
|
||||
*/
|
||||
bool isValid() const noexcept { return object != nullptr; }
|
||||
|
||||
/** Returns a deep copy of this tree and all its sub-trees. */
|
||||
ValueTree createCopy() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the type of this tree.
|
||||
The type is specified when the ValueTree is created.
|
||||
@see hasType
|
||||
*/
|
||||
Identifier getType() const noexcept;
|
||||
|
||||
/** Returns true if the tree has this type.
|
||||
The comparison is case-sensitive.
|
||||
@see getType
|
||||
*/
|
||||
bool hasType (const Identifier& typeName) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the value of a named property.
|
||||
If no such property has been set, this will return a void variant.
|
||||
You can also use operator[] to get a property.
|
||||
@see var, setProperty, getPropertyPointer, hasProperty
|
||||
*/
|
||||
const var& getProperty (const Identifier& name) const noexcept;
|
||||
|
||||
/** Returns the value of a named property, or the value of defaultReturnValue
|
||||
if the property doesn't exist.
|
||||
You can also use operator[] and getProperty to get a property.
|
||||
@see var, getProperty, getPropertyPointer, setProperty, hasProperty
|
||||
*/
|
||||
var getProperty (const Identifier& name, const var& defaultReturnValue) const;
|
||||
|
||||
/** Returns a pointer to the value of a named property, or nullptr if the property
|
||||
doesn't exist.
|
||||
@see var, getProperty, setProperty, hasProperty
|
||||
*/
|
||||
const var* getPropertyPointer (const Identifier& name) const noexcept;
|
||||
|
||||
/** Returns the value of a named property.
|
||||
If no such property has been set, this will return a void variant. This is the same as
|
||||
calling getProperty().
|
||||
@see getProperty
|
||||
*/
|
||||
const var& operator[] (const Identifier& name) const noexcept;
|
||||
|
||||
/** Changes a named property of the tree.
|
||||
The name identifier must not be an empty string.
|
||||
If the undoManager parameter is not nullptr, its UndoManager::perform() method will be used,
|
||||
so that this change can be undone. Be very careful not to mix undoable and non-undoable changes!
|
||||
@see var, getProperty, removeProperty
|
||||
@returns a reference to the value tree, so that you can daisy-chain calls to this method.
|
||||
*/
|
||||
ValueTree& setProperty (const Identifier& name, const var& newValue, UndoManager* undoManager);
|
||||
|
||||
/** Returns true if the tree contains a named property. */
|
||||
bool hasProperty (const Identifier& name) const noexcept;
|
||||
|
||||
/** Removes a property from the tree.
|
||||
If the undoManager parameter is not nullptr, its UndoManager::perform() method will be used,
|
||||
so that this change can be undone. Be very careful not to mix undoable and non-undoable changes!
|
||||
*/
|
||||
void removeProperty (const Identifier& name, UndoManager* undoManager);
|
||||
|
||||
/** Removes all properties from the tree.
|
||||
If the undoManager parameter is not nullptr, its UndoManager::perform() method will be used,
|
||||
so that this change can be undone. Be very careful not to mix undoable and non-undoable changes!
|
||||
*/
|
||||
void removeAllProperties (UndoManager* undoManager);
|
||||
|
||||
/** Returns the total number of properties that the tree contains.
|
||||
@see getProperty.
|
||||
*/
|
||||
int getNumProperties() const noexcept;
|
||||
|
||||
/** Returns the identifier of the property with a given index.
|
||||
Note that properties are not guaranteed to be stored in any particular order, so don't
|
||||
expect that the index will correspond to the order in which the property was added, or
|
||||
that it will remain constant when other properties are added or removed.
|
||||
@see getNumProperties
|
||||
*/
|
||||
Identifier getPropertyName (int index) const noexcept;
|
||||
|
||||
/** Returns a Value object that can be used to control and respond to one of the tree's properties.
|
||||
|
||||
The Value object will maintain a reference to this tree, and will use the undo manager when
|
||||
it needs to change the value. Attaching a Value::Listener to the value object will provide
|
||||
callbacks whenever the property changes.
|
||||
If shouldUpdateSynchronously is true the Value::Listener will be updated synchronously.
|
||||
@see ValueSource::sendChangeMessage (bool)
|
||||
*/
|
||||
Value getPropertyAsValue (const Identifier& name, UndoManager* undoManager,
|
||||
bool shouldUpdateSynchronously = false);
|
||||
|
||||
/** Overwrites all the properties in this tree with the properties of the source tree.
|
||||
Any properties that already exist will be updated; and new ones will be added, and
|
||||
any that are not present in the source tree will be removed.
|
||||
*/
|
||||
void copyPropertiesFrom (const ValueTree& source, UndoManager* undoManager);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of child trees inside this one.
|
||||
@see getChild
|
||||
*/
|
||||
int getNumChildren() const noexcept;
|
||||
|
||||
/** Returns one of this tree's sub-trees.
|
||||
If the index is out of range, it'll return an invalid tree. (You can use isValid() to
|
||||
check whether a tree is valid)
|
||||
*/
|
||||
ValueTree getChild (int index) const;
|
||||
|
||||
/** Returns the first sub-tree with the specified type name.
|
||||
If no such child tree exists, it'll return an invalid tree. (You can use isValid() to
|
||||
check whether a tree is valid)
|
||||
@see getOrCreateChildWithName
|
||||
*/
|
||||
ValueTree getChildWithName (const Identifier& type) const;
|
||||
|
||||
/** Returns the first sub-tree with the specified type name, creating and adding
|
||||
a child with this name if there wasn't already one there.
|
||||
The only time this will return an invalid object is when the object that you're calling
|
||||
the method on is itself invalid.
|
||||
@see getChildWithName
|
||||
*/
|
||||
ValueTree getOrCreateChildWithName (const Identifier& type, UndoManager* undoManager);
|
||||
|
||||
/** Looks for the first sub-tree that has the specified property value.
|
||||
This will scan the child trees in order, until it finds one that has property that matches
|
||||
the specified value.
|
||||
If no such tree is found, it'll return an invalid object. (You can use isValid() to
|
||||
check whether a tree is valid)
|
||||
*/
|
||||
ValueTree getChildWithProperty (const Identifier& propertyName, const var& propertyValue) const;
|
||||
|
||||
/** Adds a child to this tree.
|
||||
Make sure that the child being added has first been removed from any former parent before
|
||||
calling this, or else you'll hit an assertion.
|
||||
If the index is < 0 or greater than the current number of sub-trees, the new one will be
|
||||
added at the end of the list.
|
||||
If the undoManager parameter is not nullptr, its UndoManager::perform() method will be used,
|
||||
so that this change can be undone. Be very careful not to mix undoable and non-undoable changes!
|
||||
@see appendChild, removeChild
|
||||
*/
|
||||
void addChild (const ValueTree& child, int index, UndoManager* undoManager);
|
||||
|
||||
/** Appends a new child sub-tree to this tree.
|
||||
This is equivalent to calling addChild() with an index of -1. See addChild() for more details.
|
||||
@see addChild, removeChild
|
||||
*/
|
||||
void appendChild (const ValueTree& child, UndoManager* undoManager);
|
||||
|
||||
/** Removes the specified child from this tree's child-list.
|
||||
If the undoManager parameter is not nullptr, its UndoManager::perform() method will be used,
|
||||
so that this change can be undone. Be very careful not to mix undoable and non-undoable changes!
|
||||
*/
|
||||
void removeChild (const ValueTree& child, UndoManager* undoManager);
|
||||
|
||||
/** Removes a sub-tree from this tree.
|
||||
If the index is out-of-range, nothing will be changed.
|
||||
If the undoManager parameter is not nullptr, its UndoManager::perform() method will be used,
|
||||
so that this change can be undone. Be very careful not to mix undoable and non-undoable changes!
|
||||
*/
|
||||
void removeChild (int childIndex, UndoManager* undoManager);
|
||||
|
||||
/** Removes all child-trees.
|
||||
If the undoManager parameter is not nullptr, its UndoManager::perform() method will be used,
|
||||
so that this change can be undone. Be very careful not to mix undoable and non-undoable changes!
|
||||
*/
|
||||
void removeAllChildren (UndoManager* undoManager);
|
||||
|
||||
/** Moves one of the sub-trees to a different index.
|
||||
This will move the child to a specified index, shuffling along any intervening
|
||||
items as required. So for example, if you have a list of { 0, 1, 2, 3, 4, 5 }, then
|
||||
calling move (2, 4) would result in { 0, 1, 3, 4, 2, 5 }.
|
||||
|
||||
@param currentIndex the index of the item to be moved. If this isn't a
|
||||
valid index, then nothing will be done
|
||||
@param newIndex the index at which you'd like this item to end up. If this
|
||||
is less than zero, the value will be moved to the end
|
||||
of the list
|
||||
@param undoManager the optional UndoManager to use to store this transaction
|
||||
*/
|
||||
void moveChild (int currentIndex, int newIndex, UndoManager* undoManager);
|
||||
|
||||
/** Returns true if this tree is a sub-tree (at any depth) of the given parent.
|
||||
This searches recursively, so returns true if it's a sub-tree at any level below the parent.
|
||||
*/
|
||||
bool isAChildOf (const ValueTree& possibleParent) const noexcept;
|
||||
|
||||
/** Returns the index of a child item in this parent.
|
||||
If the child isn't found, this returns -1.
|
||||
*/
|
||||
int indexOf (const ValueTree& child) const noexcept;
|
||||
|
||||
/** Returns the parent tree that contains this one.
|
||||
If the tree has no parent, this will return an invalid object. (You can use isValid() to
|
||||
check whether a tree is valid)
|
||||
*/
|
||||
ValueTree getParent() const noexcept;
|
||||
|
||||
/** Recursively finds the highest-level parent tree that contains this one.
|
||||
If the tree has no parent, this will return itself.
|
||||
*/
|
||||
ValueTree getRoot() const noexcept;
|
||||
|
||||
/** Returns one of this tree's siblings in its parent's child list.
|
||||
The delta specifies how far to move through the list, so a value of 1 would return the tree
|
||||
that follows this one, -1 would return the tree before it, 0 will return this one, etc.
|
||||
If the requested position is beyond the start or end of the child list, this will return an
|
||||
invalid object.
|
||||
*/
|
||||
ValueTree getSibling (int delta) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Iterator for a ValueTree.
|
||||
You shouldn't ever need to use this class directly - it's used internally by ValueTree::begin()
|
||||
and ValueTree::end() to allow range-based-for loops on a ValueTree.
|
||||
*/
|
||||
struct Iterator
|
||||
{
|
||||
Iterator (const ValueTree&, bool isEnd) noexcept;
|
||||
Iterator& operator++() noexcept;
|
||||
|
||||
bool operator!= (const Iterator&) const noexcept;
|
||||
ValueTree operator*() const;
|
||||
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using value_type = ValueTree;
|
||||
using reference = ValueTree&;
|
||||
using pointer = ValueTree*;
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
|
||||
private:
|
||||
void* internal;
|
||||
};
|
||||
|
||||
/** Returns a start iterator for the children in this tree. */
|
||||
Iterator begin() const noexcept;
|
||||
|
||||
/** Returns an end iterator for the children in this tree. */
|
||||
Iterator end() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Creates an XmlElement that holds a complete image of this tree and all its children.
|
||||
If this tree is invalid, this may return nullptr. Otherwise, the XML that is produced can
|
||||
be used to recreate a similar tree by calling ValueTree::fromXml().
|
||||
The caller must delete the object that is returned.
|
||||
@see fromXml, toXmlString
|
||||
*/
|
||||
XmlElement* createXml() const;
|
||||
|
||||
/** Tries to recreate a tree from its XML representation.
|
||||
This isn't designed to cope with random XML data - it should only be fed XML that was created
|
||||
by the createXml() method.
|
||||
*/
|
||||
static ValueTree fromXml (const XmlElement& xml);
|
||||
|
||||
/** This returns a string containing an XML representation of the tree.
|
||||
This is quite handy for debugging purposes, as it provides a quick way to view a tree.
|
||||
@see createXml()
|
||||
*/
|
||||
String toXmlString() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Stores this tree (and all its children) in a binary format.
|
||||
|
||||
Once written, the data can be read back with readFromStream().
|
||||
|
||||
It's much faster to load/save your tree in binary form than as XML, but
|
||||
obviously isn't human-readable.
|
||||
*/
|
||||
void writeToStream (OutputStream& output) const;
|
||||
|
||||
/** Reloads a tree from a stream that was written with writeToStream(). */
|
||||
static ValueTree readFromStream (InputStream& input);
|
||||
|
||||
/** Reloads a tree from a data block that was written with writeToStream(). */
|
||||
static ValueTree readFromData (const void* data, size_t numBytes);
|
||||
|
||||
/** Reloads a tree from a data block that was written with writeToStream() and
|
||||
then zipped using GZIPCompressorOutputStream.
|
||||
*/
|
||||
static ValueTree readFromGZIPData (const void* data, size_t numBytes);
|
||||
|
||||
//==============================================================================
|
||||
/** Listener class for events that happen to a ValueTree.
|
||||
|
||||
To get events from a ValueTree, make your class implement this interface, and use
|
||||
ValueTree::addListener() and ValueTree::removeListener() to register it.
|
||||
*/
|
||||
class JUCE_API Listener
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~Listener() {}
|
||||
|
||||
/** This method is called when a property of this tree (or of one of its sub-trees) is changed.
|
||||
Note that when you register a listener to a tree, it will receive this callback for
|
||||
property changes in that tree, and also for any of its children, (recursively, at any depth).
|
||||
If your tree has sub-trees but you only want to know about changes to the top level tree,
|
||||
simply check the tree parameter in this callback to make sure it's the tree you're interested in.
|
||||
*/
|
||||
virtual void valueTreePropertyChanged (ValueTree& treeWhosePropertyHasChanged,
|
||||
const Identifier& property) = 0;
|
||||
|
||||
/** This method is called when a child sub-tree is added.
|
||||
Note that when you register a listener to a tree, it will receive this callback for
|
||||
child changes in both that tree and any of its children, (recursively, at any depth).
|
||||
If your tree has sub-trees but you only want to know about changes to the top level tree,
|
||||
just check the parentTree parameter to make sure it's the one that you're interested in.
|
||||
*/
|
||||
virtual void valueTreeChildAdded (ValueTree& parentTree,
|
||||
ValueTree& childWhichHasBeenAdded) = 0;
|
||||
|
||||
/** This method is called when a child sub-tree is removed.
|
||||
|
||||
Note that when you register a listener to a tree, it will receive this callback for
|
||||
child changes in both that tree and any of its children, (recursively, at any depth).
|
||||
If your tree has sub-trees but you only want to know about changes to the top level tree,
|
||||
just check the parentTree parameter to make sure it's the one that you're interested in.
|
||||
*/
|
||||
virtual void valueTreeChildRemoved (ValueTree& parentTree,
|
||||
ValueTree& childWhichHasBeenRemoved,
|
||||
int indexFromWhichChildWasRemoved) = 0;
|
||||
|
||||
/** This method is called when a tree's children have been re-shuffled.
|
||||
|
||||
Note that when you register a listener to a tree, it will receive this callback for
|
||||
child changes in both that tree and any of its children, (recursively, at any depth).
|
||||
If your tree has sub-trees but you only want to know about changes to the top level tree,
|
||||
just check the parameter to make sure it's the tree that you're interested in.
|
||||
*/
|
||||
virtual void valueTreeChildOrderChanged (ValueTree& parentTreeWhoseChildrenHaveMoved,
|
||||
int oldIndex, int newIndex) = 0;
|
||||
|
||||
/** This method is called when a tree has been added or removed from a parent.
|
||||
|
||||
This callback happens when the tree to which the listener was registered is added or
|
||||
removed from a parent. Unlike the other callbacks, it applies only to the tree to which
|
||||
the listener is registered, and not to any of its children.
|
||||
*/
|
||||
virtual void valueTreeParentChanged (ValueTree& treeWhoseParentHasChanged) = 0;
|
||||
|
||||
/** This method is called when a tree is made to point to a different internal shared object.
|
||||
When operator= is used to make a ValueTree refer to a different object, this callback
|
||||
will be made.
|
||||
*/
|
||||
virtual void valueTreeRedirected (ValueTree& treeWhichHasBeenChanged);
|
||||
};
|
||||
|
||||
/** Adds a listener to receive callbacks when this tree is changed in some way.
|
||||
|
||||
The listener is added to this specific ValueTree 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 ValueTree still exist. And if you
|
||||
use the operator= to make this refer to a different ValueTree, any listeners will
|
||||
begin listening to changes to the new tree instead of the old one.
|
||||
|
||||
When you're adding a listener, make sure that you add it to a ValueTree 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 ValueTree, and would usually add one to a member variable.
|
||||
|
||||
@see removeListener
|
||||
*/
|
||||
void addListener (Listener* listener);
|
||||
|
||||
/** Removes a listener that was previously added with addListener(). */
|
||||
void removeListener (Listener* listener);
|
||||
|
||||
/** Changes a named property of the tree, but will not notify a specified listener of the change.
|
||||
@see setProperty
|
||||
*/
|
||||
ValueTree& setPropertyExcludingListener (Listener* listenerToExclude,
|
||||
const Identifier& name, const var& newValue,
|
||||
UndoManager* undoManager);
|
||||
|
||||
/** Causes a property-change callback to be triggered for the specified property,
|
||||
calling any listeners that are registered.
|
||||
*/
|
||||
void sendPropertyChangeMessage (const Identifier& property);
|
||||
|
||||
//==============================================================================
|
||||
/** This method uses a comparator object to sort the tree's children into order.
|
||||
|
||||
The object provided must have a method of the form:
|
||||
@code
|
||||
int compareElements (const ValueTree& first, const ValueTree& second);
|
||||
@endcode
|
||||
|
||||
..and this method must return:
|
||||
- a value of < 0 if the first comes before the second
|
||||
- a value of 0 if the two objects are equivalent
|
||||
- a value of > 0 if the second comes before the first
|
||||
|
||||
To improve performance, the compareElements() method can be declared as static or const.
|
||||
|
||||
@param comparator the comparator to use for comparing elements.
|
||||
@param undoManager optional UndoManager for storing the changes
|
||||
@param retainOrderOfEquivalentItems if this is true, then items which the comparator says are
|
||||
equivalent will be kept in the order in which they currently appear in the array.
|
||||
This is slower to perform, but may be important in some cases. If it's false, a
|
||||
faster algorithm is used, but equivalent elements may be rearranged.
|
||||
*/
|
||||
template <typename ElementComparator>
|
||||
void sort (ElementComparator& comparator, UndoManager* undoManager, bool retainOrderOfEquivalentItems)
|
||||
{
|
||||
if (object != nullptr)
|
||||
{
|
||||
OwnedArray<ValueTree> sortedList;
|
||||
createListOfChildren (sortedList);
|
||||
ComparatorAdapter<ElementComparator> adapter (comparator);
|
||||
sortedList.sort (adapter, retainOrderOfEquivalentItems);
|
||||
reorderChildren (sortedList, undoManager);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the total number of references to the shared underlying data structure that this
|
||||
ValueTree is using.
|
||||
*/
|
||||
int getReferenceCount() const noexcept;
|
||||
|
||||
/* An invalid ValueTree that can be used if you need to return one as an error condition, etc.
|
||||
@deprecated If you need an empty ValueTree object, just use ValueTree() or {}.
|
||||
*/
|
||||
JUCE_DEPRECATED_STATIC (static const ValueTree invalid;)
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
JUCE_PUBLIC_IN_DLL_BUILD (class SharedObject)
|
||||
friend class SharedObject;
|
||||
|
||||
ReferenceCountedObjectPtr<SharedObject> object;
|
||||
ListenerList<Listener> listeners;
|
||||
|
||||
template <typename ElementComparator>
|
||||
struct ComparatorAdapter
|
||||
{
|
||||
ComparatorAdapter (ElementComparator& comp) noexcept : comparator (comp) {}
|
||||
|
||||
int compareElements (const ValueTree* const first, const ValueTree* const second)
|
||||
{
|
||||
return comparator.compareElements (*first, *second);
|
||||
}
|
||||
|
||||
private:
|
||||
ElementComparator& comparator;
|
||||
JUCE_DECLARE_NON_COPYABLE (ComparatorAdapter)
|
||||
};
|
||||
|
||||
void createListOfChildren (OwnedArray<ValueTree>&) const;
|
||||
void reorderChildren (const OwnedArray<ValueTree>&, UndoManager*);
|
||||
|
||||
explicit ValueTree (SharedObject*) noexcept;
|
||||
};
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,242 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
void ValueTreeSynchroniser::valueTreeParentChanged (ValueTree&) {} // (No action needed here)
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
default:
|
||||
jassertfalse; // Seem to have received some corrupt data?
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,99 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
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. */
|
||||
virtual ~ValueTreeSynchroniser();
|
||||
|
||||
/** 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;
|
||||
void valueTreeParentChanged (ValueTree&) override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueTreeSynchroniser)
|
||||
};
|
||||
|
||||
} // namespace juce
|
229
modules/juce_data_structures/values/juce_ValueWithDefault.h
Normal file
229
modules/juce_data_structures/values/juce_ValueWithDefault.h
Normal file
@ -0,0 +1,229 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
This class acts as a wrapper around a property inside a ValueTree.
|
||||
|
||||
If the property inside the ValueTree is missing or empty 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() : undoManager (nullptr) {}
|
||||
|
||||
/** 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 or is empty,
|
||||
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 or is empty. */
|
||||
bool isUsingDefault() const
|
||||
{
|
||||
return ! targetTree.hasProperty (targetProperty);
|
||||
}
|
||||
|
||||
/** Resets the property to an empty var. */
|
||||
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; }
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
ValueTree targetTree;
|
||||
Identifier targetProperty;
|
||||
UndoManager* undoManager;
|
||||
var defaultValue;
|
||||
|
||||
String delimiter;
|
||||
|
||||
//==============================================================================
|
||||
void referToWithDefault (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;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace juce
|
Reference in New Issue
Block a user