Remove use of i18n macros in headers. Prevents our gettext.h being included before...
[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
34 namespace PBD {
35
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
40  *  any change.
41  */
42 template<typename Container>
43 class SequenceProperty : public PropertyBase
44 {
45   public:
46         typedef std::set<typename Container::value_type> ChangeContainer;
47
48         /** A record of changes made */
49         struct ChangeRecord { 
50             ChangeContainer added;
51             ChangeContainer removed;
52         };
53
54         SequenceProperty (PropertyID id, const boost::function<void(const ChangeRecord&)>& update)
55                 : PropertyBase (id), _update_callback (update) {}
56         
57         virtual typename Container::value_type lookup_id (const PBD::ID&) = 0;
58
59         void invert_changes () {
60
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.
66                 */
67
68                 _change.removed.swap (_change.added);
69         }
70
71         void add_history_state (XMLNode* history_node) const {
72
73                 /* We could record the whole of the current state here, but its
74                    obviously more efficient just to record what has changed.
75                 */
76                 
77                 XMLNode* child = new XMLNode (PBD::capitalize (property_name()));
78                 history_node->add_child_nocopy (*child);
79                 
80                 /* record the change described in our change member */
81
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());
87                         }
88                 }
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());
94                         }
95                 }
96         }
97
98         bool set_state_from_owner_state (XMLNode const& owner_state) {
99
100                 XMLProperty const* n = owner_state.property ("name");
101
102                 if (!n) {
103                         return false;
104                 }
105
106                 assert (g_quark_from_string (n->value().c_str()) == property_id());
107
108                 const XMLNodeList& children = owner_state.children();
109
110                 for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) {
111
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");
116                                         if (prop) {
117                                                 typename Container::value_type v = lookup_id (PBD::ID (prop->value()));
118                                                 if (v) {
119                                                         _change.added.insert (v);
120                                                 }
121                                         }
122                                 }
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");
127                                         if (prop) {
128                                                 typename Container::value_type v = lookup_id (PBD::ID (prop->value()));
129                                                 if (v) {
130                                                         _change.removed.insert (v);
131                                                 }
132                                         }
133                                 }
134                         }
135                 }
136
137                 return true;
138         }
139
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 ());
143                 } 
144         }
145
146         bool changed () const {
147                 return !_change.added.empty() || !_change.removed.empty();
148         }
149         
150         void clear_history () {
151                 _change.added.clear ();
152                 _change.removed.clear ();
153         }
154
155         void set_state_from_property (PropertyBase const * p) {
156                 const ChangeRecord& change (dynamic_cast<const SequenceProperty*> (p)->change ());
157                 update (change);
158         }
159
160         /** Given a record of changes to this property, pass it to a callback that will
161          *  update the property in some appropriate way. 
162          *
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.
166          */
167         void update (const ChangeRecord& cr) {
168                 _update_callback (cr);
169         }
170
171         /* Wrap salient methods of Sequence
172          */
173
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(); }
178
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(); }
183
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);
187         }
188
189         typename Container::iterator erase (typename Container::iterator i) {
190                 if (i != _val.end()) {
191                         _change.removed.insert (*i);
192                 }
193                 return _val.erase (i);
194         }
195
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);
199                 }
200                 return _val.erase (f, l);
201         }
202
203         void push_back (const typename Container::value_type& v) {
204                 _change.added.insert (v);
205                 _val.push_back (v);
206         }
207
208         void push_front (const typename Container::value_type& v) {
209                 _change.added.insert (v);
210                 _val.push_front (v);
211         }
212
213         void pop_front () {
214                 if (!_val.empty()) {
215                         _change.removed.insert (front());
216                 }
217                 _val.pop_front ();
218         }
219
220         void pop_back () {
221                 if (!_val.empty()) {
222                         _change.removed.insert (front());
223                 }
224                 _val.pop_back ();
225         }
226
227         void clear () {
228                 _change.removed.insert (_val.begin(), _val.end());
229                 _val.clear ();
230         }
231         
232         typename Container::size_type size() const { 
233                 return _val.size();
234         }
235
236         bool empty() const { 
237                 return _val.empty();
238         }
239
240         Container& operator= (const Container& other) {
241                 _change.removed.insert (_val.begin(), _val.end());
242                 _change.added.insert (other.begin(), other.end());
243                 return _val = other;
244         }
245
246         typename Container::reference front() { 
247                 return _val.front ();
248         }
249
250         typename Container::const_reference front() const { 
251                 return _val.front ();
252         }
253
254         typename Container::reference back() { 
255                 return _val.back ();
256         }
257
258         typename Container::const_reference back() const { 
259                 return _val.back ();
260         }
261
262         void sort() { 
263                 _val.sort ();
264         }
265
266         template<class BinaryPredicate> void sort(BinaryPredicate comp) {
267                 _val.sort (comp);
268         }
269         
270         const ChangeRecord& change() const { return _change; }
271
272         /* for use in building up a SequenceProperty from a serialized
273            version on disk.
274         */
275
276         void record_addition (typename Container::value_type v) {
277                 _change.added.insert (v);
278         }
279         void record_removal (typename Container::value_type v) {
280                 _change.added.erase (v);
281         }
282
283   protected:
284         Container _val;
285         ChangeRecord _change;
286         boost::function<void(const ChangeRecord&)> _update_callback;
287
288         /** Load serialized change history.
289          * @return true if loading succeeded, false otherwise
290          */
291
292         bool load_history_state (const XMLNode& history_node) {
293
294                 const XMLNodeList& children (history_node.children());
295
296                 for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
297                         const XMLProperty* prop = (*i)->property ("id");
298                         if (prop) {
299                                 PBD::ID id (prop->value());
300                                 typename Container::value_type v = lookup_id (id);
301                                 if (!v) {
302                                         std::cerr << "No such item, ID = " << id.to_s() << " (from " << prop->value() << ")\n";
303                                         return false;
304                                 }
305                                 if ((*i)->name() == "Add") {
306                                         _change.added.insert (v);
307                                 } else if ((*i)->name() == "Remove") {
308                                         _change.removed.insert (v);
309                                 }
310                         }
311                 }
312
313                 return true;
314         }
315 };
316
317 }
318
319 #endif /* __libpbd_sequence_property_h__ */