/* 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 #include #include #include #include "pbd/convert.h" #include "pbd/id.h" #include "pbd/property_basics.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 class SequenceProperty : public PropertyBase { public: typedef std::set ChangeContainer; /** A record of changes made */ struct ChangeRecord { ChangeContainer added; ChangeContainer removed; }; SequenceProperty (PropertyID id, const boost::function& 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 add_history_state (XMLNode* history_node) const { /* We could record the whole of the current state here, but its obviously more efficient just to record what has changed. */ 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 ("Add"); child->add_child_nocopy (*add_node); add_node->add_property ("id", (*i)->id().to_s()); } } if (!_change.removed.empty()) { for (typename ChangeContainer::iterator i = _change.removed.begin(); i != _change.removed.end(); ++i) { XMLNode* remove_node = new XMLNode ("Remove"); child->add_child_nocopy (*remove_node); remove_node->add_property ("id", (*i)->id().to_s()); } } } bool set_state_from_owner_state (XMLNode const& owner_state) { XMLProperty const* n = owner_state.property ("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() == "Added") { const XMLNodeList& grandchildren = (*c)->children(); for (XMLNodeList::const_iterator gc = grandchildren.begin(); gc != grandchildren.end(); ++gc) { const XMLProperty* prop = (*gc)->property ("id"); if (prop) { typename Container::value_type v = lookup_id (PBD::ID (prop->value())); if (v) { _change.added.insert (v); } } } } else if ((*c)->name() == "Removed") { const XMLNodeList& grandchildren = (*c)->children(); for (XMLNodeList::const_iterator gc = grandchildren.begin(); gc != grandchildren.end(); ++gc) { const XMLProperty* prop = (*gc)->property ("id"); if (prop) { typename Container::value_type v = lookup_id (PBD::ID (prop->value())); if (v) { _change.removed.insert (v); } } } } } return true; } void add_state_to_owner_state (XMLNode& owner_state_node) const { for (typename Container::const_iterator i = _val.begin(); i != _val.end(); ++i) { owner_state_node.add_child_nocopy ((*i)->get_state ()); } } bool changed () const { return !_change.added.empty() || !_change.removed.empty(); } void clear_history () { _change.added.clear (); _change.removed.clear (); } void set_state_from_property (PropertyBase const * p) { const ChangeRecord& change (dynamic_cast (p)->change ()); update (change); } /** Given a record of changes to this property, pass it to a callback that will * 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 * be much more complex than that. */ void update (const ChangeRecord& cr) { _update_callback (cr); } /* Wrap salient methods of Sequence */ typename Container::iterator begin() { return _val.begin(); } typename Container::iterator end() { return _val.end(); } typename Container::const_iterator begin() const { return _val.begin(); } typename Container::const_iterator end() const { return _val.end(); } typename Container::reverse_iterator rbegin() { return _val.rbegin(); } typename Container::reverse_iterator rend() { return _val.rend(); } typename Container::const_reverse_iterator rbegin() const { return _val.rbegin(); } 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); return _val.insert (i, v); } typename Container::iterator erase (typename Container::iterator i) { if (i != _val.end()) { _change.removed.insert (*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); } return _val.erase (f, l); } void push_back (const typename Container::value_type& v) { _change.added.insert (v); _val.push_back (v); } void push_front (const typename Container::value_type& v) { _change.added.insert (v); _val.push_front (v); } void pop_front () { if (!_val.empty()) { _change.removed.insert (front()); } _val.pop_front (); } void pop_back () { if (!_val.empty()) { _change.removed.insert (front()); } _val.pop_back (); } void clear () { _change.removed.insert (_val.begin(), _val.end()); _val.clear (); } typename Container::size_type size() const { return _val.size(); } 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()); return _val = other; } typename Container::reference front() { return _val.front (); } typename Container::const_reference front() const { return _val.front (); } typename Container::reference back() { return _val.back (); } typename Container::const_reference back() const { return _val.back (); } void sort() { _val.sort (); } template 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. */ 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: Container _val; ChangeRecord _change; boost::function _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); } } } return true; } }; } #endif /* __libpbd_sequence_property_h__ */