d9ef6255d25a766c38edad5a8edb3d5451405c9e
[ardour.git] / libs / pbd / pbd / sequence_property.h
1 #ifndef __libpbd_sequence_property_h__
2 #define __libpbd_sequence_property_h__
3
4 #include <iostream>
5
6 #include <set>
7 #include <list>
8
9 #include <boost/function.hpp>
10
11 #include "pbd/convert.h"
12 #include "pbd/id.h"
13 #include "pbd/property_basics.h"
14
15 #include "i18n.h"
16
17 namespace PBD {
18 template<typename Container>
19 class SequenceProperty : public PropertyBase
20 {
21   public:
22         typedef std::set<typename Container::value_type> ChangeContainer;
23         
24         struct ChangeRecord { 
25             ChangeContainer added;
26             ChangeContainer removed;
27         };
28
29         SequenceProperty (PropertyID id, const boost::function<void(const ChangeRecord&)>& update)
30                 : PropertyBase (id), _update_callback (update) {}
31         
32         virtual typename Container::value_type lookup_id (const PBD::ID&) = 0;
33
34         void invert_changes () {
35
36                 /* reverse the adds/removes so that this property's change member
37                    correctly describes how to undo the changes it currently
38                    reflects. A derived instance of this type of property will
39                    create a pdiff() pair by copying the property twice, and
40                    calling this method on the "before" item of the pair.
41                 */
42
43                 _change.removed.swap (_change.added);
44         }
45
46         void add_history_state (XMLNode* history_node) const {
47                 
48                 XMLNode* child = new XMLNode (PBD::capitalize (property_name()));
49                 history_node->add_child_nocopy (*child);
50                 
51                 /* record the change described in our change member */
52
53                 if (!_change.added.empty()) {
54                         for (typename ChangeContainer::iterator i = _change.added.begin(); i != _change.added.end(); ++i) {
55                                 XMLNode* add_node = new XMLNode (X_("Add"));
56                                 child->add_child_nocopy (*add_node);
57                                 add_node->add_property (X_("id"), (*i)->id().to_s());
58                         }
59                 }
60                 if (!_change.removed.empty()) {
61                         for (typename ChangeContainer::iterator i = _change.removed.begin(); i != _change.removed.end(); ++i) {
62                                 XMLNode* remove_node = new XMLNode (X_("Remove"));
63                                 child->add_child_nocopy (*remove_node);
64                                 remove_node->add_property (X_("id"), (*i)->id().to_s());
65                         }
66                 }
67         }
68
69         bool set_state_from_owner_state (XMLNode const& owner_state) {
70
71                 XMLProperty const* n = owner_state.property (X_("name"));
72
73                 if (!n) {
74                         return false;
75                 }
76
77                 assert (g_quark_from_string (n->value().c_str()) == property_id());
78
79                 const XMLNodeList& children = owner_state.children();
80
81                 for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) {
82
83                         if ((*c)->name() == X_("Added")) {
84                                 const XMLNodeList& grandchildren = (*c)->children();
85                                 for (XMLNodeList::const_iterator gc = grandchildren.begin(); gc != grandchildren.end(); ++gc) {
86                                         const XMLProperty* prop = (*gc)->property (X_("id"));
87                                         if (prop) {
88                                                 typename Container::value_type v = lookup_id (PBD::ID (prop->value()));
89                                                 if (v) {
90                                                         _change.added.insert (v);
91                                                 }
92                                         }
93                                 }
94                         } else if ((*c)->name() == X_("Removed")) {
95                                 const XMLNodeList& grandchildren = (*c)->children();
96                                 for (XMLNodeList::const_iterator gc = grandchildren.begin(); gc != grandchildren.end(); ++gc) {
97                                         const XMLProperty* prop = (*gc)->property (X_("id"));
98                                         if (prop) {
99                                                 typename Container::value_type v = lookup_id (PBD::ID (prop->value()));
100                                                 if (v) {
101                                                         _change.removed.insert (v);
102                                                 }
103                                         }
104                                 }
105                         }
106                 }
107
108                 return true;
109         }
110
111         void add_state_to_owner_state (XMLNode& owner_state_node) const {
112                 for (typename Container::const_iterator i = _val.begin(); i != _val.end(); ++i) {
113                         owner_state_node.add_child_nocopy ((*i)->get_state ());
114                 } 
115         }
116
117         bool changed () const {
118                 return !_change.added.empty() || !_change.removed.empty();
119         }
120         
121         void clear_history () {
122                 _change.added.clear ();
123                 _change.removed.clear ();
124         }
125
126         void set_state_from_property (PropertyBase const * p) {
127                 const ChangeRecord& change (dynamic_cast<const SequenceProperty*> (p)->change ());
128                 update (change);
129         }
130
131         /** Given a record of changes to this property, pass it to a callback that will
132          *  update the property in some appropriate way. 
133          *
134          *  This exists because simply using std::sequence methods to add/remove items
135          *  from the property is far too simplistic - the semantics of add/remove may
136          *  be much more complex than that.
137          */
138         void update (const ChangeRecord& cr) {
139                 _update_callback (cr);
140         }
141
142         /* Wrap salient methods of Sequence
143          */
144
145         typename Container::iterator begin() { return _val.begin(); }
146         typename Container::iterator end() { return _val.end(); }
147         typename Container::const_iterator begin() const { return _val.begin(); }
148         typename Container::const_iterator end() const { return _val.end(); }
149
150         typename Container::reverse_iterator rbegin() { return _val.rbegin(); }
151         typename Container::reverse_iterator rend() { return _val.rend(); }
152         typename Container::const_reverse_iterator rbegin() const { return _val.rbegin(); }
153         typename Container::const_reverse_iterator rend() const { return _val.rend(); }
154
155         typename Container::iterator insert (typename Container::iterator i, const typename Container::value_type& v) {
156                 _change.added.insert (v);
157                 return _val.insert (i, v);
158         }
159
160         typename Container::iterator erase (typename Container::iterator i) {
161                 if (i != _val.end()) {
162                         _change.removed.insert (*i);
163                 }
164                 return _val.erase (i);
165         }
166
167         typename Container::iterator erase (typename Container::iterator f, typename Container::iterator l) {
168                 for (typename Container::const_iterator i = f; i != l; ++i) {
169                         _change.removed.insert(*i);
170                 }
171                 return _val.erase (f, l);
172         }
173
174         void push_back (const typename Container::value_type& v) {
175                 _change.added.insert (v);
176                 _val.push_back (v);
177         }
178
179         void push_front (const typename Container::value_type& v) {
180                 _change.added.insert (v);
181                 _val.push_front (v);
182         }
183
184         void pop_front () {
185                 if (!_val.empty()) {
186                         _change.removed.insert (front());
187                 }
188                 _val.pop_front ();
189         }
190
191         void pop_back () {
192                 if (!_val.empty()) {
193                         _change.removed.insert (front());
194                 }
195                 _val.pop_back ();
196         }
197
198         void clear () {
199                 _change.removed.insert (_val.begin(), _val.end());
200                 _val.clear ();
201         }
202         
203         typename Container::size_type size() const { 
204                 return _val.size();
205         }
206
207         bool empty() const { 
208                 return _val.empty();
209         }
210
211         Container& operator= (const Container& other) {
212                 _change.removed.insert (_val.begin(), _val.end());
213                 _change.added.insert (other.begin(), other.end());
214                 return _val = other;
215         }
216
217         typename Container::reference front() { 
218                 return _val.front ();
219         }
220
221         typename Container::const_reference front() const { 
222                 return _val.front ();
223         }
224
225         typename Container::reference back() { 
226                 return _val.back ();
227         }
228
229         typename Container::const_reference back() const { 
230                 return _val.back ();
231         }
232
233         void sort() { 
234                 _val.sort ();
235         }
236
237         template<class BinaryPredicate> void sort(BinaryPredicate comp) {
238                 _val.sort (comp);
239         }
240         
241         const ChangeRecord& change() const { return _change; }
242
243         /* for use in building up a SequenceProperty from a serialized
244            version on disk.
245         */
246
247         void record_addition (typename Container::value_type v) {
248                 _change.added.insert (v);
249         }
250         void record_removal (typename Container::value_type v) {
251                 _change.added.erase (v);
252         }
253
254   protected:
255         Container _val;
256         ChangeRecord _change;
257         boost::function<void(const ChangeRecord&)> _update_callback;
258
259         /** Load serialized change history.
260          * @return true if loading succeeded, false otherwise
261          */
262
263         bool load_history_state (const XMLNode& history_node) {
264
265                 const XMLNodeList& children (history_node.children());
266
267                 for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
268                         const XMLProperty* prop = (*i)->property ("id");
269                         if (prop) {
270                                 PBD::ID id (prop->value());
271                                 typename Container::value_type v = lookup_id (id);
272                                 if (!v) {
273                                         std::cerr << "No such item, ID = " << id.to_s() << " (from " << prop->value() << ")\n";
274                                         return false;
275                                 }
276                                 if ((*i)->name() == "Add") {
277                                         _change.added.insert (v);
278                                 } else if ((*i)->name() == "Remove") {
279                                         _change.removed.insert (v);
280                                 }
281                         }
282                 }
283
284                 return true;
285         }
286 };
287
288 }
289
290 #endif /* __libpbd_sequence_property_h__ */