diff --git a/src/db/db/dbManager.cc b/src/db/db/dbManager.cc index 51dec8ab9..b7f842394 100644 --- a/src/db/db/dbManager.cc +++ b/src/db/db/dbManager.cc @@ -143,6 +143,22 @@ Manager::last_transaction_id () const return m_transactions.empty () ? 0 : reinterpret_cast (& m_transactions.back ()); } +void +Manager::cancel () +{ + if (db::transactions_enabled ()) { + + // commit and undo - revert changes done so far + commit (); + undo (); + + // delete all following transactions + erase_transactions (m_current, m_transactions.end ()); + m_current = m_transactions.end (); + + } +} + void Manager::commit () { diff --git a/src/db/db/dbManager.h b/src/db/db/dbManager.h index 014fb49c4..5dfa2fb02 100644 --- a/src/db/db/dbManager.h +++ b/src/db/db/dbManager.h @@ -140,6 +140,14 @@ public: */ void commit (); + /** + * @brief Cancels a transaction + * + * If called instead of commit, this method will undo all operations of the pending + * transaction. + */ + void cancel (); + /** * @brief Undo the current transaction * @@ -256,13 +264,20 @@ private: * * This object controls a transaction through it's lifetime. On construction, the * transaction is started, on destruction, the transaction is committed. + * + * "cancel" can be used to cancel the operation. This will undo all operations collected + * so far and delete the transaction. + * + * "close" temporarily disable the collection of operations. + * "open" will enable operation collection again and continue + * collection at the point when it was stopped with "close". */ class DB_PUBLIC Transaction { public: Transaction (db::Manager *manager, const std::string &desc) - : mp_manager (manager), m_transaction_id (0) + : mp_manager (manager), m_transaction_id (0), m_description (desc) { if (mp_manager) { m_transaction_id = mp_manager->transaction (desc); @@ -270,7 +285,7 @@ public: } Transaction (db::Manager *manager, const std::string &desc, db::Manager::transaction_id_t join_with) - : mp_manager (manager), m_transaction_id (0) + : mp_manager (manager), m_transaction_id (0), m_description (desc) { if (mp_manager) { m_transaction_id = mp_manager->transaction (desc, join_with); @@ -280,11 +295,36 @@ public: ~Transaction () { if (mp_manager) { - mp_manager->commit (); + if (mp_manager->transacting ()) { + mp_manager->commit (); + } mp_manager = 0; } } + void cancel () + { + if (mp_manager) { + open (); + mp_manager->cancel (); + mp_manager = 0; + } + } + + void close () + { + if (mp_manager->transacting ()) { + mp_manager->commit (); + } + } + + void open () + { + if (! mp_manager->transacting ()) { + mp_manager->transaction (m_description, m_transaction_id); + } + } + db::Manager::transaction_id_t id () const { return m_transaction_id; @@ -293,6 +333,7 @@ public: private: db::Manager *mp_manager; db::Manager::transaction_id_t m_transaction_id; + std::string m_description; // no copying. Transaction (const Transaction &); diff --git a/src/db/unit_tests/dbObject.cc b/src/db/unit_tests/dbObject.cc index 036bee571..aa7c434ed 100644 --- a/src/db/unit_tests/dbObject.cc +++ b/src/db/unit_tests/dbObject.cc @@ -151,6 +151,8 @@ struct B : public db::Object { if (transacting ()) { manager ()->queue (this, new BO (d)); + } else { + x += d; } } @@ -230,3 +232,84 @@ TEST(2) EXPECT_EQ (BO::inst_count (), 0); } +TEST(3) +{ + db::Manager *man = new db::Manager (); + { + EXPECT_EQ (man->available_undo ().first, false); + EXPECT_EQ (man->available_redo ().first, false); + + B b (man); + man->transaction ("add 1"); + b.add (1); + man->commit (); + + EXPECT_EQ (b.x, 1); + EXPECT_EQ (man->available_undo ().first, true); + EXPECT_EQ (man->available_undo ().second, "add 1"); + + man->transaction ("add 1,2"); + b.add (1); + b.add (2); + man->cancel (); + EXPECT_EQ (b.x, 1); + EXPECT_EQ (man->available_undo ().first, true); + EXPECT_EQ (man->available_redo ().first, false); + + man->undo (); + EXPECT_EQ (b.x, 0); + EXPECT_EQ (man->available_undo ().first, false); + EXPECT_EQ (man->available_redo ().first, true); + } + + delete man; + EXPECT_EQ (BO::inst_count (), 0); +} + +TEST(4) +{ + db::Manager *man = new db::Manager (); + { + EXPECT_EQ (man->available_undo ().first, false); + EXPECT_EQ (man->available_redo ().first, false); + + B b (man); + { + db::Transaction t (man, "add 1"); + b.add (1); + } + + EXPECT_EQ (b.x, 1); + EXPECT_EQ (man->available_undo ().first, true); + EXPECT_EQ (man->available_undo ().second, "add 1"); + + { + db::Transaction t (man, "add 1,2"); + b.add (1); + EXPECT_EQ (b.x, 2); + EXPECT_EQ (man->transacting (), true); + t.close (); + EXPECT_EQ (man->transacting (), false); + b.add (1); // after close -> not undone! + EXPECT_EQ (b.x, 3); + t.open (); + EXPECT_EQ (man->transacting (), true); + b.add (2); + EXPECT_EQ (b.x, 5); + t.cancel (); + } + + EXPECT_EQ (b.x, 2); + EXPECT_EQ (man->available_undo ().first, true); + EXPECT_EQ (man->available_redo ().first, false); + + man->undo (); + EXPECT_EQ (b.x, 1); + EXPECT_EQ (man->available_undo ().first, false); + EXPECT_EQ (man->available_redo ().first, true); + } + + delete man; + EXPECT_EQ (BO::inst_count (), 0); +} + diff --git a/src/laybasic/laybasic/layEditable.cc b/src/laybasic/laybasic/layEditable.cc index d2dff0e4f..e85c67087 100644 --- a/src/laybasic/laybasic/layEditable.cc +++ b/src/laybasic/laybasic/layEditable.cc @@ -28,6 +28,7 @@ #include "layPropertiesDialog.h" #include +#include namespace lay { @@ -82,25 +83,30 @@ Editables::~Editables () } void -Editables::del () +Editables::del (db::Transaction *transaction) { + std::auto_ptr trans_holder (transaction ? transaction : new db::Transaction (manager (), tl::to_string (QObject::tr ("Delete")))); + if (selection_size () > 0) { - cancel_edits (); + try { - // begin the transaction - tl_assert (! manager ()->transacting ()); - manager ()->transaction (tl::to_string (QObject::tr ("Delete"))); - // this dummy operation will update the screen: - manager ()->queue (this, new db::Op ()); + trans_holder->open (); - for (iterator e = begin (); e != end (); ++e) { - e->del (); + cancel_edits (); + + // this dummy operation will update the screen: + manager ()->queue (this, new db::Op ()); + + for (iterator e = begin (); e != end (); ++e) { + e->del (); + } + + } catch (...) { + trans_holder->cancel (); + throw; } - // end the transaction - manager ()->commit (); - } } @@ -155,14 +161,16 @@ Editables::selection_catch_bbox () } void -Editables::transform (const db::DCplxTrans &tr) +Editables::transform (const db::DCplxTrans &tr, db::Transaction *transaction) { + std::auto_ptr trans_holder (transaction ? transaction : new db::Transaction (manager (), tl::to_string (QObject::tr ("Transform")))); + if (selection_size () > 0) { try { - tl_assert (! manager ()->transacting ()); - manager ()->transaction (tl::to_string (QObject::tr ("Transform"))); + trans_holder->open (); + // this dummy operation will update the screen: manager ()->queue (this, new db::Op ()); @@ -170,11 +178,8 @@ Editables::transform (const db::DCplxTrans &tr) e->transform (tr); } - // end the transaction - manager ()->commit (); - } catch (...) { - manager ()->clear (); + trans_holder->cancel (); throw; } @@ -525,13 +530,14 @@ Editables::move_transform (const db::DPoint &p, db::DFTrans t, lay::angle_constr } void -Editables::end_move (const db::DPoint &p, lay::angle_constraint_type ac) +Editables::end_move (const db::DPoint &p, lay::angle_constraint_type ac, db::Transaction *transaction) { + std::auto_ptr trans_holder (transaction ? transaction : new db::Transaction (manager (), tl::to_string (QObject::tr ("Move")))); + if (m_any_move_operation) { - // begin the transaction - tl_assert (! manager ()->transacting ()); - manager ()->transaction (tl::to_string (QObject::tr ("Move"))); + trans_holder->open (); + // this dummy operation will update the screen: manager ()->queue (this, new db::Op ()); @@ -539,9 +545,6 @@ Editables::end_move (const db::DPoint &p, lay::angle_constraint_type ac) e->end_move (p, ac); } - // end the transaction - manager ()->commit (); - // clear the selection that was set previously if (m_move_selection) { clear_selection (); @@ -549,6 +552,8 @@ Editables::end_move (const db::DPoint &p, lay::angle_constraint_type ac) } else { + trans_holder->cancel (); + // if nothing was moved, treat the end_move as a select which makes the move sticky or // replaces a complex selection by a simple one edit_cancel (); diff --git a/src/laybasic/laybasic/layEditable.h b/src/laybasic/laybasic/layEditable.h index 6b48aa169..18d184521 100644 --- a/src/laybasic/laybasic/layEditable.h +++ b/src/laybasic/laybasic/layEditable.h @@ -32,6 +32,7 @@ #include "dbPoint.h" #include "dbBox.h" #include "dbObject.h" +#include "dbManager.h" #include #include @@ -386,8 +387,11 @@ public: /** * @brief The delete operation + * + * If a transaction is given, the operation will be appended to this pending transaction + * The Editables object takes ownership over the Transaction object. */ - void del (); + void del (db::Transaction *transaction = 0); /** * @brief "cut" operation @@ -419,8 +423,11 @@ public: * @brief transform the selection * * The transformation is given in micron units. + * + * If a transaction is given, the operation will be appended to this pending transaction. + * The Editables object takes ownership over the Transaction object. */ - void transform (const db::DCplxTrans &tr); + void transform (const db::DCplxTrans &tr, db::Transaction *transaction = 0); /** * @brief Enable or disable a certain editable @@ -494,8 +501,11 @@ public: /** * @brief End "move" operation + * + * If a transaction is given, the operation will be appended to this pending transaction + * The Editables object takes ownership over the Transaction object. */ - void end_move (const db::DPoint &p, lay::angle_constraint_type ac); + void end_move (const db::DPoint &p, lay::angle_constraint_type ac, db::Transaction *transaction = 0); /** * @brief Tell how many objects are selected. diff --git a/src/laybasic/laybasic/layLayoutView.cc b/src/laybasic/laybasic/layLayoutView.cc index 3f0c2ce78..31e7709a8 100644 --- a/src/laybasic/laybasic/layLayoutView.cc +++ b/src/laybasic/laybasic/layLayoutView.cc @@ -5090,9 +5090,9 @@ LayoutView::paste_interactive () { clear_selection (); - { - db::Transaction trans (manager (), tl::to_string (QObject::tr ("Paste"))); + std::auto_ptr trans (new db::Transaction (manager (), tl::to_string (QObject::tr ("Paste and move")))); + { // let the receivers sort out who is pasting what .. if (mp_hierarchy_panel) { mp_hierarchy_panel->paste (); @@ -5103,7 +5103,11 @@ LayoutView::paste_interactive () lay::Editables::paste (); } - if (mp_move_service->begin_move ()) { + // temporarily close the transaction and pass to the move service for appending it's own + // operations. + trans->close (); + + if (mp_move_service->begin_move (trans.release ())) { switch_mode (-1); // move mode } } diff --git a/src/laybasic/laybasic/layMove.cc b/src/laybasic/laybasic/layMove.cc index 7a6a5e708..8e366293c 100644 --- a/src/laybasic/laybasic/layMove.cc +++ b/src/laybasic/laybasic/layMove.cc @@ -163,7 +163,7 @@ MoveService::mouse_click_event (const db::DPoint &p, unsigned int buttons, bool return true; } if (prio && (buttons & lay::LeftButton) != 0) { - if (handle_dragging (p, buttons)) { + if (handle_dragging (p, buttons, 0)) { return true; } } @@ -216,7 +216,7 @@ bool MoveService::mouse_press_event (const db::DPoint &p, unsigned int buttons, bool prio) { if (prio && (buttons & lay::LeftButton) != 0) { - if (handle_dragging (p, buttons)) { + if (handle_dragging (p, buttons, 0)) { return true; } } @@ -230,8 +230,10 @@ MoveService::mouse_press_event (const db::DPoint &p, unsigned int buttons, bool } bool -MoveService::begin_move () +MoveService::begin_move (db::Transaction *transaction) { + std::auto_ptr trans_holder (transaction); + drag_cancel (); db::DBox bbox = mp_editables->selection_bbox (); @@ -243,14 +245,18 @@ MoveService::begin_move () set_cursor (lay::Cursor::size_all); // emulate a "begin move" at the center of the selection bbox - this will become the reference point - return handle_dragging (bbox.center (), 0); + return handle_dragging (bbox.center (), 0, trans_holder.release ()); } bool -MoveService::handle_dragging (const db::DPoint &p, unsigned int buttons) +MoveService::handle_dragging (const db::DPoint &p, unsigned int buttons, db::Transaction *transaction) { + std::auto_ptr trans_holder (transaction); + if (! m_dragging) { + mp_transaction.reset (trans_holder.release ()); + if (mp_editables->begin_move (p, ac_from_buttons (buttons))) { lay::SelectionService *selector = mp_view->selection_service (); @@ -273,7 +279,7 @@ MoveService::handle_dragging (const db::DPoint &p, unsigned int buttons) m_dragging = false; widget ()->ungrab_mouse (this); - mp_editables->end_move (p, ac_from_buttons (buttons)); + mp_editables->end_move (p, ac_from_buttons (buttons), mp_transaction.release ()); return true; } @@ -285,9 +291,17 @@ MoveService::drag_cancel () { m_shift = db::DPoint (); if (m_dragging) { + mp_editables->edit_cancel (); widget ()->ungrab_mouse (this); + m_dragging = false; + + if (mp_transaction.get ()) { + mp_transaction->cancel (); + } + mp_transaction.reset (0); + } } diff --git a/src/laybasic/laybasic/layMove.h b/src/laybasic/laybasic/layMove.h index 0c12efaf2..b3bf39b2e 100644 --- a/src/laybasic/laybasic/layMove.h +++ b/src/laybasic/laybasic/layMove.h @@ -25,11 +25,14 @@ #ifndef HDR_layMove #define HDR_layMove +#include "dbManager.h" #include "layViewObject.h" #include #include +#include + namespace lay { class Editables; @@ -46,7 +49,7 @@ public: ~MoveService (); virtual bool configure (const std::string &name, const std::string &value); - bool begin_move (); + bool begin_move (db::Transaction *transaction = 0); private: virtual bool mouse_press_event (const db::DPoint &p, unsigned int buttons, bool prio); @@ -59,13 +62,14 @@ private: virtual void drag_cancel (); virtual void deactivated (); - bool handle_dragging (const db::DPoint &p, unsigned int buttons); + bool handle_dragging (const db::DPoint &p, unsigned int buttons, db::Transaction *transaction); bool m_dragging; lay::Editables *mp_editables; lay::LayoutView *mp_view; double m_global_grid; db::DPoint m_shift; + std::auto_ptr mp_transaction; }; }