From 6f77c43d7686f1cdf037e51211e154356ff545f9 Mon Sep 17 00:00:00 2001 From: Rebecca Breu Date: Fri, 16 Apr 2021 22:28:13 +0200 Subject: [PATCH] Add actions for undoing transformations --- beeref/actions/actions.py | 29 +++++++++ beeref/actions/menu_structure.py | 5 ++ beeref/commands.py | 81 ++++++++++++++++++++++++ beeref/selection.py | 6 +- beeref/view.py | 16 +++++ tests/test_commands.py | 104 +++++++++++++++++++++++++++++++ 6 files changed, 240 insertions(+), 1 deletion(-) diff --git a/beeref/actions/actions.py b/beeref/actions/actions.py index a88a348..db58a76 100644 --- a/beeref/actions/actions.py +++ b/beeref/actions/actions.py @@ -134,4 +134,33 @@ actions = [ 'group': 'active_when_selection', 'enabled': False, }, + { + 'id': 'reset_scale', + 'text': 'Reset &Scale', + 'callback': 'on_action_reset_scale', + 'group': 'active_when_selection', + 'enabled': False, + }, + { + 'id': 'reset_rotation', + 'text': 'Reset &Rotation', + 'callback': 'on_action_reset_rotation', + 'group': 'active_when_selection', + 'enabled': False, + }, + { + 'id': 'reset_flip', + 'text': 'Reset &Flip', + 'callback': 'on_action_reset_flip', + 'group': 'active_when_selection', + 'enabled': False, + }, + { + 'id': 'reset_transforms', + 'text': 'Reset &All', + 'shortcuts': ['R'], + 'callback': 'on_action_reset_transforms', + 'group': 'active_when_selection', + 'enabled': False, + }, ] diff --git a/beeref/actions/menu_structure.py b/beeref/actions/menu_structure.py index fbbf146..3949878 100644 --- a/beeref/actions/menu_structure.py +++ b/beeref/actions/menu_structure.py @@ -50,6 +50,11 @@ menu_structure = [ 'items': [ 'flip_horizontally', 'flip_vertically', + MENU_SEPARATOR, + 'reset_scale', + 'reset_rotation', + 'reset_flip', + 'reset_transforms', ], }, { diff --git a/beeref/commands.py b/beeref/commands.py index 2383bb0..23f0f4e 100644 --- a/beeref/commands.py +++ b/beeref/commands.py @@ -160,3 +160,84 @@ class FlipItems(QtGui.QUndoCommand): def undo(self): self.redo() + + +class ResetScale(QtGui.QUndoCommand): + + def __init__(self, items): + super().__init__('Reset Scale') + self.items = items + + def redo(self): + self.old_scale_factors = [] + for item in self.items: + self.old_scale_factors.append(item.scale()) + item.setScale(1, anchor=item.center) + + def undo(self): + for item, scale_factor in zip(self.items, self.old_scale_factors): + item.setScale(scale_factor, anchor=item.center) + + +class ResetRotation(QtGui.QUndoCommand): + + def __init__(self, items): + super().__init__('Reset Rotation') + self.items = items + + def redo(self): + self.old_rotations = [] + for item in self.items: + self.old_rotations.append(item.rotation()) + item.setRotation(0, anchor=item.center) + + def undo(self): + for item, rotation in zip(self.items, self.old_rotations): + item.setRotation(rotation, anchor=item.center) + + +class ResetFlip(QtGui.QUndoCommand): + + def __init__(self, items): + super().__init__('Reset Flip') + self.items = items + + def redo(self): + self.old_flips = [] + for item in self.items: + self.old_flips.append(item.flip()) + if item.flip() == -1: + item.do_flip(anchor=item.center) + + def undo(self): + for item, flip in zip(self.items, self.old_flips): + if flip == -1: + item.do_flip(anchor=item.center) + + +class ResetTransforms(QtGui.QUndoCommand): + + def __init__(self, items): + super().__init__('Reset All Transformations') + self.items = items + + def redo(self): + self.old_values = [] + for item in self.items: + self.old_values.append({ + 'scale': item.scale(), + 'rotation': item.rotation(), + 'flip': item.flip(), + }) + + item.setScale(1, anchor=item.center) + item.setRotation(0, anchor=item.center) + if item.flip() == -1: + item.do_flip(anchor=item.center) + + def undo(self): + for item, old in zip(self.items, self.old_values): + item.setScale(old['scale'], anchor=item.center) + item.setRotation(old['rotation'], anchor=item.center) + if old['flip'] == -1: + item.do_flip(anchor=item.center) diff --git a/beeref/selection.py b/beeref/selection.py index be55f80..51fc9d2 100644 --- a/beeref/selection.py +++ b/beeref/selection.py @@ -96,10 +96,14 @@ class BaseItemMixin: if vertical: self.setRotation(self.rotation() + 180) + @property + def center(self): + return QtCore.QPointF(self.width, self.height) / 2 + @property def center_scene_coords(self): """The item's center in scene coordinates.""" - return self.mapToScene(QtCore.QPointF(self.width, self.height) / 2) + return self.mapToScene(self.center) class SelectableMixin(BaseItemMixin): diff --git a/beeref/view.py b/beeref/view.py index f6d6e4c..04d001d 100644 --- a/beeref/view.py +++ b/beeref/view.py @@ -171,6 +171,22 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin): def on_action_flip_vertically(self): self.scene.flip_items(vertical=True) + def on_action_reset_scale(self): + self.undo_stack.push(commands.ResetScale( + self.scene.selectedItems())) + + def on_action_reset_rotation(self): + self.undo_stack.push(commands.ResetRotation( + self.scene.selectedItems())) + + def on_action_reset_flip(self): + self.undo_stack.push(commands.ResetFlip( + self.scene.selectedItems())) + + def on_action_reset_transforms(self): + self.undo_stack.push(commands.ResetTransforms( + self.scene.selectedItems())) + def on_items_loaded(self, value): self.scene.add_delayed_items() diff --git a/tests/test_commands.py b/tests/test_commands.py index 9e9b9b5..fd6973d 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -291,3 +291,107 @@ class FlipItemsTestCase(BeeTestCase): assert item2.flip() == -1 assert item2.rotation() == 30 assert item2.pos() == QtCore.QPointF(100, 100) + + +class ResetScaleTestCase(BeeTestCase): + + def test_redo_undo(self): + item = BeePixmapItem(QtGui.QImage()) + item.setScale(2) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + command = commands.ResetScale([item]) + command.redo() + assert item.scale() == 1 + assert item.pos().x() == 50 + assert item.pos().y() == 40 + command.undo() + assert item.scale() == 2 + assert item.pos().x() == 0 + assert item.pos().y() == 0 + + +class ResetRotateTestCase(BeeTestCase): + + def test_redo_undo(self): + item = BeePixmapItem(QtGui.QImage()) + item.setRotation(180) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + command = commands.ResetRotation([item]) + command.redo() + assert item.rotation() == 0 + assert item.pos().x() == -100 + assert item.pos().y() == -80 + command.undo() + assert item.rotation() == 180 + assert item.pos().x() == 0 + assert item.pos().y() == 0 + + +class ResetFlipTestCase(BeeTestCase): + + def test_redo_undo(self): + item1 = BeePixmapItem(QtGui.QImage()) + item1.do_flip() + item2 = BeePixmapItem(QtGui.QImage()) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + command = commands.ResetFlip([item1, item2]) + command.redo() + assert item1.flip() == 1 + assert item1.pos().x() == -100 + assert item1.pos().y() == 0 + assert item2.flip() == 1 + assert item2.pos().x() == 0 + assert item2.pos().y() == 0 + command.undo() + assert item1.flip() == -1 + assert item1.pos().x() == 0 + assert item1.pos().y() == 0 + assert item2.flip() == 1 + assert item2.pos().x() == 0 + assert item2.pos().y() == 0 + + +class ResetTransformsTestCase(BeeTestCase): + + def test_redo_undo(self): + item1 = BeePixmapItem(QtGui.QImage()) + item1.setScale(2) + item1.do_flip() + item2 = BeePixmapItem(QtGui.QImage()) + item2.setRotation(180) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + command = commands.ResetTransforms([item1, item2]) + command.redo() + assert item1.scale() == 1 + assert item1.rotation() == 0 + assert item1.flip() == 1 + assert item1.pos().x() == -150 + assert item1.pos().y() == 40 + assert item2.scale() == 1 + assert item2.rotation() == 0 + assert item2.flip() == 1 + assert item2.pos().x() == -100 + assert item2.pos().y() == -80 + command.undo() + assert item1.scale() == 2 + assert item1.rotation() == 0 + assert item1.flip() == -1 + assert item1.pos().x() == 0 + assert item1.pos().y() == 0 + assert item2.scale() == 1 + assert item2.rotation() == 180 + assert item2.flip() == 1 + assert item2.pos().x() == 0 + assert item2.pos().y() == 0