+/*
+ Copyright (C) 2010 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
#ifndef __libpbd_sequence_property_h__
#define __libpbd_sequence_property_h__
#include <boost/function.hpp>
+#include "pbd/libpbd_visibility.h"
#include "pbd/convert.h"
#include "pbd/id.h"
#include "pbd/property_basics.h"
-
-#include "i18n.h"
+#include "pbd/property_list.h"
+#include "pbd/stateful_diff_command.h"
+#include "pbd/error.h"
namespace PBD {
+
+/** A base class for properties whose state is a container of other
+ * things. Its behaviour is `specialised' for this purpose in that
+ * it holds state changes as additions to and removals from the
+ * container, which is more efficient than storing entire state after
+ * any change.
+ */
template<typename Container>
-class SequenceProperty : public PropertyBase
+class /*LIBPBD_API*/ SequenceProperty : public PropertyBase
{
public:
typedef std::set<typename Container::value_type> ChangeContainer;
-
- struct ChangeRecord {
- ChangeContainer added;
- ChangeContainer removed;
+
+ /** A record of changes made */
+ struct ChangeRecord {
+
+ void add (typename Container::value_type const & r) {
+ typename ChangeContainer::iterator i = removed.find (r);
+ if (i != removed.end()) {
+ /* we're adding, and this thing has already been marked as removed, so
+ just remove it from the removed list
+ */
+ removed.erase (r);
+ } else {
+ added.insert (r);
+ }
+ }
+
+ void remove (typename Container::value_type const & r) {
+ typename ChangeContainer::iterator i = added.find (r);
+ if (i != added.end()) {
+ /* we're removing, and this thing has already been marked as added, so
+ just remove it from the added list
+ */
+ added.erase (i);
+ } else {
+ removed.insert (r);
+ }
+ }
+
+ ChangeContainer added;
+ ChangeContainer removed;
};
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 pdiff() 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 add_history_state (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) {
- XMLNode* add_node = new XMLNode (X_("Add"));
+ if (!_changes.added.empty()) {
+ for (typename ChangeContainer::const_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 (X_("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) {
- XMLNode* remove_node = new XMLNode (X_("Remove"));
+ if (!_changes.removed.empty()) {
+ for (typename ChangeContainer::const_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 (X_("id"), (*i)->id().to_s());
+ get_content_as_xml (*i, *remove_node);
}
}
}
- bool set_state_from_owner_state (XMLNode const& owner_state) {
-
- XMLProperty const* n = owner_state.property (X_("name"));
-
- if (!n) {
- return false;
- }
-
- assert (g_quark_from_string (n->value().c_str()) == property_id());
-
- const XMLNodeList& children = owner_state.children();
-
- for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) {
-
- if ((*c)->name() == X_("Added")) {
- const XMLNodeList& grandchildren = (*c)->children();
- for (XMLNodeList::const_iterator gc = grandchildren.begin(); gc != grandchildren.end(); ++gc) {
- const XMLProperty* prop = (*gc)->property (X_("id"));
- if (prop) {
- typename Container::value_type v = lookup_id (PBD::ID (prop->value()));
- if (v) {
- _change.added.insert (v);
- }
- }
- }
- } else if ((*c)->name() == X_("Removed")) {
- const XMLNodeList& grandchildren = (*c)->children();
- for (XMLNodeList::const_iterator gc = grandchildren.begin(); gc != grandchildren.end(); ++gc) {
- const XMLProperty* prop = (*gc)->property (X_("id"));
- if (prop) {
- typename Container::value_type v = lookup_id (PBD::ID (prop->value()));
- if (v) {
- _change.removed.insert (v);
- }
- }
- }
- }
- }
+ /** 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;
- return true;
+ bool set_value (XMLNode const &) {
+ /* XXX: not used, but probably should be */
+ assert (false);
+ return false;
}
- void add_state_to_owner_state (XMLNode& owner_state_node) const {
+ void get_value (XMLNode & node) const {
for (typename Container::const_iterator i = _val.begin(); i != _val.end(); ++i) {
- owner_state_node.add_child_nocopy ((*i)->get_state ());
- }
+ node.add_child_nocopy ((*i)->get_state ());
+ }
}
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 set_state_from_property (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);
}
/** Given a record of changes to this property, pass it to a callback that will
- * update the property in some appropriate way.
+ * update the property in some appropriate way.
*
* This exists because simply using std::sequence methods to add/remove items
* from the property is far too simplistic - the semantics of add/remove may
_update_callback (cr);
}
+ 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::const_iterator i = a->changes().added.begin(); i != a->changes().added.end(); ++i) {
+ (*i)->DropReferences.connect_same_thread (*cmd, boost::bind (&Destructible::drop_references, cmd));
+ }
+ }
+ }
+
+ SequenceProperty<Container>* clone_from_xml (XMLNode const & node) const {
+
+ 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;
+ }
+
+ /** 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
*/
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.added.insert (v);
+ _changes.add (v);
return _val.insert (i, v);
}
typename Container::iterator erase (typename Container::iterator i) {
if (i != _val.end()) {
- _change.removed.insert (*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.removed.insert(*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.added.insert (v);
+ _changes.add (v);
_val.push_back (v);
}
void push_front (const typename Container::value_type& v) {
- _change.added.insert (v);
+ _changes.add (v);
_val.push_front (v);
}
void pop_front () {
if (!_val.empty()) {
- _change.removed.insert (front());
+ _changes.remove (front());
}
_val.pop_front ();
}
void pop_back () {
if (!_val.empty()) {
- _change.removed.insert (front());
+ _changes.remove (back());
}
_val.pop_back ();
}
void clear () {
- _change.removed.insert (_val.begin(), _val.end());
+ for (typename Container::iterator i = _val.begin(); i != _val.end(); ++i) {
+ _changes.remove (*i);
+ }
_val.clear ();
}
-
- typename Container::size_type size() const {
+
+ typename Container::size_type size() const {
return _val.size();
}
- bool empty() const {
+ bool empty() const {
return _val.empty();
}
Container& operator= (const Container& other) {
- _change.removed.insert (_val.begin(), _val.end());
- _change.added.insert (other.begin(), other.end());
+ for (typename Container::const_iterator i = _val.begin(); i != _val.end(); ++i) {
+ _changes.remove (*i);
+ }
+ for (typename Container::const_iterator i = other.begin(); i != other.end(); ++i) {
+ _changes.add (*i);
+ }
return _val = other;
}
- typename Container::reference front() {
+ typename Container::reference front() {
return _val.front ();
}
- typename Container::const_reference front() const {
+ typename Container::const_reference front() const {
return _val.front ();
}
- typename Container::reference back() {
+ typename Container::reference back() {
return _val.back ();
}
- typename Container::const_reference back() const {
+ typename Container::const_reference back() const {
return _val.back ();
}
- void sort() {
+ void sort() {
_val.sort ();
}
template<class BinaryPredicate> void sort(BinaryPredicate comp) {
_val.sort (comp);
}
-
- const ChangeRecord& change() const { return _change; }
- /* for use in building up a SequenceProperty from a serialized
- version on disk.
- */
+ const ChangeRecord& changes () const { return _changes; }
- void record_addition (typename Container::value_type v) {
- _change.added.insert (v);
- }
- void record_removal (typename Container::value_type v) {
- _change.added.erase (v);
- }
+protected:
- protected:
- Container _val;
- ChangeRecord _change;
- boost::function<void(const ChangeRecord&)> _update_callback;
+ /* copy construction only by subclasses */
+ SequenceProperty (SequenceProperty<Container> const & p)
+ : PropertyBase (p)
+ , _val (p._val)
+ , _changes (p._changes)
+ , _update_callback (p._update_callback)
+ {}
- /** Load serialized change history.
- * @return true if loading succeeded, false otherwise
- */
-
- bool load_history_state (const XMLNode& 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);
- }
- }
- }
+ 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;
- return true;
- }
+private:
+ virtual SequenceProperty<Container>* create () const = 0;
};
}