X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fpbd%2Fundo.cc;h=42f7d574ea4030f44f486215901dedba64c0af25;hb=9907d25ea572f008fcb626eb7cc7ffa4cf9e1d82;hp=f2f11b1c5cc3fc4b9546f50ebafc89ca76a92540;hpb=22c20ab6f215c0ab24702a479aa6821c25a7d058;p=ardour.git diff --git a/libs/pbd/undo.cc b/libs/pbd/undo.cc index f2f11b1c5c..42f7d574ea 100644 --- a/libs/pbd/undo.cc +++ b/libs/pbd/undo.cc @@ -18,129 +18,347 @@ $Id$ */ -#include +#include +#include +#include -#include +#include "pbd/undo.h" +#include "pbd/xml++.h" + +#include using namespace std; using namespace sigc; -UndoCommand::UndoCommand () +UndoTransaction::UndoTransaction () + : _clearing(false) { + gettimeofday (&_timestamp, 0); } -UndoCommand::UndoCommand (const UndoCommand& rhs) +UndoTransaction::UndoTransaction (const UndoTransaction& rhs) + : Command(rhs._name) + , _clearing(false) { - _name = rhs._name; + _timestamp = rhs._timestamp; + clear (); + actions.insert(actions.end(),rhs.actions.begin(),rhs.actions.end()); +} + +UndoTransaction::~UndoTransaction () +{ + drop_references (); clear (); - undo_actions.insert(undo_actions.end(),rhs.undo_actions.begin(),rhs.undo_actions.end()); - redo_actions.insert(redo_actions.end(),rhs.redo_actions.begin(),rhs.redo_actions.end()); } -UndoCommand& -UndoCommand::operator= (const UndoCommand& rhs) +void +command_death (UndoTransaction* ut, Command* c) +{ + if (ut->clearing()) { + return; + } + + ut->remove_command (c); + + if (ut->empty()) { + delete ut; + } +} + +UndoTransaction& +UndoTransaction::operator= (const UndoTransaction& rhs) { if (this == &rhs) return *this; _name = rhs._name; clear (); - undo_actions.insert(undo_actions.end(),rhs.undo_actions.begin(),rhs.undo_actions.end()); - redo_actions.insert(redo_actions.end(),rhs.redo_actions.begin(),rhs.redo_actions.end()); + actions.insert(actions.end(),rhs.actions.begin(),rhs.actions.end()); return *this; } void -UndoCommand::add_undo (const UndoAction& action) +UndoTransaction::add_command (Command *const cmd) { - undo_actions.push_back (action); + /* catch death of command (e.g. caused by death of object to + which it refers. command_death() is a normal static function + so there is no need to manage this connection. + */ + + cmd->DropReferences.connect_same_thread (*this, boost::bind (&command_death, this, cmd)); + actions.push_back (cmd); } void -UndoCommand::add_redo (const UndoAction& action) +UndoTransaction::remove_command (Command* const action) +{ + actions.remove (action); +} + +bool +UndoTransaction::empty () const { - redo_actions.push_back (action); - redo_actions.back()(); // operator() + return actions.empty(); } void -UndoCommand::add_redo_no_execute (const UndoAction& action) +UndoTransaction::clear () { - redo_actions.push_back (action); + _clearing = true; + for (list::iterator i = actions.begin(); i != actions.end(); ++i) { + delete *i; + } + actions.clear (); + _clearing = false; } void -UndoCommand::clear () +UndoTransaction::operator() () { - undo_actions.clear (); - redo_actions.clear (); + for (list::iterator i = actions.begin(); i != actions.end(); ++i) { + (*(*i))(); + } } void -UndoCommand::undo () +UndoTransaction::undo () { - cerr << "Undo " << _name << endl; - for (list::reverse_iterator i = undo_actions.rbegin(); i != undo_actions.rend(); ++i) { - (*i)(); + for (list::reverse_iterator i = actions.rbegin(); i != actions.rend(); ++i) { + (*i)->undo(); + } +} + +void +UndoTransaction::redo () +{ + (*this)(); +} + +XMLNode &UndoTransaction::get_state() +{ + XMLNode *node = new XMLNode ("UndoTransaction"); + stringstream ss; + ss << _timestamp.tv_sec; + node->add_property("tv_sec", ss.str()); + ss.str(""); + ss << _timestamp.tv_usec; + node->add_property("tv_usec", ss.str()); + node->add_property("name", _name); + + list::iterator it; + for (it=actions.begin(); it!=actions.end(); it++) + node->add_child_nocopy((*it)->get_state()); + + return *node; +} + +class UndoRedoSignaller { +public: + UndoRedoSignaller (UndoHistory& uh) + : _history (uh) { + _history.BeginUndoRedo(); + } + ~UndoRedoSignaller() { + _history.EndUndoRedo(); + } + +private: + UndoHistory& _history; +}; + +UndoHistory::UndoHistory () +{ + _clearing = false; + _depth = 0; +} + +void +UndoHistory::set_depth (uint32_t d) +{ + UndoTransaction* ut; + uint32_t current_depth = UndoList.size(); + + _depth = d; + + if (d > current_depth) { + /* not even transactions to meet request */ + return; + } + + if (_depth > 0) { + + uint32_t cnt = current_depth - d; + + while (cnt--) { + ut = UndoList.front(); + UndoList.pop_front (); + delete ut; + } } } void -UndoCommand::redo () +UndoHistory::add (UndoTransaction* const ut) { - cerr << "Redo " << _name << endl; - for (list::iterator i = redo_actions.begin(); i != redo_actions.end(); ++i) { - (*i)(); + uint32_t current_depth = UndoList.size(); + + ut->DropReferences.connect_same_thread (*this, boost::bind (&UndoHistory::remove, this, ut)); + + /* if the current undo history is larger than or equal to the currently + requested depth, then pop off at least 1 element to make space + at the back for new one. + */ + + if ((_depth > 0) && current_depth && (current_depth >= _depth)) { + + uint32_t cnt = 1 + (current_depth - _depth); + + while (cnt--) { + UndoTransaction* ut; + ut = UndoList.front (); + UndoList.pop_front (); + delete ut; + } } + + UndoList.push_back (ut); + + /* we are now owners of the transaction and must delete it when finished with it */ + + Changed (); /* EMIT SIGNAL */ } void -UndoHistory::add (UndoCommand uc) +UndoHistory::remove (UndoTransaction* const ut) { - UndoList.push_back (uc); + if (_clearing) { + return; + } + + UndoList.remove (ut); + RedoList.remove (ut); + + Changed (); /* EMIT SIGNAL */ } +/** Undo some transactions. + * @param n Number of transactions to undo. + */ void UndoHistory::undo (unsigned int n) { - while (n--) { - if (UndoList.size() == 0) { - return; + if (n == 0) { + return; + } + + { + UndoRedoSignaller exception_safe_signaller (*this); + + while (n--) { + if (UndoList.size() == 0) { + return; + } + UndoTransaction* ut = UndoList.back (); + UndoList.pop_back (); + ut->undo (); + RedoList.push_back (ut); } - UndoCommand uc = UndoList.back (); - UndoList.pop_back (); - uc.undo (); - RedoList.push_back (uc); } + + Changed (); /* EMIT SIGNAL */ } void UndoHistory::redo (unsigned int n) { - while (n--) { - if (RedoList.size() == 0) { - return; + if (n == 0) { + return; + } + + { + UndoRedoSignaller exception_safe_signaller (*this); + + while (n--) { + if (RedoList.size() == 0) { + return; + } + UndoTransaction* ut = RedoList.back (); + RedoList.pop_back (); + ut->redo (); + UndoList.push_back (ut); } - UndoCommand cmd = RedoList.back (); - RedoList.pop_back (); - cmd.redo (); - UndoList.push_back (cmd); } + + Changed (); /* EMIT SIGNAL */ } void UndoHistory::clear_redo () { + _clearing = true; + for (std::list::iterator i = RedoList.begin(); i != RedoList.end(); ++i) { + delete *i; + } RedoList.clear (); + _clearing = false; + + Changed (); /* EMIT SIGNAL */ + } void UndoHistory::clear_undo () { + _clearing = true; + for (std::list::iterator i = UndoList.begin(); i != UndoList.end(); ++i) { + delete *i; + } UndoList.clear (); + _clearing = false; + + Changed (); /* EMIT SIGNAL */ } void UndoHistory::clear () { - RedoList.clear (); - UndoList.clear (); + clear_undo (); + clear_redo (); + + Changed (); /* EMIT SIGNAL */ } + +XMLNode& +UndoHistory::get_state (int32_t depth) +{ + XMLNode *node = new XMLNode ("UndoHistory"); + + if (depth == 0) { + + return (*node); + + } else if (depth < 0) { + + /* everything */ + + for (list::iterator it = UndoList.begin(); it != UndoList.end(); ++it) { + node->add_child_nocopy((*it)->get_state()); + } + + } else { + + /* just the last "depth" transactions */ + + list in_order; + + for (list::reverse_iterator it = UndoList.rbegin(); it != UndoList.rend() && depth; ++it, depth--) { + in_order.push_front (*it); + } + + for (list::iterator it = in_order.begin(); it != in_order.end(); it++) { + node->add_child_nocopy((*it)->get_state()); + } + } + + return *node; +} + +