mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Added a new ValueWithDefault class to juce_data_structures and extended ChoicePropertyComponent and TextPropertyComponent to use this class
This commit is contained in:
parent
9717758901
commit
1d5c75546f
6 changed files with 407 additions and 27 deletions
|
|
@ -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"
|
||||
|
|
|
|||
163
modules/juce_data_structures/values/juce_ValueWithDefault.h
Normal file
163
modules/juce_data_structures/values/juce_ValueWithDefault.h
Normal file
|
|
@ -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 (choices[i].isNotEmpty())
|
||||
comboBox.addItem (choices[i], i + 1);
|
||||
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 (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;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue