diff --git a/ChangeList.txt b/ChangeList.txt index a37dad2a41..9010b19388 100644 --- a/ChangeList.txt +++ b/ChangeList.txt @@ -4,6 +4,9 @@ This file just lists the more notable headline features. For more detailed info about minor changes and bugfixes, please see the git log! +Version 4.2.1 + - new class CachedValue, for providing easy and efficient access to ValueTree properties + Version 4.2 - Added support for AudioUnit v3 on OS X and iOS - Simplified the JUCE module format. Removed the json module definition files, and made diff --git a/modules/juce_core/containers/juce_Variant.h b/modules/juce_core/containers/juce_Variant.h index 25d0dcd700..8720a0d080 100644 --- a/modules/juce_core/containers/juce_Variant.h +++ b/modules/juce_core/containers/juce_Variant.h @@ -100,12 +100,12 @@ public: var& operator= (NativeFunction method); #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS - var (var&& other) noexcept; - var (String&& value); - var (MemoryBlock&& binaryData); - var (Array&& value); - var& operator= (var&& other) noexcept; - var& operator= (String&& value); + var (var&&) noexcept; + var (String&&); + var (MemoryBlock&&); + var (Array&&); + var& operator= (var&&) noexcept; + var& operator= (String&&); #endif void swapWith (var& other) noexcept; @@ -314,13 +314,30 @@ private: }; /** Compares the values of two var objects, using the var::equals() comparison. */ -bool operator== (const var& v1, const var& v2) noexcept; +bool operator== (const var&, const var&) noexcept; /** Compares the values of two var objects, using the var::equals() comparison. */ -bool operator!= (const var& v1, const var& v2) noexcept; -bool operator== (const var& v1, const String& v2); -bool operator!= (const var& v1, const String& v2); -bool operator== (const var& v1, const char* v2); -bool operator!= (const var& v1, const char* v2); +bool operator!= (const var&, const var&) noexcept; +bool operator== (const var&, const String&); +bool operator!= (const var&, const String&); +bool operator== (const var&, const char*); +bool operator!= (const var&, const char*); + +//============================================================================== +/** This template-overloaded class can be used to convert between var and custom types. */ +template +struct VariantConverter +{ + static Type fromVar (const var& v) { return static_cast (v); } + static var toVar (const Type& t) { return t; } +}; + +/** This template-overloaded class can be used to convert between var and custom types. */ +template <> +struct VariantConverter +{ + static String fromVar (const var& v) { return v.toString(); } + static var toVar (const String& s) { return s; } +}; #endif // JUCE_VARIANT_H_INCLUDED diff --git a/modules/juce_data_structures/juce_data_structures.cpp b/modules/juce_data_structures/juce_data_structures.cpp index e6d7784a4f..e278cb4494 100644 --- a/modules/juce_data_structures/juce_data_structures.cpp +++ b/modules/juce_data_structures/juce_data_structures.cpp @@ -39,6 +39,7 @@ namespace juce #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" diff --git a/modules/juce_data_structures/juce_data_structures.h b/modules/juce_data_structures/juce_data_structures.h index 193c433906..b56bd23482 100644 --- a/modules/juce_data_structures/juce_data_structures.h +++ b/modules/juce_data_structures/juce_data_structures.h @@ -60,6 +60,7 @@ namespace juce #include "values/juce_Value.h" #include "values/juce_ValueTree.h" #include "values/juce_ValueTreeSynchroniser.h" +#include "values/juce_CachedValue.h" #include "app_properties/juce_PropertiesFile.h" #include "app_properties/juce_ApplicationProperties.h" diff --git a/modules/juce_data_structures/values/juce_CachedValue.cpp b/modules/juce_data_structures/values/juce_CachedValue.cpp new file mode 100644 index 0000000000..becf90cb9e --- /dev/null +++ b/modules/juce_data_structures/values/juce_CachedValue.cpp @@ -0,0 +1,152 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#if JUCE_UNIT_TESTS + +class CachedValueTests : public UnitTest +{ +public: + CachedValueTests() : UnitTest ("CachedValues") {} + + void runTest() override + { + beginTest ("default constructor"); + { + CachedValue cv; + expect (cv.isUsingDefault()); + expect (cv.get() == String()); + } + + beginTest ("without default value"); + { + ValueTree t ("root"); + t.setProperty ("testkey", "testvalue", nullptr); + + CachedValue 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 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 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 cv (t, "testkey", nullptr, "defaultvalue"); + + expect (! cv.isUsingDefault()); + expect (cv == ""); + expectEquals (cv.get(), String()); + } + + beginTest ("with non-existent value"); + { + ValueTree t ("root"); + + CachedValue 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 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 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 \ No newline at end of file diff --git a/modules/juce_data_structures/values/juce_CachedValue.h b/modules/juce_data_structures/values/juce_CachedValue.h new file mode 100644 index 0000000000..1d8a21c288 --- /dev/null +++ b/modules/juce_data_structures/values/juce_CachedValue.h @@ -0,0 +1,311 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2016 - ROLI Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef JUCE_CACHEDVALUE_H_INCLUDED +#define JUCE_CACHEDVALUE_H_INCLUDED + + +//============================================================================== +/** + 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 allows you to read or write the + property as an int, and a CachedValue 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. +*/ +template +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 + 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 + 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 thereferenced 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 +CachedValue::CachedValue() : undoManager (nullptr) {} + +template +CachedValue::CachedValue (ValueTree& v, const Identifier& i, UndoManager* um) + : targetTree (v), targetProperty (i), undoManager (um), + defaultValue(), cachedValue (getTypedValue()) +{ + targetTree.addListener (this); +} + +template +CachedValue::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 +Value CachedValue::getPropertyAsValue() +{ + return targetTree.getPropertyAsValue (targetProperty, undoManager); +} + +template +bool CachedValue::isUsingDefault() const +{ + return ! targetTree.hasProperty (targetProperty); +} + +template +CachedValue& CachedValue::operator= (const Type& newValue) +{ + setValue (newValue, undoManager); + return *this; +} + +template +void CachedValue::setValue (const Type& newValue, UndoManager* undoManagerToUse) +{ + if (cachedValue != newValue) + { + cachedValue = newValue; + targetTree.setProperty (targetProperty, VariantConverter::toVar (newValue), undoManagerToUse); + } +} + +template +void CachedValue::resetToDefault() +{ + resetToDefault (undoManager); +} + +template +void CachedValue::resetToDefault (UndoManager* undoManagerToUse) +{ + targetTree.removeProperty (targetProperty, undoManagerToUse); + forceUpdateOfCachedValue(); +} + +template +void CachedValue::referTo (ValueTree& v, const Identifier& i, UndoManager* um) +{ + referToWithDefault (v, i, um, Type()); +} + +template +void CachedValue::referTo (ValueTree& v, const Identifier& i, UndoManager* um, const Type& defaultVal) +{ + referToWithDefault (v, i, um, defaultVal); +} + +template +void CachedValue::forceUpdateOfCachedValue() +{ + cachedValue = getTypedValue(); +} + +template +void CachedValue::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 +Type CachedValue::getTypedValue() const +{ + if (const var* property = targetTree.getPropertyPointer (targetProperty)) + return VariantConverter::fromVar (*property); + + return defaultValue; +} + +template +void CachedValue::valueTreePropertyChanged (ValueTree& changedTree, const Identifier& changedProperty) +{ + if (changedProperty == targetProperty && targetTree == changedTree) + forceUpdateOfCachedValue(); +} + +#endif // JUCE_CACHEDVALUE_H_INCLUDED