From c243a02c998f585295f2179657673e2cf0fa4428 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Thu, 26 Aug 2010 01:44:11 +0000 Subject: [PATCH] Fix crossfade undo using the stateful diff system. Fixes #3257. git-svn-id: svn://localhost/ardour2/branches/3.0@7694 d708f5d6-7413-0410-9779-e7cbd77b26cf --- gtk2_ardour/crossfade_edit.cc | 4 +- gtk2_ardour/editor_drag.cc | 16 +++++- gtk2_ardour/editor_ops.cc | 29 ++++------- gtk2_ardour/route_time_axis.cc | 18 +++---- libs/ardour/ardour/audioplaylist.h | 35 +++++++++++-- libs/ardour/ardour/crossfade.h | 2 +- libs/ardour/ardour/playlist.h | 6 +-- libs/ardour/ardour/session.h | 2 + libs/ardour/audio_playlist.cc | 75 +++++++++++++++++++++++++++- libs/ardour/crossfade.cc | 4 +- libs/ardour/globals.cc | 3 +- libs/ardour/playlist.cc | 44 +++++++++++----- libs/ardour/session_state.cc | 8 +++ libs/ardour/wscript | 2 +- libs/pbd/pbd/command.h | 4 ++ libs/pbd/pbd/property_basics.h | 2 +- libs/pbd/pbd/sequence_property.h | 31 ++++++++---- libs/pbd/pbd/stateful.h | 2 +- libs/pbd/pbd/stateful_diff_command.h | 2 + libs/pbd/stateful.cc | 2 +- libs/pbd/stateful_diff_command.cc | 6 +++ 21 files changed, 225 insertions(+), 72 deletions(-) diff --git a/gtk2_ardour/crossfade_edit.cc b/gtk2_ardour/crossfade_edit.cc index b6dcf07920..49e5ee0609 100644 --- a/gtk2_ardour/crossfade_edit.cc +++ b/gtk2_ardour/crossfade_edit.cc @@ -788,10 +788,10 @@ CrossfadeEditor::apply () _session->begin_reversible_command (_("Edit crossfade")); XMLNode& before = xfade->get_state (); - + _apply_to (xfade); - _session->add_command (new MementoCommand (*xfade.get(), &before, &xfade->get_state())); + _session->add_command (new MementoCommand (*xfade.get(), &before, &xfade->get_state ())); _session->commit_reversible_command (); } diff --git a/gtk2_ardour/editor_drag.cc b/gtk2_ardour/editor_drag.cc index f2acf2e7a8..cef27660a6 100644 --- a/gtk2_ardour/editor_drag.cc +++ b/gtk2_ardour/editor_drag.cc @@ -1037,6 +1037,15 @@ RegionMoveDrag::finished_no_copy ( playlist->freeze (); } + /* this movement may result in a crossfade being modified, so we need to get undo + data from the playlist as well as the region. + */ + + r = modified_playlists.insert (playlist); + if (r.second) { + playlist->clear_changes (); + } + rv->region()->set_position (where, (void*) this); _editor->session()->add_command (new StatefulDiffCommand (rv->region())); @@ -1172,7 +1181,12 @@ void RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists) { for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) { - _editor->session()->add_command (new StatefulDiffCommand (*i)); + StatefulDiffCommand* c = new StatefulDiffCommand (*i); + if (!c->empty()) { + _editor->session()->add_command (new StatefulDiffCommand (*i)); + } else { + delete c; + } } } diff --git a/gtk2_ardour/editor_ops.cc b/gtk2_ardour/editor_ops.cc index f4c63531cc..d540134f17 100644 --- a/gtk2_ardour/editor_ops.cc +++ b/gtk2_ardour/editor_ops.cc @@ -2820,11 +2820,9 @@ Editor::separate_regions_between (const TimeSelection& ts) /* pick up changes to existing regions */ - vector cmds; + vector cmds; playlist->rdiff (cmds); - for (vector::iterator j = cmds.begin(); j != cmds.end(); ++j) { - _session->add_command (*j); - } + _session->add_commands (cmds); /* pick up changes to the playlist itself (adds/removes) */ @@ -3773,11 +3771,9 @@ Editor::bounce_range_selection (bool replace, bool enable_processing) playlist->add_region (r, start); } - vector cmds; + vector cmds; playlist->rdiff (cmds); - for (vector::iterator j = cmds.begin(); j != cmds.end(); ++j) { - _session->add_command (*j); - } + _session->add_commands (cmds); _session->add_command (new StatefulDiffCommand (playlist)); } @@ -4492,13 +4488,10 @@ Editor::nudge_track (bool use_edit, bool forwards) playlist->nudge_after (start, distance, forwards); - vector cmds; + vector cmds; playlist->rdiff (cmds); - - for (vector::iterator c = cmds.begin(); c != cmds.end(); ++c) { - _session->add_command (*c); - } + _session->add_commands (cmds); _session->add_command (new StatefulDiffCommand (playlist)); } @@ -6568,14 +6561,10 @@ Editor::insert_time (nframes64_t pos, nframes64_t frames, InsertTimeOption opt, pl->shift (pos, frames, (opt == MoveIntersected), ignore_music_glue); - vector cmds; - + vector cmds; pl->rdiff (cmds); - - for (vector::iterator c = cmds.begin(); c != cmds.end(); ++c) { - _session->add_command (*c); - } - + _session->add_commands (cmds); + _session->add_command (new StatefulDiffCommand (pl)); commit = true; } diff --git a/gtk2_ardour/route_time_axis.cc b/gtk2_ardour/route_time_axis.cc index 391b08eac6..abc8101195 100644 --- a/gtk2_ardour/route_time_axis.cc +++ b/gtk2_ardour/route_time_axis.cc @@ -1339,13 +1339,10 @@ RouteTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op) if ((what_we_got = playlist->cut (time)) != 0) { _editor.get_cut_buffer().add (what_we_got); - vector cmds; - + vector cmds; playlist->rdiff (cmds); - - for (vector::iterator c = cmds.begin(); c != cmds.end(); ++c) { - _session->add_command (*c); - } + _session->add_commands (cmds); + _session->add_command (new StatefulDiffCommand (playlist)); } break; @@ -1357,13 +1354,10 @@ RouteTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op) case Clear: if ((what_we_got = playlist->cut (time)) != 0) { - vector cmds; - + + vector cmds; playlist->rdiff (cmds); - - for (vector::iterator c = cmds.begin(); c != cmds.end(); ++c) { - _session->add_command (*c); - } + _session->add_commands (cmds); _session->add_command (new StatefulDiffCommand (playlist)); what_we_got->release (); } diff --git a/libs/ardour/ardour/audioplaylist.h b/libs/ardour/ardour/audioplaylist.h index aee5fb3f64..f2d60bce8b 100644 --- a/libs/ardour/ardour/audioplaylist.h +++ b/libs/ardour/ardour/audioplaylist.h @@ -33,12 +33,39 @@ class Region; class AudioRegion; class Source; +namespace Properties { + /* fake the type, since crossfades are handled by SequenceProperty which doesn't + care about such things. + */ + extern PBD::PropertyDescriptor crossfades; +} + +class AudioPlaylist; + +class CrossfadeListProperty : public PBD::SequenceProperty > > +{ +public: + CrossfadeListProperty (AudioPlaylist &); + + void get_content_as_xml (boost::shared_ptr, XMLNode &) const; + boost::shared_ptr get_content_from_xml (XMLNode const &) const; + +private: + CrossfadeListProperty* clone () const; + CrossfadeListProperty* create () const; + + friend class AudioPlaylist; + /* we live and die with our playlist, no lifetime management needed */ + AudioPlaylist& _playlist; +}; + + class AudioPlaylist : public ARDOUR::Playlist { - public: +public: typedef std::list > Crossfades; + static void make_property_quarks (); - public: AudioPlaylist (Session&, const XMLNode&, bool hidden = false); AudioPlaylist (Session&, std::string name, bool hidden = false); AudioPlaylist (boost::shared_ptr, std::string name, bool hidden = false); @@ -59,6 +86,8 @@ class AudioPlaylist : public ARDOUR::Playlist bool destroy_region (boost::shared_ptr); + void update (const CrossfadeListProperty::ChangeRecord &); + protected: /* playlist "callbacks" */ @@ -72,7 +101,7 @@ class AudioPlaylist : public ARDOUR::Playlist void remove_dependents (boost::shared_ptr region); private: - Crossfades _crossfades; + CrossfadeListProperty _crossfades; Crossfades _pending_xfade_adds; void crossfade_invalidated (boost::shared_ptr); diff --git a/libs/ardour/ardour/crossfade.h b/libs/ardour/ardour/crossfade.h index 4c75a0226b..2ce504eacf 100644 --- a/libs/ardour/ardour/crossfade.h +++ b/libs/ardour/ardour/crossfade.h @@ -73,7 +73,7 @@ class Crossfade : public ARDOUR::AudioRegion /* the usual XML constructor */ - Crossfade (const Playlist&, XMLNode&); + Crossfade (const Playlist&, XMLNode const &); virtual ~Crossfade(); static void make_property_quarks (); diff --git a/libs/ardour/ardour/playlist.h b/libs/ardour/ardour/playlist.h index 6473211126..a1a7bb752a 100644 --- a/libs/ardour/ardour/playlist.h +++ b/libs/ardour/ardour/playlist.h @@ -64,8 +64,8 @@ class RegionListProperty : public PBD::SequenceProperty lookup_id (const PBD::ID& id) const; + void get_content_as_xml (boost::shared_ptr, XMLNode &) const; + boost::shared_ptr get_content_from_xml (XMLNode const &) const; private: RegionListProperty* create () const; @@ -90,7 +90,7 @@ public: void update (const RegionListProperty::ChangeRecord&); void clear_owned_changes (); - void rdiff (std::vector&) const; + void rdiff (std::vector&) const; boost::shared_ptr region_by_id (const PBD::ID&) const; diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h index c408714c94..75b9fb1831 100644 --- a/libs/ardour/ardour/session.h +++ b/libs/ardour/ardour/session.h @@ -680,6 +680,8 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi _current_trans.top()->add_command (cmd); } + void add_commands (std::vector const & cmds); + std::map registry; // these commands are implemented in libs/ardour/session_command.cc diff --git a/libs/ardour/audio_playlist.cc b/libs/ardour/audio_playlist.cc index cfc5a4028a..2ac3edff80 100644 --- a/libs/ardour/audio_playlist.cc +++ b/libs/ardour/audio_playlist.cc @@ -21,7 +21,6 @@ #include - #include "ardour/types.h" #include "ardour/debug.h" #include "ardour/configuration.h" @@ -38,14 +37,70 @@ using namespace ARDOUR; using namespace std; using namespace PBD; +namespace ARDOUR { + namespace Properties { + PBD::PropertyDescriptor crossfades; + } +} + +void +AudioPlaylist::make_property_quarks () +{ + Properties::crossfades.property_id = g_quark_from_static_string (X_("crossfades")); + DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for crossfades = %1\n", Properties::crossfades.property_id)); +} + +CrossfadeListProperty::CrossfadeListProperty (AudioPlaylist& pl) + : SequenceProperty > > (Properties::crossfades.property_id, boost::bind (&AudioPlaylist::update, &pl, _1)) + , _playlist (pl) +{ + +} + + +CrossfadeListProperty * +CrossfadeListProperty::create () const +{ + return new CrossfadeListProperty (_playlist); +} + +CrossfadeListProperty * +CrossfadeListProperty::clone () const +{ + return new CrossfadeListProperty (*this); +} + +void +CrossfadeListProperty::get_content_as_xml (boost::shared_ptr xfade, XMLNode & node) const +{ + /* Crossfades are not written to any state when they are no + longer in use, so we must write their state here. + */ + + XMLNode& c = xfade->get_state (); + node.add_child_nocopy (c); +} + +boost::shared_ptr +CrossfadeListProperty::get_content_from_xml (XMLNode const & node) const +{ + XMLNodeList const c = node.children (); + assert (c.size() == 1); + return boost::shared_ptr (new Crossfade (_playlist, *c.front())); +} + + AudioPlaylist::AudioPlaylist (Session& session, const XMLNode& node, bool hidden) : Playlist (session, node, DataType::AUDIO, hidden) + , _crossfades (*this) { #ifndef NDEBUG const XMLProperty* prop = node.property("type"); assert(!prop || DataType(prop->value()) == DataType::AUDIO); #endif + add_property (_crossfades); + in_set_state++; set_state (node, Stateful::loading_state_version); in_set_state--; @@ -53,12 +108,17 @@ AudioPlaylist::AudioPlaylist (Session& session, const XMLNode& node, bool hidden AudioPlaylist::AudioPlaylist (Session& session, string name, bool hidden) : Playlist (session, name, DataType::AUDIO, hidden) + , _crossfades (*this) { + add_property (_crossfades); } AudioPlaylist::AudioPlaylist (boost::shared_ptr other, string name, bool hidden) : Playlist (other, name, hidden) + , _crossfades (*this) { + add_property (_crossfades); + RegionList::const_iterator in_o = other->regions.begin(); RegionList::iterator in_n = regions.begin(); @@ -99,7 +159,10 @@ AudioPlaylist::AudioPlaylist (boost::shared_ptr other, stri AudioPlaylist::AudioPlaylist (boost::shared_ptr other, nframes_t start, nframes_t cnt, string name, bool hidden) : Playlist (other, start, cnt, name, hidden) + , _crossfades (*this) { + add_property (_crossfades); + /* this constructor does NOT notify others (session) */ } @@ -795,3 +858,13 @@ AudioPlaylist::foreach_crossfade (boost::function a, boost::shared_ptr -inf..+6dB @@ -132,7 +132,7 @@ Crossfade::Crossfade (const Playlist& playlist, XMLNode& node) { boost::shared_ptr r; - XMLProperty* prop; + XMLProperty const * prop; LocaleGuard lg (X_("POSIX")); /* we have to find the in/out regions before we can do anything else */ diff --git a/libs/ardour/globals.cc b/libs/ardour/globals.cc index 2ecea550b5..0e12883d82 100644 --- a/libs/ardour/globals.cc +++ b/libs/ardour/globals.cc @@ -69,7 +69,7 @@ #include "ardour/debug.h" #include "ardour/filesystem_paths.h" #include "ardour/mix.h" -#include "ardour/playlist.h" +#include "ardour/audioplaylist.h" #include "ardour/plugin_manager.h" #include "ardour/process_thread.h" #include "ardour/profile.h" @@ -251,6 +251,7 @@ ARDOUR::init (bool use_vst, bool try_optimization) AudioRegion::make_property_quarks (); RouteGroup::make_property_quarks (); Playlist::make_property_quarks (); + AudioPlaylist::make_property_quarks (); /* this is a useful ready to use PropertyChange that many things need to check. This avoids having to compose diff --git a/libs/ardour/playlist.cc b/libs/ardour/playlist.cc index 93a4d520e8..1b2d64baba 100644 --- a/libs/ardour/playlist.cc +++ b/libs/ardour/playlist.cc @@ -110,12 +110,40 @@ RegionListProperty::RegionListProperty (Playlist& pl) : SequenceProperty > > (Properties::regions.property_id, boost::bind (&Playlist::update, &pl, _1)) , _playlist (pl) { + +} + +RegionListProperty * +RegionListProperty::clone () const +{ + return new RegionListProperty (*this); +} + +RegionListProperty * +RegionListProperty::create () const +{ + return new RegionListProperty (_playlist); +} + +void +RegionListProperty::get_content_as_xml (boost::shared_ptr region, XMLNode & node) const +{ + /* All regions (even those which are deleted) have their state saved by other + code, so we can just store ID here. + */ + + node.add_property ("id", region->id().to_s ()); } boost::shared_ptr -RegionListProperty::lookup_id (const ID& id) const +RegionListProperty::get_content_from_xml (XMLNode const & node) const { - boost::shared_ptr ret = _playlist.region_by_id (id); + XMLProperty const * prop = node.property ("id"); + assert (prop); + + PBD::ID id (prop->value ()); + + boost::shared_ptr ret = _playlist.region_by_id (id); if (!ret) { ret = RegionFactory::region_by_id (id); @@ -124,16 +152,6 @@ RegionListProperty::lookup_id (const ID& id) const return ret; } -RegionListProperty* RegionListProperty::clone () const -{ - return new RegionListProperty (*this); -} - -RegionListProperty* RegionListProperty::create () const -{ - return new RegionListProperty (_playlist); -} - Playlist::Playlist (Session& sess, string nom, DataType type, bool hide) : SessionObject(sess, nom) , regions (*this) @@ -2100,7 +2118,7 @@ Playlist::mark_session_dirty () } void -Playlist::rdiff (vector& cmds) const +Playlist::rdiff (vector& cmds) const { RegionLock rlock (const_cast (this)); Stateful::rdiff (cmds); diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc index 31282a210f..225b7c1909 100644 --- a/libs/ardour/session_state.cc +++ b/libs/ardour/session_state.cc @@ -2312,6 +2312,14 @@ Session::finish_reversible_command (UndoTransaction& ut) _history.add (&ut); } +void +Session::add_commands (vector const & cmds) +{ + for (vector::const_iterator i = cmds.begin(); i != cmds.end(); ++i) { + add_command (*i); + } +} + void Session::begin_reversible_command(const string& name) { diff --git a/libs/ardour/wscript b/libs/ardour/wscript index 051054c9a7..f877f3c3be 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -286,7 +286,7 @@ def build(bld): obj.includes = ['.', '../surfaces/control_protocol', '..'] obj.name = 'libardour' obj.target = 'ardour' - obj.uselib = 'GLIBMM GTHREAD AUBIO SIGCPP XML UUID JACK SNDFILE SAMPLERATE LRDF OSX COREAUDIO' + obj.uselib = 'GLIBMM GTHREAD AUBIO SIGCPP XML UUID JACK SNDFILE SAMPLERATE LRDF OSX COREAUDIO CURL DL' obj.uselib_local = 'libpbd libmidipp libevoral libvamphost libvampplugin libtaglib librubberband libaudiographer' obj.vnum = LIBARDOUR_LIB_VERSION obj.install_path = os.path.join(bld.env['LIBDIR'], 'ardour3') diff --git a/libs/pbd/pbd/command.h b/libs/pbd/pbd/command.h index c6c3c8d3fd..db4d2bbd81 100644 --- a/libs/pbd/pbd/command.h +++ b/libs/pbd/pbd/command.h @@ -43,6 +43,10 @@ public: virtual XMLNode &get_state(); virtual int set_state(const XMLNode&, int /*version*/) { /* noop */ return 0; } + virtual bool empty () const { + return false; + } + protected: Command() {} Command(const std::string& name) : _name(name) {} diff --git a/libs/pbd/pbd/property_basics.h b/libs/pbd/pbd/property_basics.h index cfae36df20..145e84abfc 100644 --- a/libs/pbd/pbd/property_basics.h +++ b/libs/pbd/pbd/property_basics.h @@ -103,7 +103,7 @@ public: virtual void get_changes_as_properties (PropertyList& changes, Command *) const = 0; /** Collect StatefulDiffCommands for changes to anything that we own */ - virtual void rdiff (std::vector &) const {} + virtual void rdiff (std::vector &) const {} /** Look in an XML node written by get_changes_as_xml and, if XML from this property * is found, create a property with the changes from the XML. diff --git a/libs/pbd/pbd/sequence_property.h b/libs/pbd/pbd/sequence_property.h index 5b37d7a0fc..4b494d4a8d 100644 --- a/libs/pbd/pbd/sequence_property.h +++ b/libs/pbd/pbd/sequence_property.h @@ -31,6 +31,7 @@ #include "pbd/id.h" #include "pbd/property_basics.h" #include "pbd/property_list.h" +#include "pbd/stateful_diff_command.h" namespace PBD { @@ -80,8 +81,6 @@ class SequenceProperty : public PropertyBase SequenceProperty (PropertyID id, const boost::function& update) : PropertyBase (id), _update_callback (update) {} - virtual typename Container::value_type lookup_id (const PBD::ID&) const = 0; - void invert () { _changes.removed.swap (_changes.added); } @@ -97,18 +96,25 @@ class SequenceProperty : public PropertyBase for (typename ChangeContainer::iterator i = _changes.added.begin(); i != _changes.added.end(); ++i) { XMLNode* add_node = new XMLNode ("Add"); child->add_child_nocopy (*add_node); - add_node->add_property ("id", (*i)->id().to_s()); + get_content_as_xml (*i, *add_node); } } if (!_changes.removed.empty()) { for (typename ChangeContainer::iterator i = _changes.removed.begin(); i != _changes.removed.end(); ++i) { XMLNode* remove_node = new XMLNode ("Remove"); child->add_child_nocopy (*remove_node); - remove_node->add_property ("id", (*i)->id().to_s()); + get_content_as_xml (*i, *remove_node); } } } + /** Get a representation of one of our items as XML. The representation must be sufficient to + * restore the item's state later; an ID is ok if someone else is storing the item state, + * otherwise it needs to be the full state. The supplied node is an or + * which this method can either add properties or children to. + */ + virtual void get_content_as_xml (typename ChangeContainer::value_type, XMLNode &) const = 0; + bool set_value (XMLNode const &) { /* XXX: not used, but probably should be */ assert (false); @@ -191,11 +197,10 @@ class SequenceProperty : public PropertyBase XMLNodeList const & grandchildren = (*i)->children (); for (XMLNodeList::const_iterator j = grandchildren.begin(); j != grandchildren.end(); ++j) { - XMLProperty const * prop = (*j)->property ("id"); - assert (prop); - PBD::ID id (prop->value ()); - typename Container::value_type v = lookup_id (id); + + typename Container::value_type v = get_content_from_xml (**j); assert (v); + if ((*j)->name() == "Add") { p->_changes.added.insert (v); } else if ((*j)->name() == "Remove") { @@ -206,13 +211,16 @@ class SequenceProperty : public PropertyBase return p; } + /** Given an or node as passed into get_content_to_xml, obtain an item */ + virtual typename Container::value_type get_content_from_xml (XMLNode const & node) const = 0; + void clear_owned_changes () { for (typename Container::iterator i = begin(); i != end(); ++i) { (*i)->clear_changes (); } } - void rdiff (std::vector& cmds) const { + void rdiff (std::vector& cmds) const { for (typename Container::const_iterator i = begin(); i != end(); ++i) { if ((*i)->changed ()) { StatefulDiffCommand* sdc = new StatefulDiffCommand (*i); @@ -255,6 +263,11 @@ class SequenceProperty : public PropertyBase return _val.erase (f, l); } + void remove (const typename Container::value_type& v) { + _changes.remove (v); + _val.remove (v); + } + void push_back (const typename Container::value_type& v) { _changes.add (v); _val.push_back (v); diff --git a/libs/pbd/pbd/stateful.h b/libs/pbd/pbd/stateful.h index 5c1f079bc6..735ffbdc4a 100644 --- a/libs/pbd/pbd/stateful.h +++ b/libs/pbd/pbd/stateful.h @@ -70,7 +70,7 @@ class Stateful { void clear_changes (); virtual void clear_owned_changes (); PropertyList* get_changes_as_properties (Command *) const; - virtual void rdiff (std::vector &) const; + virtual void rdiff (std::vector &) const; bool changed() const; /* create a property list from an XMLNode diff --git a/libs/pbd/pbd/stateful_diff_command.h b/libs/pbd/pbd/stateful_diff_command.h index 2d5c234d76..2a213d7a17 100644 --- a/libs/pbd/pbd/stateful_diff_command.h +++ b/libs/pbd/pbd/stateful_diff_command.h @@ -45,6 +45,8 @@ public: XMLNode& get_state (); + bool empty () const; + private: boost::weak_ptr _object; ///< the object in question PBD::PropertyList* _changes; ///< property changes to execute this command diff --git a/libs/pbd/stateful.cc b/libs/pbd/stateful.cc index 8411dc4e60..e13a499e41 100644 --- a/libs/pbd/stateful.cc +++ b/libs/pbd/stateful.cc @@ -340,7 +340,7 @@ Stateful::property_factory (const XMLNode& history_node) const } void -Stateful::rdiff (vector& cmds) const +Stateful::rdiff (vector& cmds) const { for (OwnedPropertyList::const_iterator i = _properties->begin(); i != _properties->end(); ++i) { i->second->rdiff (cmds); diff --git a/libs/pbd/stateful_diff_command.cc b/libs/pbd/stateful_diff_command.cc index 93a215ad8a..0f456d2d6e 100644 --- a/libs/pbd/stateful_diff_command.cc +++ b/libs/pbd/stateful_diff_command.cc @@ -118,3 +118,9 @@ StatefulDiffCommand::get_state () return *node; } + +bool +StatefulDiffCommand::empty () const +{ + return _changes->empty(); +}