|  | /*
  ==============================================================================
   This file is part of the JUCE library.
   Copyright (c) 2020 - Raw Material Software Limited
   JUCE is an open source library subject to commercial or open-source
   licensing.
   By using JUCE, you agree to the terms of both the JUCE 6 End-User License
   Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
   End User License Agreement: www.juce.com/juce-6-licence
   Privacy Policy: www.juce.com/juce-privacy-policy
   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).
   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.
  ==============================================================================
*/
namespace juce
{
//==============================================================================
/**
    This class acts as a wrapper around a property inside a ValueTree.
    If the property inside the ValueTree is missing the ValueWithDefault will automatically
    return a default value, which can be specified when initialising the ValueWithDefault.
    @tags{DataStructures}
*/
class ValueWithDefault
{
public:
    //==============================================================================
    /** Creates an unitialised ValueWithDefault. Initialise it using one of the referTo() methods. */
    ValueWithDefault() = default;
    /** Creates an ValueWithDefault object. The default value will be an empty var. */
    ValueWithDefault (ValueTree& tree, const Identifier& propertyID, UndoManager* um)
        : targetTree (tree),
          targetProperty (propertyID),
          undoManager (um),
          defaultValue()
    {
    }
    /** Creates an ValueWithDefault object. The default value will be defaultToUse. */
    ValueWithDefault (ValueTree& tree, const Identifier& propertyID, UndoManager* um,
                      const var& defaultToUse)
        : targetTree (tree),
          targetProperty (propertyID),
          undoManager (um),
          defaultValue (defaultToUse)
    {
    }
    /** Creates an ValueWithDefault object. The default value will be defaultToUse.
        Use this constructor if the underlying var object being controlled is an array and
        it will handle the conversion to/from a delimited String that can be written to
        XML format.
    */
    ValueWithDefault (ValueTree& tree, const Identifier& propertyID, UndoManager* um,
                      const var& defaultToUse, StringRef arrayDelimiter)
        : targetTree (tree),
          targetProperty (propertyID),
          undoManager (um),
          defaultValue (defaultToUse),
          delimiter (arrayDelimiter)
    {
    }
    /** Creates a ValueWithDefault object from another ValueWithDefault object. */
    ValueWithDefault (const ValueWithDefault& other)
        : targetTree (other.targetTree),
          targetProperty (other.targetProperty),
          undoManager (other.undoManager),
          defaultValue (other.defaultValue),
          delimiter (other.delimiter)
    {
    }
    //==============================================================================
    /** Returns the current value of the property. If the property does not exist this
        returns the default value.
    */
    var get() const noexcept
    {
        if (isUsingDefault())
            return defaultValue;
        if (delimiter.isNotEmpty())
            return delimitedStringToVarArray (targetTree[targetProperty].toString());
        return targetTree[targetProperty];
    }
    /** Returns the current property as a Value object. */
    Value getPropertyAsValue()     { return targetTree.getPropertyAsValue (targetProperty, undoManager); }
    /** Returns the current default value. */
    var getDefault() const         { return defaultValue; }
    /** Sets the default value to a new var. */
    void setDefault (const var& newDefault)
    {
        if (defaultValue != newDefault)
        {
            defaultValue = newDefault;
            if (onDefaultChange != nullptr)
                onDefaultChange();
        }
    }
    /** Returns true if the property does not exist in the referenced ValueTree. */
    bool isUsingDefault() const
    {
        return ! targetTree.hasProperty (targetProperty);
    }
    /** Removes the property from the referenced ValueTree. */
    void resetToDefault() noexcept
    {
        targetTree.removeProperty (targetProperty, nullptr);
    }
    /** You can assign a lambda to this callback object to have it called when the default value is changed. */
    std::function<void()> onDefaultChange;
    //==============================================================================
    /** Sets the property and returns the new ValueWithDefault. This will modify the property in the referenced ValueTree. */
    ValueWithDefault& operator= (const var& newValue)
    {
        setValue (newValue, undoManager);
        return *this;
    }
    /** Sets the property. This will actually modify the property in the referenced ValueTree. */
    void setValue (const var& newValue, UndoManager* undoManagerToUse)
    {
        if (auto* array = newValue.getArray())
            targetTree.setProperty (targetProperty, varArrayToDelimitedString (*array), undoManagerToUse);
        else
            targetTree.setProperty (targetProperty, newValue, undoManagerToUse);
    }
    //==============================================================================
    /** Makes the ValueWithDefault refer to the specified property inside the given ValueTree. */
    void referTo (ValueTree& tree, const Identifier& property, UndoManager* um)
    {
        referToWithDefault (tree, property, um, var(), {});
    }
    /** Makes the ValueWithDefault refer to the specified property inside the given ValueTree,
        and specifies a default value to use.
     */
    void referTo (ValueTree& tree, const Identifier& property, UndoManager* um, const var& defaultVal)
    {
        referToWithDefault (tree, property, um, defaultVal, {});
    }
    void referTo (ValueTree& tree, const Identifier& property, UndoManager* um,
                  const var& defaultVal, StringRef arrayDelimiter)
    {
        referToWithDefault (tree, property, um, defaultVal, arrayDelimiter);
    }
    //==============================================================================
    /** Returns a reference to the ValueTree containing the referenced property. */
    ValueTree& getValueTree() noexcept                      { return targetTree; }
    /** Returns the property ID of the referenced property. */
    Identifier& getPropertyID() noexcept                    { return targetProperty; }
    /** Returns the UndoManager that is being used. */
    UndoManager* getUndoManager() noexcept                  { return undoManager; }
    //==============================================================================
    ValueWithDefault& operator= (const ValueWithDefault& other)
    {
        referToWithDefault (other.targetTree, other.targetProperty, other.undoManager,
                            other.defaultValue, other.delimiter);
        return *this;
    }
private:
    //==============================================================================
    ValueTree targetTree;
    Identifier targetProperty;
    UndoManager* undoManager = nullptr;
    var defaultValue;
    String delimiter;
    //==============================================================================
    void referToWithDefault (const ValueTree& v, const Identifier& i, UndoManager* um,
                             const var& defaultVal, StringRef del)
    {
        targetTree = v;
        targetProperty = i;
        undoManager = um;
        defaultValue = defaultVal;
        delimiter = del;
    }
    //==============================================================================
    String varArrayToDelimitedString (const Array<var>& input) const noexcept
    {
        // if you are trying to control a var that is an array then you need to
        // set a delimiter string that will be used when writing to XML!
        jassert (delimiter.isNotEmpty());
        StringArray elements;
        for (auto& v : input)
            elements.add (v.toString());
        return elements.joinIntoString (delimiter);
    }
    Array<var> delimitedStringToVarArray (StringRef input) const noexcept
    {
        Array<var> arr;
        for (auto t : StringArray::fromTokens (input, delimiter, {}))
            arr.add (t);
        return arr;
    }
    //==============================================================================
    JUCE_DECLARE_WEAK_REFERENCEABLE (ValueWithDefault)
};
} // namespace juce
 |