From a5c59175eb561cca6b704d6c50ee20290cc9a210 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Tue, 9 Feb 2010 14:44:01 +0000 Subject: [PATCH] Modify Stateful to allow undo to be done using differences in state. git-svn-id: svn://localhost/ardour2/branches/3.0@6664 d708f5d6-7413-0410-9779-e7cbd77b26cf --- gtk2_ardour/editor_drag.cc | 9 +-- libs/ardour/ardour/region.h | 7 ++- libs/ardour/region.cc | 100 +++++++++++++++++-------------- libs/pbd/pbd/stateful.h | 116 +++++++++++++++++++++++++++++++++++- libs/pbd/stateful.cc | 31 ++++++++++ libs/pbd/wscript | 1 + 6 files changed, 208 insertions(+), 56 deletions(-) diff --git a/gtk2_ardour/editor_drag.cc b/gtk2_ardour/editor_drag.cc index 17c09737d2..2dec55a0ad 100644 --- a/gtk2_ardour/editor_drag.cc +++ b/gtk2_ardour/editor_drag.cc @@ -21,6 +21,7 @@ #include #include "pbd/memento_command.h" #include "pbd/basename.h" +#include "pbd/stateful_diff_command.h" #include "ardour/diskstream.h" #include "ardour/region_command.h" #include "ardour/session.h" @@ -950,7 +951,7 @@ RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred) } } else { - RegionCommand* rcmd = new RegionCommand (rv->region()); + rv->region()->clear_history (); /* motion on the same track. plonk the previously reparented region @@ -967,10 +968,8 @@ RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred) boost::shared_ptr playlist = dest_rtv->playlist(); if (dest_rtv->view()->layer_display() == Stacked) { - layer_t old_layer = rv->region()->layer(); rv->region()->set_layer (dest_layer); rv->region()->set_pending_explicit_relayer (true); - rcmd->add_property_change (RegionCommand::Layer, old_layer, dest_layer); } /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */ @@ -981,11 +980,9 @@ RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred) playlist->freeze(); } - nframes64_t old_pos = rv->region()->position(); rv->region()->set_position (where, (void*) this); - rcmd->add_property_change (RegionCommand::Position, old_pos, where); - _editor->session()->add_command (rcmd); + _editor->session()->add_command (new StatefulDiffCommand (rv->region().get())); } if (changed_tracks && !_copy) { diff --git a/libs/ardour/ardour/region.h b/libs/ardour/ardour/region.h index 310bd582cd..7060607a8d 100644 --- a/libs/ardour/ardour/region.h +++ b/libs/ardour/ardour/region.h @@ -315,14 +315,14 @@ class Region DataType _type; Flag _flags; - nframes_t _start; + PBD::State _start; nframes_t _length; nframes_t _last_length; - nframes_t _position; + PBD::State _position; nframes_t _last_position; PositionLockStyle _positional_lock_style; nframes_t _sync_position; - layer_t _layer; + PBD::State _layer; mutable RegionEditState _first_edit; int _frozen; nframes64_t _ancestral_start; @@ -347,6 +347,7 @@ class Region private: + void register_states (); void use_sources (SourceList const &); }; diff --git a/libs/ardour/region.cc b/libs/ardour/region.cc index f1dd92abe5..e9b896f26e 100644 --- a/libs/ardour/region.cc +++ b/libs/ardour/region.cc @@ -56,18 +56,27 @@ Change Region::HiddenChanged = ARDOUR::new_change (); PBD::Signal1 > Region::RegionPropertyChanged; +void +Region::register_states () +{ + _xml_node_name = X_("Region"); + add_state (_start); + add_state (_position); + add_state (_layer); +} + /* derived-from-derived constructor (no sources in constructor) */ Region::Region (Session& s, nframes_t start, nframes_t length, const string& name, DataType type, layer_t layer, Region::Flag flags) : SessionObject(s, name) , _type(type) , _flags(Flag (flags|DoNotSendPropertyChanges)) - , _start(start) + , _start (X_("start"), start) , _length(length) - , _position(0) + , _position (X_("position"), 0) , _last_position(0) , _positional_lock_style(AudioTime) , _sync_position(_start) - , _layer(layer) + , _layer (X_("layer"), layer) , _first_edit(EditChangesNothing) , _frozen(0) , _ancestral_start (0) @@ -79,6 +88,8 @@ Region::Region (Session& s, nframes_t start, nframes_t length, const string& nam , _last_layer_op(0) , _pending_explicit_relayer (false) { + register_states (); + /* no sources at this point */ } @@ -87,13 +98,13 @@ Region::Region (boost::shared_ptr src, nframes_t start, nframes_t length : SessionObject(src->session(), name) , _type(type) , _flags(Flag (flags|DoNotSendPropertyChanges)) - , _start(start) + , _start (X_("start"), start) , _length(length) - , _position(0) + , _position (X_("position"), 0) , _last_position(0) , _positional_lock_style(AudioTime) , _sync_position(_start) - , _layer(layer) + , _layer (X_("layer"), layer) , _first_edit(EditChangesNothing) , _frozen(0) , _ancestral_start (0) @@ -106,6 +117,8 @@ Region::Region (boost::shared_ptr src, nframes_t start, nframes_t length , _last_layer_op(0) , _pending_explicit_relayer (false) { + register_states (); + _sources.push_back (src); _master_sources.push_back (src); @@ -120,13 +133,13 @@ Region::Region (const SourceList& srcs, nframes_t start, nframes_t length, const : SessionObject(srcs.front()->session(), name) , _type(type) , _flags(Flag (flags|DoNotSendPropertyChanges)) - , _start(start) + , _start (X_("start"), start) , _length(length) - , _position(0) + , _position (X_("position"), 0) , _last_position(0) , _positional_lock_style(AudioTime) , _sync_position(_start) - , _layer(layer) + , _layer (X_("layer"), layer) , _first_edit(EditChangesNothing) , _frozen(0) , _ancestral_start (0) @@ -138,6 +151,8 @@ Region::Region (const SourceList& srcs, nframes_t start, nframes_t length, const , _last_layer_op(0) , _pending_explicit_relayer (false) { + register_states (); + use_sources (srcs); assert(_sources.size() > 0); } @@ -146,9 +161,14 @@ Region::Region (const SourceList& srcs, nframes_t start, nframes_t length, const Region::Region (boost::shared_ptr other, nframes_t offset, nframes_t length, const string& name, layer_t layer, Flag flags) : SessionObject(other->session(), name) , _type (other->data_type()) + , _start (X_("start"), 0) + , _position (X_("position"), 0) + , _layer (X_("layer"), 0) , _pending_explicit_relayer (false) { + register_states (); + _start = other->_start + offset; copy_stuff (other, offset, length, name, layer, flags); @@ -186,8 +206,13 @@ Region::Region (boost::shared_ptr other, nframes_t offset, nframes Region::Region (boost::shared_ptr other, nframes_t length, const string& name, layer_t layer, Flag flags) : SessionObject(other->session(), name) , _type (other->data_type()) + , _start (X_("start"), 0) + , _position (X_("position"), 0) + , _layer (X_("layer"), 0) , _pending_explicit_relayer (false) { + register_states (); + /* create a new Region exactly like another but starting at 0 in its sources */ _start = 0; @@ -261,6 +286,8 @@ Region::Region (boost::shared_ptr other) , _last_layer_op(other->_last_layer_op) , _pending_explicit_relayer (false) { + register_states (); + _flags = Flag (_flags | DoNotSendPropertyChanges); other->_first_edit = EditChangesName; @@ -279,13 +306,13 @@ Region::Region (const SourceList& srcs, const XMLNode& node) : SessionObject(srcs.front()->session(), X_("error: XML did not reset this")) , _type(DataType::NIL) // to be loaded from XML , _flags(DoNotSendPropertyChanges) - , _start(0) + , _start (X_("start"), 0) , _length(0) - , _position(0) + , _position (X_("position"), 0) , _last_position(0) , _positional_lock_style(AudioTime) , _sync_position(_start) - , _layer(0) + , _layer (X_("layer"), 0) , _first_edit(EditChangesNothing) , _frozen(0) , _stretch(1.0) @@ -295,6 +322,8 @@ Region::Region (const SourceList& srcs, const XMLNode& node) , _last_layer_op(0) , _pending_explicit_relayer (false) { + register_states (); + use_sources (srcs); if (set_state (node, Stateful::loading_state_version)) { @@ -309,13 +338,13 @@ Region::Region (boost::shared_ptr src, const XMLNode& node) : SessionObject(src->session(), X_("error: XML did not reset this")) , _type(DataType::NIL) , _flags(DoNotSendPropertyChanges) - , _start(0) + , _start (X_("start"), 0) , _length(0) - , _position(0) + , _position (X_("position"), 0) , _last_position(0) , _positional_lock_style(AudioTime) , _sync_position(_start) - , _layer(0) + , _layer (X_("layer"), 0) , _first_edit(EditChangesNothing) , _frozen(0) , _stretch(1.0) @@ -325,6 +354,8 @@ Region::Region (boost::shared_ptr src, const XMLNode& node) , _last_layer_op(0) , _pending_explicit_relayer (false) { + register_states (); + _sources.push_back (src); if (set_state (node, Stateful::loading_state_version)) { @@ -1065,11 +1096,11 @@ Region::state (bool /*full_state*/) node->add_property ("id", buf); node->add_property ("name", _name); node->add_property ("type", _type.to_string()); - snprintf (buf, sizeof (buf), "%u", _start); + snprintf (buf, sizeof (buf), "%u", _start.get ()); node->add_property ("start", buf); snprintf (buf, sizeof (buf), "%u", _length); node->add_property ("length", buf); - snprintf (buf, sizeof (buf), "%u", _position); + snprintf (buf, sizeof (buf), "%u", _position.get ()); node->add_property ("position", buf); snprintf (buf, sizeof (buf), "%" PRIi64, _ancestral_start); node->add_property ("ancestral-start", buf); @@ -1099,7 +1130,7 @@ Region::state (bool /*full_state*/) /* note: flags are stored by derived classes */ - snprintf (buf, sizeof (buf), "%d", (int) _layer); + snprintf (buf, sizeof (buf), "%d", (int) _layer.get()); node->add_property ("layer", buf); snprintf (buf, sizeof (buf), "%" PRIu32, _sync_position); node->add_property ("sync-position", buf); @@ -1131,13 +1162,10 @@ Region::set_live_state (const XMLNode& node, int /*version*/, Change& what_chang that are mutable after construction. */ - if ((prop = node.property ("name")) == 0) { - error << _("XMLNode describing a Region is incomplete (no name)") << endmsg; - return -1; + if ((prop = node.property ("name"))) { + _name = prop->value(); } - _name = prop->value(); - if ((prop = node.property ("type")) == 0) { _type = DataType::AUDIO; } else { @@ -1151,8 +1179,6 @@ Region::set_live_state (const XMLNode& node, int /*version*/, Change& what_chang cerr << _name << " start changed\n"; _start = val; } - } else { - _start = 0; } if ((prop = node.property ("length")) != 0) { @@ -1163,9 +1189,6 @@ Region::set_live_state (const XMLNode& node, int /*version*/, Change& what_chang _last_length = _length; _length = val; } - } else { - _last_length = _length; - _length = 1; } if ((prop = node.property ("position")) != 0) { @@ -1176,9 +1199,6 @@ Region::set_live_state (const XMLNode& node, int /*version*/, Change& what_chang _last_position = _position; _position = val; } - } else { - _last_position = _position; - _position = 0; } if ((prop = node.property ("layer")) != 0) { @@ -1189,8 +1209,6 @@ Region::set_live_state (const XMLNode& node, int /*version*/, Change& what_chang cerr << _name << " layer changed\n"; _layer = x; } - } else { - _layer = 0; } if ((prop = node.property ("sync-position")) != 0) { @@ -1200,8 +1218,6 @@ Region::set_live_state (const XMLNode& node, int /*version*/, Change& what_chang cerr << _name << " sync changed\n"; _sync_position = val; } - } else { - _sync_position = _start; } if ((prop = node.property ("positional-lock-style")) != 0) { @@ -1221,8 +1237,6 @@ Region::set_live_state (const XMLNode& node, int /*version*/, Change& what_chang } } - } else { - _positional_lock_style = AudioTime; } /* XXX FIRST EDIT !!! */ @@ -1270,9 +1284,6 @@ Region::set_live_state (const XMLNode& node, int /*version*/, Change& what_chang /* note: derived classes set flags */ - delete _extra_xml; - _extra_xml = 0; - for (XMLNodeConstIterator niter = nlist.begin(); niter != nlist.end(); ++niter) { XMLNode *child; @@ -1280,6 +1291,7 @@ Region::set_live_state (const XMLNode& node, int /*version*/, Change& what_chang child = (*niter); if (child->name () == "Extra") { + delete _extra_xml; _extra_xml = new XMLNode (*child); break; } @@ -1301,13 +1313,10 @@ Region::set_state (const XMLNode& node, int version) /* ID is not allowed to change, ever */ - if ((prop = node.property ("id")) == 0) { - error << _("Session: XMLNode describing a Region is incomplete (no id)") << endmsg; - return -1; + if ((prop = node.property ("id"))) { + _id = prop->value(); } - _id = prop->value(); - _first_edit = EditChangesNothing; set_live_state (node, version, what_changed, true); @@ -1382,6 +1391,7 @@ Region::send_change (Change what_changed) cerr << _name << " actually sends prop change " << hex << what_changed << dec << " @ " << get_microseconds() << endl; RegionPropertyChanged (rptr); cerr << _name << " done with prop change @ " << get_microseconds() << endl; + } catch (...) { /* no shared_ptr available, relax; */ } diff --git a/libs/pbd/pbd/stateful.h b/libs/pbd/pbd/stateful.h index 0204c8084a..60c1c6b23c 100644 --- a/libs/pbd/pbd/stateful.h +++ b/libs/pbd/pbd/stateful.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2000 Paul Davis + Copyright (C) 2000-2010 Paul Davis This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,7 +21,9 @@ #define __pbd_stateful_h__ #include +#include #include "pbd/id.h" +#include "pbd/xml++.h" class XMLNode; @@ -31,15 +33,119 @@ namespace sys { class path; } +/** Base (non template) part of State */ +class StateBase +{ +public: + StateBase (std::string const & p) + : _have_old (false) + , _xml_property_name (p) + { + + } + + StateBase (StateBase const & s) + : _have_old (s._have_old) + , _xml_property_name (s._xml_property_name) + { + + } + + /** Forget about any old value for this state */ + void clear_history () { + _have_old = false; + } + + virtual void diff (XMLNode *, XMLNode *) const = 0; + +protected: + bool _have_old; + std::string _xml_property_name; +}; + +/** Class to represent a single piece of state in a Stateful object */ +template +class State : public StateBase +{ +public: + State (std::string const & p, T const & v) + : StateBase (p) + , _current (v) + { + + } + + State (State const & s) + : StateBase (s) + { + _current = s._current; + _old = s._old; + } + + State & operator= (State const & s) { + /* XXX: isn't there a nicer place to do this? */ + _have_old = s._have_old; + _xml_property_name = s._xml_property_name; + + _current = s._current; + _old = s._old; + return *this; + } + + T & operator= (T const & v) { + set (v); + return _current; + } + + T & operator+= (T const & v) { + set (_current + v); + return _current; + } + + operator T () const { + return _current; + } + + T const & get () const { + return _current; + } + + void diff (XMLNode* old, XMLNode* current) const { + if (_have_old) { + std::stringstream o; + o << _old; + old->add_property (_xml_property_name.c_str(), o.str().c_str()); + std::stringstream c; + c << _current; + current->add_property (_xml_property_name.c_str(), c.str().c_str()); + } + } + +private: + void set (T const & v) { + _old = _current; + _have_old = true; + _current = v; + } + + T _current; + T _old; +}; + +/** Base class for objects with saveable and undoable state */ class Stateful { public: - Stateful(); + Stateful (); virtual ~Stateful(); virtual XMLNode& get_state (void) = 0; virtual int set_state (const XMLNode&, int version) = 0; + void add_state (StateBase & s) { + _states.push_back (&s); + } + /* Extra XML nodes */ void add_extra_xml (XMLNode&); @@ -47,6 +153,9 @@ class Stateful { const PBD::ID& id() const { return _id; } + void clear_history (); + std::pair diff (); + static int current_state_version; static int loading_state_version; @@ -58,6 +167,9 @@ class Stateful { XMLNode *_extra_xml; XMLNode *_instant_xml; PBD::ID _id; + + std::string _xml_node_name; ///< name of node to use for this object in XML + std::list _states; ///< state variables that this object has }; } // namespace PBD diff --git a/libs/pbd/stateful.cc b/libs/pbd/stateful.cc index 596402576e..9f510b85d4 100644 --- a/libs/pbd/stateful.cc +++ b/libs/pbd/stateful.cc @@ -47,6 +47,10 @@ Stateful::~Stateful () // means it needs to live on indefinately. delete _instant_xml; + + for (list::iterator i = _states.begin(); i != _states.end(); ++i) { + delete *i; + } } void @@ -149,4 +153,31 @@ Stateful::instant_xml (const string& str, const sys::path& directory_path) return 0; } +/** Forget about any old state for this object */ +void +Stateful::clear_history () +{ + for (list::iterator i = _states.begin(); i != _states.end(); ++i) { + (*i)->clear_history (); + } +} + +/** @return A pair of XMLNodes representing state that has changed since the last time clear_history + * was called on this object; the first is the state before, the second the state after. + * + * It is the caller's responsibility to delete the returned XMLNodes. + */ +pair +Stateful::diff () +{ + XMLNode* old = new XMLNode (_xml_node_name); + XMLNode* current = new XMLNode (_xml_node_name); + + for (list::iterator i = _states.begin(); i != _states.end(); ++i) { + (*i)->diff (old, current); + } + + return make_pair (old, current); +} + } // namespace PBD diff --git a/libs/pbd/wscript b/libs/pbd/wscript index 1f458678bd..8bd00bcff9 100644 --- a/libs/pbd/wscript +++ b/libs/pbd/wscript @@ -80,6 +80,7 @@ def build(bld): shortpath.cc signals.cc stacktrace.cc + stateful_diff_command.cc stateful.cc strreplace.cc strsplit.cc -- 2.30.2