/* ============================================================================== This file is part of the JUCE 6 technical preview. Copyright (c) 2020 - Raw Material Software Limited You may use this code under the terms of the GPL v3 (see www.gnu.org/licenses). For this technical preview, this file is not subject to commercial licensing. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ namespace juce { class ValueTree::SharedObject : public ReferenceCountedObject { public: using Ptr = ReferenceCountedObjectPtr; explicit SharedObject (const Identifier& t) noexcept : type (t) {} SharedObject (const SharedObject& other) : ReferenceCountedObject(), type (other.type), properties (other.properties) { for (auto* c : other.children) { auto* child = new SharedObject (*c); child->parent = this; children.add (child); } } SharedObject& operator= (const SharedObject&) = delete; ~SharedObject() { jassert (parent == nullptr); // this should never happen unless something isn't obeying the ref-counting! for (auto i = children.size(); --i >= 0;) { const Ptr c (children.getObjectPointerUnchecked (i)); c->parent = nullptr; children.remove (i); c->sendParentChangeMessage(); } } SharedObject& getRoot() noexcept { return parent == nullptr ? *this : parent->getRoot(); } template void callListeners (ValueTree::Listener* listenerToExclude, Function fn) const { auto numListeners = valueTreesWithListeners.size(); if (numListeners == 1) { valueTreesWithListeners.getUnchecked (0)->listeners.callExcluding (listenerToExclude, fn); } else if (numListeners > 0) { auto listenersCopy = valueTreesWithListeners; for (int i = 0; i < numListeners; ++i) { auto* v = listenersCopy.getUnchecked (i); if (i == 0 || valueTreesWithListeners.contains (v)) v->listeners.callExcluding (listenerToExclude, fn); } } } template void callListenersForAllParents (ValueTree::Listener* listenerToExclude, Function fn) const { for (auto* t = this; t != nullptr; t = t->parent) t->callListeners (listenerToExclude, fn); } void sendPropertyChangeMessage (const Identifier& property, ValueTree::Listener* listenerToExclude = nullptr) { ValueTree tree (*this); callListenersForAllParents (listenerToExclude, [&] (Listener& l) { l.valueTreePropertyChanged (tree, property); }); } void sendChildAddedMessage (ValueTree child) { ValueTree tree (*this); callListenersForAllParents (nullptr, [&] (Listener& l) { l.valueTreeChildAdded (tree, child); }); } void sendChildRemovedMessage (ValueTree child, int index) { ValueTree tree (*this); callListenersForAllParents (nullptr, [=, &tree, &child] (Listener& l) { l.valueTreeChildRemoved (tree, child, index); }); } void sendChildOrderChangedMessage (int oldIndex, int newIndex) { ValueTree tree (*this); callListenersForAllParents (nullptr, [=, &tree] (Listener& l) { l.valueTreeChildOrderChanged (tree, oldIndex, newIndex); }); } void sendParentChangeMessage() { ValueTree tree (*this); for (auto j = children.size(); --j >= 0;) if (auto* child = children.getObjectPointer (j)) child->sendParentChangeMessage(); callListeners (nullptr, [&] (Listener& l) { l.valueTreeParentChanged (tree); }); } void setProperty (const Identifier& name, const var& newValue, UndoManager* undoManager, ValueTree::Listener* listenerToExclude = nullptr) { if (undoManager == nullptr) { if (properties.set (name, newValue)) sendPropertyChangeMessage (name, listenerToExclude); } else { if (auto* existingValue = properties.getVarPointer (name)) { if (*existingValue != newValue) undoManager->perform (new SetPropertyAction (*this, name, newValue, *existingValue, false, false, listenerToExclude)); } else { undoManager->perform (new SetPropertyAction (*this, name, newValue, {}, true, false, listenerToExclude)); } } } bool hasProperty (const Identifier& name) const noexcept { return properties.contains (name); } void removeProperty (const Identifier& name, UndoManager* undoManager) { if (undoManager == nullptr) { if (properties.remove (name)) sendPropertyChangeMessage (name); } else { if (properties.contains (name)) undoManager->perform (new SetPropertyAction (*this, name, {}, properties[name], false, true)); } } void removeAllProperties (UndoManager* undoManager) { if (undoManager == nullptr) { while (properties.size() > 0) { auto name = properties.getName (properties.size() - 1); properties.remove (name); sendPropertyChangeMessage (name); } } else { for (auto i = properties.size(); --i >= 0;) undoManager->perform (new SetPropertyAction (*this, properties.getName (i), {}, properties.getValueAt (i), false, true)); } } void copyPropertiesFrom (const SharedObject& source, UndoManager* undoManager) { for (auto i = properties.size(); --i >= 0;) if (! source.properties.contains (properties.getName (i))) removeProperty (properties.getName (i), undoManager); for (int i = 0; i < source.properties.size(); ++i) setProperty (source.properties.getName (i), source.properties.getValueAt (i), undoManager); } ValueTree getChildWithName (const Identifier& typeToMatch) const { for (auto* s : children) if (s->type == typeToMatch) return ValueTree (*s); return {}; } ValueTree getOrCreateChildWithName (const Identifier& typeToMatch, UndoManager* undoManager) { for (auto* s : children) if (s->type == typeToMatch) return ValueTree (*s); auto newObject = new SharedObject (typeToMatch); addChild (newObject, -1, undoManager); return ValueTree (*newObject); } ValueTree getChildWithProperty (const Identifier& propertyName, const var& propertyValue) const { for (auto* s : children) if (s->properties[propertyName] == propertyValue) return ValueTree (*s); return {}; } bool isAChildOf (const SharedObject* possibleParent) const noexcept { for (auto* p = parent; p != nullptr; p = p->parent) if (p == possibleParent) return true; return false; } int indexOf (const ValueTree& child) const noexcept { return children.indexOf (child.object); } void addChild (SharedObject* child, int index, UndoManager* undoManager) { if (child != nullptr && child->parent != this) { if (child != this && ! isAChildOf (child)) { // You should always make sure that a child is removed from its previous parent before // adding it somewhere else - otherwise, it's ambiguous as to whether a different // undomanager should be used when removing it from its current parent.. jassert (child->parent == nullptr); if (child->parent != nullptr) { jassert (child->parent->children.indexOf (child) >= 0); child->parent->removeChild (child->parent->children.indexOf (child), undoManager); } if (undoManager == nullptr) { children.insert (index, child); child->parent = this; sendChildAddedMessage (ValueTree (*child)); child->sendParentChangeMessage(); } else { if (! isPositiveAndBelow (index, children.size())) index = children.size(); undoManager->perform (new AddOrRemoveChildAction (*this, index, child)); } } else { // You're attempting to create a recursive loop! A node // can't be a child of one of its own children! jassertfalse; } } } void removeChild (int childIndex, UndoManager* undoManager) { if (auto child = Ptr (children.getObjectPointer (childIndex))) { if (undoManager == nullptr) { children.remove (childIndex); child->parent = nullptr; sendChildRemovedMessage (ValueTree (child), childIndex); child->sendParentChangeMessage(); } else { undoManager->perform (new AddOrRemoveChildAction (*this, childIndex, {})); } } } void removeAllChildren (UndoManager* undoManager) { while (children.size() > 0) removeChild (children.size() - 1, undoManager); } void moveChild (int currentIndex, int newIndex, UndoManager* undoManager) { // The source index must be a valid index! jassert (isPositiveAndBelow (currentIndex, children.size())); if (currentIndex != newIndex && isPositiveAndBelow (currentIndex, children.size())) { if (undoManager == nullptr) { children.move (currentIndex, newIndex); sendChildOrderChangedMessage (currentIndex, newIndex); } else { if (! isPositiveAndBelow (newIndex, children.size())) newIndex = children.size() - 1; undoManager->perform (new MoveChildAction (*this, currentIndex, newIndex)); } } } void reorderChildren (const OwnedArray& newOrder, UndoManager* undoManager) { jassert (newOrder.size() == children.size()); for (int i = 0; i < children.size(); ++i) { auto* child = newOrder.getUnchecked (i)->object.get(); if (children.getObjectPointerUnchecked (i) != child) { auto oldIndex = children.indexOf (child); jassert (oldIndex >= 0); moveChild (oldIndex, i, undoManager); } } } bool isEquivalentTo (const SharedObject& other) const noexcept { if (type != other.type || properties.size() != other.properties.size() || children.size() != other.children.size() || properties != other.properties) return false; for (int i = 0; i < children.size(); ++i) if (! children.getObjectPointerUnchecked (i)->isEquivalentTo (*other.children.getObjectPointerUnchecked (i))) return false; return true; } XmlElement* createXml() const { auto* xml = new XmlElement (type); properties.copyToXmlAttributes (*xml); // (NB: it's faster to add nodes to XML elements in reverse order) for (auto i = children.size(); --i >= 0;) xml->prependChildElement (children.getObjectPointerUnchecked (i)->createXml()); return xml; } void writeToStream (OutputStream& output) const { output.writeString (type.toString()); output.writeCompressedInt (properties.size()); for (int j = 0; j < properties.size(); ++j) { output.writeString (properties.getName (j).toString()); properties.getValueAt (j).writeToStream (output); } output.writeCompressedInt (children.size()); for (auto* c : children) writeObjectToStream (output, c); } static void writeObjectToStream (OutputStream& output, const SharedObject* object) { if (object != nullptr) { object->writeToStream (output); } else { output.writeString ({}); output.writeCompressedInt (0); output.writeCompressedInt (0); } } //============================================================================== struct SetPropertyAction : public UndoableAction { SetPropertyAction (Ptr targetObject, const Identifier& propertyName, const var& newVal, const var& oldVal, bool isAdding, bool isDeleting, ValueTree::Listener* listenerToExclude = nullptr) : target (std::move (targetObject)), name (propertyName), newValue (newVal), oldValue (oldVal), isAddingNewProperty (isAdding), isDeletingProperty (isDeleting), excludeListener (listenerToExclude) { } bool perform() override { jassert (! (isAddingNewProperty && target->hasProperty (name))); if (isDeletingProperty) target->removeProperty (name, nullptr); else target->setProperty (name, newValue, nullptr, excludeListener); return true; } bool undo() override { if (isAddingNewProperty) target->removeProperty (name, nullptr); else target->setProperty (name, oldValue, nullptr); return true; } int getSizeInUnits() override { return (int) sizeof (*this); //xxx should be more accurate } UndoableAction* createCoalescedAction (UndoableAction* nextAction) override { if (! (isAddingNewProperty || isDeletingProperty)) { if (auto* next = dynamic_cast (nextAction)) if (next->target == target && next->name == name && ! (next->isAddingNewProperty || next->isDeletingProperty)) return new SetPropertyAction (*target, name, next->newValue, oldValue, false, false); } return nullptr; } private: const Ptr target; const Identifier name; const var newValue; var oldValue; const bool isAddingNewProperty : 1, isDeletingProperty : 1; ValueTree::Listener* excludeListener; JUCE_DECLARE_NON_COPYABLE (SetPropertyAction) }; //============================================================================== struct AddOrRemoveChildAction : public UndoableAction { AddOrRemoveChildAction (Ptr parentObject, int index, SharedObject* newChild) : target (std::move (parentObject)), child (newChild != nullptr ? newChild : target->children.getObjectPointer (index)), childIndex (index), isDeleting (newChild == nullptr) { jassert (child != nullptr); } bool perform() override { if (isDeleting) target->removeChild (childIndex, nullptr); else target->addChild (child.get(), childIndex, nullptr); return true; } bool undo() override { if (isDeleting) { target->addChild (child.get(), childIndex, nullptr); } else { // If you hit this, it seems that your object's state is getting confused - probably // because you've interleaved some undoable and non-undoable operations? jassert (childIndex < target->children.size()); target->removeChild (childIndex, nullptr); } return true; } int getSizeInUnits() override { return (int) sizeof (*this); //xxx should be more accurate } private: const Ptr target, child; const int childIndex; const bool isDeleting; JUCE_DECLARE_NON_COPYABLE (AddOrRemoveChildAction) }; //============================================================================== struct MoveChildAction : public UndoableAction { MoveChildAction (Ptr parentObject, int fromIndex, int toIndex) noexcept : parent (std::move (parentObject)), startIndex (fromIndex), endIndex (toIndex) { } bool perform() override { parent->moveChild (startIndex, endIndex, nullptr); return true; } bool undo() override { parent->moveChild (endIndex, startIndex, nullptr); return true; } int getSizeInUnits() override { return (int) sizeof (*this); //xxx should be more accurate } UndoableAction* createCoalescedAction (UndoableAction* nextAction) override { if (auto* next = dynamic_cast (nextAction)) if (next->parent == parent && next->startIndex == endIndex) return new MoveChildAction (parent, startIndex, next->endIndex); return nullptr; } private: const Ptr parent; const int startIndex, endIndex; JUCE_DECLARE_NON_COPYABLE (MoveChildAction) }; //============================================================================== const Identifier type; NamedValueSet properties; ReferenceCountedArray children; SortedSet valueTreesWithListeners; SharedObject* parent = nullptr; JUCE_LEAK_DETECTOR (SharedObject) }; //============================================================================== ValueTree::ValueTree() noexcept { } JUCE_DECLARE_DEPRECATED_STATIC (const ValueTree ValueTree::invalid;) ValueTree::ValueTree (const Identifier& type) : object (new ValueTree::SharedObject (type)) { jassert (type.toString().isNotEmpty()); // All objects must be given a sensible type name! } ValueTree::ValueTree (const Identifier& type, std::initializer_list properties, std::initializer_list subTrees) : ValueTree (type) { object->properties = NamedValueSet (std::move (properties)); for (auto& tree : subTrees) addChild (tree, -1, nullptr); } ValueTree::ValueTree (SharedObject::Ptr so) noexcept : object (std::move (so)) {} ValueTree::ValueTree (SharedObject& so) noexcept : object (so) {} ValueTree::ValueTree (const ValueTree& other) noexcept : object (other.object) { } ValueTree& ValueTree::operator= (const ValueTree& other) { if (object != other.object) { if (listeners.isEmpty()) { object = other.object; } else { if (object != nullptr) object->valueTreesWithListeners.removeValue (this); if (other.object != nullptr) other.object->valueTreesWithListeners.add (this); object = other.object; listeners.call ([this] (Listener& l) { l.valueTreeRedirected (*this); }); } } return *this; } ValueTree::ValueTree (ValueTree&& other) noexcept : object (std::move (other.object)) { if (object != nullptr) object->valueTreesWithListeners.removeValue (&other); } ValueTree::~ValueTree() { if (! listeners.isEmpty() && object != nullptr) object->valueTreesWithListeners.removeValue (this); } bool ValueTree::operator== (const ValueTree& other) const noexcept { return object == other.object; } bool ValueTree::operator!= (const ValueTree& other) const noexcept { return object != other.object; } bool ValueTree::isEquivalentTo (const ValueTree& other) const { return object == other.object || (object != nullptr && other.object != nullptr && object->isEquivalentTo (*other.object)); } ValueTree ValueTree::createCopy() const { if (object != nullptr) return ValueTree (*new SharedObject (*object)); return {}; } void ValueTree::copyPropertiesFrom (const ValueTree& source, UndoManager* undoManager) { jassert (object != nullptr || source.object == nullptr); // Trying to add properties to a null ValueTree will fail! if (source.object == nullptr) removeAllProperties (undoManager); else if (object != nullptr) object->copyPropertiesFrom (*(source.object), undoManager); } void ValueTree::copyPropertiesAndChildrenFrom (const ValueTree& source, UndoManager* undoManager) { jassert (object != nullptr || source.object == nullptr); // Trying to copy to a null ValueTree will fail! copyPropertiesFrom (source, undoManager); removeAllChildren (undoManager); if (object != nullptr && source.object != nullptr) for (auto& child : source.object->children) object->addChild (createCopyIfNotNull (child), -1, undoManager); } bool ValueTree::hasType (const Identifier& typeName) const noexcept { return object != nullptr && object->type == typeName; } Identifier ValueTree::getType() const noexcept { return object != nullptr ? object->type : Identifier(); } ValueTree ValueTree::getParent() const noexcept { if (object != nullptr) if (auto p = object->parent) return ValueTree (*p); return {}; } ValueTree ValueTree::getRoot() const noexcept { if (object != nullptr) return ValueTree (object->getRoot()); return {}; } ValueTree ValueTree::getSibling (int delta) const noexcept { if (object != nullptr) if (auto* p = object->parent) if (auto* c = p->children.getObjectPointer (p->indexOf (*this) + delta)) return ValueTree (*c); return {}; } static const var& getNullVarRef() noexcept { static var nullVar; return nullVar; } const var& ValueTree::operator[] (const Identifier& name) const noexcept { return object == nullptr ? getNullVarRef() : object->properties[name]; } const var& ValueTree::getProperty (const Identifier& name) const noexcept { return object == nullptr ? getNullVarRef() : object->properties[name]; } var ValueTree::getProperty (const Identifier& name, const var& defaultReturnValue) const { return object == nullptr ? defaultReturnValue : object->properties.getWithDefault (name, defaultReturnValue); } const var* ValueTree::getPropertyPointer (const Identifier& name) const noexcept { return object == nullptr ? nullptr : object->properties.getVarPointer (name); } ValueTree& ValueTree::setProperty (const Identifier& name, const var& newValue, UndoManager* undoManager) { return setPropertyExcludingListener (nullptr, name, newValue, undoManager); } ValueTree& ValueTree::setPropertyExcludingListener (Listener* listenerToExclude, const Identifier& name, const var& newValue, UndoManager* undoManager) { jassert (name.toString().isNotEmpty()); // Must have a valid property name! jassert (object != nullptr); // Trying to add a property to a null ValueTree will fail! if (object != nullptr) object->setProperty (name, newValue, undoManager, listenerToExclude); return *this; } bool ValueTree::hasProperty (const Identifier& name) const noexcept { return object != nullptr && object->hasProperty (name); } void ValueTree::removeProperty (const Identifier& name, UndoManager* undoManager) { if (object != nullptr) object->removeProperty (name, undoManager); } void ValueTree::removeAllProperties (UndoManager* undoManager) { if (object != nullptr) object->removeAllProperties (undoManager); } int ValueTree::getNumProperties() const noexcept { return object == nullptr ? 0 : object->properties.size(); } Identifier ValueTree::getPropertyName (int index) const noexcept { return object == nullptr ? Identifier() : object->properties.getName (index); } int ValueTree::getReferenceCount() const noexcept { return object != nullptr ? object->getReferenceCount() : 0; } //============================================================================== struct ValueTreePropertyValueSource : public Value::ValueSource, private ValueTree::Listener { ValueTreePropertyValueSource (const ValueTree& vt, const Identifier& prop, UndoManager* um, bool sync) : tree (vt), property (prop), undoManager (um), updateSynchronously (sync) { tree.addListener (this); } ~ValueTreePropertyValueSource() override { tree.removeListener (this); } var getValue() const override { return tree[property]; } void setValue (const var& newValue) override { tree.setProperty (property, newValue, undoManager); } private: ValueTree tree; const Identifier property; UndoManager* const undoManager; const bool updateSynchronously; void valueTreePropertyChanged (ValueTree& changedTree, const Identifier& changedProperty) override { if (tree == changedTree && property == changedProperty) sendChangeMessage (updateSynchronously); } void valueTreeChildAdded (ValueTree&, ValueTree&) override {} void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override {} void valueTreeChildOrderChanged (ValueTree&, int, int) override {} void valueTreeParentChanged (ValueTree&) override {} JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueTreePropertyValueSource) }; Value ValueTree::getPropertyAsValue (const Identifier& name, UndoManager* undoManager, bool updateSynchronously) { return Value (new ValueTreePropertyValueSource (*this, name, undoManager, updateSynchronously)); } //============================================================================== int ValueTree::getNumChildren() const noexcept { return object == nullptr ? 0 : object->children.size(); } ValueTree ValueTree::getChild (int index) const { if (object != nullptr) if (auto* c = object->children.getObjectPointer (index)) return ValueTree (*c); return {}; } ValueTree::Iterator::Iterator (const ValueTree& v, bool isEnd) : internal (v.object != nullptr ? (isEnd ? v.object->children.end() : v.object->children.begin()) : nullptr) { } ValueTree::Iterator& ValueTree::Iterator::operator++() { internal = static_cast (internal) + 1; return *this; } bool ValueTree::Iterator::operator== (const Iterator& other) const { return internal == other.internal; } bool ValueTree::Iterator::operator!= (const Iterator& other) const { return internal != other.internal; } ValueTree ValueTree::Iterator::operator*() const { return ValueTree (SharedObject::Ptr (*static_cast (internal))); } ValueTree::Iterator ValueTree::begin() const noexcept { return Iterator (*this, false); } ValueTree::Iterator ValueTree::end() const noexcept { return Iterator (*this, true); } ValueTree ValueTree::getChildWithName (const Identifier& type) const { return object != nullptr ? object->getChildWithName (type) : ValueTree(); } ValueTree ValueTree::getOrCreateChildWithName (const Identifier& type, UndoManager* undoManager) { return object != nullptr ? object->getOrCreateChildWithName (type, undoManager) : ValueTree(); } ValueTree ValueTree::getChildWithProperty (const Identifier& propertyName, const var& propertyValue) const { return object != nullptr ? object->getChildWithProperty (propertyName, propertyValue) : ValueTree(); } bool ValueTree::isAChildOf (const ValueTree& possibleParent) const noexcept { return object != nullptr && object->isAChildOf (possibleParent.object.get()); } int ValueTree::indexOf (const ValueTree& child) const noexcept { return object != nullptr ? object->indexOf (child) : -1; } void ValueTree::addChild (const ValueTree& child, int index, UndoManager* undoManager) { jassert (object != nullptr); // Trying to add a child to a null ValueTree! if (object != nullptr) object->addChild (child.object.get(), index, undoManager); } void ValueTree::appendChild (const ValueTree& child, UndoManager* undoManager) { addChild (child, -1, undoManager); } void ValueTree::removeChild (int childIndex, UndoManager* undoManager) { if (object != nullptr) object->removeChild (childIndex, undoManager); } void ValueTree::removeChild (const ValueTree& child, UndoManager* undoManager) { if (object != nullptr) object->removeChild (object->children.indexOf (child.object), undoManager); } void ValueTree::removeAllChildren (UndoManager* undoManager) { if (object != nullptr) object->removeAllChildren (undoManager); } void ValueTree::moveChild (int currentIndex, int newIndex, UndoManager* undoManager) { if (object != nullptr) object->moveChild (currentIndex, newIndex, undoManager); } //============================================================================== void ValueTree::createListOfChildren (OwnedArray& list) const { jassert (object != nullptr); for (auto* o : object->children) { jassert (o != nullptr); list.add (new ValueTree (*o)); } } void ValueTree::reorderChildren (const OwnedArray& newOrder, UndoManager* undoManager) { jassert (object != nullptr); object->reorderChildren (newOrder, undoManager); } //============================================================================== void ValueTree::addListener (Listener* listener) { if (listener != nullptr) { if (listeners.isEmpty() && object != nullptr) object->valueTreesWithListeners.add (this); listeners.add (listener); } } void ValueTree::removeListener (Listener* listener) { listeners.remove (listener); if (listeners.isEmpty() && object != nullptr) object->valueTreesWithListeners.removeValue (this); } void ValueTree::sendPropertyChangeMessage (const Identifier& property) { if (object != nullptr) object->sendPropertyChangeMessage (property); } //============================================================================== std::unique_ptr ValueTree::createXml() const { return std::unique_ptr (object != nullptr ? object->createXml() : nullptr); } ValueTree ValueTree::fromXml (const XmlElement& xml) { if (! xml.isTextElement()) { ValueTree v (xml.getTagName()); v.object->properties.setFromXmlAttributes (xml); forEachXmlChildElement (xml, e) v.appendChild (fromXml (*e), nullptr); return v; } // ValueTrees don't have any equivalent to XML text elements! jassertfalse; return {}; } ValueTree ValueTree::fromXml (const String& xmlText) { if (auto xml = parseXML (xmlText)) return fromXml (*xml); return {}; } String ValueTree::toXmlString (const XmlElement::TextFormat& format) const { if (auto xml = createXml()) return xml->toString (format); return {}; } //============================================================================== void ValueTree::writeToStream (OutputStream& output) const { SharedObject::writeObjectToStream (output, object.get()); } ValueTree ValueTree::readFromStream (InputStream& input) { auto type = input.readString(); if (type.isEmpty()) return {}; ValueTree v (type); auto numProps = input.readCompressedInt(); if (numProps < 0) { jassertfalse; // trying to read corrupted data! return v; } for (int i = 0; i < numProps; ++i) { auto name = input.readString(); if (name.isNotEmpty()) v.object->properties.set (name, var::readFromStream (input)); else jassertfalse; // trying to read corrupted data! } auto numChildren = input.readCompressedInt(); v.object->children.ensureStorageAllocated (numChildren); for (int i = 0; i < numChildren; ++i) { auto child = readFromStream (input); if (! child.isValid()) return v; v.object->children.add (child.object); child.object->parent = v.object.get(); } return v; } ValueTree ValueTree::readFromData (const void* data, size_t numBytes) { MemoryInputStream in (data, numBytes, false); return readFromStream (in); } ValueTree ValueTree::readFromGZIPData (const void* data, size_t numBytes) { MemoryInputStream in (data, numBytes, false); GZIPDecompressorInputStream gzipStream (in); return readFromStream (gzipStream); } void ValueTree::Listener::valueTreePropertyChanged (ValueTree&, const Identifier&) {} void ValueTree::Listener::valueTreeChildAdded (ValueTree&, ValueTree&) {} void ValueTree::Listener::valueTreeChildRemoved (ValueTree&, ValueTree&, int) {} void ValueTree::Listener::valueTreeChildOrderChanged (ValueTree&, int, int) {} void ValueTree::Listener::valueTreeParentChanged (ValueTree&) {} void ValueTree::Listener::valueTreeRedirected (ValueTree&) {} //============================================================================== //============================================================================== #if JUCE_UNIT_TESTS class ValueTreeTests : public UnitTest { public: ValueTreeTests() : UnitTest ("ValueTrees", UnitTestCategories::values) {} static String createRandomIdentifier (Random& r) { char buffer[50] = { 0 }; const char chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-:"; for (int i = 1 + r.nextInt (numElementsInArray (buffer) - 2); --i >= 0;) buffer[i] = chars[r.nextInt (sizeof (chars) - 1)]; String result (buffer); if (! XmlElement::isValidXmlName (result)) result = createRandomIdentifier (r); return result; } static String createRandomWideCharString (Random& r) { juce_wchar buffer[50] = { 0 }; for (int i = r.nextInt (numElementsInArray (buffer) - 1); --i >= 0;) { if (r.nextBool()) { do { buffer[i] = (juce_wchar) (1 + r.nextInt (0x10ffff - 1)); } while (! CharPointer_UTF16::canRepresent (buffer[i])); } else buffer[i] = (juce_wchar) (1 + r.nextInt (0x7e)); } return CharPointer_UTF32 (buffer); } static ValueTree createRandomTree (UndoManager* undoManager, int depth, Random& r) { ValueTree v (createRandomIdentifier (r)); for (int i = r.nextInt (10); --i >= 0;) { switch (r.nextInt (5)) { case 0: v.setProperty (createRandomIdentifier (r), createRandomWideCharString (r), undoManager); break; case 1: v.setProperty (createRandomIdentifier (r), r.nextInt(), undoManager); break; case 2: if (depth < 5) v.addChild (createRandomTree (undoManager, depth + 1, r), r.nextInt (v.getNumChildren() + 1), undoManager); break; case 3: v.setProperty (createRandomIdentifier (r), r.nextBool(), undoManager); break; case 4: v.setProperty (createRandomIdentifier (r), r.nextDouble(), undoManager); break; default: break; } } return v; } void runTest() override { { beginTest ("ValueTree"); auto r = getRandom(); for (int i = 10; --i >= 0;) { MemoryOutputStream mo; auto v1 = createRandomTree (nullptr, 0, r); v1.writeToStream (mo); MemoryInputStream mi (mo.getData(), mo.getDataSize(), false); auto v2 = ValueTree::readFromStream (mi); expect (v1.isEquivalentTo (v2)); MemoryOutputStream zipped; { GZIPCompressorOutputStream zippedOut (zipped); v1.writeToStream (zippedOut); } expect (v1.isEquivalentTo (ValueTree::readFromGZIPData (zipped.getData(), zipped.getDataSize()))); auto xml1 = v1.createXml(); auto xml2 = v2.createCopy().createXml(); expect (xml1->isEquivalentTo (xml2.get(), false)); auto v4 = v2.createCopy(); expect (v1.isEquivalentTo (v4)); } } { beginTest ("Float formatting"); ValueTree testVT ("Test"); Identifier number ("number"); std::map tests; tests[1] = "1.0"; tests[1.1] = "1.1"; tests[1.01] = "1.01"; tests[0.76378] = "0.76378"; tests[-10] = "-10.0"; tests[10.01] = "10.01"; tests[0.0123] = "0.0123"; tests[-3.7e-27] = "-3.7e-27"; tests[1e+40] = "1.0e40"; tests[-12345678901234567.0] = "-1.234567890123457e16"; tests[192000] = "192000.0"; tests[1234567] = "1.234567e6"; tests[0.00006] = "0.00006"; tests[0.000006] = "6.0e-6"; for (auto& test : tests) { testVT.setProperty (number, test.first, nullptr); auto lines = StringArray::fromLines (testVT.toXmlString()); lines.removeEmptyStrings(); auto numLines = lines.size(); expect (numLines > 1); expectEquals (lines[numLines - 1], ""); } } } }; static ValueTreeTests valueTreeTests; #endif } // namespace juce