Merge branch 'master' into windows
[ardour.git] / libs / pbd / pbd / sequence_property.h
index 1486437f58a6e2205334d146d2a7fc1052f778b5..b9d59724ddc56175fb6d9dab62ec1ea1ae352cb5 100644 (file)
@@ -31,6 +31,8 @@
 #include "pbd/id.h"
 #include "pbd/property_basics.h"
 #include "pbd/property_list.h"
+#include "pbd/stateful_diff_command.h"
+#include "pbd/error.h"
 
 namespace PBD {
 
@@ -79,44 +81,41 @@ class SequenceProperty : public PropertyBase
 
        SequenceProperty (PropertyID id, const boost::function<void(const ChangeRecord&)>& update)
                 : PropertyBase (id), _update_callback (update) {}
-        
-       virtual typename Container::value_type lookup_id (const PBD::ID&) = 0;
-
-        void invert_changes () {
-
-                /* reverse the adds/removes so that this property's change member
-                   correctly describes how to undo the changes it currently
-                   reflects. A derived instance of this type of property will
-                   create a diff() pair by copying the property twice, and
-                   calling this method on the "before" item of the pair.
-                */
 
-               _change.removed.swap (_change.added);
+        void invert () {
+               _changes.removed.swap (_changes.added);
         }
 
-       void get_change (XMLNode* history_node) const {
+       void get_changes_as_xml (XMLNode* history_node) const {
 
                 XMLNode* child = new XMLNode (PBD::capitalize (property_name()));
                 history_node->add_child_nocopy (*child);
                 
                /* record the change described in our change member */
 
-               if (!_change.added.empty()) {
-                       for (typename ChangeContainer::iterator i = _change.added.begin(); i != _change.added.end(); ++i) {
+               if (!_changes.added.empty()) {
+                       for (typename ChangeContainer::iterator i = _changes.added.begin(); i != _changes.added.end(); ++i) {
                                 XMLNode* add_node = new XMLNode ("Add");
                                 child->add_child_nocopy (*add_node);
-                                add_node->add_property ("id", (*i)->id().to_s());
+                               get_content_as_xml (*i, *add_node);
                        }
                }
-               if (!_change.removed.empty()) {
-                       for (typename ChangeContainer::iterator i = _change.removed.begin(); i != _change.removed.end(); ++i) {
+               if (!_changes.removed.empty()) {
+                       for (typename ChangeContainer::iterator i = _changes.removed.begin(); i != _changes.removed.end(); ++i) {
                                 XMLNode* remove_node = new XMLNode ("Remove");
                                 child->add_child_nocopy (*remove_node);
-                                remove_node->add_property ("id", (*i)->id().to_s());
+                               get_content_as_xml (*i, *remove_node);
                        }
                }
        }
 
+       /** Get a representation of one of our items as XML.  The representation must be sufficient to
+        *  restore the item's state later; an ID is ok if someone else is storing the item state,
+        *  otherwise it needs to be the full state.  The supplied node is an \<Add\> or \<Remove\>
+        *  which this method can either add properties or children to.
+        */
+       virtual void get_content_as_xml (typename ChangeContainer::value_type, XMLNode &) const = 0;
+
        bool set_value (XMLNode const &) {
                /* XXX: not used, but probably should be */
                assert (false);
@@ -130,16 +129,16 @@ class SequenceProperty : public PropertyBase
        }
 
        bool changed () const {
-               return !_change.added.empty() || !_change.removed.empty();
+               return !_changes.added.empty() || !_changes.removed.empty();
        }
        
-       void clear_history () {
-               _change.added.clear ();
-               _change.removed.clear ();
+       void clear_changes () {
+               _changes.added.clear ();
+               _changes.removed.clear ();
        }
 
-       void apply_change (PropertyBase const * p) {
-               const ChangeRecord& change (dynamic_cast<const SequenceProperty*> (p)->change ());
+       void apply_changes (PropertyBase const * p) {
+               const ChangeRecord& change (dynamic_cast<const SequenceProperty*> (p)->changes ());
                update (change);
        }
 
@@ -154,34 +153,86 @@ class SequenceProperty : public PropertyBase
                _update_callback (cr);
        }
 
-       void diff (PBD::PropertyList& undo, PBD::PropertyList& redo, Command* cmd) const {
-               if (changed ()) {
-                       /* list of the removed/added items since clear_history() was last called */
-                       SequenceProperty<Container>* a = copy_for_history ();
+       void get_changes_as_properties (PBD::PropertyList& changes, Command* cmd) const {
+               if (!changed ()) {
+                       return;
+               }
+               
+               /* Create a property with just the changes and not the actual values */
+               SequenceProperty<Container>* a = create ();
+               a->_changes = _changes;
+               changes.add (a);
+               
+               if (cmd) {
+                       /* whenever one of the items emits DropReferences, make sure
+                          that the Destructible we've been told to notify hears about
+                          it. the Destructible is likely to be the Command being built
+                          with this diff().
+                       */
+                        
+                       for (typename ChangeContainer::iterator i = a->changes().added.begin(); i != a->changes().added.end(); ++i) {
+                               (*i)->DropReferences.connect_same_thread (*cmd, boost::bind (&Destructible::drop_references, cmd));
+                       }
+               }
+        }
 
-                       /* the same list, but with removed/added lists swapped (for undo purposes) */
-                       SequenceProperty<Container>* b = copy_for_history ();
-                       b->invert_changes ();
+       SequenceProperty<Container>* clone_from_xml (XMLNode const & node) const {
 
-                       if (cmd) {
-                               /* whenever one of the items emits DropReferences, make sure
-                                  that the Destructible we've been told to notify hears about
-                                  it. the Destructible is likely to be the Command being built
-                                  with this diff().
-                               */
-                        
-                               for (typename ChangeContainer::iterator i = a->change().added.begin(); i != a->change().added.end(); ++i) {
-                                       (*i)->DropReferences.connect_same_thread (*cmd, boost::bind (&Destructible::drop_references, cmd));
-                               }
-                        }
-                       
-                       undo.add (b);
-                       redo.add (a);
+               XMLNodeList const children = node.children ();
+
+               /* find the node for this property name */
+               
+               std::string const c = capitalize (property_name ());
+               XMLNodeList::const_iterator i = children.begin();
+               while (i != children.end() && (*i)->name() != c) {
+                       ++i;
+               }
+
+               if (i == children.end()) {
+                       return 0;
                }
+
+               /* create a property with the changes */
+               
+               SequenceProperty<Container>* p = create ();
+
+               XMLNodeList const & grandchildren = (*i)->children ();
+               for (XMLNodeList::const_iterator j = grandchildren.begin(); j != grandchildren.end(); ++j) {
+
+                       typename Container::value_type v = get_content_from_xml (**j);
+
+                       if (!v) {
+                               warning << "undo transaction references an unknown object" << endmsg;
+                       } else if ((*j)->name() == "Add") {
+                               p->_changes.added.insert (v);
+                       } else if ((*j)->name() == "Remove") {
+                               p->_changes.removed.insert (v);
+                       }
+               }
+
+               return p;
         }
 
-        Container rlist() { return _val; }
-       
+       /** Given an \<Add\> or \<Remove\> node as passed into get_content_to_xml, obtain an item */
+       virtual typename Container::value_type get_content_from_xml (XMLNode const & node) const = 0;
+
+       void clear_owned_changes () {
+               for (typename Container::iterator i = begin(); i != end(); ++i) {
+                       (*i)->clear_changes ();
+               }
+       }
+
+       void rdiff (std::vector<Command*>& cmds) const {
+               for (typename Container::const_iterator i = begin(); i != end(); ++i) {
+                       if ((*i)->changed ()) {
+                               StatefulDiffCommand* sdc = new StatefulDiffCommand (*i);
+                               cmds.push_back (sdc);
+                       }
+               }
+       }
+
+        Container rlist() const { return _val; }
+
        /* Wrap salient methods of Sequence
         */
 
@@ -196,51 +247,56 @@ class SequenceProperty : public PropertyBase
        typename Container::const_reverse_iterator rend() const { return _val.rend(); }
 
        typename Container::iterator insert (typename Container::iterator i, const typename Container::value_type& v) {
-               _change.add (v);
+               _changes.add (v);
                return _val.insert (i, v);
        }
 
        typename Container::iterator erase (typename Container::iterator i) {
                if (i != _val.end()) {
-                       _change.remove (*i);
+                       _changes.remove (*i);
                }
                return _val.erase (i);
        }
 
        typename Container::iterator erase (typename Container::iterator f, typename Container::iterator l) {
                for (typename Container::const_iterator i = f; i != l; ++i) {
-                       _change.remove (*i);
+                       _changes.remove (*i);
                }
                return _val.erase (f, l);
        }
 
+       void remove (const typename Container::value_type& v) {
+               _changes.remove (v);
+               _val.remove (v);
+       }
+
        void push_back (const typename Container::value_type& v) {
-               _change.add (v);
+               _changes.add (v);
                _val.push_back (v);
        }
 
        void push_front (const typename Container::value_type& v) {
-               _change.add (v);
+               _changes.add (v);
                _val.push_front (v);
        }
 
        void pop_front () {
                 if (!_val.empty()) {
-                        _change.remove (front());
+                        _changes.remove (front());
                 }
                _val.pop_front ();
        }
 
        void pop_back () {
                 if (!_val.empty()) {
-                        _change.remove (back());
+                        _changes.remove (back());
                 }
                _val.pop_back ();
        }
 
        void clear () {
                for (typename Container::iterator i = _val.begin(); i != _val.end(); ++i) {
-                       _change.remove (*i);
+                       _changes.remove (*i);
                }
                _val.clear ();
        }
@@ -255,10 +311,10 @@ class SequenceProperty : public PropertyBase
 
        Container& operator= (const Container& other) {
                for (typename Container::iterator i = _val.begin(); i != _val.end(); ++i) {
-                       _change.remove (*i);
+                       _changes.remove (*i);
                }
                for (typename Container::iterator i = other.begin(); i != other.end(); ++i) {
-                       _change.add (*i);
+                       _changes.add (*i);
                }
                return _val = other;
        }
@@ -287,56 +343,24 @@ class SequenceProperty : public PropertyBase
                _val.sort (comp);
        }
         
-        const ChangeRecord& change() const { return _change; }
+        const ChangeRecord& changes () const { return _changes; }
 
-  protected:
-       Container _val;
-       ChangeRecord _change;
-       boost::function<void(const ChangeRecord&)> _update_callback;
+protected:
 
-        /** Load serialized change history.
-         * @return true if loading succeeded, false otherwise
-         */
-
-       bool set_change (XMLNode const & history_node) {
-
-                const XMLNodeList& children (history_node.children());
-
-                for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
-                        const XMLProperty* prop = (*i)->property ("id");
-                        if (prop) {
-                                PBD::ID id (prop->value());
-                                typename Container::value_type v = lookup_id (id);
-                                if (!v) {
-                                        std::cerr << "No such item, ID = " << id.to_s() << " (from " << prop->value() << ")\n";
-                                        return false;
-                                }
-                                if ((*i)->name() == "Add") {
-                                        _change.added.insert (v);
-                                } else if ((*i)->name() == "Remove") {
-                                        _change.removed.insert (v);
-                                }
-                        }
-                }
-
-                return true;
-        }
+       /* copy construction only by subclasses */
+       SequenceProperty (SequenceProperty<Container> const & p)
+               : PropertyBase (p)
+               , _val (p._val)
+               , _changes (p._changes)
+               , _update_callback (p._update_callback)
+       {}
+       
+       Container _val; ///< our actual container of things
+       ChangeRecord _changes; ///< changes to the container (adds/removes) that have happened since clear_changes() was last called
+       boost::function<void(const ChangeRecord&)> _update_callback;
 
-private:
+private:       
        virtual SequenceProperty<Container>* create () const = 0;
-
-        /* create a copy of this ListSequenceProperty that only
-           has what is needed for use in a history list command. This
-           means that it won't contain the actual item list but
-           will have the added/removed list.
-        */
-       
-       SequenceProperty<Container>* copy_for_history () const {
-               SequenceProperty<Container>* copy = create ();
-               /* this is all we need */
-               copy->_change = _change;
-               return copy;
-       }
 };
 
 }