diff --git a/libs/pbd/history_owner.cc b/libs/pbd/history_owner.cc new file mode 100644 index 0000000000..9a2b5ecbff --- /dev/null +++ b/libs/pbd/history_owner.cc @@ -0,0 +1,183 @@ +#include +#include + +#include "pbd/command.h" +#include "pbd/compose.h" +#include "pbd/debug.h" +#include "pbd/error.h" +#include "pbd/history_owner.h" +#include "pbd/stateful_diff_command.h" +#include "pbd/undo.h" + +using namespace std; +using namespace PBD; + +HistoryOwner::HistoryOwner (std::string const & str) + : _name (str) + , _current_trans (nullptr) +{ +} + +HistoryOwner::~HistoryOwner() +{ + delete _current_trans; +} + +void +HistoryOwner::add_commands (vector const & cmds) +{ + for (vector::const_iterator i = cmds.begin(); i != cmds.end(); ++i) { + add_command (*i); + } +} + +void +HistoryOwner::add_command (Command* const cmd) +{ + assert (_current_trans); + if (!_current_trans) { + error << "Attempted to add an UNDO command without a current transaction. ignoring command (" << cmd->name() << ")" << endl; + return; + } + DEBUG_TRACE (DEBUG::UndoHistory, + string_compose ("Current Undo Transaction %1, adding command: %2\n", + _current_trans->name (), + cmd->name ())); + _current_trans->add_command (cmd); +} + +PBD::StatefulDiffCommand* +HistoryOwner::add_stateful_diff_command (std::shared_ptr sfd) +{ + PBD::StatefulDiffCommand* cmd = new PBD::StatefulDiffCommand (sfd); + add_command (cmd); + return cmd; +} + +void +HistoryOwner::begin_reversible_command (const string& name) +{ + begin_reversible_command (g_quark_from_string (name.c_str ())); +} + +/** Begin a reversible command using a GQuark to identify it. + * begin_reversible_command() and commit_reversible_command() calls may be nested, + * but there must be as many begin...()s as there are commit...()s. + */ +void +HistoryOwner::begin_reversible_command (GQuark q) +{ + if (_current_trans) { +#ifndef NDEBUG + cerr << "An UNDO transaction was started while a prior command was underway. Aborting command (" << g_quark_to_string (q) << ") and prior (" << _current_trans->name() << ")" << "\n"; +#else + PBD::warning << "An UNDO transaction was started while a prior command was underway. Aborting command (" << g_quark_to_string (q) << ") and prior (" << _current_trans->name() << ")" << endmsg; +#endif + abort_reversible_command(); + assert (false); + return; + } + + /* If nested begin/commit pairs are used, we create just one UndoTransaction + to hold all the commands that are committed. This keeps the order of + commands correct in the history. + */ + + if (_current_trans == 0) { + DEBUG_TRACE (DEBUG::UndoHistory, string_compose ("Begin Reversible Command, new transaction: %1\n", g_quark_to_string (q))); + + /* start a new transaction */ + assert (_current_trans_quarks.empty ()); + _current_trans = new UndoTransaction(); + _current_trans->set_name (g_quark_to_string (q)); + } else { + DEBUG_TRACE (DEBUG::UndoHistory, string_compose ("Begin Reversible Command, current transaction: %1\n", _current_trans->name ())); + } + + _current_trans_quarks.push_front (q); +} + +void +HistoryOwner::abort_reversible_command () +{ + if (!_current_trans) { + return; + } + DEBUG_TRACE (DEBUG::UndoHistory, string_compose ("Abort Reversible Command: %1\n", _current_trans->name ())); + _current_trans->clear(); + delete _current_trans; + _current_trans = nullptr; + _current_trans_quarks.clear(); +} + +bool +HistoryOwner::abort_empty_reversible_command () +{ + if (!collected_undo_commands ()) { + abort_reversible_command (); + return true; + } + return false; +} + +void +HistoryOwner::commit_reversible_command (Command *cmd) +{ + assert (_current_trans); + assert (!_current_trans_quarks.empty ()); + if (!_current_trans) { + return; + } + + struct timeval now; + + if (cmd) { + DEBUG_TRACE (DEBUG::UndoHistory, + string_compose ("Current Undo Transaction %1, adding command: %2\n", + _current_trans->name (), + cmd->name ())); + _current_trans->add_command (cmd); + } + + DEBUG_TRACE (DEBUG::UndoHistory, + string_compose ("Commit Reversible Command, current transaction: %1\n", + _current_trans->name ())); + + _current_trans_quarks.pop_front (); + + if (!_current_trans_quarks.empty ()) { + DEBUG_TRACE (DEBUG::UndoHistory, + string_compose ("Commit Reversible Command, transaction is not " + "top-level, current transaction: %1\n", + _current_trans->name ())); + /* the transaction we're committing is not the top-level one */ + return; + } + + if (_current_trans->empty()) { + /* no commands were added to the transaction, so just get rid of it */ + DEBUG_TRACE (DEBUG::UndoHistory, + string_compose ("Commit Reversible Command, No commands were " + "added to current transaction: %1\n", + _current_trans->name ())); + delete _current_trans; + _current_trans = nullptr; + return; + } + + gettimeofday (&now, 0); + _current_trans->set_timestamp (now); + + DEBUG_TRACE (DEBUG::UndoHistory, + string_compose ("Commit Reversible Command, add to history %1\n", + _current_trans->name ())); + _history.add (_current_trans); + _current_trans = nullptr; +} + +bool +HistoryOwner::operation_in_progress (GQuark op) const +{ + return (find (_current_trans_quarks.begin(), _current_trans_quarks.end(), op) != _current_trans_quarks.end()); +} + diff --git a/libs/pbd/pbd/history_owner.h b/libs/pbd/pbd/history_owner.h new file mode 100644 index 0000000000..e6297f0c16 --- /dev/null +++ b/libs/pbd/pbd/history_owner.h @@ -0,0 +1,102 @@ +#ifndef __libpbd_history_owner_h__ +#define __libpbd_history_owner_h__ + +#include +#include +#include + +#include + +#include "pbd/undo.h" +#include "pbd/libpbd_visibility.h" + +namespace PBD { + class Command; + + +class LIBPBD_API HistoryOwner +{ + public: + HistoryOwner (std::string const & name); + virtual ~HistoryOwner(); + + /** begin collecting undo information + * + * This call must always be followed by either + * begin_reversible_command() or commit_reversible_command() + * + * @param cmd_name human readable name for the undo operation + */ + void begin_reversible_command (const std::string& cmd_name); + void begin_reversible_command (GQuark); + /** abort an open undo command + * This must only be called after begin_reversible_command () + */ + void abort_reversible_command (); + /** finalize an undo command and commit pending transactions + * + * This must only be called after begin_reversible_command () + * @param cmd (additional) command to add + */ + void commit_reversible_command (PBD::Command* cmd = 0); + + void add_command (PBD::Command *const cmd); + + /** create an StatefulDiffCommand from the given object and add it to the stack. + * + * This function must only be called after begin_reversible_command. + * Failing to do so may lead to a crash. + * + * @param sfd the object to diff + * @returns the allocated StatefulDiffCommand (already added via add_command) + */ + PBD::StatefulDiffCommand* add_stateful_diff_command (std::shared_ptr sfd); + + /** @return The list of operations that are currently in progress */ + std::list const & current_operations () { + return _current_trans_quarks; + } + + bool operation_in_progress (GQuark) const; + + /** + * Test if any undo commands were added since the + * call to begin_reversible_command () + * + * This is useful to determine if an undoable + * action was performed before adding additional + * information (e.g. selection changes) to the + * undo transaction. + * + * @return true if undo operation is valid but empty + */ + bool collected_undo_commands () const { + return _current_trans && !_current_trans->empty (); + } + + PBD::UndoTransaction* current_reversible_command() { return _current_trans; } + + /** + * Abort reversible command IFF no undo changes + * have been collected. + * @return true if undo operation was aborted. + */ + bool abort_empty_reversible_command (); + + void add_commands (std::vector const & cmds); + + protected: + std::string _name; + PBD::UndoHistory _history; + /** current undo transaction, or 0 */ + PBD::UndoTransaction* _current_trans; + /** GQuarks to describe the reversible commands that are currently in progress. + * These may be nested, in which case more recently-started commands are toward + * the front of the list. + */ + std::list _current_trans_quarks; +}; + +} /* namespace PBD */ + +#endif /* __libpbd_history_owner_h__ */ diff --git a/libs/pbd/wscript b/libs/pbd/wscript index df92345d40..8380e97d28 100644 --- a/libs/pbd/wscript +++ b/libs/pbd/wscript @@ -43,6 +43,7 @@ libpbd_sources = [ 'file_utils.cc', 'fpu.cc', 'glib_event_source.cc', + 'history_owner.cc', 'id.cc', 'inflater.cc', 'locale_guard.cc',