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"
34 #include "pbd/stateful_diff_command.h"
38 /** A base class for properties whose state is a container of other
39 * things. Its behaviour is `specialised' for this purpose in that
40 * it holds state changes as additions to and removals from the
41 * container, which is more efficient than storing entire state after
44 template<typename Container>
45 class SequenceProperty : public PropertyBase
48 typedef std::set<typename Container::value_type> ChangeContainer;
50 /** A record of changes made */
53 void add (typename Container::value_type const & r) {
54 typename ChangeContainer::iterator i = removed.find (r);
55 if (i != removed.end()) {
56 /* we're adding, and this thing has already been marked as removed, so
57 just remove it from the removed list
65 void remove (typename Container::value_type const & r) {
66 typename ChangeContainer::iterator i = added.find (r);
67 if (i != added.end()) {
68 /* we're removing, and this thing has already been marked as added, so
69 just remove it from the added list
77 ChangeContainer added;
78 ChangeContainer removed;
81 SequenceProperty (PropertyID id, const boost::function<void(const ChangeRecord&)>& update)
82 : PropertyBase (id), _update_callback (update) {}
85 _changes.removed.swap (_changes.added);
88 void get_changes_as_xml (XMLNode* history_node) const {
90 XMLNode* child = new XMLNode (PBD::capitalize (property_name()));
91 history_node->add_child_nocopy (*child);
93 /* record the change described in our change member */
95 if (!_changes.added.empty()) {
96 for (typename ChangeContainer::iterator i = _changes.added.begin(); i != _changes.added.end(); ++i) {
97 XMLNode* add_node = new XMLNode ("Add");
98 child->add_child_nocopy (*add_node);
99 get_content_as_xml (*i, *add_node);
102 if (!_changes.removed.empty()) {
103 for (typename ChangeContainer::iterator i = _changes.removed.begin(); i != _changes.removed.end(); ++i) {
104 XMLNode* remove_node = new XMLNode ("Remove");
105 child->add_child_nocopy (*remove_node);
106 get_content_as_xml (*i, *remove_node);
111 /** Get a representation of one of our items as XML. The representation must be sufficient to
112 * restore the item's state later; an ID is ok if someone else is storing the item state,
113 * otherwise it needs to be the full state. The supplied node is an <Add> or <Remove>
114 * which this method can either add properties or children to.
116 virtual void get_content_as_xml (typename ChangeContainer::value_type, XMLNode &) const = 0;
118 bool set_value (XMLNode const &) {
119 /* XXX: not used, but probably should be */
124 void get_value (XMLNode & node) const {
125 for (typename Container::const_iterator i = _val.begin(); i != _val.end(); ++i) {
126 node.add_child_nocopy ((*i)->get_state ());
130 bool changed () const {
131 return !_changes.added.empty() || !_changes.removed.empty();
134 void clear_changes () {
135 _changes.added.clear ();
136 _changes.removed.clear ();
139 void apply_changes (PropertyBase const * p) {
140 const ChangeRecord& change (dynamic_cast<const SequenceProperty*> (p)->changes ());
144 /** Given a record of changes to this property, pass it to a callback that will
145 * update the property in some appropriate way.
147 * This exists because simply using std::sequence methods to add/remove items
148 * from the property is far too simplistic - the semantics of add/remove may
149 * be much more complex than that.
151 void update (const ChangeRecord& cr) {
152 _update_callback (cr);
155 void get_changes_as_properties (PBD::PropertyList& changes, Command* cmd) const {
160 /* Create a property with just the changes and not the actual values */
161 SequenceProperty<Container>* a = create ();
162 a->_changes = _changes;
166 /* whenever one of the items emits DropReferences, make sure
167 that the Destructible we've been told to notify hears about
168 it. the Destructible is likely to be the Command being built
172 for (typename ChangeContainer::iterator i = a->changes().added.begin(); i != a->changes().added.end(); ++i) {
173 (*i)->DropReferences.connect_same_thread (*cmd, boost::bind (&Destructible::drop_references, cmd));
178 SequenceProperty<Container>* clone_from_xml (XMLNode const & node) const {
180 XMLNodeList const children = node.children ();
182 /* find the node for this property name */
184 std::string const c = capitalize (property_name ());
185 XMLNodeList::const_iterator i = children.begin();
186 while (i != children.end() && (*i)->name() != c) {
190 if (i == children.end()) {
194 /* create a property with the changes */
196 SequenceProperty<Container>* p = create ();
198 XMLNodeList const & grandchildren = (*i)->children ();
199 for (XMLNodeList::const_iterator j = grandchildren.begin(); j != grandchildren.end(); ++j) {
201 typename Container::value_type v = get_content_from_xml (**j);
204 if ((*j)->name() == "Add") {
205 p->_changes.added.insert (v);
206 } else if ((*j)->name() == "Remove") {
207 p->_changes.removed.insert (v);
214 /** Given an <Add> or <Remove> node as passed into get_content_to_xml, obtain an item */
215 virtual typename Container::value_type get_content_from_xml (XMLNode const & node) const = 0;
217 void clear_owned_changes () {
218 for (typename Container::iterator i = begin(); i != end(); ++i) {
219 (*i)->clear_changes ();
223 void rdiff (std::vector<Command*>& cmds) const {
224 for (typename Container::const_iterator i = begin(); i != end(); ++i) {
225 if ((*i)->changed ()) {
226 StatefulDiffCommand* sdc = new StatefulDiffCommand (*i);
227 cmds.push_back (sdc);
232 Container rlist() const { return _val; }
234 /* Wrap salient methods of Sequence
237 typename Container::iterator begin() { return _val.begin(); }
238 typename Container::iterator end() { return _val.end(); }
239 typename Container::const_iterator begin() const { return _val.begin(); }
240 typename Container::const_iterator end() const { return _val.end(); }
242 typename Container::reverse_iterator rbegin() { return _val.rbegin(); }
243 typename Container::reverse_iterator rend() { return _val.rend(); }
244 typename Container::const_reverse_iterator rbegin() const { return _val.rbegin(); }
245 typename Container::const_reverse_iterator rend() const { return _val.rend(); }
247 typename Container::iterator insert (typename Container::iterator i, const typename Container::value_type& v) {
249 return _val.insert (i, v);
252 typename Container::iterator erase (typename Container::iterator i) {
253 if (i != _val.end()) {
254 _changes.remove (*i);
256 return _val.erase (i);
259 typename Container::iterator erase (typename Container::iterator f, typename Container::iterator l) {
260 for (typename Container::const_iterator i = f; i != l; ++i) {
261 _changes.remove (*i);
263 return _val.erase (f, l);
266 void remove (const typename Container::value_type& v) {
271 void push_back (const typename Container::value_type& v) {
276 void push_front (const typename Container::value_type& v) {
283 _changes.remove (front());
290 _changes.remove (back());
296 for (typename Container::iterator i = _val.begin(); i != _val.end(); ++i) {
297 _changes.remove (*i);
302 typename Container::size_type size() const {
310 Container& operator= (const Container& other) {
311 for (typename Container::iterator i = _val.begin(); i != _val.end(); ++i) {
312 _changes.remove (*i);
314 for (typename Container::iterator i = other.begin(); i != other.end(); ++i) {
320 typename Container::reference front() {
321 return _val.front ();
324 typename Container::const_reference front() const {
325 return _val.front ();
328 typename Container::reference back() {
332 typename Container::const_reference back() const {
340 template<class BinaryPredicate> void sort(BinaryPredicate comp) {
344 const ChangeRecord& changes () const { return _changes; }
348 /* copy construction only by subclasses */
349 SequenceProperty (SequenceProperty<Container> const & p)
352 , _changes (p._changes)
353 , _update_callback (p._update_callback)
356 Container _val; ///< our actual container of things
357 ChangeRecord _changes; ///< changes to the container (adds/removes) that have happened since clear_changes() was last called
358 boost::function<void(const ChangeRecord&)> _update_callback;
361 virtual SequenceProperty<Container>* create () const = 0;
366 #endif /* __libpbd_sequence_property_h__ */