OpenShot Library | libopenshot-audio 0.2.0
juce_ValueTree.cpp
1/*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2017 - ROLI Ltd.
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 5 End-User License
11 Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
12 27th April 2017).
13
14 End User License Agreement: www.juce.com/juce-5-licence
15 Privacy Policy: www.juce.com/juce-5-privacy-policy
16
17 Or: You may also use this code under the terms of the GPL v3 (see
18 www.gnu.org/licenses).
19
20 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
21 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
22 DISCLAIMED.
23
24 ==============================================================================
25*/
26
27namespace juce
28{
29
31{
32public:
34
35 explicit SharedObject (const Identifier& t) noexcept : type (t) {}
36
37 SharedObject (const SharedObject& other)
38 : ReferenceCountedObject(), type (other.type), properties (other.properties)
39 {
40 for (auto* c : other.children)
41 {
42 auto* child = new SharedObject (*c);
43 child->parent = this;
44 children.add (child);
45 }
46 }
47
48 SharedObject& operator= (const SharedObject&) = delete;
49
51 {
52 jassert (parent == nullptr); // this should never happen unless something isn't obeying the ref-counting!
53
54 for (auto i = children.size(); --i >= 0;)
55 {
56 const Ptr c (children.getObjectPointerUnchecked (i));
57 c->parent = nullptr;
58 children.remove (i);
59 c->sendParentChangeMessage();
60 }
61 }
62
63 SharedObject& getRoot() noexcept
64 {
65 return parent == nullptr ? *this : parent->getRoot();
66 }
67
68 template <typename Function>
69 void callListeners (ValueTree::Listener* listenerToExclude, Function fn) const
70 {
71 auto numListeners = valueTreesWithListeners.size();
72
73 if (numListeners == 1)
74 {
75 valueTreesWithListeners.getUnchecked (0)->listeners.callExcluding (listenerToExclude, fn);
76 }
77 else if (numListeners > 0)
78 {
79 auto listenersCopy = valueTreesWithListeners;
80
81 for (int i = 0; i < numListeners; ++i)
82 {
83 auto* v = listenersCopy.getUnchecked (i);
84
85 if (i == 0 || valueTreesWithListeners.contains (v))
86 v->listeners.callExcluding (listenerToExclude, fn);
87 }
88 }
89 }
90
91 template <typename Function>
92 void callListenersForAllParents (ValueTree::Listener* listenerToExclude, Function fn) const
93 {
94 for (auto* t = this; t != nullptr; t = t->parent)
95 t->callListeners (listenerToExclude, fn);
96 }
97
98 void sendPropertyChangeMessage (const Identifier& property, ValueTree::Listener* listenerToExclude = nullptr)
99 {
100 ValueTree tree (*this);
101 callListenersForAllParents (listenerToExclude, [&] (Listener& l) { l.valueTreePropertyChanged (tree, property); });
102 }
103
104 void sendChildAddedMessage (ValueTree child)
105 {
106 ValueTree tree (*this);
107 callListenersForAllParents (nullptr, [&] (Listener& l) { l.valueTreeChildAdded (tree, child); });
108 }
109
110 void sendChildRemovedMessage (ValueTree child, int index)
111 {
112 ValueTree tree (*this);
113 callListenersForAllParents (nullptr, [=, &tree, &child] (Listener& l) { l.valueTreeChildRemoved (tree, child, index); });
114 }
115
116 void sendChildOrderChangedMessage (int oldIndex, int newIndex)
117 {
118 ValueTree tree (*this);
119 callListenersForAllParents (nullptr, [=, &tree] (Listener& l) { l.valueTreeChildOrderChanged (tree, oldIndex, newIndex); });
120 }
121
122 void sendParentChangeMessage()
123 {
124 ValueTree tree (*this);
125
126 for (auto j = children.size(); --j >= 0;)
127 if (auto* child = children.getObjectPointer (j))
128 child->sendParentChangeMessage();
129
130 callListeners (nullptr, [&] (Listener& l) { l.valueTreeParentChanged (tree); });
131 }
132
133 void setProperty (const Identifier& name, const var& newValue, UndoManager* undoManager,
134 ValueTree::Listener* listenerToExclude = nullptr)
135 {
136 if (undoManager == nullptr)
137 {
138 if (properties.set (name, newValue))
139 sendPropertyChangeMessage (name, listenerToExclude);
140 }
141 else
142 {
143 if (auto* existingValue = properties.getVarPointer (name))
144 {
145 if (*existingValue != newValue)
146 undoManager->perform (new SetPropertyAction (*this, name, newValue, *existingValue,
147 false, false, listenerToExclude));
148 }
149 else
150 {
151 undoManager->perform (new SetPropertyAction (*this, name, newValue, {},
152 true, false, listenerToExclude));
153 }
154 }
155 }
156
157 bool hasProperty (const Identifier& name) const noexcept
158 {
159 return properties.contains (name);
160 }
161
162 void removeProperty (const Identifier& name, UndoManager* undoManager)
163 {
164 if (undoManager == nullptr)
165 {
166 if (properties.remove (name))
167 sendPropertyChangeMessage (name);
168 }
169 else
170 {
171 if (properties.contains (name))
172 undoManager->perform (new SetPropertyAction (*this, name, {}, properties[name], false, true));
173 }
174 }
175
176 void removeAllProperties (UndoManager* undoManager)
177 {
178 if (undoManager == nullptr)
179 {
180 while (properties.size() > 0)
181 {
182 auto name = properties.getName (properties.size() - 1);
183 properties.remove (name);
184 sendPropertyChangeMessage (name);
185 }
186 }
187 else
188 {
189 for (auto i = properties.size(); --i >= 0;)
190 undoManager->perform (new SetPropertyAction (*this, properties.getName (i), {},
191 properties.getValueAt (i), false, true));
192 }
193 }
194
195 void copyPropertiesFrom (const SharedObject& source, UndoManager* undoManager)
196 {
197 for (auto i = properties.size(); --i >= 0;)
198 if (! source.properties.contains (properties.getName (i)))
199 removeProperty (properties.getName (i), undoManager);
200
201 for (int i = 0; i < source.properties.size(); ++i)
202 setProperty (source.properties.getName (i), source.properties.getValueAt (i), undoManager);
203 }
204
205 ValueTree getChildWithName (const Identifier& typeToMatch) const
206 {
207 for (auto* s : children)
208 if (s->type == typeToMatch)
209 return ValueTree (*s);
210
211 return {};
212 }
213
214 ValueTree getOrCreateChildWithName (const Identifier& typeToMatch, UndoManager* undoManager)
215 {
216 for (auto* s : children)
217 if (s->type == typeToMatch)
218 return ValueTree (*s);
219
220 auto newObject = new SharedObject (typeToMatch);
221 addChild (newObject, -1, undoManager);
222 return ValueTree (*newObject);
223 }
224
225 ValueTree getChildWithProperty (const Identifier& propertyName, const var& propertyValue) const
226 {
227 for (auto* s : children)
228 if (s->properties[propertyName] == propertyValue)
229 return ValueTree (*s);
230
231 return {};
232 }
233
234 bool isAChildOf (const SharedObject* possibleParent) const noexcept
235 {
236 for (auto* p = parent; p != nullptr; p = p->parent)
237 if (p == possibleParent)
238 return true;
239
240 return false;
241 }
242
243 int indexOf (const ValueTree& child) const noexcept
244 {
245 return children.indexOf (child.object);
246 }
247
248 void addChild (SharedObject* child, int index, UndoManager* undoManager)
249 {
250 if (child != nullptr && child->parent != this)
251 {
252 if (child != this && ! isAChildOf (child))
253 {
254 // You should always make sure that a child is removed from its previous parent before
255 // adding it somewhere else - otherwise, it's ambiguous as to whether a different
256 // undomanager should be used when removing it from its current parent..
257 jassert (child->parent == nullptr);
258
259 if (child->parent != nullptr)
260 {
261 jassert (child->parent->children.indexOf (child) >= 0);
262 child->parent->removeChild (child->parent->children.indexOf (child), undoManager);
263 }
264
265 if (undoManager == nullptr)
266 {
267 children.insert (index, child);
268 child->parent = this;
269 sendChildAddedMessage (ValueTree (*child));
270 child->sendParentChangeMessage();
271 }
272 else
273 {
274 if (! isPositiveAndBelow (index, children.size()))
275 index = children.size();
276
277 undoManager->perform (new AddOrRemoveChildAction (*this, index, child));
278 }
279 }
280 else
281 {
282 // You're attempting to create a recursive loop! A node
283 // can't be a child of one of its own children!
284 jassertfalse;
285 }
286 }
287 }
288
289 void removeChild (int childIndex, UndoManager* undoManager)
290 {
291 if (auto child = Ptr (children.getObjectPointer (childIndex)))
292 {
293 if (undoManager == nullptr)
294 {
295 children.remove (childIndex);
296 child->parent = nullptr;
297 sendChildRemovedMessage (ValueTree (child), childIndex);
298 child->sendParentChangeMessage();
299 }
300 else
301 {
302 undoManager->perform (new AddOrRemoveChildAction (*this, childIndex, {}));
303 }
304 }
305 }
306
307 void removeAllChildren (UndoManager* undoManager)
308 {
309 while (children.size() > 0)
310 removeChild (children.size() - 1, undoManager);
311 }
312
313 void moveChild (int currentIndex, int newIndex, UndoManager* undoManager)
314 {
315 // The source index must be a valid index!
316 jassert (isPositiveAndBelow (currentIndex, children.size()));
317
318 if (currentIndex != newIndex
319 && isPositiveAndBelow (currentIndex, children.size()))
320 {
321 if (undoManager == nullptr)
322 {
323 children.move (currentIndex, newIndex);
324 sendChildOrderChangedMessage (currentIndex, newIndex);
325 }
326 else
327 {
328 if (! isPositiveAndBelow (newIndex, children.size()))
329 newIndex = children.size() - 1;
330
331 undoManager->perform (new MoveChildAction (*this, currentIndex, newIndex));
332 }
333 }
334 }
335
336 void reorderChildren (const OwnedArray<ValueTree>& newOrder, UndoManager* undoManager)
337 {
338 jassert (newOrder.size() == children.size());
339
340 for (int i = 0; i < children.size(); ++i)
341 {
342 auto* child = newOrder.getUnchecked (i)->object.get();
343
344 if (children.getObjectPointerUnchecked (i) != child)
345 {
346 auto oldIndex = children.indexOf (child);
347 jassert (oldIndex >= 0);
348 moveChild (oldIndex, i, undoManager);
349 }
350 }
351 }
352
353 bool isEquivalentTo (const SharedObject& other) const noexcept
354 {
355 if (type != other.type
356 || properties.size() != other.properties.size()
357 || children.size() != other.children.size()
358 || properties != other.properties)
359 return false;
360
361 for (int i = 0; i < children.size(); ++i)
362 if (! children.getObjectPointerUnchecked (i)->isEquivalentTo (*other.children.getObjectPointerUnchecked (i)))
363 return false;
364
365 return true;
366 }
367
368 XmlElement* createXml() const
369 {
370 auto* xml = new XmlElement (type);
371 properties.copyToXmlAttributes (*xml);
372
373 // (NB: it's faster to add nodes to XML elements in reverse order)
374 for (auto i = children.size(); --i >= 0;)
375 xml->prependChildElement (children.getObjectPointerUnchecked (i)->createXml());
376
377 return xml;
378 }
379
380 void writeToStream (OutputStream& output) const
381 {
382 output.writeString (type.toString());
383 output.writeCompressedInt (properties.size());
384
385 for (int j = 0; j < properties.size(); ++j)
386 {
387 output.writeString (properties.getName (j).toString());
388 properties.getValueAt (j).writeToStream (output);
389 }
390
391 output.writeCompressedInt (children.size());
392
393 for (auto* c : children)
394 writeObjectToStream (output, c);
395 }
396
397 static void writeObjectToStream (OutputStream& output, const SharedObject* object)
398 {
399 if (object != nullptr)
400 {
401 object->writeToStream (output);
402 }
403 else
404 {
405 output.writeString ({});
406 output.writeCompressedInt (0);
407 output.writeCompressedInt (0);
408 }
409 }
410
411 //==============================================================================
413 {
414 SetPropertyAction (Ptr targetObject, const Identifier& propertyName,
415 const var& newVal, const var& oldVal, bool isAdding, bool isDeleting,
416 ValueTree::Listener* listenerToExclude = nullptr)
417 : target (std::move (targetObject)),
418 name (propertyName), newValue (newVal), oldValue (oldVal),
419 isAddingNewProperty (isAdding), isDeletingProperty (isDeleting),
420 excludeListener (listenerToExclude)
421 {
422 }
423
424 bool perform() override
425 {
426 jassert (! (isAddingNewProperty && target->hasProperty (name)));
427
428 if (isDeletingProperty)
429 target->removeProperty (name, nullptr);
430 else
431 target->setProperty (name, newValue, nullptr, excludeListener);
432
433 return true;
434 }
435
436 bool undo() override
437 {
438 if (isAddingNewProperty)
439 target->removeProperty (name, nullptr);
440 else
441 target->setProperty (name, oldValue, nullptr);
442
443 return true;
444 }
445
446 int getSizeInUnits() override
447 {
448 return (int) sizeof (*this); //xxx should be more accurate
449 }
450
452 {
453 if (! (isAddingNewProperty || isDeletingProperty))
454 {
455 if (auto* next = dynamic_cast<SetPropertyAction*> (nextAction))
456 if (next->target == target && next->name == name
457 && ! (next->isAddingNewProperty || next->isDeletingProperty))
458 return new SetPropertyAction (*target, name, next->newValue, oldValue, false, false);
459 }
460
461 return nullptr;
462 }
463
464 private:
465 const Ptr target;
466 const Identifier name;
467 const var newValue;
468 var oldValue;
469 const bool isAddingNewProperty : 1, isDeletingProperty : 1;
470 ValueTree::Listener* excludeListener;
471
472 JUCE_DECLARE_NON_COPYABLE (SetPropertyAction)
473 };
474
475 //==============================================================================
477 {
478 AddOrRemoveChildAction (Ptr parentObject, int index, SharedObject* newChild)
479 : target (std::move (parentObject)),
480 child (newChild != nullptr ? newChild : target->children.getObjectPointer (index)),
481 childIndex (index),
482 isDeleting (newChild == nullptr)
483 {
484 jassert (child != nullptr);
485 }
486
487 bool perform() override
488 {
489 if (isDeleting)
490 target->removeChild (childIndex, nullptr);
491 else
492 target->addChild (child.get(), childIndex, nullptr);
493
494 return true;
495 }
496
497 bool undo() override
498 {
499 if (isDeleting)
500 {
501 target->addChild (child.get(), childIndex, nullptr);
502 }
503 else
504 {
505 // If you hit this, it seems that your object's state is getting confused - probably
506 // because you've interleaved some undoable and non-undoable operations?
507 jassert (childIndex < target->children.size());
508 target->removeChild (childIndex, nullptr);
509 }
510
511 return true;
512 }
513
514 int getSizeInUnits() override
515 {
516 return (int) sizeof (*this); //xxx should be more accurate
517 }
518
519 private:
520 const Ptr target, child;
521 const int childIndex;
522 const bool isDeleting;
523
524 JUCE_DECLARE_NON_COPYABLE (AddOrRemoveChildAction)
525 };
526
527 //==============================================================================
529 {
530 MoveChildAction (Ptr parentObject, int fromIndex, int toIndex) noexcept
531 : parent (std::move (parentObject)), startIndex (fromIndex), endIndex (toIndex)
532 {
533 }
534
535 bool perform() override
536 {
537 parent->moveChild (startIndex, endIndex, nullptr);
538 return true;
539 }
540
541 bool undo() override
542 {
543 parent->moveChild (endIndex, startIndex, nullptr);
544 return true;
545 }
546
547 int getSizeInUnits() override
548 {
549 return (int) sizeof (*this); //xxx should be more accurate
550 }
551
553 {
554 if (auto* next = dynamic_cast<MoveChildAction*> (nextAction))
555 if (next->parent == parent && next->startIndex == endIndex)
556 return new MoveChildAction (parent, startIndex, next->endIndex);
557
558 return nullptr;
559 }
560
561 private:
562 const Ptr parent;
563 const int startIndex, endIndex;
564
565 JUCE_DECLARE_NON_COPYABLE (MoveChildAction)
566 };
567
568 //==============================================================================
569 const Identifier type;
570 NamedValueSet properties;
572 SortedSet<ValueTree*> valueTreesWithListeners;
573 SharedObject* parent = nullptr;
574
575 JUCE_LEAK_DETECTOR (SharedObject)
576};
577
578//==============================================================================
580{
581}
582
583JUCE_DECLARE_DEPRECATED_STATIC (const ValueTree ValueTree::invalid;)
584
585ValueTree::ValueTree (const Identifier& type) : object (new ValueTree::SharedObject (type))
586{
587 jassert (type.toString().isNotEmpty()); // All objects must be given a sensible type name!
588}
589
591 std::initializer_list<NamedValueSet::NamedValue> properties,
592 std::initializer_list<ValueTree> subTrees)
593 : ValueTree (type)
594{
595 object->properties = NamedValueSet (std::move (properties));
596
597 for (auto& tree : subTrees)
598 addChild (tree, -1, nullptr);
599}
600
601ValueTree::ValueTree (SharedObject::Ptr so) noexcept : object (std::move (so)) {}
602ValueTree::ValueTree (SharedObject& so) noexcept : object (so) {}
603
604ValueTree::ValueTree (const ValueTree& other) noexcept : object (other.object)
605{
606}
607
609{
610 if (object != other.object)
611 {
612 if (listeners.isEmpty())
613 {
614 object = other.object;
615 }
616 else
617 {
618 if (object != nullptr)
619 object->valueTreesWithListeners.removeValue (this);
620
621 if (other.object != nullptr)
622 other.object->valueTreesWithListeners.add (this);
623
624 object = other.object;
625
626 listeners.call ([this] (Listener& l) { l.valueTreeRedirected (*this); });
627 }
628 }
629
630 return *this;
631}
632
634 : object (std::move (other.object))
635{
636 if (object != nullptr)
637 object->valueTreesWithListeners.removeValue (&other);
638}
639
641{
642 if (! listeners.isEmpty() && object != nullptr)
643 object->valueTreesWithListeners.removeValue (this);
644}
645
646bool ValueTree::operator== (const ValueTree& other) const noexcept
647{
648 return object == other.object;
649}
650
651bool ValueTree::operator!= (const ValueTree& other) const noexcept
652{
653 return object != other.object;
654}
655
656bool ValueTree::isEquivalentTo (const ValueTree& other) const
657{
658 return object == other.object
659 || (object != nullptr && other.object != nullptr
660 && object->isEquivalentTo (*other.object));
661}
662
664{
665 if (object != nullptr)
666 return ValueTree (*new SharedObject (*object));
667
668 return {};
669}
670
671void ValueTree::copyPropertiesFrom (const ValueTree& source, UndoManager* undoManager)
672{
673 jassert (object != nullptr || source.object == nullptr); // Trying to add properties to a null ValueTree will fail!
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 copyPropertiesFrom (source, undoManager);
686 removeAllChildren (undoManager);
687
688 if (object != nullptr && source.object != nullptr)
689 for (auto& child : source.object->children)
690 object->addChild (createCopyIfNotNull (child), -1, undoManager);
691}
692
693bool ValueTree::hasType (const Identifier& typeName) const noexcept
694{
695 return object != nullptr && object->type == typeName;
696}
697
699{
700 return object != nullptr ? object->type : Identifier();
701}
702
704{
705 if (object != nullptr)
706 if (auto p = object->parent)
707 return ValueTree (*p);
708
709 return {};
710}
711
713{
714 if (object != nullptr)
715 return ValueTree (object->getRoot());
716
717 return {};
718}
719
720ValueTree ValueTree::getSibling (int delta) const noexcept
721{
722 if (object != nullptr)
723 if (auto* p = object->parent)
724 if (auto* c = p->children.getObjectPointer (p->indexOf (*this) + delta))
725 return ValueTree (*c);
726
727 return {};
728}
729
730static const var& getNullVarRef() noexcept
731{
732 static var nullVar;
733 return nullVar;
734}
735
736const var& ValueTree::operator[] (const Identifier& name) const noexcept
737{
738 return object == nullptr ? getNullVarRef() : object->properties[name];
739}
740
741const var& ValueTree::getProperty (const Identifier& name) const noexcept
742{
743 return object == nullptr ? getNullVarRef() : object->properties[name];
744}
745
746var ValueTree::getProperty (const Identifier& name, const var& defaultReturnValue) const
747{
748 return object == nullptr ? defaultReturnValue
749 : object->properties.getWithDefault (name, defaultReturnValue);
750}
751
752const var* ValueTree::getPropertyPointer (const Identifier& name) const noexcept
753{
754 return object == nullptr ? nullptr
755 : object->properties.getVarPointer (name);
756}
757
758ValueTree& ValueTree::setProperty (const Identifier& name, const var& newValue, UndoManager* undoManager)
759{
760 return setPropertyExcludingListener (nullptr, name, newValue, undoManager);
761}
762
764 const var& newValue, UndoManager* undoManager)
765{
766 jassert (name.toString().isNotEmpty()); // Must have a valid property name!
767 jassert (object != nullptr); // Trying to add a property to a null ValueTree will fail!
768
769 if (object != nullptr)
770 object->setProperty (name, newValue, undoManager, listenerToExclude);
771
772 return *this;
773}
774
775bool ValueTree::hasProperty (const Identifier& name) const noexcept
776{
777 return object != nullptr && object->hasProperty (name);
778}
779
780void ValueTree::removeProperty (const Identifier& name, UndoManager* undoManager)
781{
782 if (object != nullptr)
783 object->removeProperty (name, undoManager);
784}
785
787{
788 if (object != nullptr)
789 object->removeAllProperties (undoManager);
790}
791
792int ValueTree::getNumProperties() const noexcept
793{
794 return object == nullptr ? 0 : object->properties.size();
795}
796
797Identifier ValueTree::getPropertyName (int index) const noexcept
798{
799 return object == nullptr ? Identifier()
800 : object->properties.getName (index);
801}
802
804{
805 return object != nullptr ? object->getReferenceCount() : 0;
806}
807
808//==============================================================================
810 private ValueTree::Listener
811{
812 ValueTreePropertyValueSource (const ValueTree& vt, const Identifier& prop, UndoManager* um, bool sync)
813 : tree (vt), property (prop), undoManager (um), updateSynchronously (sync)
814 {
815 tree.addListener (this);
816 }
817
819 {
820 tree.removeListener (this);
821 }
822
823 var getValue() const override { return tree[property]; }
824 void setValue (const var& newValue) override { tree.setProperty (property, newValue, undoManager); }
825
826private:
827 ValueTree tree;
828 const Identifier property;
829 UndoManager* const undoManager;
830 const bool updateSynchronously;
831
832 void valueTreePropertyChanged (ValueTree& changedTree, const Identifier& changedProperty) override
833 {
834 if (tree == changedTree && property == changedProperty)
835 sendChangeMessage (updateSynchronously);
836 }
837
838 void valueTreeChildAdded (ValueTree&, ValueTree&) override {}
839 void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override {}
840 void valueTreeChildOrderChanged (ValueTree&, int, int) override {}
841 void valueTreeParentChanged (ValueTree&) override {}
842
843 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueTreePropertyValueSource)
844};
845
846Value ValueTree::getPropertyAsValue (const Identifier& name, UndoManager* undoManager, bool updateSynchronously)
847{
848 return Value (new ValueTreePropertyValueSource (*this, name, undoManager, updateSynchronously));
849}
850
851//==============================================================================
852int ValueTree::getNumChildren() const noexcept
853{
854 return object == nullptr ? 0 : object->children.size();
855}
856
858{
859 if (object != nullptr)
860 if (auto* c = object->children.getObjectPointer (index))
861 return ValueTree (*c);
862
863 return {};
864}
865
866ValueTree::Iterator::Iterator (const ValueTree& v, bool isEnd)
867 : internal (v.object != nullptr ? (isEnd ? v.object->children.end() : v.object->children.begin()) : nullptr)
868{
869}
870
871ValueTree::Iterator& ValueTree::Iterator::operator++()
872{
873 internal = static_cast<SharedObject**> (internal) + 1;
874 return *this;
875}
876
877bool ValueTree::Iterator::operator== (const Iterator& other) const { return internal == other.internal; }
878bool ValueTree::Iterator::operator!= (const Iterator& other) const { return internal != other.internal; }
879
880ValueTree ValueTree::Iterator::operator*() const
881{
882 return ValueTree (SharedObject::Ptr (*static_cast<SharedObject**> (internal)));
883}
884
885ValueTree::Iterator ValueTree::begin() const noexcept { return Iterator (*this, false); }
886ValueTree::Iterator ValueTree::end() const noexcept { return Iterator (*this, true); }
887
889{
890 return object != nullptr ? object->getChildWithName (type) : ValueTree();
891}
892
894{
895 return object != nullptr ? object->getOrCreateChildWithName (type, undoManager) : ValueTree();
896}
897
898ValueTree ValueTree::getChildWithProperty (const Identifier& propertyName, const var& propertyValue) const
899{
900 return object != nullptr ? object->getChildWithProperty (propertyName, propertyValue) : ValueTree();
901}
902
903bool ValueTree::isAChildOf (const ValueTree& possibleParent) const noexcept
904{
905 return object != nullptr && object->isAChildOf (possibleParent.object.get());
906}
907
908int ValueTree::indexOf (const ValueTree& child) const noexcept
909{
910 return object != nullptr ? object->indexOf (child) : -1;
911}
912
913void ValueTree::addChild (const ValueTree& child, int index, UndoManager* undoManager)
914{
915 jassert (object != nullptr); // Trying to add a child to a null ValueTree!
916
917 if (object != nullptr)
918 object->addChild (child.object.get(), index, undoManager);
919}
920
921void ValueTree::appendChild (const ValueTree& child, UndoManager* undoManager)
922{
923 addChild (child, -1, undoManager);
924}
925
926void ValueTree::removeChild (int childIndex, UndoManager* undoManager)
927{
928 if (object != nullptr)
929 object->removeChild (childIndex, undoManager);
930}
931
932void ValueTree::removeChild (const ValueTree& child, UndoManager* undoManager)
933{
934 if (object != nullptr)
935 object->removeChild (object->children.indexOf (child.object), undoManager);
936}
937
939{
940 if (object != nullptr)
941 object->removeAllChildren (undoManager);
942}
943
944void ValueTree::moveChild (int currentIndex, int newIndex, UndoManager* undoManager)
945{
946 if (object != nullptr)
947 object->moveChild (currentIndex, newIndex, undoManager);
948}
949
950//==============================================================================
951void ValueTree::createListOfChildren (OwnedArray<ValueTree>& list) const
952{
953 jassert (object != nullptr);
954
955 for (auto* o : object->children)
956 {
957 jassert (o != nullptr);
958 list.add (new ValueTree (*o));
959 }
960}
961
962void ValueTree::reorderChildren (const OwnedArray<ValueTree>& newOrder, UndoManager* undoManager)
963{
964 jassert (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 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 forEachXmlChildElement (xml, e)
1008 v.appendChild (fromXml (*e), nullptr);
1009
1010 return v;
1011 }
1012
1013 // ValueTrees don't have any equivalent to XML text elements!
1014 jassertfalse;
1015 return {};
1016}
1017
1019{
1020 std::unique_ptr<XmlElement> xml (createXml());
1021
1022 if (xml != nullptr)
1023 return xml->createDocument ({});
1024
1025 return {};
1026}
1027
1028//==============================================================================
1030{
1031 SharedObject::writeObjectToStream (output, object.get());
1032}
1033
1035{
1036 auto type = input.readString();
1037
1038 if (type.isEmpty())
1039 return {};
1040
1041 ValueTree v (type);
1042
1043 auto numProps = input.readCompressedInt();
1044
1045 if (numProps < 0)
1046 {
1047 jassertfalse; // trying to read corrupted data!
1048 return v;
1049 }
1050
1051 for (int i = 0; i < numProps; ++i)
1052 {
1053 auto name = input.readString();
1054
1055 if (name.isNotEmpty())
1056 v.object->properties.set (name, var::readFromStream (input));
1057 else
1058 jassertfalse; // trying to read corrupted data!
1059 }
1060
1061 auto numChildren = input.readCompressedInt();
1062 v.object->children.ensureStorageAllocated (numChildren);
1063
1064 for (int i = 0; i < numChildren; ++i)
1065 {
1066 auto child = readFromStream (input);
1067
1068 if (! child.isValid())
1069 return v;
1070
1071 v.object->children.add (child.object);
1072 child.object->parent = v.object.get();
1073 }
1074
1075 return v;
1076}
1077
1078ValueTree ValueTree::readFromData (const void* data, size_t numBytes)
1079{
1080 MemoryInputStream in (data, numBytes, false);
1081 return readFromStream (in);
1082}
1083
1084ValueTree ValueTree::readFromGZIPData (const void* data, size_t numBytes)
1085{
1086 MemoryInputStream in (data, numBytes, false);
1087 GZIPDecompressorInputStream gzipStream (in);
1088 return readFromStream (gzipStream);
1089}
1090
1092
1093//==============================================================================
1094#if JUCE_UNIT_TESTS
1095
1096class ValueTreeTests : public UnitTest
1097{
1098public:
1099 ValueTreeTests() : UnitTest ("ValueTrees", "Values") {}
1100
1101 static String createRandomIdentifier (Random& r)
1102 {
1103 char buffer[50] = { 0 };
1104 const char chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-:";
1105
1106 for (int i = 1 + r.nextInt (numElementsInArray (buffer) - 2); --i >= 0;)
1107 buffer[i] = chars[r.nextInt (sizeof (chars) - 1)];
1108
1109 String result (buffer);
1110
1111 if (! XmlElement::isValidXmlName (result))
1112 result = createRandomIdentifier (r);
1113
1114 return result;
1115 }
1116
1117 static String createRandomWideCharString (Random& r)
1118 {
1119 juce_wchar buffer[50] = { 0 };
1120
1121 for (int i = r.nextInt (numElementsInArray (buffer) - 1); --i >= 0;)
1122 {
1123 if (r.nextBool())
1124 {
1125 do
1126 {
1127 buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1));
1128 }
1129 while (! CharPointer_UTF16::canRepresent (buffer[i]));
1130 }
1131 else
1132 buffer[i] = (juce_wchar) (1 + r.nextInt (0x7e));
1133 }
1134
1135 return CharPointer_UTF32 (buffer);
1136 }
1137
1138 static ValueTree createRandomTree (UndoManager* undoManager, int depth, Random& r)
1139 {
1140 ValueTree v (createRandomIdentifier (r));
1141
1142 for (int i = r.nextInt (10); --i >= 0;)
1143 {
1144 switch (r.nextInt (5))
1145 {
1146 case 0: v.setProperty (createRandomIdentifier (r), createRandomWideCharString (r), undoManager); break;
1147 case 1: v.setProperty (createRandomIdentifier (r), r.nextInt(), undoManager); break;
1148 case 2: if (depth < 5) v.addChild (createRandomTree (undoManager, depth + 1, r), r.nextInt (v.getNumChildren() + 1), undoManager); break;
1149 case 3: v.setProperty (createRandomIdentifier (r), r.nextBool(), undoManager); break;
1150 case 4: v.setProperty (createRandomIdentifier (r), r.nextDouble(), undoManager); break;
1151 default: break;
1152 }
1153 }
1154
1155 return v;
1156 }
1157
1158 void runTest() override
1159 {
1160 {
1161 beginTest ("ValueTree");
1162
1163 auto r = getRandom();
1164
1165 for (int i = 10; --i >= 0;)
1166 {
1167 MemoryOutputStream mo;
1168 auto v1 = createRandomTree (nullptr, 0, r);
1169 v1.writeToStream (mo);
1170
1171 MemoryInputStream mi (mo.getData(), mo.getDataSize(), false);
1172 auto v2 = ValueTree::readFromStream (mi);
1173 expect (v1.isEquivalentTo (v2));
1174
1175 MemoryOutputStream zipped;
1176 {
1177 GZIPCompressorOutputStream zippedOut (zipped);
1178 v1.writeToStream (zippedOut);
1179 }
1180 expect (v1.isEquivalentTo (ValueTree::readFromGZIPData (zipped.getData(), zipped.getDataSize())));
1181
1182 std::unique_ptr<XmlElement> xml1 (v1.createXml());
1183 std::unique_ptr<XmlElement> xml2 (v2.createCopy().createXml());
1184 expect (xml1->isEquivalentTo (xml2.get(), false));
1185
1186 auto v4 = v2.createCopy();
1187 expect (v1.isEquivalentTo (v4));
1188 }
1189 }
1190
1191 {
1192 beginTest ("Float formatting");
1193
1194 ValueTree testVT ("Test");
1195 Identifier number ("number");
1196
1197 std::map<double, String> tests;
1198 tests[1] = "1.0";
1199 tests[1.1] = "1.1";
1200 tests[1.01] = "1.01";
1201 tests[0.76378] = "0.76378";
1202 tests[-10] = "-10.0";
1203 tests[10.01] = "10.01";
1204 tests[0.0123] = "0.0123";
1205 tests[-3.7e-27] = "-3.7e-27";
1206 tests[1e+40] = "1.0e40";
1207 tests[-12345678901234567.0] = "-1.234567890123457e16";
1208 tests[192000] = "192000.0";
1209 tests[1234567] = "1.234567e6";
1210 tests[0.00006] = "0.00006";
1211 tests[0.000006] = "6.0e-6";
1212
1213 for (auto& test : tests)
1214 {
1215 testVT.setProperty (number, test.first, nullptr);
1216 auto lines = StringArray::fromLines (testVT.toXmlString());
1217 lines.removeEmptyStrings();
1218 auto numLines = lines.size();
1219 expect (numLines > 1);
1220 expectEquals (lines[numLines - 1], "<Test number=\"" + test.second + "\"/>");
1221 }
1222 }
1223 }
1224};
1225
1226static ValueTreeTests valueTreeTests;
1227
1228#endif
1229
1230} // 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.
var * getVarPointer(const Identifier &name) const noexcept
Returns a pointer to the var that holds a named value, or null if there is no value with this name.
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.
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.
int size() const noexcept
Returns the number of items currently in the array.
ObjectClass * getUnchecked(const int index) const noexcept
Returns a pointer to the object at this index in the array, without checking whether the index is in-...
ObjectClass * add(ObjectClass *newObject) noexcept
Appends a new object to the end of the array.
A random number generator.
Definition juce_Random.h:39
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:43
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)=0
This method is called when a child sub-tree is removed.
virtual void valueTreeChildAdded(ValueTree &parentTree, ValueTree &childWhichHasBeenAdded)=0
This method is called when a child sub-tree is added.
virtual void valueTreeParentChanged(ValueTree &treeWhoseParentHasChanged)=0
This method is called when a tree has been added or removed from a parent.
virtual void valueTreeChildOrderChanged(ValueTree &parentTreeWhoseChildrenHaveMoved, int oldIndex, int newIndex)=0
This method is called when a tree's children have been re-shuffled.
virtual void valueTreePropertyChanged(ValueTree &treeWhosePropertyHasChanged, const Identifier &property)=0
This method is called when a property of this tree (or of one of its sub-trees) is changed.
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.
String toXmlString() const
This returns a string containing an XML representation of the tree.
bool operator!=(const ValueTree &) const noexcept
Returns true if this and the other tree refer to different underlying structures.
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.
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.
XmlElement * createXml() const
Creates an XmlElement that holds a complete image of this tree and all its children.
~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:184
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:56
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.
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.
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.