aab6a57ea1744504ad6b8c939be8a98ca571db87
[ardour.git] / libs / pbd / pbd / sequence_property.h
1 /*
2     Copyright (C) 2010 Paul Davis
3
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.
8
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.
13
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.
17
18 */
19
20 #ifndef __libpbd_sequence_property_h__
21 #define __libpbd_sequence_property_h__
22
23 #include <iostream>
24
25 #include <set>
26 #include <list>
27
28 #include <boost/function.hpp>
29
30 #include "pbd/convert.h"
31 #include "pbd/id.h"
32 #include "pbd/property_basics.h"
33 #include "pbd/property_list.h"
34
35 namespace PBD {
36
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
41  *  any change.
42  */
43 template<typename Container>
44 class SequenceProperty : public PropertyBase
45 {
46   public:
47         typedef std::set<typename Container::value_type> ChangeContainer;
48
49         /** A record of changes made */
50         struct ChangeRecord {
51
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
57                                 */
58                                 removed.erase (r);
59                         } else {
60                                 added.insert (r);
61                         }
62                 }
63
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
69                                 */
70                                 added.erase (i);
71                         } else {
72                                 removed.insert (r);
73                         }
74                 }
75
76                 ChangeContainer added;
77                 ChangeContainer removed;
78         };
79
80         SequenceProperty (PropertyID id, const boost::function<void(const ChangeRecord&)>& update)
81                 : PropertyBase (id), _update_callback (update) {}
82         
83         virtual typename Container::value_type lookup_id (const PBD::ID&) = 0;
84
85         void invert_changes () {
86
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.
92                 */
93
94                 _change.removed.swap (_change.added);
95         }
96
97         void add_history_state (XMLNode* history_node) const {
98
99                 /* We could record the whole of the current state here, but its
100                    obviously more efficient just to record what has changed.
101                 */
102                 
103                 XMLNode* child = new XMLNode (PBD::capitalize (property_name()));
104                 history_node->add_child_nocopy (*child);
105                 
106                 /* record the change described in our change member */
107
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());
113                         }
114                 }
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());
120                         }
121                 }
122         }
123
124         bool set_state_from_owner_state (XMLNode const& owner_state) {
125                 assert (false);
126                 return false;
127         }
128
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 ());
132                 } 
133         }
134
135         bool changed () const {
136                 return !_change.added.empty() || !_change.removed.empty();
137         }
138         
139         void clear_history () {
140                 _change.added.clear ();
141                 _change.removed.clear ();
142         }
143
144         void set_state_from_property (PropertyBase const * p) {
145                 const ChangeRecord& change (dynamic_cast<const SequenceProperty*> (p)->change ());
146                 update (change);
147         }
148
149         /** Given a record of changes to this property, pass it to a callback that will
150          *  update the property in some appropriate way. 
151          *
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.
155          */
156         void update (const ChangeRecord& cr) {
157                 _update_callback (cr);
158         }
159
160         void diff (PBD::PropertyList& undo, PBD::PropertyList& redo, Command* cmd) const {
161                 if (changed ()) {
162                         /* list of the removed/added items since clear_history() was last called */
163                         SequenceProperty<Container>* a = copy_for_history ();
164
165                         /* the same list, but with removed/added lists swapped (for undo purposes) */
166                         SequenceProperty<Container>* b = copy_for_history ();
167                         b->invert_changes ();
168
169                         if (cmd) {
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
173                                    with this diff().
174                                 */
175                         
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));
178                                 }
179                         }
180                         
181                         undo.add (b);
182                         redo.add (a);
183                 }
184         }
185
186         Container rlist() { return _val; }
187         
188         /* Wrap salient methods of Sequence
189          */
190
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(); }
195
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(); }
200
201         typename Container::iterator insert (typename Container::iterator i, const typename Container::value_type& v) {
202                 _change.add (v);
203                 return _val.insert (i, v);
204         }
205
206         typename Container::iterator erase (typename Container::iterator i) {
207                 if (i != _val.end()) {
208                         _change.remove (*i);
209                 }
210                 return _val.erase (i);
211         }
212
213         typename Container::iterator erase (typename Container::iterator f, typename Container::iterator l) {
214                 for (typename Container::const_iterator i = f; i != l; ++i) {
215                         _change.remove (*i);
216                 }
217                 return _val.erase (f, l);
218         }
219
220         void push_back (const typename Container::value_type& v) {
221                 _change.add (v);
222                 _val.push_back (v);
223         }
224
225         void push_front (const typename Container::value_type& v) {
226                 _change.add (v);
227                 _val.push_front (v);
228         }
229
230         void pop_front () {
231                 if (!_val.empty()) {
232                         _change.remove (front());
233                 }
234                 _val.pop_front ();
235         }
236
237         void pop_back () {
238                 if (!_val.empty()) {
239                         _change.remove (back());
240                 }
241                 _val.pop_back ();
242         }
243
244         void clear () {
245                 for (typename Container::iterator i = _val.begin(); i != _val.end(); ++i) {
246                         _change.remove (*i);
247                 }
248                 _val.clear ();
249         }
250         
251         typename Container::size_type size() const { 
252                 return _val.size();
253         }
254
255         bool empty() const { 
256                 return _val.empty();
257         }
258
259         Container& operator= (const Container& other) {
260                 for (typename Container::iterator i = _val.begin(); i != _val.end(); ++i) {
261                         _change.remove (*i);
262                 }
263                 for (typename Container::iterator i = other.begin(); i != other.end(); ++i) {
264                         _change.add (*i);
265                 }
266                 return _val = other;
267         }
268
269         typename Container::reference front() { 
270                 return _val.front ();
271         }
272
273         typename Container::const_reference front() const { 
274                 return _val.front ();
275         }
276
277         typename Container::reference back() { 
278                 return _val.back ();
279         }
280
281         typename Container::const_reference back() const { 
282                 return _val.back ();
283         }
284
285         void sort() { 
286                 _val.sort ();
287         }
288
289         template<class BinaryPredicate> void sort(BinaryPredicate comp) {
290                 _val.sort (comp);
291         }
292         
293         const ChangeRecord& change() const { return _change; }
294
295   protected:
296         Container _val;
297         ChangeRecord _change;
298         boost::function<void(const ChangeRecord&)> _update_callback;
299
300         /** Load serialized change history.
301          * @return true if loading succeeded, false otherwise
302          */
303
304         bool load_history_state (const XMLNode& history_node) {
305
306                 const XMLNodeList& children (history_node.children());
307
308                 for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
309                         const XMLProperty* prop = (*i)->property ("id");
310                         if (prop) {
311                                 PBD::ID id (prop->value());
312                                 typename Container::value_type v = lookup_id (id);
313                                 if (!v) {
314                                         std::cerr << "No such item, ID = " << id.to_s() << " (from " << prop->value() << ")\n";
315                                         return false;
316                                 }
317                                 if ((*i)->name() == "Add") {
318                                         _change.added.insert (v);
319                                 } else if ((*i)->name() == "Remove") {
320                                         _change.removed.insert (v);
321                                 }
322                         }
323                 }
324
325                 return true;
326         }
327
328 private:
329         virtual SequenceProperty<Container>* create () const = 0;
330
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.
335         */
336         
337         SequenceProperty<Container>* copy_for_history () const {
338                 SequenceProperty<Container>* copy = create ();
339                 /* this is all we need */
340                 copy->_change = _change;
341                 return copy;
342         }
343 };
344
345 }
346
347 #endif /* __libpbd_sequence_property_h__ */