| @@ -60,5 +60,6 @@ | |||
| #include "values/juce_ValueTree.h" | |||
| #include "values/juce_ValueTreeSynchroniser.h" | |||
| #include "values/juce_CachedValue.h" | |||
| #include "values/juce_ValueWithDefault.h" | |||
| #include "app_properties/juce_PropertiesFile.h" | |||
| #include "app_properties/juce_ApplicationProperties.h" | |||
| @@ -0,0 +1,163 @@ | |||
| /* | |||
| ============================================================================== | |||
| 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. | |||
| */ | |||
| 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 a ValueWithDefault object from another ValueWithDefault object. */ | |||
| ValueWithDefault (const ValueWithDefault& other) | |||
| : targetTree (other.targetTree), | |||
| targetProperty (other.targetProperty), | |||
| undoManager (other.undoManager), | |||
| defaultValue (other.defaultValue) | |||
| { | |||
| } | |||
| //============================================================================== | |||
| /** 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; | |||
| 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; | |||
| } | |||
| /** 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); | |||
| } | |||
| //============================================================================== | |||
| /** 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) | |||
| { | |||
| 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); | |||
| } | |||
| //============================================================================== | |||
| /** 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; | |||
| //============================================================================== | |||
| void referToWithDefault (ValueTree& v, const Identifier& i, UndoManager* um, const var& defaultVal) | |||
| { | |||
| targetTree = v; | |||
| targetProperty = i; | |||
| undoManager = um; | |||
| defaultValue = defaultVal; | |||
| } | |||
| }; | |||
| } // namespace juce | |||
| @@ -27,30 +27,32 @@ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| class ChoicePropertyComponent::RemapperValueSource : public Value::ValueSource, | |||
| private Value::Listener | |||
| { | |||
| public: | |||
| RemapperValueSource (const Value& source, const Array<var>& map) | |||
| : sourceValue (source), mappings (map) | |||
| : sourceValue (source), | |||
| mappings (map) | |||
| { | |||
| sourceValue.addListener (this); | |||
| } | |||
| var getValue() const | |||
| var getValue() const override | |||
| { | |||
| const var targetValue (sourceValue.getValue()); | |||
| auto targetValue = sourceValue.getValue(); | |||
| for (int i = 0; i < mappings.size(); ++i) | |||
| if (mappings.getReference(i).equalsWithSameType (targetValue)) | |||
| return i + 1; | |||
| for (auto& map : mappings) | |||
| if (map.equalsWithSameType (targetValue)) | |||
| return mappings.indexOf (map) + 1; | |||
| return mappings.indexOf (targetValue) + 1; | |||
| } | |||
| void setValue (const var& newValue) | |||
| void setValue (const var& newValue) override | |||
| { | |||
| const var remappedVal (mappings [static_cast<int> (newValue) - 1]); | |||
| auto remappedVal = mappings [static_cast<int> (newValue) - 1]; | |||
| if (! remappedVal.equalsWithSameType (sourceValue)) | |||
| sourceValue = remappedVal; | |||
| @@ -60,14 +62,66 @@ protected: | |||
| Value sourceValue; | |||
| Array<var> mappings; | |||
| void valueChanged (Value&) | |||
| { | |||
| sendChangeMessage (true); | |||
| } | |||
| void valueChanged (Value&) override { sendChangeMessage (true); } | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RemapperValueSource) | |||
| }; | |||
| //============================================================================== | |||
| class ChoicePropertyComponent::RemapperValueSourceWithDefault : public Value::ValueSource, | |||
| private Value::Listener | |||
| { | |||
| public: | |||
| RemapperValueSourceWithDefault (const ValueWithDefault& vwd, const Array<var>& map) | |||
| : valueWithDefault (vwd), | |||
| sourceValue (valueWithDefault.getPropertyAsValue()), | |||
| mappings (map) | |||
| { | |||
| sourceValue.addListener (this); | |||
| } | |||
| var getValue() const override | |||
| { | |||
| if (valueWithDefault.isUsingDefault()) | |||
| return -1; | |||
| auto targetValue = sourceValue.getValue(); | |||
| for (auto map : mappings) | |||
| if (map.equalsWithSameType (targetValue)) | |||
| return mappings.indexOf (map) + 1; | |||
| return mappings.indexOf (targetValue) + 1; | |||
| } | |||
| void setValue (const var& newValue) override | |||
| { | |||
| auto newValueInt = static_cast<int> (newValue); | |||
| if (newValueInt == -1) | |||
| { | |||
| valueWithDefault.resetToDefault(); | |||
| } | |||
| else | |||
| { | |||
| auto remappedVal = mappings [newValueInt - 1]; | |||
| if (! remappedVal.equalsWithSameType (sourceValue)) | |||
| valueWithDefault = remappedVal; | |||
| } | |||
| } | |||
| private: | |||
| ValueWithDefault valueWithDefault; | |||
| Value sourceValue; | |||
| Array<var> mappings; | |||
| void valueChanged (Value&) override { sendChangeMessage (true); } | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RemapperValueSourceWithDefault) | |||
| }; | |||
| //============================================================================== | |||
| ChoicePropertyComponent::ChoicePropertyComponent (const String& name) | |||
| @@ -76,24 +130,54 @@ ChoicePropertyComponent::ChoicePropertyComponent (const String& name) | |||
| { | |||
| } | |||
| ChoicePropertyComponent::ChoicePropertyComponent (const Value& valueToControl, | |||
| const String& name, | |||
| ChoicePropertyComponent::ChoicePropertyComponent (const String& name, | |||
| const StringArray& choiceList, | |||
| const Array<var>& correspondingValues) | |||
| : PropertyComponent (name), | |||
| choices (choiceList), | |||
| isCustomClass (false) | |||
| choices (choiceList) | |||
| { | |||
| // The array of corresponding values must contain one value for each of the items in | |||
| // the choices array! | |||
| jassert (correspondingValues.size() == choices.size()); | |||
| ignoreUnused (correspondingValues); | |||
| } | |||
| ChoicePropertyComponent::ChoicePropertyComponent (const Value& valueToControl, | |||
| const String& name, | |||
| const StringArray& choiceList, | |||
| const Array<var>& correspondingValues) | |||
| : ChoicePropertyComponent (name, choiceList, correspondingValues) | |||
| { | |||
| createComboBox(); | |||
| comboBox.getSelectedIdAsValue().referTo (Value (new RemapperValueSource (valueToControl, | |||
| correspondingValues))); | |||
| } | |||
| ChoicePropertyComponent::ChoicePropertyComponent (ValueWithDefault valueToControl, | |||
| const String& name, | |||
| const StringArray& choiceList, | |||
| const Array<var>& correspondingValues) | |||
| : ChoicePropertyComponent (name, choiceList, correspondingValues) | |||
| { | |||
| createComboBoxWithDefault (choiceList [correspondingValues.indexOf (valueToControl.getDefault())]); | |||
| comboBox.getSelectedIdAsValue().referTo (Value (new RemapperValueSourceWithDefault (valueToControl, | |||
| correspondingValues))); | |||
| } | |||
| ChoicePropertyComponent::ChoicePropertyComponent (ValueWithDefault valueToControl, | |||
| const String& name) | |||
| : PropertyComponent (name), | |||
| choices ({ "Enabled", "Disabled" }) | |||
| { | |||
| createComboBoxWithDefault (valueToControl.getDefault() ? "Enabled" : "Disabled"); | |||
| comboBox.getSelectedIdAsValue().referTo (Value (new RemapperValueSourceWithDefault (valueToControl, | |||
| { true, false }))); | |||
| } | |||
| ChoicePropertyComponent::~ChoicePropertyComponent() | |||
| { | |||
| } | |||
| @@ -103,10 +187,27 @@ void ChoicePropertyComponent::createComboBox() | |||
| { | |||
| addAndMakeVisible (comboBox); | |||
| for (int i = 0; i < choices.size(); ++i) | |||
| for (auto choice : choices) | |||
| { | |||
| if (choice.isNotEmpty()) | |||
| comboBox.addItem (choice, choices.indexOf (choice) + 1); | |||
| else | |||
| comboBox.addSeparator(); | |||
| } | |||
| comboBox.setEditableText (false); | |||
| } | |||
| void ChoicePropertyComponent::createComboBoxWithDefault (const String& defaultString) | |||
| { | |||
| addAndMakeVisible (comboBox); | |||
| comboBox.addItem ("Default" + (defaultString.isNotEmpty() ? " (" + defaultString + ")" : ""), -1); | |||
| for (auto choice : choices) | |||
| { | |||
| if (choices[i].isNotEmpty()) | |||
| comboBox.addItem (choices[i], i + 1); | |||
| if (choice.isNotEmpty()) | |||
| comboBox.addItem (choice, choices.indexOf (choice) + 1); | |||
| else | |||
| comboBox.addSeparator(); | |||
| } | |||
| @@ -149,7 +250,7 @@ void ChoicePropertyComponent::comboBoxChanged (ComboBox*) | |||
| { | |||
| if (isCustomClass) | |||
| { | |||
| const int newIndex = comboBox.getSelectedId() - 1; | |||
| auto newIndex = comboBox.getSelectedId() - 1; | |||
| if (newIndex != getIndex()) | |||
| setIndex (newIndex); | |||
| @@ -75,6 +75,31 @@ public: | |||
| const StringArray& choices, | |||
| const Array<var>& correspondingValues); | |||
| /** Creates the component using a ValueWithDefault object. This will add an item to the ComboBox for the | |||
| default value with an ID of -1. | |||
| @param valueToControl the ValueWithDefault object that contains the Value object that the combo box will read and control | |||
| @param propertyName the name of the property | |||
| @param choices the list of possible values that the drop-down list will contain | |||
| @param correspondingValues a list of values corresponding to each item in the 'choices' StringArray. | |||
| These are the values that will be read and written to the | |||
| valueToControl value. This array must contain the same number of items | |||
| as the choices array | |||
| */ | |||
| ChoicePropertyComponent (ValueWithDefault valueToControl, | |||
| const String& propertyName, | |||
| const StringArray& choices, | |||
| const Array<var>& correspondingValues); | |||
| /** Creates the component using a ValueWithDefault object, adding an item to the ComboBox for the | |||
| default value with an ID of -1 as well as adding separate "Enabled" and "Disabled" options. | |||
| This is useful for simple on/off choices that also need a default value. | |||
| */ | |||
| ChoicePropertyComponent (ValueWithDefault valueToControl, | |||
| const String& propertyName); | |||
| /** Destructor. */ | |||
| ~ChoicePropertyComponent(); | |||
| @@ -95,10 +120,9 @@ public: | |||
| /** Returns the list of options. */ | |||
| const StringArray& getChoices() const; | |||
| //============================================================================== | |||
| /** @internal */ | |||
| void refresh(); | |||
| void refresh() override; | |||
| protected: | |||
| /** The list of options that will be shown in the combo box. | |||
| @@ -110,12 +134,19 @@ protected: | |||
| StringArray choices; | |||
| private: | |||
| /** Delegating constructor. */ | |||
| ChoicePropertyComponent (const String&, const StringArray&, const Array<var>&); | |||
| ComboBox comboBox; | |||
| bool isCustomClass; | |||
| bool isCustomClass = false; | |||
| class RemapperValueSource; | |||
| class RemapperValueSourceWithDefault; | |||
| void createComboBox(); | |||
| void comboBoxChanged (ComboBox*); | |||
| void createComboBoxWithDefault (const String&); | |||
| void comboBoxChanged (ComboBox*) override; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChoicePropertyComponent) | |||
| }; | |||
| @@ -27,6 +27,7 @@ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| class TextPropertyComponent::LabelComp : public Label, | |||
| public FileDragAndDropTarget | |||
| { | |||
| @@ -85,11 +86,66 @@ public: | |||
| interestedInFileDrag = isInterested; | |||
| } | |||
| void setTextToDisplayWhenEmpty (const String& text, float alpha) | |||
| { | |||
| textToDisplayWhenEmpty = text; | |||
| alphaToUseForEmptyText = alpha; | |||
| } | |||
| void paintOverChildren (Graphics& g) override | |||
| { | |||
| if (getText().isEmpty() && ! isBeingEdited()) | |||
| { | |||
| auto textArea = getBorderSize().subtractedFrom (getLocalBounds()); | |||
| auto labelFont = owner.getLookAndFeel().getLabelFont (*this); | |||
| g.setColour (owner.findColour (TextPropertyComponent::textColourId).withAlpha (alphaToUseForEmptyText)); | |||
| g.setFont (labelFont); | |||
| g.drawFittedText (textToDisplayWhenEmpty, textArea, getJustificationType(), | |||
| jmax (1, (int) (textArea.getHeight() / labelFont.getHeight())), | |||
| getMinimumHorizontalScale()); | |||
| } | |||
| } | |||
| private: | |||
| TextPropertyComponent& owner; | |||
| int maxChars; | |||
| bool isMultiline; | |||
| bool interestedInFileDrag = true; | |||
| String textToDisplayWhenEmpty; | |||
| float alphaToUseForEmptyText = 0.0f; | |||
| }; | |||
| //============================================================================== | |||
| class TextPropertyComponent::RemapperValueSourceWithDefault : public Value::ValueSource | |||
| { | |||
| public: | |||
| RemapperValueSourceWithDefault (const ValueWithDefault& vwd) | |||
| : valueWithDefault (vwd) | |||
| { | |||
| } | |||
| var getValue() const override | |||
| { | |||
| return valueWithDefault.isUsingDefault() ? var() : valueWithDefault.get(); | |||
| } | |||
| void setValue (const var& newValue) override | |||
| { | |||
| if (newValue.toString().isEmpty()) | |||
| valueWithDefault.resetToDefault(); | |||
| else | |||
| valueWithDefault = newValue; | |||
| } | |||
| private: | |||
| ValueWithDefault valueWithDefault; | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RemapperValueSourceWithDefault) | |||
| }; | |||
| //============================================================================== | |||
| @@ -107,13 +163,22 @@ TextPropertyComponent::TextPropertyComponent (const Value& valueToControl, | |||
| int maxNumChars, | |||
| bool isMultiLine, | |||
| bool isEditable) | |||
| : PropertyComponent (name) | |||
| : TextPropertyComponent (name, maxNumChars, isMultiLine, isEditable) | |||
| { | |||
| createEditor (maxNumChars, isMultiLine, isEditable); | |||
| textEditor->getTextValue().referTo (valueToControl); | |||
| } | |||
| TextPropertyComponent::TextPropertyComponent (const ValueWithDefault& valueToControl, | |||
| const String& name, | |||
| int maxNumChars, | |||
| bool isMultiLine, | |||
| bool isEditable) | |||
| : TextPropertyComponent (name, maxNumChars, isMultiLine, isEditable) | |||
| { | |||
| textEditor->getTextValue().referTo (Value (new RemapperValueSourceWithDefault (valueToControl))); | |||
| textEditor->setTextToDisplayWhenEmpty (valueToControl.getDefault(), 0.5f); | |||
| } | |||
| TextPropertyComponent::~TextPropertyComponent() | |||
| { | |||
| } | |||
| @@ -70,6 +70,23 @@ public: | |||
| bool isMultiLine, | |||
| bool isEditable = true); | |||
| /** Creates a text property component with a default value. | |||
| @param valueToControl The ValueWithDefault that is controlled by the TextPropertyComponent | |||
| @param propertyName The name of the property | |||
| @param maxNumChars If not zero, then this specifies the maximum allowable length of | |||
| the string. If zero, then the string will have no length limit. | |||
| @param isMultiLine Sets whether the text editor allows carriage returns. | |||
| @param isEditable Sets whether the text editor is editable. The default is true. | |||
| @see TextEditor, setEditable | |||
| */ | |||
| TextPropertyComponent (const ValueWithDefault& valueToControl, | |||
| const String& propertyName, | |||
| int maxNumChars, | |||
| bool isMultiLine, | |||
| bool isEditable = true); | |||
| /** Destructor. */ | |||
| ~TextPropertyComponent(); | |||
| @@ -144,6 +161,8 @@ public: | |||
| virtual void textWasEdited(); | |||
| private: | |||
| class RemapperValueSourceWithDefault; | |||
| class LabelComp; | |||
| friend class LabelComp; | |||