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"
33 #include "pbd/property_list.h"
37 /** A base class for properties whose state is a container of other
38 * things. Its behaviour is `specialised' for this purpose in that
39 * it holds state changes as additions to and removals from the
40 * container, which is more efficient than storing entire state after
43 template<typename Container>
44 class SequenceProperty : public PropertyBase
47 typedef std::set<typename Container::value_type> ChangeContainer;
49 /** A record of changes made */
52 void add (typename Container::value_type const & r) {
53 typename ChangeContainer::iterator i = removed.find (r);
54 if (i != removed.end()) {
55 /* we're adding, and this thing has already been marked as removed, so
56 just remove it from the removed list
64 void remove (typename Container::value_type const & r) {
65 typename ChangeContainer::iterator i = added.find (r);
66 if (i != added.end()) {
67 /* we're removing, and this thing has already been marked as added, so
68 just remove it from the added list
76 ChangeContainer added;
77 ChangeContainer removed;
80 SequenceProperty (PropertyID id, const boost::function<void(const ChangeRecord&)>& update)
81 : PropertyBase (id), _update_callback (update) {}
83 virtual typename Container::value_type lookup_id (const PBD::ID&) = 0;
86 _changes.removed.swap (_changes.added);
89 void get_changes_as_xml (XMLNode* history_node) const {
91 XMLNode* child = new XMLNode (PBD::capitalize (property_name()));
92 history_node->add_child_nocopy (*child);
94 /* record the change described in our change member */
96 if (!_changes.added.empty()) {
97 for (typename ChangeContainer::iterator i = _changes.added.begin(); i != _changes.added.end(); ++i) {
98 XMLNode* add_node = new XMLNode ("Add");
99 child->add_child_nocopy (*add_node);
100 add_node->add_property ("id", (*i)->id().to_s());
103 if (!_changes.removed.empty()) {
104 for (typename ChangeContainer::iterator i = _changes.removed.begin(); i != _changes.removed.end(); ++i) {
105 XMLNode* remove_node = new XMLNode ("Remove");
106 child->add_child_nocopy (*remove_node);
107 remove_node->add_property ("id", (*i)->id().to_s());
112 bool set_value (XMLNode const &) {
113 /* XXX: not used, but probably should be */
118 void get_value (XMLNode & node) const {
119 for (typename Container::const_iterator i = _val.begin(); i != _val.end(); ++i) {
120 node.add_child_nocopy ((*i)->get_state ());
124 bool changed () const {
125 return !_changes.added.empty() || !_changes.removed.empty();
128 void clear_history () {
129 _changes.added.clear ();
130 _changes.removed.clear ();
133 void apply_changes (PropertyBase const * p) {
134 const ChangeRecord& change (dynamic_cast<const SequenceProperty*> (p)->changes ());
138 /** Given a record of changes to this property, pass it to a callback that will
139 * update the property in some appropriate way.
141 * This exists because simply using std::sequence methods to add/remove items
142 * from the property is far too simplistic - the semantics of add/remove may
143 * be much more complex than that.
145 void update (const ChangeRecord& cr) {
146 _update_callback (cr);
149 void get_changes_as_properties (PBD::PropertyList& changes, Command* cmd) const {
151 SequenceProperty<Container>* a = copy_for_history ();
155 /* whenever one of the items emits DropReferences, make sure
156 that the Destructible we've been told to notify hears about
157 it. the Destructible is likely to be the Command being built
161 for (typename ChangeContainer::iterator i = a->changes().added.begin(); i != a->changes().added.end(); ++i) {
162 (*i)->DropReferences.connect_same_thread (*cmd, boost::bind (&Destructible::drop_references, cmd));
168 SequenceProperty<Container>* maybe_clone_self_if_found_in_history_node (XMLNode const & node) const {
170 XMLNodeList const children = node.children ();
172 for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
174 if ((*i)->name() == capitalize (property_name())) {
176 SequenceProperty<Container>* p = create ();
178 if (p->set_changes (**i)) {
189 void clear_owned_history () {
190 for (typename Container::iterator i = begin(); i != end(); ++i) {
191 (*i)->clear_history ();
195 void rdiff (std::vector<StatefulDiffCommand*>& cmds) const {
196 for (typename Container::const_iterator i = begin(); i != end(); ++i) {
197 if ((*i)->changed ()) {
198 StatefulDiffCommand* sdc = new StatefulDiffCommand (*i);
199 cmds.push_back (sdc);
204 Container rlist() { return _val; }
206 /* Wrap salient methods of Sequence
209 typename Container::iterator begin() { return _val.begin(); }
210 typename Container::iterator end() { return _val.end(); }
211 typename Container::const_iterator begin() const { return _val.begin(); }
212 typename Container::const_iterator end() const { return _val.end(); }
214 typename Container::reverse_iterator rbegin() { return _val.rbegin(); }
215 typename Container::reverse_iterator rend() { return _val.rend(); }
216 typename Container::const_reverse_iterator rbegin() const { return _val.rbegin(); }
217 typename Container::const_reverse_iterator rend() const { return _val.rend(); }
219 typename Container::iterator insert (typename Container::iterator i, const typename Container::value_type& v) {
221 return _val.insert (i, v);
224 typename Container::iterator erase (typename Container::iterator i) {
225 if (i != _val.end()) {
226 _changes.remove (*i);
228 return _val.erase (i);
231 typename Container::iterator erase (typename Container::iterator f, typename Container::iterator l) {
232 for (typename Container::const_iterator i = f; i != l; ++i) {
233 _changes.remove (*i);
235 return _val.erase (f, l);
238 void push_back (const typename Container::value_type& v) {
243 void push_front (const typename Container::value_type& v) {
250 _changes.remove (front());
257 _changes.remove (back());
263 for (typename Container::iterator i = _val.begin(); i != _val.end(); ++i) {
264 _changes.remove (*i);
269 typename Container::size_type size() const {
277 Container& operator= (const Container& other) {
278 for (typename Container::iterator i = _val.begin(); i != _val.end(); ++i) {
279 _changes.remove (*i);
281 for (typename Container::iterator i = other.begin(); i != other.end(); ++i) {
287 typename Container::reference front() {
288 return _val.front ();
291 typename Container::const_reference front() const {
292 return _val.front ();
295 typename Container::reference back() {
299 typename Container::const_reference back() const {
307 template<class BinaryPredicate> void sort(BinaryPredicate comp) {
311 const ChangeRecord& changes () const { return _changes; }
315 ChangeRecord _changes;
316 boost::function<void(const ChangeRecord&)> _update_callback;
318 /** Load serialized change history.
319 * @return true if loading succeeded, false otherwise
322 bool set_changes (XMLNode const & history_node) {
324 const XMLNodeList& children (history_node.children());
326 for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
327 const XMLProperty* prop = (*i)->property ("id");
329 PBD::ID id (prop->value());
330 typename Container::value_type v = lookup_id (id);
332 std::cerr << "No such item, ID = " << id.to_s() << " (from " << prop->value() << ")\n";
335 if ((*i)->name() == "Add") {
336 _changes.added.insert (v);
337 } else if ((*i)->name() == "Remove") {
338 _changes.removed.insert (v);
347 virtual SequenceProperty<Container>* create () const = 0;
349 /* create a copy of this ListSequenceProperty that only
350 has what is needed for use in a history list command. This
351 means that it won't contain the actual item list but
352 will have the added/removed list.
355 SequenceProperty<Container>* copy_for_history () const {
356 SequenceProperty<Container>* copy = create ();
357 /* this is all we need */
358 copy->_changes = _changes;
365 #endif /* __libpbd_sequence_property_h__ */