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"
35 #include "pbd/error.h"
39 /** A base class for properties whose state is a container of other
40 * things. Its behaviour is `specialised' for this purpose in that
41 * it holds state changes as additions to and removals from the
42 * container, which is more efficient than storing entire state after
45 template<typename Container>
46 class SequenceProperty : public PropertyBase
49 typedef std::set<typename Container::value_type> ChangeContainer;
51 /** A record of changes made */
54 void add (typename Container::value_type const & r) {
55 typename ChangeContainer::iterator i = removed.find (r);
56 if (i != removed.end()) {
57 /* we're adding, and this thing has already been marked as removed, so
58 just remove it from the removed list
66 void remove (typename Container::value_type const & r) {
67 typename ChangeContainer::iterator i = added.find (r);
68 if (i != added.end()) {
69 /* we're removing, and this thing has already been marked as added, so
70 just remove it from the added list
78 ChangeContainer added;
79 ChangeContainer removed;
82 SequenceProperty (PropertyID id, const boost::function<void(const ChangeRecord&)>& update)
83 : PropertyBase (id), _update_callback (update) {}
86 _changes.removed.swap (_changes.added);
89 void get_changes_as_xml (XMLNode* history_node) const {
91 XMLNode* child = new XMLNode (PBD::capitalize (property_name()));
92 history_node->add_child_nocopy (*child);
94 /* record the change described in our change member */
96 if (!_changes.added.empty()) {
97 for (typename ChangeContainer::const_iterator i = _changes.added.begin(); i != _changes.added.end(); ++i) {
98 XMLNode* add_node = new XMLNode ("Add");
99 child->add_child_nocopy (*add_node);
100 get_content_as_xml (*i, *add_node);
103 if (!_changes.removed.empty()) {
104 for (typename ChangeContainer::const_iterator i = _changes.removed.begin(); i != _changes.removed.end(); ++i) {
105 XMLNode* remove_node = new XMLNode ("Remove");
106 child->add_child_nocopy (*remove_node);
107 get_content_as_xml (*i, *remove_node);
112 /** Get a representation of one of our items as XML. The representation must be sufficient to
113 * restore the item's state later; an ID is ok if someone else is storing the item state,
114 * otherwise it needs to be the full state. The supplied node is an \<Add\> or \<Remove\>
115 * which this method can either add properties or children to.
117 virtual void get_content_as_xml (typename ChangeContainer::value_type, XMLNode &) const = 0;
119 bool set_value (XMLNode const &) {
120 /* XXX: not used, but probably should be */
125 void get_value (XMLNode & node) const {
126 for (typename Container::const_iterator i = _val.begin(); i != _val.end(); ++i) {
127 node.add_child_nocopy ((*i)->get_state ());
131 bool changed () const {
132 return !_changes.added.empty() || !_changes.removed.empty();
135 void clear_changes () {
136 _changes.added.clear ();
137 _changes.removed.clear ();
140 void apply_changes (PropertyBase const * p) {
141 const ChangeRecord& change (dynamic_cast<const SequenceProperty*> (p)->changes ());
145 /** Given a record of changes to this property, pass it to a callback that will
146 * update the property in some appropriate way.
148 * This exists because simply using std::sequence methods to add/remove items
149 * from the property is far too simplistic - the semantics of add/remove may
150 * be much more complex than that.
152 void update (const ChangeRecord& cr) {
153 _update_callback (cr);
156 void get_changes_as_properties (PBD::PropertyList& changes, Command* cmd) const {
161 /* Create a property with just the changes and not the actual values */
162 SequenceProperty<Container>* a = create ();
163 a->_changes = _changes;
167 /* whenever one of the items emits DropReferences, make sure
168 that the Destructible we've been told to notify hears about
169 it. the Destructible is likely to be the Command being built
173 for (typename ChangeContainer::const_iterator i = a->changes().added.begin(); i != a->changes().added.end(); ++i) {
174 (*i)->DropReferences.connect_same_thread (*cmd, boost::bind (&Destructible::drop_references, cmd));
179 SequenceProperty<Container>* clone_from_xml (XMLNode const & node) const {
181 XMLNodeList const children = node.children ();
183 /* find the node for this property name */
185 std::string const c = capitalize (property_name ());
186 XMLNodeList::const_iterator i = children.begin();
187 while (i != children.end() && (*i)->name() != c) {
191 if (i == children.end()) {
195 /* create a property with the changes */
197 SequenceProperty<Container>* p = create ();
199 XMLNodeList const & grandchildren = (*i)->children ();
200 for (XMLNodeList::const_iterator j = grandchildren.begin(); j != grandchildren.end(); ++j) {
202 typename Container::value_type v = get_content_from_xml (**j);
205 warning << "undo transaction references an unknown object" << endmsg;
206 } else if ((*j)->name() == "Add") {
207 p->_changes.added.insert (v);
208 } else if ((*j)->name() == "Remove") {
209 p->_changes.removed.insert (v);
216 /** Given an \<Add\> or \<Remove\> node as passed into get_content_to_xml, obtain an item */
217 virtual typename Container::value_type get_content_from_xml (XMLNode const & node) const = 0;
219 void clear_owned_changes () {
220 for (typename Container::iterator i = begin(); i != end(); ++i) {
221 (*i)->clear_changes ();
225 void rdiff (std::vector<Command*>& cmds) const {
226 for (typename Container::const_iterator i = begin(); i != end(); ++i) {
227 if ((*i)->changed ()) {
228 StatefulDiffCommand* sdc = new StatefulDiffCommand (*i);
229 cmds.push_back (sdc);
234 Container rlist() const { return _val; }
236 /* Wrap salient methods of Sequence
239 typename Container::iterator begin() { return _val.begin(); }
240 typename Container::iterator end() { return _val.end(); }
241 typename Container::const_iterator begin() const { return _val.begin(); }
242 typename Container::const_iterator end() const { return _val.end(); }
244 typename Container::reverse_iterator rbegin() { return _val.rbegin(); }
245 typename Container::reverse_iterator rend() { return _val.rend(); }
246 typename Container::const_reverse_iterator rbegin() const { return _val.rbegin(); }
247 typename Container::const_reverse_iterator rend() const { return _val.rend(); }
249 typename Container::iterator insert (typename Container::iterator i, const typename Container::value_type& v) {
251 return _val.insert (i, v);
254 typename Container::iterator erase (typename Container::iterator i) {
255 if (i != _val.end()) {
256 _changes.remove (*i);
258 return _val.erase (i);
261 typename Container::iterator erase (typename Container::iterator f, typename Container::iterator l) {
262 for (typename Container::const_iterator i = f; i != l; ++i) {
263 _changes.remove (*i);
265 return _val.erase (f, l);
268 void remove (const typename Container::value_type& v) {
273 void push_back (const typename Container::value_type& v) {
278 void push_front (const typename Container::value_type& v) {
285 _changes.remove (front());
292 _changes.remove (back());
298 for (typename Container::iterator i = _val.begin(); i != _val.end(); ++i) {
299 _changes.remove (*i);
304 typename Container::size_type size() const {
312 Container& operator= (const Container& other) {
313 for (typename Container::iterator i = _val.begin(); i != _val.end(); ++i) {
314 _changes.remove (*i);
316 for (typename Container::iterator i = other.begin(); i != other.end(); ++i) {
322 typename Container::reference front() {
323 return _val.front ();
326 typename Container::const_reference front() const {
327 return _val.front ();
330 typename Container::reference back() {
334 typename Container::const_reference back() const {
342 template<class BinaryPredicate> void sort(BinaryPredicate comp) {
346 const ChangeRecord& changes () const { return _changes; }
350 /* copy construction only by subclasses */
351 SequenceProperty (SequenceProperty<Container> const & p)
354 , _changes (p._changes)
355 , _update_callback (p._update_callback)
358 Container _val; ///< our actual container of things
359 ChangeRecord _changes; ///< changes to the container (adds/removes) that have happened since clear_changes() was last called
360 boost::function<void(const ChangeRecord&)> _update_callback;
363 virtual SequenceProperty<Container>* create () const = 0;
368 #endif /* __libpbd_sequence_property_h__ */