JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_ValueTree.cpp
Go to the documentation of this file.
1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11 Agreement and JUCE Privacy Policy.
12
13 End User License Agreement: www.juce.com/juce-7-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
15
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
18
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.
22
23 ==============================================================================
24*/
25
26namespace juce
27{
28
30{
31public:
33
34 explicit SharedObject (const Identifier& t) noexcept : type (t) {}
35
37 : ReferenceCountedObject(), type (other.type), properties (other.properties)
38 {
39 for (auto* c : other.children)
40 {
41 auto* child = new SharedObject (*c);
42 child->parent = this;
43 children.add (child);
44 }
45 }
46
47 SharedObject& operator= (const SharedObject&) = delete;
48
49 ~SharedObject() override
50 {
51 jassert (parent == nullptr); // this should never happen unless something isn't obeying the ref-counting!
52
53 for (auto i = children.size(); --i >= 0;)
54 {
55 const Ptr c (children.getObjectPointerUnchecked (i));
56 c->parent = nullptr;
57 children.remove (i);
58 c->sendParentChangeMessage();
59 }
60 }
61
62 SharedObject& getRoot() noexcept
63 {
64 return parent == nullptr ? *this : parent->getRoot();
65 }
66
67 template <typename Function>
68 void callListeners (ValueTree::Listener* listenerToExclude, Function fn) const
69 {
70 auto numListeners = valueTreesWithListeners.size();
71
72 if (numListeners == 1)
73 {
74 valueTreesWithListeners.getUnchecked (0)->listeners.callExcluding (listenerToExclude, fn);
75 }
76 else if (numListeners > 0)
77 {
78 auto listenersCopy = valueTreesWithListeners;
79
80 for (int i = 0; i < numListeners; ++i)
81 {
82 auto* v = listenersCopy.getUnchecked (i);
83
84 if (i == 0 || valueTreesWithListeners.contains (v))
85 v->listeners.callExcluding (listenerToExclude, fn);
86 }
87 }
88 }
89
90 template <typename Function>
91 void callListenersForAllParents (ValueTree::Listener* listenerToExclude, Function fn) const
92 {
93 for (auto* t = this; t != nullptr; t = t->parent)
94 t->callListeners (listenerToExclude, fn);
95 }
96
97 void sendPropertyChangeMessage (const Identifier& property, ValueTree::Listener* listenerToExclude = nullptr)
98 {
99 ValueTree tree (*this);
100 callListenersForAllParents (listenerToExclude, [&] (Listener& l) { l.valueTreePropertyChanged (tree, property); });
101 }
102
103 void sendChildAddedMessage (ValueTree child)
104 {
105 ValueTree tree (*this);
106 callListenersForAllParents (nullptr, [&] (Listener& l) { l.valueTreeChildAdded (tree, child); });
107 }
108
109 void sendChildRemovedMessage (ValueTree child, int index)
110 {
111 ValueTree tree (*this);
112 callListenersForAllParents (nullptr, [=, &tree, &child] (Listener& l) { l.valueTreeChildRemoved (tree, child, index); });
113 }
114
115 void sendChildOrderChangedMessage (int oldIndex, int newIndex)
116 {
117 ValueTree tree (*this);
118 callListenersForAllParents (nullptr, [=, &tree] (Listener& l) { l.valueTreeChildOrderChanged (tree, oldIndex, newIndex); });
119 }
120
121 void sendParentChangeMessage()
122 {
123 ValueTree tree (*this);
124
125 for (auto j = children.size(); --j >= 0;)
126 if (auto* child = children.getObjectPointer (j))
127 child->sendParentChangeMessage();
128
129 callListeners (nullptr, [&] (Listener& l) { l.valueTreeParentChanged (tree); });
130 }
131
132 void setProperty (const Identifier& name, const var& newValue, UndoManager* undoManager,
134 {
135 if (undoManager == nullptr)
136 {
137 if (properties.set (name, newValue))
138 sendPropertyChangeMessage (name, listenerToExclude);
139 }
140 else
141 {
142 if (auto* existingValue = properties.getVarPointer (name))
143 {
144 if (*existingValue != newValue)
145 undoManager->perform (new SetPropertyAction (*this, name, newValue, *existingValue,
146 false, false, listenerToExclude));
147 }
148 else
149 {
150 undoManager->perform (new SetPropertyAction (*this, name, newValue, {},
151 true, false, listenerToExclude));
152 }
153 }
154 }
155
156 bool hasProperty (const Identifier& name) const noexcept
157 {
158 return properties.contains (name);
159 }
160
161 void removeProperty (const Identifier& name, UndoManager* undoManager)
162 {
163 if (undoManager == nullptr)
164 {
165 if (properties.remove (name))
166 sendPropertyChangeMessage (name);
167 }
168 else
169 {
170 if (properties.contains (name))
171 undoManager->perform (new SetPropertyAction (*this, name, {}, properties[name], false, true));
172 }
173 }
174
175 void removeAllProperties (UndoManager* undoManager)
176 {
177 if (undoManager == nullptr)
178 {
179 while (properties.size() > 0)
180 {
181 auto name = properties.getName (properties.size() - 1);
182 properties.remove (name);
183 sendPropertyChangeMessage (name);
184 }
185 }
186 else
187 {
188 for (auto i = properties.size(); --i >= 0;)
189 undoManager->perform (new SetPropertyAction (*this, properties.getName (i), {},
190 properties.getValueAt (i), false, true));
191 }
192 }
193
194 void copyPropertiesFrom (const SharedObject& source, UndoManager* undoManager)
195 {
196 for (auto i = properties.size(); --i >= 0;)
197 if (! source.properties.contains (properties.getName (i)))
198 removeProperty (properties.getName (i), undoManager);
199
200 for (int i = 0; i < source.properties.size(); ++i)
201 setProperty (source.properties.getName (i), source.properties.getValueAt (i), undoManager);
202 }
203
204 ValueTree getChildWithName (const Identifier& typeToMatch) const
205 {
206 for (auto* s : children)
207 if (s->type == typeToMatch)
208 return ValueTree (*s);
209
210 return {};
211 }
212
213 ValueTree getOrCreateChildWithName (const Identifier& typeToMatch, UndoManager* undoManager)
214 {
215 for (auto* s : children)
216 if (s->type == typeToMatch)
217 return ValueTree (*s);
218
220 addChild (newObject, -1, undoManager);
221 return ValueTree (*newObject);
222 }
223
224 ValueTree getChildWithProperty (const Identifier& propertyName, const var& propertyValue) const
225 {
226 for (auto* s : children)
227 if (s->properties[propertyName] == propertyValue)
228 return ValueTree (*s);
229
230 return {};
231 }
232
233 bool isAChildOf (const SharedObject* possibleParent) const noexcept
234 {
235 for (auto* p = parent; p != nullptr; p = p->parent)
236 if (p == possibleParent)
237 return true;
238
239 return false;
240 }
241
242 int indexOf (const ValueTree& child) const noexcept
243 {
244 return children.indexOf (child.object);
245 }
246
247 void addChild (SharedObject* child, int index, UndoManager* undoManager)
248 {
249 if (child != nullptr && child->parent != this)
250 {
251 if (child != this && ! isAChildOf (child))
252 {
253 // You should always make sure that a child is removed from its previous parent before
254 // adding it somewhere else - otherwise, it's ambiguous as to whether a different
255 // undomanager should be used when removing it from its current parent..
256 jassert (child->parent == nullptr);
257
258 if (child->parent != nullptr)
259 {
260 jassert (child->parent->children.indexOf (child) >= 0);
261 child->parent->removeChild (child->parent->children.indexOf (child), undoManager);
262 }
263
264 if (undoManager == nullptr)
265 {
266 children.insert (index, child);
267 child->parent = this;
268 sendChildAddedMessage (ValueTree (*child));
269 child->sendParentChangeMessage();
270 }
271 else
272 {
273 if (! isPositiveAndBelow (index, children.size()))
274 index = children.size();
275
276 undoManager->perform (new AddOrRemoveChildAction (*this, index, child));
277 }
278 }
279 else
280 {
281 // You're attempting to create a recursive loop! A node
282 // can't be a child of one of its own children!
284 }
285 }
286 }
287
288 void removeChild (int childIndex, UndoManager* undoManager)
289 {
290 if (auto child = Ptr (children.getObjectPointer (childIndex)))
291 {
292 if (undoManager == nullptr)
293 {
294 children.remove (childIndex);
295 child->parent = nullptr;
296 sendChildRemovedMessage (ValueTree (child), childIndex);
297 child->sendParentChangeMessage();
298 }
299 else
300 {
301 undoManager->perform (new AddOrRemoveChildAction (*this, childIndex, {}));
302 }
303 }
304 }
305
306 void removeAllChildren (UndoManager* undoManager)
307 {
308 while (children.size() > 0)
309 removeChild (children.size() - 1, undoManager);
310 }
311
312 void moveChild (int currentIndex, int newIndex, UndoManager* undoManager)
313 {
314 // The source index must be a valid index!
315 jassert (isPositiveAndBelow (currentIndex, children.size()));
316
318 && isPositiveAndBelow (currentIndex, children.size()))
319 {
320 if (undoManager == nullptr)
321 {
322 children.move (currentIndex, newIndex);
323 sendChildOrderChangedMessage (currentIndex, newIndex);
324 }
325 else
326 {
327 if (! isPositiveAndBelow (newIndex, children.size()))
328 newIndex = children.size() - 1;
329
330 undoManager->perform (new MoveChildAction (*this, currentIndex, newIndex));
331 }
332 }
333 }
334
335 void reorderChildren (const OwnedArray<ValueTree>& newOrder, UndoManager* undoManager)
336 {
337 jassert (newOrder.size() == children.size());
338
339 for (int i = 0; i < children.size(); ++i)
340 {
341 auto* child = newOrder.getUnchecked (i)->object.get();
342
343 if (children.getObjectPointerUnchecked (i) != child)
344 {
345 auto oldIndex = children.indexOf (child);
346 jassert (oldIndex >= 0);
347 moveChild (oldIndex, i, undoManager);
348 }
349 }
350 }
351
352 bool isEquivalentTo (const SharedObject& other) const noexcept
353 {
354 if (type != other.type
355 || properties.size() != other.properties.size()
356 || children.size() != other.children.size()
357 || properties != other.properties)
358 return false;
359
360 for (int i = 0; i < children.size(); ++i)
361 if (! children.getObjectPointerUnchecked (i)->isEquivalentTo (*other.children.getObjectPointerUnchecked (i)))
362 return false;
363
364 return true;
365 }
366
367 XmlElement* createXml() const
368 {
369 auto* xml = new XmlElement (type);
370 properties.copyToXmlAttributes (*xml);
371
372 // (NB: it's faster to add nodes to XML elements in reverse order)
373 for (auto i = children.size(); --i >= 0;)
374 xml->prependChildElement (children.getObjectPointerUnchecked (i)->createXml());
375
376 return xml;
377 }
378
379 void writeToStream (OutputStream& output) const
380 {
381 output.writeString (type.toString());
382 output.writeCompressedInt (properties.size());
383
384 for (int j = 0; j < properties.size(); ++j)
385 {
386 output.writeString (properties.getName (j).toString());
387 properties.getValueAt (j).writeToStream (output);
388 }
389
390 output.writeCompressedInt (children.size());
391
392 for (auto* c : children)
393 writeObjectToStream (output, c);
394 }
395
396 static void writeObjectToStream (OutputStream& output, const SharedObject* object)
397 {
398 if (object != nullptr)
399 {
400 object->writeToStream (output);
401 }
402 else
403 {
404 output.writeString ({});
405 output.writeCompressedInt (0);
406 output.writeCompressedInt (0);
407 }
408 }
409
410 //==============================================================================
412 {
414 const var& newVal, const var& oldVal, bool isAdding, bool isDeleting,
416 : target (std::move (targetObject)),
417 name (propertyName), newValue (newVal), oldValue (oldVal),
418 isAddingNewProperty (isAdding), isDeletingProperty (isDeleting),
419 excludeListener (listenerToExclude)
420 {
421 }
422
423 bool perform() override
424 {
425 jassert (! (isAddingNewProperty && target->hasProperty (name)));
426
427 if (isDeletingProperty)
428 target->removeProperty (name, nullptr);
429 else
430 target->setProperty (name, newValue, nullptr, excludeListener);
431
432 return true;
433 }
434
435 bool undo() override
436 {
437 if (isAddingNewProperty)
438 target->removeProperty (name, nullptr);
439 else
440 target->setProperty (name, oldValue, nullptr);
441
442 return true;
443 }
444
445 int getSizeInUnits() override
446 {
447 return (int) sizeof (*this); //xxx should be more accurate
448 }
449
451 {
452 if (! (isAddingNewProperty || isDeletingProperty))
453 {
454 if (auto* next = dynamic_cast<SetPropertyAction*> (nextAction))
455 if (next->target == target && next->name == name
456 && ! (next->isAddingNewProperty || next->isDeletingProperty))
457 return new SetPropertyAction (*target, name, next->newValue, oldValue, false, false);
458 }
459
460 return nullptr;
461 }
462
463 private:
464 const Ptr target;
465 const Identifier name;
466 const var newValue;
467 var oldValue;
468 const bool isAddingNewProperty : 1, isDeletingProperty : 1;
469 ValueTree::Listener* excludeListener;
470
472 };
473
474 //==============================================================================
476 {
478 : target (std::move (parentObject)),
479 child (newChild != nullptr ? newChild : target->children.getObjectPointer (index)),
480 childIndex (index),
481 isDeleting (newChild == nullptr)
482 {
483 jassert (child != nullptr);
484 }
485
486 bool perform() override
487 {
488 if (isDeleting)
489 target->removeChild (childIndex, nullptr);
490 else
491 target->addChild (child.get(), childIndex, nullptr);
492
493 return true;
494 }
495
496 bool undo() override
497 {
498 if (isDeleting)
499 {
500 target->addChild (child.get(), childIndex, nullptr);
501 }
502 else
503 {
504 // If you hit this, it seems that your object's state is getting confused - probably
505 // because you've interleaved some undoable and non-undoable operations?
506 jassert (childIndex < target->children.size());
507 target->removeChild (childIndex, nullptr);
508 }
509
510 return true;
511 }
512
513 int getSizeInUnits() override
514 {
515 return (int) sizeof (*this); //xxx should be more accurate
516 }
517
518 private:
519 const Ptr target, child;
520 const int childIndex;
521 const bool isDeleting;
522
524 };
525
526 //==============================================================================
528 {
530 : parent (std::move (parentObject)), startIndex (fromIndex), endIndex (toIndex)
531 {
532 }
533
534 bool perform() override
535 {
536 parent->moveChild (startIndex, endIndex, nullptr);
537 return true;
538 }
539
540 bool undo() override
541 {
542 parent->moveChild (endIndex, startIndex, nullptr);
543 return true;
544 }
545
546 int getSizeInUnits() override
547 {
548 return (int) sizeof (*this); //xxx should be more accurate
549 }
550
552 {
553 if (auto* next = dynamic_cast<MoveChildAction*> (nextAction))
554 if (next->parent == parent && next->startIndex == endIndex)
555 return new MoveChildAction (parent, startIndex, next->endIndex);
556
557 return nullptr;
558 }
559
560 private:
561 const Ptr parent;
562 const int startIndex, endIndex;
563
565 };
566
567 //==============================================================================
568 const Identifier type;
569 NamedValueSet properties;
571 SortedSet<ValueTree*> valueTreesWithListeners;
572 SharedObject* parent = nullptr;
573
575};
576
577//==============================================================================
579{
580}
581
582ValueTree::ValueTree (const Identifier& type) : object (new ValueTree::SharedObject (type))
583{
584 jassert (type.toString().isNotEmpty()); // All objects must be given a sensible type name!
585}
586
590 : ValueTree (type)
591{
592 object->properties = NamedValueSet (std::move (properties));
593
594 for (auto& tree : subTrees)
595 addChild (tree, -1, nullptr);
596}
597
598ValueTree::ValueTree (SharedObject::Ptr so) noexcept : object (std::move (so)) {}
599ValueTree::ValueTree (SharedObject& so) noexcept : object (so) {}
600
601ValueTree::ValueTree (const ValueTree& other) noexcept : object (other.object)
602{
603}
604
606{
607 if (object != other.object)
608 {
609 if (listeners.isEmpty())
610 {
611 object = other.object;
612 }
613 else
614 {
615 if (object != nullptr)
616 object->valueTreesWithListeners.removeValue (this);
617
618 if (other.object != nullptr)
619 other.object->valueTreesWithListeners.add (this);
620
621 object = other.object;
622
623 listeners.call ([this] (Listener& l) { l.valueTreeRedirected (*this); });
624 }
625 }
626
627 return *this;
628}
629
631 : object (std::move (other.object))
632{
633 if (object != nullptr)
634 object->valueTreesWithListeners.removeValue (&other);
635}
636
638{
639 if (! listeners.isEmpty() && object != nullptr)
640 object->valueTreesWithListeners.removeValue (this);
641}
642
643bool ValueTree::operator== (const ValueTree& other) const noexcept
644{
645 return object == other.object;
646}
647
648bool ValueTree::operator!= (const ValueTree& other) const noexcept
649{
650 return object != other.object;
651}
652
654{
655 return object == other.object
656 || (object != nullptr && other.object != nullptr
657 && object->isEquivalentTo (*other.object));
658}
659
661{
662 if (object != nullptr)
663 return ValueTree (*new SharedObject (*object));
664
665 return {};
666}
667
668void ValueTree::copyPropertiesFrom (const ValueTree& source, UndoManager* undoManager)
669{
670 jassert (object != nullptr || source.object == nullptr); // Trying to add properties to a null ValueTree will fail!
671
672 if (source == *this)
673 return;
674
675 if (source.object == nullptr)
676 removeAllProperties (undoManager);
677 else if (object != nullptr)
678 object->copyPropertiesFrom (*(source.object), undoManager);
679}
680
682{
683 jassert (object != nullptr || source.object == nullptr); // Trying to copy to a null ValueTree will fail!
684
685 if (source == *this)
686 return;
687
688 copyPropertiesFrom (source, undoManager);
689 removeAllChildren (undoManager);
690
691 if (object != nullptr && source.object != nullptr)
692 for (auto& child : source.object->children)
693 object->addChild (createCopyIfNotNull (child), -1, undoManager);
694}
695
696bool ValueTree::hasType (const Identifier& typeName) const noexcept
697{
698 return object != nullptr && object->type == typeName;
699}
700
702{
703 return object != nullptr ? object->type : Identifier();
704}
705
707{
708 if (object != nullptr)
709 if (auto p = object->parent)
710 return ValueTree (*p);
711
712 return {};
713}
714
716{
717 if (object != nullptr)
718 return ValueTree (object->getRoot());
719
720 return {};
721}
722
723ValueTree ValueTree::getSibling (int delta) const noexcept
724{
725 if (object != nullptr)
726 if (auto* p = object->parent)
727 if (auto* c = p->children.getObjectPointer (p->indexOf (*this) + delta))
728 return ValueTree (*c);
729
730 return {};
731}
732
733static const var& getNullVarRef() noexcept
734{
735 static var nullVar;
736 return nullVar;
737}
738
739const var& ValueTree::operator[] (const Identifier& name) const noexcept
740{
741 return object == nullptr ? getNullVarRef() : object->properties[name];
742}
743
744const var& ValueTree::getProperty (const Identifier& name) const noexcept
745{
746 return object == nullptr ? getNullVarRef() : object->properties[name];
747}
748
750{
751 return object == nullptr ? defaultReturnValue
752 : object->properties.getWithDefault (name, defaultReturnValue);
753}
754
755const var* ValueTree::getPropertyPointer (const Identifier& name) const noexcept
756{
757 return object == nullptr ? nullptr
758 : object->properties.getVarPointer (name);
759}
760
761ValueTree& ValueTree::setProperty (const Identifier& name, const var& newValue, UndoManager* undoManager)
762{
763 return setPropertyExcludingListener (nullptr, name, newValue, undoManager);
764}
765
767 const var& newValue, UndoManager* undoManager)
768{
769 jassert (name.toString().isNotEmpty()); // Must have a valid property name!
770 jassert (object != nullptr); // Trying to add a property to a null ValueTree will fail!
771
772 if (object != nullptr)
773 object->setProperty (name, newValue, undoManager, listenerToExclude);
774
775 return *this;
776}
777
778bool ValueTree::hasProperty (const Identifier& name) const noexcept
779{
780 return object != nullptr && object->hasProperty (name);
781}
782
783void ValueTree::removeProperty (const Identifier& name, UndoManager* undoManager)
784{
785 if (object != nullptr)
786 object->removeProperty (name, undoManager);
787}
788
790{
791 if (object != nullptr)
792 object->removeAllProperties (undoManager);
793}
794
796{
797 return object == nullptr ? 0 : object->properties.size();
798}
799
800Identifier ValueTree::getPropertyName (int index) const noexcept
801{
802 return object == nullptr ? Identifier()
803 : object->properties.getName (index);
804}
805
807{
808 return object != nullptr ? object->getReferenceCount() : 0;
809}
810
811//==============================================================================
813 private ValueTree::Listener
814{
816 : tree (vt), property (prop), undoManager (um), updateSynchronously (sync)
817 {
818 tree.addListener (this);
819 }
820
822 {
823 tree.removeListener (this);
824 }
825
826 var getValue() const override { return tree[property]; }
827 void setValue (const var& newValue) override { tree.setProperty (property, newValue, undoManager); }
828
829private:
830 ValueTree tree;
831 const Identifier property;
832 UndoManager* const undoManager;
833 const bool updateSynchronously;
834
835 void valueTreePropertyChanged (ValueTree& changedTree, const Identifier& changedProperty) override
836 {
837 if (tree == changedTree && property == changedProperty)
838 sendChangeMessage (updateSynchronously);
839 }
840
841 void valueTreeChildAdded (ValueTree&, ValueTree&) override {}
842 void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override {}
843 void valueTreeChildOrderChanged (ValueTree&, int, int) override {}
844 void valueTreeParentChanged (ValueTree&) override {}
845
846 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueTreePropertyValueSource)
847};
848
849Value ValueTree::getPropertyAsValue (const Identifier& name, UndoManager* undoManager, bool updateSynchronously)
850{
851 return Value (new ValueTreePropertyValueSource (*this, name, undoManager, updateSynchronously));
852}
853
854//==============================================================================
856{
857 return object == nullptr ? 0 : object->children.size();
858}
859
861{
862 if (object != nullptr)
863 if (auto* c = object->children.getObjectPointer (index))
864 return ValueTree (*c);
865
866 return {};
867}
868
869ValueTree::Iterator::Iterator (const ValueTree& v, bool isEnd)
870 : internal (v.object != nullptr ? (isEnd ? v.object->children.end() : v.object->children.begin()) : nullptr)
871{
872}
873
874ValueTree::Iterator& ValueTree::Iterator::operator++()
875{
876 ++internal;
877 return *this;
878}
879
880bool ValueTree::Iterator::operator== (const Iterator& other) const { return internal == other.internal; }
881bool ValueTree::Iterator::operator!= (const Iterator& other) const { return internal != other.internal; }
882
883ValueTree ValueTree::Iterator::operator*() const
884{
885 return ValueTree (SharedObject::Ptr (*internal));
886}
887
888ValueTree::Iterator ValueTree::begin() const noexcept { return Iterator (*this, false); }
889ValueTree::Iterator ValueTree::end() const noexcept { return Iterator (*this, true); }
890
892{
893 return object != nullptr ? object->getChildWithName (type) : ValueTree();
894}
895
897{
898 return object != nullptr ? object->getOrCreateChildWithName (type, undoManager) : ValueTree();
899}
900
905
907{
908 return object != nullptr && object->isAChildOf (possibleParent.object.get());
909}
910
911int ValueTree::indexOf (const ValueTree& child) const noexcept
912{
913 return object != nullptr ? object->indexOf (child) : -1;
914}
915
916void ValueTree::addChild (const ValueTree& child, int index, UndoManager* undoManager)
917{
918 jassert (object != nullptr); // Trying to add a child to a null ValueTree!
919
920 if (object != nullptr)
921 object->addChild (child.object.get(), index, undoManager);
922}
923
924void ValueTree::appendChild (const ValueTree& child, UndoManager* undoManager)
925{
926 addChild (child, -1, undoManager);
927}
928
929void ValueTree::removeChild (int childIndex, UndoManager* undoManager)
930{
931 if (object != nullptr)
932 object->removeChild (childIndex, undoManager);
933}
934
935void ValueTree::removeChild (const ValueTree& child, UndoManager* undoManager)
936{
937 if (object != nullptr)
938 object->removeChild (object->children.indexOf (child.object), undoManager);
939}
940
942{
943 if (object != nullptr)
944 object->removeAllChildren (undoManager);
945}
946
948{
949 if (object != nullptr)
950 object->moveChild (currentIndex, newIndex, undoManager);
951}
952
953//==============================================================================
954void ValueTree::createListOfChildren (OwnedArray<ValueTree>& list) const
955{
956 if (object != nullptr)
957 for (auto* o : object->children)
958 if (o != nullptr)
959 list.add (new ValueTree (*o));
960}
961
962void ValueTree::reorderChildren (const OwnedArray<ValueTree>& newOrder, UndoManager* undoManager)
963{
964 if (object != nullptr)
965 object->reorderChildren (newOrder, undoManager);
966}
967
968//==============================================================================
970{
971 if (listener != nullptr)
972 {
973 if (listeners.isEmpty() && object != nullptr)
974 object->valueTreesWithListeners.add (this);
975
976 listeners.add (listener);
977 }
978}
979
981{
982 listeners.remove (listener);
983
984 if (listeners.isEmpty() && object != nullptr)
985 object->valueTreesWithListeners.removeValue (this);
986}
987
989{
990 if (object != nullptr)
991 object->sendPropertyChangeMessage (property);
992}
993
994//==============================================================================
996{
997 return std::unique_ptr<XmlElement> (object != nullptr ? object->createXml() : nullptr);
998}
999
1001{
1002 if (! xml.isTextElement())
1003 {
1004 ValueTree v (xml.getTagName());
1005 v.object->properties.setFromXmlAttributes (xml);
1006
1007 for (auto* e : xml.getChildIterator())
1008 v.appendChild (fromXml (*e), nullptr);
1009
1010 return v;
1011 }
1012
1013 // ValueTrees don't have any equivalent to XML text elements!
1015 return {};
1016}
1017
1019{
1020 if (auto xml = parseXML (xmlText))
1021 return fromXml (*xml);
1022
1023 return {};
1024}
1025
1027{
1028 if (auto xml = createXml())
1029 return xml->toString (format);
1030
1031 return {};
1032}
1033
1034//==============================================================================
1036{
1037 SharedObject::writeObjectToStream (output, object.get());
1038}
1039
1041{
1042 auto type = input.readString();
1043
1044 if (type.isEmpty())
1045 return {};
1046
1047 ValueTree v (type);
1048
1049 auto numProps = input.readCompressedInt();
1050
1051 if (numProps < 0)
1052 {
1053 jassertfalse; // trying to read corrupted data!
1054 return v;
1055 }
1056
1057 for (int i = 0; i < numProps; ++i)
1058 {
1059 auto name = input.readString();
1060
1061 if (name.isNotEmpty())
1062 v.object->properties.set (name, var::readFromStream (input));
1063 else
1064 jassertfalse; // trying to read corrupted data!
1065 }
1066
1067 auto numChildren = input.readCompressedInt();
1068 v.object->children.ensureStorageAllocated (numChildren);
1069
1070 for (int i = 0; i < numChildren; ++i)
1071 {
1072 auto child = readFromStream (input);
1073
1074 if (! child.isValid())
1075 return v;
1076
1077 v.object->children.add (child.object);
1078 child.object->parent = v.object.get();
1079 }
1080
1081 return v;
1082}
1083
1084ValueTree ValueTree::readFromData (const void* data, size_t numBytes)
1085{
1086 MemoryInputStream in (data, numBytes, false);
1087 return readFromStream (in);
1088}
1089
1090ValueTree ValueTree::readFromGZIPData (const void* data, size_t numBytes)
1091{
1092 MemoryInputStream in (data, numBytes, false);
1094 return readFromStream (gzipStream);
1095}
1096
1103
1104//==============================================================================
1105#if JUCE_ALLOW_STATIC_NULL_VARIABLES
1106
1107JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
1108JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996)
1109
1110const ValueTree ValueTree::invalid;
1111
1112JUCE_END_IGNORE_WARNINGS_GCC_LIKE
1113JUCE_END_IGNORE_WARNINGS_MSVC
1114
1115#endif
1116
1117//==============================================================================
1118//==============================================================================
1119#if JUCE_UNIT_TESTS
1120
1121class ValueTreeTests final : public UnitTest
1122{
1123public:
1124 ValueTreeTests()
1125 : UnitTest ("ValueTrees", UnitTestCategories::values)
1126 {}
1127
1128 static String createRandomIdentifier (Random& r)
1129 {
1130 char buffer[50] = { 0 };
1131 const char chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-:";
1132
1133 for (int i = 1 + r.nextInt (numElementsInArray (buffer) - 2); --i >= 0;)
1134 buffer[i] = chars[r.nextInt (sizeof (chars) - 1)];
1135
1136 String result (buffer);
1137
1138 if (! XmlElement::isValidXmlName (result))
1139 result = createRandomIdentifier (r);
1140
1141 return result;
1142 }
1143
1144 static String createRandomWideCharString (Random& r)
1145 {
1146 juce_wchar buffer[50] = { 0 };
1147
1148 for (int i = r.nextInt (numElementsInArray (buffer) - 1); --i >= 0;)
1149 {
1150 if (r.nextBool())
1151 {
1152 do
1153 {
1154 buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1));
1155 }
1156 while (! CharPointer_UTF16::canRepresent (buffer[i]));
1157 }
1158 else
1159 buffer[i] = (juce_wchar) (1 + r.nextInt (0x7e));
1160 }
1161
1162 return CharPointer_UTF32 (buffer);
1163 }
1164
1165 static ValueTree createRandomTree (UndoManager* undoManager, int depth, Random& r)
1166 {
1167 ValueTree v (createRandomIdentifier (r));
1168
1169 for (int i = r.nextInt (10); --i >= 0;)
1170 {
1171 switch (r.nextInt (5))
1172 {
1173 case 0: v.setProperty (createRandomIdentifier (r), createRandomWideCharString (r), undoManager); break;
1174 case 1: v.setProperty (createRandomIdentifier (r), r.nextInt(), undoManager); break;
1175 case 2: if (depth < 5) v.addChild (createRandomTree (undoManager, depth + 1, r), r.nextInt (v.getNumChildren() + 1), undoManager); break;
1176 case 3: v.setProperty (createRandomIdentifier (r), r.nextBool(), undoManager); break;
1177 case 4: v.setProperty (createRandomIdentifier (r), r.nextDouble(), undoManager); break;
1178 default: break;
1179 }
1180 }
1181
1182 return v;
1183 }
1184
1185 void runTest() override
1186 {
1187 {
1188 beginTest ("ValueTree");
1189
1190 auto r = getRandom();
1191
1192 for (int i = 10; --i >= 0;)
1193 {
1194 MemoryOutputStream mo;
1195 auto v1 = createRandomTree (nullptr, 0, r);
1196 v1.writeToStream (mo);
1197
1198 MemoryInputStream mi (mo.getData(), mo.getDataSize(), false);
1199 auto v2 = ValueTree::readFromStream (mi);
1200 expect (v1.isEquivalentTo (v2));
1201
1202 MemoryOutputStream zipped;
1203 {
1204 GZIPCompressorOutputStream zippedOut (zipped);
1205 v1.writeToStream (zippedOut);
1206 }
1207 expect (v1.isEquivalentTo (ValueTree::readFromGZIPData (zipped.getData(), zipped.getDataSize())));
1208
1209 auto xml1 = v1.createXml();
1210 auto xml2 = v2.createCopy().createXml();
1211 expect (xml1->isEquivalentTo (xml2.get(), false));
1212
1213 auto v4 = v2.createCopy();
1214 expect (v1.isEquivalentTo (v4));
1215 }
1216 }
1217
1218 {
1219 beginTest ("Float formatting");
1220
1221 ValueTree testVT ("Test");
1222 Identifier number ("number");
1223
1225 tests[1] = "1.0";
1226 tests[1.1] = "1.1";
1227 tests[1.01] = "1.01";
1228 tests[0.76378] = "0.76378";
1229 tests[-10] = "-10.0";
1230 tests[10.01] = "10.01";
1231 tests[0.0123] = "0.0123";
1232 tests[-3.7e-27] = "-3.7e-27";
1233 tests[1e+40] = "1.0e40";
1234 tests[-12345678901234567.0] = "-1.234567890123457e16";
1235 tests[192000] = "192000.0";
1236 tests[1234567] = "1.234567e6";
1237 tests[0.00006] = "0.00006";
1238 tests[0.000006] = "6.0e-6";
1239
1240 for (auto& test : tests)
1241 {
1242 testVT.setProperty (number, test.first, nullptr);
1243 auto lines = StringArray::fromLines (testVT.toXmlString());
1244 lines.removeEmptyStrings();
1245 auto numLines = lines.size();
1246 expect (numLines > 1);
1247 expectEquals (lines[numLines - 1], "<Test number=\"" + test.second + "\"/>");
1248 }
1249 }
1250 }
1251};
1252
1254
1255#endif
1256
1257} // namespace juce
This stream will decompress a source-stream using zlib.
Represents a string identifier, designed for accessing properties by name.
const String & toString() const noexcept
Returns this identifier as a string.
The base class for streams that read data.
virtual int readCompressedInt()
Reads an encoded 32-bit number from the stream using a space-saving compressed format.
virtual String readString()
Reads a zero-terminated UTF-8 string from the stream.
Allows a block of data to be accessed as a stream.
Holds a set of named var objects.
bool set(const Identifier &name, const var &newValue)
Changes or adds a named value.
bool contains(const Identifier &name) const noexcept
Returns true if the set contains an item with the specified name.
bool remove(const Identifier &name)
Removes a value from the set.
const var & getValueAt(int index) const noexcept
Returns the value of the item at a given index.
Identifier getName(int index) const noexcept
Returns the name of the value at a given index.
int size() const noexcept
Returns the total number of values that the set contains.
var * getVarPointer(const Identifier &name) noexcept
Returns a pointer to the var that holds a named value, or null if there is no value with this name.
void copyToXmlAttributes(XmlElement &xml) const
Sets attributes in an XML element corresponding to each of this object's properties.
The base class for streams that write data to some kind of destination.
virtual bool writeCompressedInt(int value)
Writes a condensed binary encoding of a 32-bit integer.
virtual bool writeString(const String &text)
Stores a string in the stream in a binary format.
An array designed for holding objects.
A random number generator.
Definition juce_Random.h:35
int nextInt() noexcept
Returns the next random 32 bit integer.
Holds a list of objects derived from ReferenceCountedObject, or which implement basic reference-count...
A smart-pointer class which points to a reference-counted object.
A base class which provides methods for reference-counting.
ReferenceCountedObject()=default
Creates the reference-counted object (with an initial ref count of zero).
Holds a set of unique primitive objects, such as ints or doubles.
The JUCE String class!
Definition juce_String.h:53
bool isNotEmpty() const noexcept
Returns true if the string contains at least one character.
Manages a list of undo/redo commands.
bool perform(UndoableAction *action)
Performs an action and adds it to the undo history list.
Used by the UndoManager class to store an action which can be done and undone.
This is a base class for classes that perform a unit test.
Listener class for events that happen to a ValueTree.
virtual void valueTreeRedirected(ValueTree &treeWhichHasBeenChanged)
This method is called when a tree is made to point to a different internal shared object.
virtual void valueTreeChildRemoved(ValueTree &parentTree, ValueTree &childWhichHasBeenRemoved, int indexFromWhichChildWasRemoved)
This method is called when a child sub-tree is removed.
virtual void valueTreeChildOrderChanged(ValueTree &parentTreeWhoseChildrenHaveMoved, int oldIndex, int newIndex)
This method is called when a tree's children have been re-shuffled.
virtual void valueTreeParentChanged(ValueTree &treeWhoseParentHasChanged)
This method is called when a tree has been added or removed from a parent.
virtual void valueTreePropertyChanged(ValueTree &treeWhosePropertyHasChanged, const Identifier &property)
This method is called when a property of this tree (or of one of its sub-trees) is changed.
virtual void valueTreeChildAdded(ValueTree &parentTree, ValueTree &childWhichHasBeenAdded)
This method is called when a child sub-tree is added.
A powerful tree structure that can be used to hold free-form data, and which can handle its own undo ...
Value getPropertyAsValue(const Identifier &name, UndoManager *undoManager, bool shouldUpdateSynchronously=false)
Returns a Value object that can be used to control and respond to one of the tree's properties.
Identifier getPropertyName(int index) const noexcept
Returns the identifier of the property with a given index.
Iterator begin() const noexcept
Returns a start iterator for the children in this tree.
bool operator!=(const ValueTree &) const noexcept
Returns true if this and the other tree refer to different underlying structures.
std::unique_ptr< XmlElement > createXml() const
Creates an XmlElement that holds a complete image of this tree and all its children.
bool hasType(const Identifier &typeName) const noexcept
Returns true if the tree has this type.
static ValueTree readFromStream(InputStream &input)
Reloads a tree from a stream that was written with writeToStream().
void removeChild(const ValueTree &child, UndoManager *undoManager)
Removes the specified child from this tree's child-list.
String toXmlString(const XmlElement::TextFormat &format={}) const
This returns a string containing an XML representation of the tree.
static ValueTree readFromGZIPData(const void *data, size_t numBytes)
Reloads a tree from a data block that was written with writeToStream() and then zipped using GZIPComp...
ValueTree getChild(int index) const
Returns one of this tree's sub-trees.
int getNumProperties() const noexcept
Returns the total number of properties that the tree contains.
int getNumChildren() const noexcept
Returns the number of child trees inside this one.
void copyPropertiesFrom(const ValueTree &source, UndoManager *undoManager)
Overwrites all the properties in this tree with the properties of the source tree.
~ValueTree()
Destructor.
int getReferenceCount() const noexcept
Returns the total number of references to the shared underlying data structure that this ValueTree is...
const var * getPropertyPointer(const Identifier &name) const noexcept
Returns a pointer to the value of a named property, or nullptr if the property doesn't exist.
ValueTree & setProperty(const Identifier &name, const var &newValue, UndoManager *undoManager)
Changes a named property of the tree.
void removeAllChildren(UndoManager *undoManager)
Removes all child-trees.
bool isAChildOf(const ValueTree &possibleParent) const noexcept
Returns true if this tree is a sub-tree (at any depth) of the given parent.
void removeAllProperties(UndoManager *undoManager)
Removes all properties from the tree.
void addListener(Listener *listener)
Adds a listener to receive callbacks when this tree is changed in some way.
void appendChild(const ValueTree &child, UndoManager *undoManager)
Appends a new child sub-tree to this tree.
int indexOf(const ValueTree &child) const noexcept
Returns the index of a child item in this parent.
bool operator==(const ValueTree &) const noexcept
Returns true if both this and the other tree refer to the same underlying structure.
ValueTree & setPropertyExcludingListener(Listener *listenerToExclude, const Identifier &name, const var &newValue, UndoManager *undoManager)
Changes a named property of the tree, but will not notify a specified listener of the change.
void addChild(const ValueTree &child, int index, UndoManager *undoManager)
Adds a child to this tree.
ValueTree getParent() const noexcept
Returns the parent tree that contains this one.
const var & getProperty(const Identifier &name) const noexcept
Returns the value of a named property.
ValueTree() noexcept
Creates an empty, invalid ValueTree.
static ValueTree fromXml(const XmlElement &xml)
Tries to recreate a tree from its XML representation.
ValueTree createCopy() const
Returns a deep copy of this tree and all its sub-trees.
Identifier getType() const noexcept
Returns the type of this tree.
ValueTree getChildWithName(const Identifier &type) const
Returns the first sub-tree with the specified type name.
Iterator end() const noexcept
Returns an end iterator for the children in this tree.
ValueTree & operator=(const ValueTree &)
Changes this object to be a reference to the given tree.
void removeListener(Listener *listener)
Removes a listener that was previously added with addListener().
void writeToStream(OutputStream &output) const
Stores this tree (and all its children) in a binary format.
bool isEquivalentTo(const ValueTree &) const
Performs a deep comparison between the properties and children of two trees.
ValueTree getOrCreateChildWithName(const Identifier &type, UndoManager *undoManager)
Returns the first sub-tree with the specified type name, creating and adding a child with this name i...
void moveChild(int currentIndex, int newIndex, UndoManager *undoManager)
Moves one of the sub-trees to a different index.
void sendPropertyChangeMessage(const Identifier &property)
Causes a property-change callback to be triggered for the specified property, calling any listeners t...
void removeProperty(const Identifier &name, UndoManager *undoManager)
Removes a property from the tree.
const var & operator[](const Identifier &name) const noexcept
Returns the value of a named property.
ValueTree getSibling(int delta) const noexcept
Returns one of this tree's siblings in its parent's child list.
ValueTree getChildWithProperty(const Identifier &propertyName, const var &propertyValue) const
Looks for the first sub-tree that has the specified property value.
void copyPropertiesAndChildrenFrom(const ValueTree &source, UndoManager *undoManager)
Replaces all children and properties of this object with copies of those from the source object.
ValueTree getRoot() const noexcept
Recursively finds the highest-level parent tree that contains this one.
static ValueTree readFromData(const void *data, size_t numBytes)
Reloads a tree from a data block that was written with writeToStream().
bool hasProperty(const Identifier &name) const noexcept
Returns true if the tree contains a named property.
Used internally by the Value class as the base class for its shared value objects.
Definition juce_Value.h:180
void sendChangeMessage(bool dispatchSynchronously)
Delivers a change message to all the listeners that are registered with this value.
Represents a shared variant value.
Definition juce_Value.h:51
Used to build a tree of elements representing an XML document.
bool isTextElement() const noexcept
Returns true if this element is a section of text.
Iterator< GetNextElement > getChildIterator() const
Allows iterating the children of an XmlElement using range-for syntax.
const String & getTagName() const noexcept
Returns this element's tag type name.
A variant class, that can be used to hold a range of primitive values.
void writeToStream(OutputStream &output) const
Writes a binary representation of this value to a stream.
static var readFromStream(InputStream &input)
Reads back a stored binary representation of a value.
#define JUCE_LEAK_DETECTOR(OwnerClass)
This macro lets you embed a leak-detecting object inside a class.
#define jassert(expression)
Platform-independent assertion macro.
#define JUCE_DECLARE_NON_COPYABLE(className)
This is a shorthand macro for deleting a class's copy constructor and copy assignment operator.
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
This is a shorthand way of writing both a JUCE_DECLARE_NON_COPYABLE and JUCE_LEAK_DETECTOR macro for ...
#define jassertfalse
This will always cause an assertion failure.
T internal(T... args)
JUCE Namespace.
Type * createCopyIfNotNull(const Type *objectToCopy)
If a pointer is non-null, this returns a new copy of the object that it points to,...
Definition juce_Memory.h:60
wchar_t juce_wchar
A platform-independent 32-bit unicode character type.
RangedDirectoryIterator end(const RangedDirectoryIterator &)
Returns a default-constructed sentinel value.
std::unique_ptr< XmlElement > parseXML(const String &textToParse)
Attempts to parse some XML text, returning a new XmlElement if it was valid.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
Definition juce_Memory.h:88
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
Returns true if a value is at least zero, and also below a specified upper limit.
RangedDirectoryIterator begin(const RangedDirectoryIterator &it)
Returns the iterator that was passed in.
constexpr int numElementsInArray(Type(&)[N]) noexcept
Handy function for getting the number of elements in a simple const C array.
var getValue() const override
Returns the current value of this object.
void setValue(const var &newValue) override
Changes the current value.
Iterator for a ValueTree.
int getSizeInUnits() override
Returns a value to indicate how much memory this object takes up.
bool undo() override
Overridden by a subclass to undo the action.
bool perform() override
Overridden by a subclass to perform the action.
bool perform() override
Overridden by a subclass to perform the action.
bool undo() override
Overridden by a subclass to undo the action.
int getSizeInUnits() override
Returns a value to indicate how much memory this object takes up.
UndoableAction * createCoalescedAction(UndoableAction *nextAction) override
Allows multiple actions to be coalesced into a single action object, to reduce storage space.
bool undo() override
Overridden by a subclass to undo the action.
int getSizeInUnits() override
Returns a value to indicate how much memory this object takes up.
UndoableAction * createCoalescedAction(UndoableAction *nextAction) override
Allows multiple actions to be coalesced into a single action object, to reduce storage space.
bool perform() override
Overridden by a subclass to perform the action.
A struct containing options for formatting the text when representing an XML element as a string.
sync