OpenShot Library | OpenShotAudio 0.2.2
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//==============================================================================
995std::unique_ptr<XmlElement> ValueTree::createXml() const
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 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 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);
1093 GZIPDecompressorInputStream gzipStream (in);
1094 return readFromStream (gzipStream);
1095}
1096
1103
1104
1105//==============================================================================
1106//==============================================================================
1107#if JUCE_UNIT_TESTS
1108
1109class ValueTreeTests : public UnitTest
1110{
1111public:
1112 ValueTreeTests()
1113 : UnitTest ("ValueTrees", UnitTestCategories::values)
1114 {}
1115
1116 static String createRandomIdentifier (Random& r)
1117 {
1118 char buffer[50] = { 0 };
1119 const char chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-:";
1120
1121 for (int i = 1 + r.nextInt (numElementsInArray (buffer) - 2); --i >= 0;)
1122 buffer[i] = chars[r.nextInt (sizeof (chars) - 1)];
1123
1124 String result (buffer);
1125
1126 if (! XmlElement::isValidXmlName (result))
1127 result = createRandomIdentifier (r);
1128
1129 return result;
1130 }
1131
1132 static String createRandomWideCharString (Random& r)
1133 {
1134 juce_wchar buffer[50] = { 0 };
1135
1136 for (int i = r.nextInt (numElementsInArray (buffer) - 1); --i >= 0;)
1137 {
1138 if (r.nextBool())
1139 {
1140 do
1141 {
1142 buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1));
1143 }
1144 while (! CharPointer_UTF16::canRepresent (buffer[i]));
1145 }
1146 else
1147 buffer[i] = (juce_wchar) (1 + r.nextInt (0x7e));
1148 }
1149
1150 return CharPointer_UTF32 (buffer);
1151 }
1152
1153 static ValueTree createRandomTree (UndoManager* undoManager, int depth, Random& r)
1154 {
1155 ValueTree v (createRandomIdentifier (r));
1156
1157 for (int i = r.nextInt (10); --i >= 0;)
1158 {
1159 switch (r.nextInt (5))
1160 {
1161 case 0: v.setProperty (createRandomIdentifier (r), createRandomWideCharString (r), undoManager); break;
1162 case 1: v.setProperty (createRandomIdentifier (r), r.nextInt(), undoManager); break;
1163 case 2: if (depth < 5) v.addChild (createRandomTree (undoManager, depth + 1, r), r.nextInt (v.getNumChildren() + 1), undoManager); break;
1164 case 3: v.setProperty (createRandomIdentifier (r), r.nextBool(), undoManager); break;
1165 case 4: v.setProperty (createRandomIdentifier (r), r.nextDouble(), undoManager); break;
1166 default: break;
1167 }
1168 }
1169
1170 return v;
1171 }
1172
1173 void runTest() override
1174 {
1175 {
1176 beginTest ("ValueTree");
1177
1178 auto r = getRandom();
1179
1180 for (int i = 10; --i >= 0;)
1181 {
1182 MemoryOutputStream mo;
1183 auto v1 = createRandomTree (nullptr, 0, r);
1184 v1.writeToStream (mo);
1185
1186 MemoryInputStream mi (mo.getData(), mo.getDataSize(), false);
1187 auto v2 = ValueTree::readFromStream (mi);
1188 expect (v1.isEquivalentTo (v2));
1189
1190 MemoryOutputStream zipped;
1191 {
1192 GZIPCompressorOutputStream zippedOut (zipped);
1193 v1.writeToStream (zippedOut);
1194 }
1195 expect (v1.isEquivalentTo (ValueTree::readFromGZIPData (zipped.getData(), zipped.getDataSize())));
1196
1197 auto xml1 = v1.createXml();
1198 auto xml2 = v2.createCopy().createXml();
1199 expect (xml1->isEquivalentTo (xml2.get(), false));
1200
1201 auto v4 = v2.createCopy();
1202 expect (v1.isEquivalentTo (v4));
1203 }
1204 }
1205
1206 {
1207 beginTest ("Float formatting");
1208
1209 ValueTree testVT ("Test");
1210 Identifier number ("number");
1211
1212 std::map<double, String> tests;
1213 tests[1] = "1.0";
1214 tests[1.1] = "1.1";
1215 tests[1.01] = "1.01";
1216 tests[0.76378] = "0.76378";
1217 tests[-10] = "-10.0";
1218 tests[10.01] = "10.01";
1219 tests[0.0123] = "0.0123";
1220 tests[-3.7e-27] = "-3.7e-27";
1221 tests[1e+40] = "1.0e40";
1222 tests[-12345678901234567.0] = "-1.234567890123457e16";
1223 tests[192000] = "192000.0";
1224 tests[1234567] = "1.234567e6";
1225 tests[0.00006] = "0.00006";
1226 tests[0.000006] = "6.0e-6";
1227
1228 for (auto& test : tests)
1229 {
1230 testVT.setProperty (number, test.first, nullptr);
1231 auto lines = StringArray::fromLines (testVT.toXmlString());
1232 lines.removeEmptyStrings();
1233 auto numLines = lines.size();
1234 expect (numLines > 1);
1235 expectEquals (lines[numLines - 1], "<Test number=\"" + test.second + "\"/>");
1236 }
1237 }
1238 }
1239};
1240
1241static ValueTreeTests valueTreeTests;
1242
1243#endif
1244
1245} // namespace juce
static bool canRepresent(juce_wchar character) noexcept
Returns true if the given unicode character can be represented in this encoding.
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.
int size() const noexcept
Returns the number of items currently in the array.
ObjectClass * getUnchecked(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)
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.
Definition: juce_Random.cpp:78
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.
static StringArray fromLines(StringRef stringToBreakUp)
Returns an array containing the lines in a given string.
The JUCE String class!
Definition: juce_String.h:43
bool isNotEmpty() const noexcept
Returns true if the string contains at least one character.
Definition: juce_String.h:306
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.
Definition: juce_UnitTest.h:74
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:184
void sendChangeMessage(bool dispatchSynchronously)
Delivers a change message to all the listeners that are registered with this value.
Definition: juce_Value.cpp:44
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.
static bool isValidXmlName(StringRef possibleName) noexcept
Checks if a given string is a valid XML name.
A variant class, that can be used to hold a range of primitive values.
Definition: juce_Variant.h:46
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.
A struct containing options for formatting the text when representing an XML element as a string.