issue #317: provide undo combination for the paste+move sequence in 'interactive paste'. Same for 'interactive dup'

This commit is contained in:
Matthias Koefferlein 2019-09-04 23:47:05 +02:00
parent 70a4ce82b3
commit fa72885020
8 changed files with 220 additions and 43 deletions

View File

@ -143,6 +143,22 @@ Manager::last_transaction_id () const
return m_transactions.empty () ? 0 : reinterpret_cast<transaction_id_t> (& m_transactions.back ()); return m_transactions.empty () ? 0 : reinterpret_cast<transaction_id_t> (& 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 void
Manager::commit () Manager::commit ()
{ {

View File

@ -140,6 +140,14 @@ public:
*/ */
void commit (); 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 * @brief Undo the current transaction
* *
@ -256,13 +264,20 @@ private:
* *
* This object controls a transaction through it's lifetime. On construction, the * This object controls a transaction through it's lifetime. On construction, the
* transaction is started, on destruction, the transaction is committed. * 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 class DB_PUBLIC Transaction
{ {
public: public:
Transaction (db::Manager *manager, const std::string &desc) 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) { if (mp_manager) {
m_transaction_id = mp_manager->transaction (desc); 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) 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) { if (mp_manager) {
m_transaction_id = mp_manager->transaction (desc, join_with); m_transaction_id = mp_manager->transaction (desc, join_with);
@ -280,11 +295,36 @@ public:
~Transaction () ~Transaction ()
{ {
if (mp_manager) { if (mp_manager) {
if (mp_manager->transacting ()) {
mp_manager->commit (); mp_manager->commit ();
}
mp_manager = 0; 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 db::Manager::transaction_id_t id () const
{ {
return m_transaction_id; return m_transaction_id;
@ -293,6 +333,7 @@ public:
private: private:
db::Manager *mp_manager; db::Manager *mp_manager;
db::Manager::transaction_id_t m_transaction_id; db::Manager::transaction_id_t m_transaction_id;
std::string m_description;
// no copying. // no copying.
Transaction (const Transaction &); Transaction (const Transaction &);

View File

@ -151,6 +151,8 @@ struct B : public db::Object
{ {
if (transacting ()) { if (transacting ()) {
manager ()->queue (this, new BO (d)); manager ()->queue (this, new BO (d));
} else {
x += d;
} }
} }
@ -230,3 +232,84 @@ TEST(2)
EXPECT_EQ (BO::inst_count (), 0); 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);
}

View File

@ -28,6 +28,7 @@
#include "layPropertiesDialog.h" #include "layPropertiesDialog.h"
#include <algorithm> #include <algorithm>
#include <memory>
namespace lay namespace lay
{ {
@ -82,15 +83,18 @@ Editables::~Editables ()
} }
void void
Editables::del () Editables::del (db::Transaction *transaction)
{ {
std::auto_ptr<db::Transaction> trans_holder (transaction ? transaction : new db::Transaction (manager (), tl::to_string (QObject::tr ("Delete"))));
if (selection_size () > 0) { if (selection_size () > 0) {
try {
trans_holder->open ();
cancel_edits (); cancel_edits ();
// begin the transaction
tl_assert (! manager ()->transacting ());
manager ()->transaction (tl::to_string (QObject::tr ("Delete")));
// this dummy operation will update the screen: // this dummy operation will update the screen:
manager ()->queue (this, new db::Op ()); manager ()->queue (this, new db::Op ());
@ -98,8 +102,10 @@ Editables::del ()
e->del (); e->del ();
} }
// end the transaction } catch (...) {
manager ()->commit (); trans_holder->cancel ();
throw;
}
} }
} }
@ -155,14 +161,16 @@ Editables::selection_catch_bbox ()
} }
void void
Editables::transform (const db::DCplxTrans &tr) Editables::transform (const db::DCplxTrans &tr, db::Transaction *transaction)
{ {
std::auto_ptr<db::Transaction> trans_holder (transaction ? transaction : new db::Transaction (manager (), tl::to_string (QObject::tr ("Transform"))));
if (selection_size () > 0) { if (selection_size () > 0) {
try { try {
tl_assert (! manager ()->transacting ()); trans_holder->open ();
manager ()->transaction (tl::to_string (QObject::tr ("Transform")));
// this dummy operation will update the screen: // this dummy operation will update the screen:
manager ()->queue (this, new db::Op ()); manager ()->queue (this, new db::Op ());
@ -170,11 +178,8 @@ Editables::transform (const db::DCplxTrans &tr)
e->transform (tr); e->transform (tr);
} }
// end the transaction
manager ()->commit ();
} catch (...) { } catch (...) {
manager ()->clear (); trans_holder->cancel ();
throw; throw;
} }
@ -525,13 +530,14 @@ Editables::move_transform (const db::DPoint &p, db::DFTrans t, lay::angle_constr
} }
void 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<db::Transaction> trans_holder (transaction ? transaction : new db::Transaction (manager (), tl::to_string (QObject::tr ("Move"))));
if (m_any_move_operation) { if (m_any_move_operation) {
// begin the transaction trans_holder->open ();
tl_assert (! manager ()->transacting ());
manager ()->transaction (tl::to_string (QObject::tr ("Move")));
// this dummy operation will update the screen: // this dummy operation will update the screen:
manager ()->queue (this, new db::Op ()); 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); e->end_move (p, ac);
} }
// end the transaction
manager ()->commit ();
// clear the selection that was set previously // clear the selection that was set previously
if (m_move_selection) { if (m_move_selection) {
clear_selection (); clear_selection ();
@ -549,6 +552,8 @@ Editables::end_move (const db::DPoint &p, lay::angle_constraint_type ac)
} else { } else {
trans_holder->cancel ();
// if nothing was moved, treat the end_move as a select which makes the move sticky or // 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 // replaces a complex selection by a simple one
edit_cancel (); edit_cancel ();

View File

@ -32,6 +32,7 @@
#include "dbPoint.h" #include "dbPoint.h"
#include "dbBox.h" #include "dbBox.h"
#include "dbObject.h" #include "dbObject.h"
#include "dbManager.h"
#include <set> #include <set>
#include <limits> #include <limits>
@ -386,8 +387,11 @@ public:
/** /**
* @brief The delete operation * @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 * @brief "cut" operation
@ -419,8 +423,11 @@ public:
* @brief transform the selection * @brief transform the selection
* *
* The transformation is given in micron units. * 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 * @brief Enable or disable a certain editable
@ -494,8 +501,11 @@ public:
/** /**
* @brief End "move" operation * @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. * @brief Tell how many objects are selected.

View File

@ -5090,9 +5090,9 @@ LayoutView::paste_interactive ()
{ {
clear_selection (); clear_selection ();
{ std::auto_ptr<db::Transaction> trans (new db::Transaction (manager (), tl::to_string (QObject::tr ("Paste and move"))));
db::Transaction trans (manager (), tl::to_string (QObject::tr ("Paste")));
{
// let the receivers sort out who is pasting what .. // let the receivers sort out who is pasting what ..
if (mp_hierarchy_panel) { if (mp_hierarchy_panel) {
mp_hierarchy_panel->paste (); mp_hierarchy_panel->paste ();
@ -5103,7 +5103,11 @@ LayoutView::paste_interactive ()
lay::Editables::paste (); 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 switch_mode (-1); // move mode
} }
} }

View File

@ -163,7 +163,7 @@ MoveService::mouse_click_event (const db::DPoint &p, unsigned int buttons, bool
return true; return true;
} }
if (prio && (buttons & lay::LeftButton) != 0) { if (prio && (buttons & lay::LeftButton) != 0) {
if (handle_dragging (p, buttons)) { if (handle_dragging (p, buttons, 0)) {
return true; return true;
} }
} }
@ -216,7 +216,7 @@ bool
MoveService::mouse_press_event (const db::DPoint &p, unsigned int buttons, bool prio) MoveService::mouse_press_event (const db::DPoint &p, unsigned int buttons, bool prio)
{ {
if (prio && (buttons & lay::LeftButton) != 0) { if (prio && (buttons & lay::LeftButton) != 0) {
if (handle_dragging (p, buttons)) { if (handle_dragging (p, buttons, 0)) {
return true; return true;
} }
} }
@ -230,8 +230,10 @@ MoveService::mouse_press_event (const db::DPoint &p, unsigned int buttons, bool
} }
bool bool
MoveService::begin_move () MoveService::begin_move (db::Transaction *transaction)
{ {
std::auto_ptr<db::Transaction> trans_holder (transaction);
drag_cancel (); drag_cancel ();
db::DBox bbox = mp_editables->selection_bbox (); db::DBox bbox = mp_editables->selection_bbox ();
@ -243,14 +245,18 @@ MoveService::begin_move ()
set_cursor (lay::Cursor::size_all); set_cursor (lay::Cursor::size_all);
// emulate a "begin move" at the center of the selection bbox - this will become the reference point // 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 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<db::Transaction> trans_holder (transaction);
if (! m_dragging) { if (! m_dragging) {
mp_transaction.reset (trans_holder.release ());
if (mp_editables->begin_move (p, ac_from_buttons (buttons))) { if (mp_editables->begin_move (p, ac_from_buttons (buttons))) {
lay::SelectionService *selector = mp_view->selection_service (); lay::SelectionService *selector = mp_view->selection_service ();
@ -273,7 +279,7 @@ MoveService::handle_dragging (const db::DPoint &p, unsigned int buttons)
m_dragging = false; m_dragging = false;
widget ()->ungrab_mouse (this); 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; return true;
} }
@ -285,9 +291,17 @@ MoveService::drag_cancel ()
{ {
m_shift = db::DPoint (); m_shift = db::DPoint ();
if (m_dragging) { if (m_dragging) {
mp_editables->edit_cancel (); mp_editables->edit_cancel ();
widget ()->ungrab_mouse (this); widget ()->ungrab_mouse (this);
m_dragging = false; m_dragging = false;
if (mp_transaction.get ()) {
mp_transaction->cancel ();
}
mp_transaction.reset (0);
} }
} }

View File

@ -25,11 +25,14 @@
#ifndef HDR_layMove #ifndef HDR_layMove
#define HDR_layMove #define HDR_layMove
#include "dbManager.h"
#include "layViewObject.h" #include "layViewObject.h"
#include <QTimer> #include <QTimer>
#include <QObject> #include <QObject>
#include <memory>
namespace lay { namespace lay {
class Editables; class Editables;
@ -46,7 +49,7 @@ public:
~MoveService (); ~MoveService ();
virtual bool configure (const std::string &name, const std::string &value); virtual bool configure (const std::string &name, const std::string &value);
bool begin_move (); bool begin_move (db::Transaction *transaction = 0);
private: private:
virtual bool mouse_press_event (const db::DPoint &p, unsigned int buttons, bool prio); 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 drag_cancel ();
virtual void deactivated (); 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; bool m_dragging;
lay::Editables *mp_editables; lay::Editables *mp_editables;
lay::LayoutView *mp_view; lay::LayoutView *mp_view;
double m_global_grid; double m_global_grid;
db::DPoint m_shift; db::DPoint m_shift;
std::auto_ptr<db::Transaction> mp_transaction;
}; };
} }