2 Copyright (C) 2010 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 #ifndef __libpbd_sequence_property_h__
21 #define __libpbd_sequence_property_h__
28 #include <boost/function.hpp>
30 #include "pbd/convert.h"
32 #include "pbd/property_basics.h"
36 /** A base class for properties whose state is a container of other
37 * things. Its behaviour is `specialised' for this purpose in that
38 * it holds state changes as additions to and removals from the
39 * container, which is more efficient than storing entire state after
42 template<typename Container>
43 class SequenceProperty : public PropertyBase
46 typedef std::set<typename Container::value_type> ChangeContainer;
48 /** A record of changes made */
50 ChangeContainer added;
51 ChangeContainer removed;
54 SequenceProperty (PropertyID id, const boost::function<void(const ChangeRecord&)>& update)
55 : PropertyBase (id), _update_callback (update) {}
57 virtual typename Container::value_type lookup_id (const PBD::ID&) = 0;
59 void invert_changes () {
61 /* reverse the adds/removes so that this property's change member
62 correctly describes how to undo the changes it currently
63 reflects. A derived instance of this type of property will
64 create a diff() pair by copying the property twice, and
65 calling this method on the "before" item of the pair.
68 _change.removed.swap (_change.added);
71 void add_history_state (XMLNode* history_node) const {
73 /* We could record the whole of the current state here, but its
74 obviously more efficient just to record what has changed.
77 XMLNode* child = new XMLNode (PBD::capitalize (property_name()));
78 history_node->add_child_nocopy (*child);
80 /* record the change described in our change member */
82 if (!_change.added.empty()) {
83 for (typename ChangeContainer::iterator i = _change.added.begin(); i != _change.added.end(); ++i) {
84 XMLNode* add_node = new XMLNode ("Add");
85 child->add_child_nocopy (*add_node);
86 add_node->add_property ("id", (*i)->id().to_s());
89 if (!_change.removed.empty()) {
90 for (typename ChangeContainer::iterator i = _change.removed.begin(); i != _change.removed.end(); ++i) {
91 XMLNode* remove_node = new XMLNode ("Remove");
92 child->add_child_nocopy (*remove_node);
93 remove_node->add_property ("id", (*i)->id().to_s());
98 bool set_state_from_owner_state (XMLNode const& owner_state) {
100 XMLProperty const* n = owner_state.property ("name");
106 assert (g_quark_from_string (n->value().c_str()) == property_id());
108 const XMLNodeList& children = owner_state.children();
110 for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) {
112 if ((*c)->name() == "Added") {
113 const XMLNodeList& grandchildren = (*c)->children();
114 for (XMLNodeList::const_iterator gc = grandchildren.begin(); gc != grandchildren.end(); ++gc) {
115 const XMLProperty* prop = (*gc)->property ("id");
117 typename Container::value_type v = lookup_id (PBD::ID (prop->value()));
119 _change.added.insert (v);
123 } else if ((*c)->name() == "Removed") {
124 const XMLNodeList& grandchildren = (*c)->children();
125 for (XMLNodeList::const_iterator gc = grandchildren.begin(); gc != grandchildren.end(); ++gc) {
126 const XMLProperty* prop = (*gc)->property ("id");
128 typename Container::value_type v = lookup_id (PBD::ID (prop->value()));
130 _change.removed.insert (v);
140 void add_state_to_owner_state (XMLNode& owner_state_node) const {
141 for (typename Container::const_iterator i = _val.begin(); i != _val.end(); ++i) {
142 owner_state_node.add_child_nocopy ((*i)->get_state ());
146 bool changed () const {
147 return !_change.added.empty() || !_change.removed.empty();
150 void clear_history () {
151 _change.added.clear ();
152 _change.removed.clear ();
155 void set_state_from_property (PropertyBase const * p) {
156 const ChangeRecord& change (dynamic_cast<const SequenceProperty*> (p)->change ());
160 /** Given a record of changes to this property, pass it to a callback that will
161 * update the property in some appropriate way.
163 * This exists because simply using std::sequence methods to add/remove items
164 * from the property is far too simplistic - the semantics of add/remove may
165 * be much more complex than that.
167 void update (const ChangeRecord& cr) {
168 _update_callback (cr);
171 /* Wrap salient methods of Sequence
174 typename Container::iterator begin() { return _val.begin(); }
175 typename Container::iterator end() { return _val.end(); }
176 typename Container::const_iterator begin() const { return _val.begin(); }
177 typename Container::const_iterator end() const { return _val.end(); }
179 typename Container::reverse_iterator rbegin() { return _val.rbegin(); }
180 typename Container::reverse_iterator rend() { return _val.rend(); }
181 typename Container::const_reverse_iterator rbegin() const { return _val.rbegin(); }
182 typename Container::const_reverse_iterator rend() const { return _val.rend(); }
184 typename Container::iterator insert (typename Container::iterator i, const typename Container::value_type& v) {
185 _change.added.insert (v);
186 return _val.insert (i, v);
189 typename Container::iterator erase (typename Container::iterator i) {
190 if (i != _val.end()) {
191 _change.removed.insert (*i);
193 return _val.erase (i);
196 typename Container::iterator erase (typename Container::iterator f, typename Container::iterator l) {
197 for (typename Container::const_iterator i = f; i != l; ++i) {
198 _change.removed.insert(*i);
200 return _val.erase (f, l);
203 void push_back (const typename Container::value_type& v) {
204 _change.added.insert (v);
208 void push_front (const typename Container::value_type& v) {
209 _change.added.insert (v);
215 _change.removed.insert (front());
222 _change.removed.insert (front());
228 _change.removed.insert (_val.begin(), _val.end());
232 typename Container::size_type size() const {
240 Container& operator= (const Container& other) {
241 _change.removed.insert (_val.begin(), _val.end());
242 _change.added.insert (other.begin(), other.end());
246 typename Container::reference front() {
247 return _val.front ();
250 typename Container::const_reference front() const {
251 return _val.front ();
254 typename Container::reference back() {
258 typename Container::const_reference back() const {
266 template<class BinaryPredicate> void sort(BinaryPredicate comp) {
270 const ChangeRecord& change() const { return _change; }
272 /* for use in building up a SequenceProperty from a serialized
276 void record_addition (typename Container::value_type v) {
277 _change.added.insert (v);
279 void record_removal (typename Container::value_type v) {
280 _change.added.erase (v);
285 ChangeRecord _change;
286 boost::function<void(const ChangeRecord&)> _update_callback;
288 /** Load serialized change history.
289 * @return true if loading succeeded, false otherwise
292 bool load_history_state (const XMLNode& history_node) {
294 const XMLNodeList& children (history_node.children());
296 for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
297 const XMLProperty* prop = (*i)->property ("id");
299 PBD::ID id (prop->value());
300 typename Container::value_type v = lookup_id (id);
302 std::cerr << "No such item, ID = " << id.to_s() << " (from " << prop->value() << ")\n";
305 if ((*i)->name() == "Add") {
306 _change.added.insert (v);
307 } else if ((*i)->name() == "Remove") {
308 _change.removed.insert (v);
319 #endif /* __libpbd_sequence_property_h__ */