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/libpbd_visibility.h"
31 #include "pbd/convert.h"
33 #include "pbd/property_basics.h"
34 #include "pbd/property_list.h"
35 #include "pbd/stateful_diff_command.h"
36 #include "pbd/error.h"
40 /** A base class for properties whose state is a container of other
41 * things. Its behaviour is `specialised' for this purpose in that
42 * it holds state changes as additions to and removals from the
43 * container, which is more efficient than storing entire state after
46 template<typename Container>
47 class /*LIBPBD_API*/ SequenceProperty : public PropertyBase
50 typedef std::set<typename Container::value_type> ChangeContainer;
52 /** A record of changes made */
55 void add (typename Container::value_type const & r) {
56 typename ChangeContainer::iterator i = removed.find (r);
57 if (i != removed.end()) {
58 /* we're adding, and this thing has already been marked as removed, so
59 just remove it from the removed list
67 void remove (typename Container::value_type const & r) {
68 typename ChangeContainer::iterator i = added.find (r);
69 if (i != added.end()) {
70 /* we're removing, and this thing has already been marked as added, so
71 just remove it from the added list
79 ChangeContainer added;
80 ChangeContainer removed;
83 SequenceProperty (PropertyID id, const boost::function<void(const ChangeRecord&)>& update)
84 : PropertyBase (id), _update_callback (update) {}
87 _changes.removed.swap (_changes.added);
90 void get_changes_as_xml (XMLNode* history_node) const {
92 XMLNode* child = new XMLNode (PBD::capitalize (property_name()));
93 history_node->add_child_nocopy (*child);
95 /* record the change described in our change member */
97 if (!_changes.added.empty()) {
98 for (typename ChangeContainer::const_iterator i = _changes.added.begin(); i != _changes.added.end(); ++i) {
99 XMLNode* add_node = new XMLNode ("Add");
100 child->add_child_nocopy (*add_node);
101 get_content_as_xml (*i, *add_node);
104 if (!_changes.removed.empty()) {
105 for (typename ChangeContainer::const_iterator i = _changes.removed.begin(); i != _changes.removed.end(); ++i) {
106 XMLNode* remove_node = new XMLNode ("Remove");
107 child->add_child_nocopy (*remove_node);
108 get_content_as_xml (*i, *remove_node);
113 /** Get a representation of one of our items as XML. The representation must be sufficient to
114 * restore the item's state later; an ID is ok if someone else is storing the item state,
115 * otherwise it needs to be the full state. The supplied node is an \<Add\> or \<Remove\>
116 * which this method can either add properties or children to.
118 virtual void get_content_as_xml (typename ChangeContainer::value_type, XMLNode &) const = 0;
120 bool set_value (XMLNode const &) {
121 /* XXX: not used, but probably should be */
126 void get_value (XMLNode & node) const {
127 for (typename Container::const_iterator i = _val.begin(); i != _val.end(); ++i) {
128 node.add_child_nocopy ((*i)->get_state ());
132 bool changed () const {
133 return !_changes.added.empty() || !_changes.removed.empty();
136 void clear_changes () {
137 _changes.added.clear ();
138 _changes.removed.clear ();
141 void apply_changes (PropertyBase const * p) {
142 const ChangeRecord& change (dynamic_cast<const SequenceProperty*> (p)->changes ());
146 /** Given a record of changes to this property, pass it to a callback that will
147 * update the property in some appropriate way.
149 * This exists because simply using std::sequence methods to add/remove items
150 * from the property is far too simplistic - the semantics of add/remove may
151 * be much more complex than that.
153 void update (const ChangeRecord& cr) {
154 _update_callback (cr);
157 void get_changes_as_properties (PBD::PropertyList& changes, Command* cmd) const {
162 /* Create a property with just the changes and not the actual values */
163 SequenceProperty<Container>* a = create ();
164 a->_changes = _changes;
168 /* whenever one of the items emits DropReferences, make sure
169 that the Destructible we've been told to notify hears about
170 it. the Destructible is likely to be the Command being built
174 for (typename ChangeContainer::const_iterator i = a->changes().added.begin(); i != a->changes().added.end(); ++i) {
175 (*i)->DropReferences.connect_same_thread (*cmd, boost::bind (&Destructible::drop_references, cmd));
180 SequenceProperty<Container>* clone_from_xml (XMLNode const & node) const {
182 XMLNodeList const children = node.children ();
184 /* find the node for this property name */
186 std::string const c = capitalize (property_name ());
187 XMLNodeList::const_iterator i = children.begin();
188 while (i != children.end() && (*i)->name() != c) {
192 if (i == children.end()) {
196 /* create a property with the changes */
198 SequenceProperty<Container>* p = create ();
200 XMLNodeList const & grandchildren = (*i)->children ();
201 for (XMLNodeList::const_iterator j = grandchildren.begin(); j != grandchildren.end(); ++j) {
203 typename Container::value_type v = get_content_from_xml (**j);
206 warning << "undo transaction references an unknown object" << endmsg;
207 } else if ((*j)->name() == "Add") {
208 p->_changes.added.insert (v);
209 } else if ((*j)->name() == "Remove") {
210 p->_changes.removed.insert (v);
217 /** Given an \<Add\> or \<Remove\> node as passed into get_content_to_xml, obtain an item */
218 virtual typename Container::value_type get_content_from_xml (XMLNode const & node) const = 0;
220 void clear_owned_changes () {
221 for (typename Container::iterator i = begin(); i != end(); ++i) {
222 (*i)->clear_changes ();
226 void rdiff (std::vector<Command*>& cmds) const {
227 for (typename Container::const_iterator i = begin(); i != end(); ++i) {
228 if ((*i)->changed ()) {
229 StatefulDiffCommand* sdc = new StatefulDiffCommand (*i);
230 cmds.push_back (sdc);
235 Container rlist() const { return _val; }
237 /* Wrap salient methods of Sequence
240 typename Container::iterator begin() { return _val.begin(); }
241 typename Container::iterator end() { return _val.end(); }
242 typename Container::const_iterator begin() const { return _val.begin(); }
243 typename Container::const_iterator end() const { return _val.end(); }
245 typename Container::reverse_iterator rbegin() { return _val.rbegin(); }
246 typename Container::reverse_iterator rend() { return _val.rend(); }
247 typename Container::const_reverse_iterator rbegin() const { return _val.rbegin(); }
248 typename Container::const_reverse_iterator rend() const { return _val.rend(); }
250 typename Container::iterator insert (typename Container::iterator i, const typename Container::value_type& v) {
252 return _val.insert (i, v);
255 typename Container::iterator erase (typename Container::iterator i) {
256 if (i != _val.end()) {
257 _changes.remove (*i);
259 return _val.erase (i);
262 typename Container::iterator erase (typename Container::iterator f, typename Container::iterator l) {
263 for (typename Container::const_iterator i = f; i != l; ++i) {
264 _changes.remove (*i);
266 return _val.erase (f, l);
269 void remove (const typename Container::value_type& v) {
274 void push_back (const typename Container::value_type& v) {
279 void push_front (const typename Container::value_type& v) {
286 _changes.remove (front());
293 _changes.remove (back());
299 for (typename Container::iterator i = _val.begin(); i != _val.end(); ++i) {
300 _changes.remove (*i);
305 typename Container::size_type size() const {
313 Container& operator= (const Container& other) {
314 for (typename Container::const_iterator i = _val.begin(); i != _val.end(); ++i) {
315 _changes.remove (*i);
317 for (typename Container::const_iterator i = other.begin(); i != other.end(); ++i) {
323 typename Container::reference front() {
324 return _val.front ();
327 typename Container::const_reference front() const {
328 return _val.front ();
331 typename Container::reference back() {
335 typename Container::const_reference back() const {
343 template<class BinaryPredicate> void sort(BinaryPredicate comp) {
347 const ChangeRecord& changes () const { return _changes; }
351 /* copy construction only by subclasses */
352 SequenceProperty (SequenceProperty<Container> const & p)
355 , _changes (p._changes)
356 , _update_callback (p._update_callback)
359 Container _val; ///< our actual container of things
360 ChangeRecord _changes; ///< changes to the container (adds/removes) that have happened since clear_changes() was last called
361 boost::function<void(const ChangeRecord&)> _update_callback;
364 virtual SequenceProperty<Container>* create () const = 0;
369 #endif /* __libpbd_sequence_property_h__ */