From 27813f5bd83a928a36d96ae553b20d753d1ca9be Mon Sep 17 00:00:00 2001 From: Rebecca Breu Date: Sun, 11 Apr 2021 17:25:56 +0200 Subject: [PATCH] Add Flip action to menu --- beeref/actions/actions.py | 16 ++++++++++++++++ beeref/actions/menu_structure.py | 7 +++++++ beeref/scene.py | 11 +++++++++++ beeref/view.py | 6 ++++++ tests/test_scene.py | 23 +++++++++++++++++++++++ tests/test_selection.py | 4 ++++ 6 files changed, 67 insertions(+) diff --git a/beeref/actions/actions.py b/beeref/actions/actions.py index 9b2c88f..53f018b 100644 --- a/beeref/actions/actions.py +++ b/beeref/actions/actions.py @@ -98,4 +98,20 @@ actions = [ 'group': 'active_when_selection', 'enabled': False, }, + { + 'id': 'flip_horizontally', + 'text': 'Flip &Horizontally', + 'shortcuts': ['H'], + 'callback': 'on_action_flip_horizontally', + 'group': 'active_when_selection', + 'enabled': False, + }, + { + 'id': 'flip_vertically', + 'text': 'Flip &Vertically', + 'shortcuts': ['V'], + 'callback': 'on_action_flip_vertically', + 'group': 'active_when_selection', + 'enabled': False, + }, ] diff --git a/beeref/actions/menu_structure.py b/beeref/actions/menu_structure.py index 671ad8b..e83e5df 100644 --- a/beeref/actions/menu_structure.py +++ b/beeref/actions/menu_structure.py @@ -36,6 +36,13 @@ menu_structure = [ 'delete', ], }, + { + 'menu': '&Transform', + 'items': [ + 'flip_horizontally', + 'flip_vertically', + ], + }, { 'menu': '&Normalize', 'items': [ diff --git a/beeref/scene.py b/beeref/scene.py index 6915097..0c5688b 100644 --- a/beeref/scene.py +++ b/beeref/scene.py @@ -86,6 +86,13 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene): self.undo_stack.push( commands.NormalizeItems(self.selectedItems(), scale_factors)) + def flip_items(self, vertical=False): + """Flip selected items.""" + self.undo_stack.push( + commands.FlipItems(self.selectedItems(), + self.get_selection_center(), + vertical=vertical)) + def has_selection(self): """Checks whether there are currently items selected.""" @@ -176,6 +183,10 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene): QtCore.QPointF(min(x), min(y)), QtCore.QPointF(max(x), max(y))) + def get_selection_center(self): + rect = self.get_selection_rect() + return (rect.topLeft() + rect.bottomRight()) / 2 + def on_selection_change(self): if self.has_multi_selection(): self.multi_select_item.fit_selection_area( diff --git a/beeref/view.py b/beeref/view.py index f85564d..3e26117 100644 --- a/beeref/view.py +++ b/beeref/view.py @@ -124,6 +124,12 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin): def on_action_normalize_size(self): self.scene.normalize_size() + def on_action_flip_horizontally(self): + self.scene.flip_items(vertical=False) + + def on_action_flip_vertically(self): + self.scene.flip_items(vertical=True) + def open_from_file(self, filename): logger.info(f'Opening file {filename}') self.scene.clear() diff --git a/tests/test_scene.py b/tests/test_scene.py index 31052e7..cd8cce0 100644 --- a/tests/test_scene.py +++ b/tests/test_scene.py @@ -6,6 +6,7 @@ from pytest import approx from PyQt6 import QtCore, QtGui, QtWidgets from PyQt6.QtCore import Qt +from beeref import commands from beeref.items import BeePixmapItem from beeref.scene import BeeGraphicsScene from .base import BeeTestCase @@ -66,6 +67,21 @@ class BeeGraphicsSceneTestCase(BeeTestCase): def test_normalize_size_when_no_items(self): self.scene.normalize_size() + def test_flip_items(self): + item = BeePixmapItem(QtGui.QImage()) + self.scene.addItem(item) + item.setSelected(True) + self.scene.undo_stack = MagicMock(push=MagicMock()) + with patch('beeref.scene.BeeGraphicsScene.get_selection_rect', + return_value=QtCore.QRectF(10, 20, 100, 60)): + self.scene.flip_items(vertical=True) + args = self.scene.undo_stack.push.call_args_list[0][0] + cmd = args[0] + isinstance(cmd, commands.FlipItems) + assert cmd.items == [item] + assert cmd.anchor == QtCore.QPointF(60, 50) + assert cmd.vertical is True + def test_has_selection_when_no_selection(self): item = BeePixmapItem(QtGui.QImage()) self.scene.addItem(item) @@ -256,6 +272,7 @@ class BeeGraphicsSceneTestCase(BeeTestCase): self.scene.undo_stack.push.assert_called_once() args = self.scene.undo_stack.push.call_args_list[0][0] cmd = args[0] + isinstance(cmd, commands.MoveItemsBy) assert cmd.items == [item] assert cmd.ignore_first_redo is True assert cmd.delta.x() == 10 @@ -368,6 +385,12 @@ class BeeGraphicsSceneTestCase(BeeTestCase): assert rect.bottomRight().x() == approx(math.sqrt(2) * 100) assert rect.bottomRight().y() == approx(math.sqrt(2) * 50) + def test_get_selection_center(self): + with patch('beeref.scene.BeeGraphicsScene.get_selection_rect', + return_value=QtCore.QRectF(10, 20, 100, 60)): + center = self.scene.get_selection_center() + assert center == QtCore.QPointF(60, 50) + def test_on_selection_change_when_multi_selection_new(self): self.scene.has_multi_selection = MagicMock(return_value=True) self.scene.multi_select_item.fit_selection_area = MagicMock() diff --git a/tests/test_selection.py b/tests/test_selection.py index e36dbd7..f0b12b9 100644 --- a/tests/test_selection.py +++ b/tests/test_selection.py @@ -6,6 +6,7 @@ from PyQt6 import QtCore, QtGui from PyQt6.QtCore import Qt from beeref.assets import BeeAssets +from beeref import commands from beeref.items import BeePixmapItem from beeref.scene import BeeGraphicsScene from beeref.selection import MultiSelectItem, RubberbandItem @@ -780,6 +781,7 @@ class SelectableMixinMouseEventsTestCase(SelectableMixinBaseTestCase): self.scene.undo_stack.push.assert_called_once() args = self.scene.undo_stack.push.call_args_list[0][0] cmd = args[0] + isinstance(cmd, commands.ScaleItemsBy) assert cmd.items == [self.item] assert cmd.factor == approx(1.5, 0.01) assert cmd.anchor == QtCore.QPointF(100, 80) @@ -797,6 +799,7 @@ class SelectableMixinMouseEventsTestCase(SelectableMixinBaseTestCase): self.item.mouseReleaseEvent(self.event) args = self.scene.undo_stack.push.call_args_list[0][0] cmd = args[0] + isinstance(cmd, commands.RotateItemsBy) assert cmd.items == [self.item] assert cmd.delta == -42 assert cmd.anchor == QtCore.QPointF(10, 20) @@ -811,6 +814,7 @@ class SelectableMixinMouseEventsTestCase(SelectableMixinBaseTestCase): self.item.mouseReleaseEvent(self.event) args = self.scene.undo_stack.push.call_args_list[0][0] cmd = args[0] + isinstance(cmd, commands.FlipItems) assert cmd.items == [self.item] assert cmd.anchor == QtCore.QPointF(50, 40) assert cmd.vertical is False