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;
85 void invert_changes () {
87 /* reverse the adds/removes so that this property's change member
88 correctly describes how to undo the changes it currently
89 reflects. A derived instance of this type of property will
90 create a diff() pair by copying the property twice, and
91 calling this method on the "before" item of the pair.
94 _change.removed.swap (_change.added);
97 void add_history_state (XMLNode* history_node) const {
99 /* We could record the whole of the current state here, but its
100 obviously more efficient just to record what has changed.
103 XMLNode* child = new XMLNode (PBD::capitalize (property_name()));
104 history_node->add_child_nocopy (*child);
106 /* record the change described in our change member */
108 if (!_change.added.empty()) {
109 for (typename ChangeContainer::iterator i = _change.added.begin(); i != _change.added.end(); ++i) {
110 XMLNode* add_node = new XMLNode ("Add");
111 child->add_child_nocopy (*add_node);
112 add_node->add_property ("id", (*i)->id().to_s());
115 if (!_change.removed.empty()) {
116 for (typename ChangeContainer::iterator i = _change.removed.begin(); i != _change.removed.end(); ++i) {
117 XMLNode* remove_node = new XMLNode ("Remove");
118 child->add_child_nocopy (*remove_node);
119 remove_node->add_property ("id", (*i)->id().to_s());
124 bool set_state_from_owner_state (XMLNode const& owner_state) {
129 void add_state_to_owner_state (XMLNode& owner_state_node) const {
130 for (typename Container::const_iterator i = _val.begin(); i != _val.end(); ++i) {
131 owner_state_node.add_child_nocopy ((*i)->get_state ());
135 bool changed () const {
136 return !_change.added.empty() || !_change.removed.empty();
139 void clear_history () {
140 _change.added.clear ();
141 _change.removed.clear ();
144 void set_state_from_property (PropertyBase const * p) {
145 const ChangeRecord& change (dynamic_cast<const SequenceProperty*> (p)->change ());
149 /** Given a record of changes to this property, pass it to a callback that will
150 * update the property in some appropriate way.
152 * This exists because simply using std::sequence methods to add/remove items
153 * from the property is far too simplistic - the semantics of add/remove may
154 * be much more complex than that.
156 void update (const ChangeRecord& cr) {
157 _update_callback (cr);
160 void diff (PBD::PropertyList& undo, PBD::PropertyList& redo, Command* cmd) const {
162 /* list of the removed/added items since clear_history() was last called */
163 SequenceProperty<Container>* a = copy_for_history ();
165 /* the same list, but with removed/added lists swapped (for undo purposes) */
166 SequenceProperty<Container>* b = copy_for_history ();
167 b->invert_changes ();
170 /* whenever one of the items emits DropReferences, make sure
171 that the Destructible we've been told to notify hears about
172 it. the Destructible is likely to be the Command being built
176 for (typename ChangeContainer::iterator i = a->change().added.begin(); i != a->change().added.end(); ++i) {
177 (*i)->DropReferences.connect_same_thread (*cmd, boost::bind (&Destructible::drop_references, cmd));
186 Container rlist() { return _val; }
188 /* Wrap salient methods of Sequence
191 typename Container::iterator begin() { return _val.begin(); }
192 typename Container::iterator end() { return _val.end(); }
193 typename Container::const_iterator begin() const { return _val.begin(); }
194 typename Container::const_iterator end() const { return _val.end(); }
196 typename Container::reverse_iterator rbegin() { return _val.rbegin(); }
197 typename Container::reverse_iterator rend() { return _val.rend(); }
198 typename Container::const_reverse_iterator rbegin() const { return _val.rbegin(); }
199 typename Container::const_reverse_iterator rend() const { return _val.rend(); }
201 typename Container::iterator insert (typename Container::iterator i, const typename Container::value_type& v) {
203 return _val.insert (i, v);
206 typename Container::iterator erase (typename Container::iterator i) {
207 if (i != _val.end()) {
210 return _val.erase (i);
213 typename Container::iterator erase (typename Container::iterator f, typename Container::iterator l) {
214 for (typename Container::const_iterator i = f; i != l; ++i) {
217 return _val.erase (f, l);
220 void push_back (const typename Container::value_type& v) {
225 void push_front (const typename Container::value_type& v) {
232 _change.remove (front());
239 _change.remove (back());
245 for (typename Container::iterator i = _val.begin(); i != _val.end(); ++i) {
251 typename Container::size_type size() const {
259 Container& operator= (const Container& other) {
260 for (typename Container::iterator i = _val.begin(); i != _val.end(); ++i) {
263 for (typename Container::iterator i = other.begin(); i != other.end(); ++i) {
269 typename Container::reference front() {
270 return _val.front ();
273 typename Container::const_reference front() const {
274 return _val.front ();
277 typename Container::reference back() {
281 typename Container::const_reference back() const {
289 template<class BinaryPredicate> void sort(BinaryPredicate comp) {
293 const ChangeRecord& change() const { return _change; }
297 ChangeRecord _change;
298 boost::function<void(const ChangeRecord&)> _update_callback;
300 /** Load serialized change history.
301 * @return true if loading succeeded, false otherwise
304 bool load_history_state (const XMLNode& history_node) {
306 const XMLNodeList& children (history_node.children());
308 for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
309 const XMLProperty* prop = (*i)->property ("id");
311 PBD::ID id (prop->value());
312 typename Container::value_type v = lookup_id (id);
314 std::cerr << "No such item, ID = " << id.to_s() << " (from " << prop->value() << ")\n";
317 if ((*i)->name() == "Add") {
318 _change.added.insert (v);
319 } else if ((*i)->name() == "Remove") {
320 _change.removed.insert (v);
329 virtual SequenceProperty<Container>* create () const = 0;
331 /* create a copy of this ListSequenceProperty that only
332 has what is needed for use in a history list command. This
333 means that it won't contain the actual item list but
334 will have the added/removed list.
337 SequenceProperty<Container>* copy_for_history () const {
338 SequenceProperty<Container>* copy = create ();
339 /* this is all we need */
340 copy->_change = _change;
347 #endif /* __libpbd_sequence_property_h__ */