VST3: fetch midi mappings all at once, use it for note/sound-off
[carla.git] / source / modules / juce_gui_basics / properties / juce_MultiChoicePropertyComponent.cpp
blob77e51fa5f17a83d7ed1e78ffa941f7b48b8f08a5
1 /*
2 ==============================================================================
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
10 By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11 Agreement and JUCE Privacy Policy.
13 End User License Agreement: www.juce.com/juce-7-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
23 ==============================================================================
27 namespace juce
30 //==============================================================================
31 class StringComparator
33 public:
34 static int compareElements (var first, var second)
36 if (first.toString() > second.toString())
37 return 1;
38 else if (first.toString() < second.toString())
39 return -1;
41 return 0;
45 static void updateButtonTickColour (ToggleButton* button, bool usingDefault)
47 button->setColour (ToggleButton::tickColourId, button->getLookAndFeel().findColour (ToggleButton::tickColourId)
48 .withAlpha (usingDefault ? 0.4f : 1.0f));
51 //==============================================================================
52 class MultiChoicePropertyComponent::MultiChoiceRemapperSource : public Value::ValueSource,
53 private Value::Listener
55 public:
56 MultiChoiceRemapperSource (const Value& source, var v, int c)
57 : sourceValue (source),
58 varToControl (v),
59 maxChoices (c)
61 sourceValue.addListener (this);
64 var getValue() const override
66 if (auto* arr = sourceValue.getValue().getArray())
67 if (arr->contains (varToControl))
68 return true;
70 return false;
73 void setValue (const var& newValue) override
75 if (auto* arr = sourceValue.getValue().getArray())
77 auto temp = *arr;
79 if (static_cast<bool> (newValue))
81 if (temp.addIfNotAlreadyThere (varToControl) && (maxChoices != -1) && (temp.size() > maxChoices))
82 temp.remove (temp.size() - 2);
84 else
86 temp.remove (arr->indexOf (varToControl));
89 StringComparator c;
90 temp.sort (c);
92 sourceValue = temp;
96 private:
97 Value sourceValue;
98 var varToControl;
100 int maxChoices;
102 //==============================================================================
103 void valueChanged (Value&) override { sendChangeMessage (true); }
105 //==============================================================================
106 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiChoiceRemapperSource)
109 //==============================================================================
110 class MultiChoicePropertyComponent::MultiChoiceRemapperSourceWithDefault : public Value::ValueSource,
111 private Value::Listener
113 public:
114 MultiChoiceRemapperSourceWithDefault (const ValueTreePropertyWithDefault& val,
115 var v, int c, ToggleButton* b)
116 : value (val),
117 varToControl (v),
118 sourceValue (value.getPropertyAsValue()),
119 maxChoices (c),
120 buttonToControl (b)
122 sourceValue.addListener (this);
125 var getValue() const override
127 auto v = value.get();
129 if (auto* arr = v.getArray())
131 if (arr->contains (varToControl))
133 updateButtonTickColour (buttonToControl, value.isUsingDefault());
134 return true;
138 return false;
141 void setValue (const var& newValue) override
143 auto v = value.get();
145 OptionalScopedPointer<Array<var>> arrayToControl;
147 if (value.isUsingDefault())
148 arrayToControl.set (new Array<var>(), true); // use an empty array so the default values are overwritten
149 else
150 arrayToControl.set (v.getArray(), false);
152 if (arrayToControl != nullptr)
154 auto temp = *arrayToControl;
156 bool newState = newValue;
158 if (value.isUsingDefault())
160 if (auto* defaultArray = v.getArray())
162 if (defaultArray->contains (varToControl))
163 newState = true; // force the state as the user is setting it explicitly
167 if (newState)
169 if (temp.addIfNotAlreadyThere (varToControl) && (maxChoices != -1) && (temp.size() > maxChoices))
170 temp.remove (temp.size() - 2);
172 else
174 temp.remove (temp.indexOf (varToControl));
177 StringComparator c;
178 temp.sort (c);
180 value = temp;
182 if (temp.size() == 0)
183 value.resetToDefault();
187 private:
188 //==============================================================================
189 void valueChanged (Value&) override { sendChangeMessage (true); }
191 //==============================================================================
192 ValueTreePropertyWithDefault value;
193 var varToControl;
194 Value sourceValue;
196 int maxChoices;
198 ToggleButton* buttonToControl;
200 //==============================================================================
201 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiChoiceRemapperSourceWithDefault)
204 //==============================================================================
205 int MultiChoicePropertyComponent::getTotalButtonsHeight (int numButtons)
207 return numButtons * buttonHeight + 1;
210 MultiChoicePropertyComponent::MultiChoicePropertyComponent (const String& propertyName,
211 const StringArray& choices,
212 const Array<var>& correspondingValues)
213 : PropertyComponent (propertyName, jmin (getTotalButtonsHeight (choices.size()), collapsedHeight))
215 // The array of corresponding values must contain one value for each of the items in
216 // the choices array!
217 jassertquiet (choices.size() == correspondingValues.size());
219 for (auto choice : choices)
220 addAndMakeVisible (choiceButtons.add (new ToggleButton (choice)));
222 if (preferredHeight >= collapsedHeight)
224 expandable = true;
225 maxHeight = getTotalButtonsHeight (choiceButtons.size()) + expandAreaHeight;
228 if (isExpandable())
231 Path expandShape;
232 expandShape.addTriangle ({ 0, 0 }, { 5, 10 }, { 10, 0});
233 expandButton.setShape (expandShape, true, true, false);
236 expandButton.onClick = [this] { setExpanded (! expanded); };
237 addAndMakeVisible (expandButton);
239 lookAndFeelChanged();
243 MultiChoicePropertyComponent::MultiChoicePropertyComponent (const Value& valueToControl,
244 const String& propertyName,
245 const StringArray& choices,
246 const Array<var>& correspondingValues,
247 int maxChoices)
248 : MultiChoicePropertyComponent (propertyName, choices, correspondingValues)
250 // The value to control must be an array!
251 jassert (valueToControl.getValue().isArray());
253 for (int i = 0; i < choiceButtons.size(); ++i)
254 choiceButtons[i]->getToggleStateValue().referTo (Value (new MultiChoiceRemapperSource (valueToControl,
255 correspondingValues[i],
256 maxChoices)));
259 MultiChoicePropertyComponent::MultiChoicePropertyComponent (const ValueTreePropertyWithDefault& valueToControl,
260 const String& propertyName,
261 const StringArray& choices,
262 const Array<var>& correspondingValues,
263 int maxChoices)
264 : MultiChoicePropertyComponent (propertyName, choices, correspondingValues)
266 value = valueToControl;
268 // The value to control must be an array!
269 jassert (value.get().isArray());
271 for (int i = 0; i < choiceButtons.size(); ++i)
272 choiceButtons[i]->getToggleStateValue().referTo (Value (new MultiChoiceRemapperSourceWithDefault (value,
273 correspondingValues[i],
274 maxChoices,
275 choiceButtons[i])));
277 value.onDefaultChange = [this] { repaint(); };
280 void MultiChoicePropertyComponent::paint (Graphics& g)
282 g.setColour (findColour (TextEditor::backgroundColourId));
283 g.fillRect (getLookAndFeel().getPropertyComponentContentPosition (*this));
285 if (isExpandable() && ! isExpanded())
287 g.setColour (findColour (TextEditor::backgroundColourId).contrasting().withAlpha (0.4f));
288 g.drawFittedText ("+ " + String (numHidden) + " more", getLookAndFeel().getPropertyComponentContentPosition (*this)
289 .removeFromBottom (expandAreaHeight).withTrimmedLeft (10),
290 Justification::centredLeft, 1);
293 PropertyComponent::paint (g);
296 void MultiChoicePropertyComponent::resized()
298 auto bounds = getLookAndFeel().getPropertyComponentContentPosition (*this);
300 if (isExpandable())
302 bounds.removeFromBottom (5);
304 auto buttonSlice = bounds.removeFromBottom (10);
305 expandButton.setSize (10, 10);
306 expandButton.setCentrePosition (buttonSlice.getCentre());
309 numHidden = 0;
311 for (auto* b : choiceButtons)
313 if (bounds.getHeight() >= buttonHeight)
315 b->setVisible (true);
316 b->setBounds (bounds.removeFromTop (buttonHeight).reduced (5, 2));
318 else
320 b->setVisible (false);
321 ++numHidden;
326 void MultiChoicePropertyComponent::setExpanded (bool shouldBeExpanded) noexcept
328 if (! isExpandable() || (isExpanded() == shouldBeExpanded))
329 return;
331 expanded = shouldBeExpanded;
332 preferredHeight = expanded ? maxHeight : collapsedHeight;
334 if (auto* propertyPanel = findParentComponentOfClass<PropertyPanel>())
335 propertyPanel->resized();
337 if (onHeightChange != nullptr)
338 onHeightChange();
340 expandButton.setTransform (AffineTransform::rotation (expanded ? MathConstants<float>::pi : MathConstants<float>::twoPi,
341 (float) expandButton.getBounds().getCentreX(),
342 (float) expandButton.getBounds().getCentreY()));
344 resized();
347 //==============================================================================
348 void MultiChoicePropertyComponent::lookAndFeelChanged()
350 auto iconColour = findColour (TextEditor::backgroundColourId).contrasting();
351 expandButton.setColours (iconColour, iconColour.darker(), iconColour.darker());
353 const auto usingDefault = value.isUsingDefault();
355 for (auto* button : choiceButtons)
356 updateButtonTickColour (button, usingDefault);
359 } // namespace juce