From 9fa50a323c1eb3d21ad10906bab7200cb1a44439 Mon Sep 17 00:00:00 2001 From: Rebecca Breu Date: Fri, 23 Apr 2021 19:07:32 +0200 Subject: [PATCH] Arrange horizontal and vertical --- beeref/actions/actions.py | 14 ++++++++ beeref/actions/menu_structure.py | 2 ++ beeref/scene.py | 46 ++++++++++++++++++++++-- beeref/view.py | 6 ++++ tests/test_scene.py | 62 ++++++++++++++++++++++++++++++++ 5 files changed, 127 insertions(+), 3 deletions(-) diff --git a/beeref/actions/actions.py b/beeref/actions/actions.py index 43f3327..fa82b24 100644 --- a/beeref/actions/actions.py +++ b/beeref/actions/actions.py @@ -106,6 +106,20 @@ actions = [ 'group': 'active_when_selection', 'enabled': False, }, + { + 'id': 'arrange_horizontal', + 'text': '&Horizontal', + 'callback': 'on_action_arrange_horizontal', + 'group': 'active_when_selection', + 'enabled': False, + }, + { + 'id': 'arrange_vertical', + 'text': '&Vertical', + 'callback': 'on_action_arrange_vertical', + 'group': 'active_when_selection', + 'enabled': False, + }, { 'id': 'flip_horizontally', 'text': 'Flip &Horizontally', diff --git a/beeref/actions/menu_structure.py b/beeref/actions/menu_structure.py index 701e667..bb5cd5f 100644 --- a/beeref/actions/menu_structure.py +++ b/beeref/actions/menu_structure.py @@ -77,6 +77,8 @@ menu_structure = [ 'menu': '&Arrange', 'items': [ 'arrange_optimal', + 'arrange_horizontal', + 'arrange_vertical', ], }, { diff --git a/beeref/scene.py b/beeref/scene.py index ed5b36d..afb94f5 100644 --- a/beeref/scene.py +++ b/beeref/scene.py @@ -101,16 +101,56 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene): commands.NormalizeItems( self.selectedItems(user_only=True), scale_factors)) + def arrange(self, vertical=False): + """Arrange items in a line (horizontally or vertically).""" + + items = self.selectedItems(user_only=True) + if len(items) < 2: + return + + center = self.get_selection_center() + positions = [] + rects = [] + for item in items: + rects.append({ + 'rect': self.itemsBoundingRect(items=[item]), + 'item': item}) + + if vertical: + rects.sort(key=lambda r: r['rect'].topLeft().y()) + sum_height = sum(map(lambda r: r['rect'].height(), rects)) + y = round(center.y() - sum_height/2) + for rect in rects: + positions.append( + QtCore.QPointF( + round(center.x() - rect['rect'].width()/2), y)) + y += rect['rect'].height() + + else: + rects.sort(key=lambda r: r['rect'].topLeft().x()) + sum_width = sum(map(lambda r: r['rect'].width(), rects)) + x = round(center.x() - sum_width/2) + for rect in rects: + positions.append( + QtCore.QPointF( + x, round(center.y() - rect['rect'].height()/2))) + x += rect['rect'].width() + + self.undo_stack.push( + commands.ArrangeItems(self, + [r['item'] for r in rects], + positions)) + def arrange_optimal(self): items = self.selectedItems(user_only=True) + if len(items) < 2: + return + sizes = [] for item in items: rect = self.itemsBoundingRect(items=[item]) sizes.append((round(rect.width()), round(rect.height()))) - if len(sizes) < 2: - return - center = self.get_selection_center() # The minimal area the items need if they could be packed optimally; diff --git a/beeref/view.py b/beeref/view.py index e9fdb48..986d069 100644 --- a/beeref/view.py +++ b/beeref/view.py @@ -196,6 +196,12 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin): def on_action_normalize_size(self): self.scene.normalize_size() + def on_action_arrange_horizontal(self): + self.scene.arrange() + + def on_action_arrange_vertical(self): + self.scene.arrange(vertical=True) + def on_action_arrange_optimal(self): self.scene.arrange_optimal() diff --git a/tests/test_scene.py b/tests/test_scene.py index 333f24a..3dc18af 100644 --- a/tests/test_scene.py +++ b/tests/test_scene.py @@ -144,6 +144,68 @@ class BeeGraphicsSceneTestCase(BeeTestCase): def test_normalize_size_when_no_items(self): self.scene.normalize_size() + def test_arrange_horizontal(self): + item1 = BeePixmapItem(QtGui.QImage()) + self.scene.addItem(item1) + item1.setSelected(True) + item1.setPos(10, -100) + item2 = BeePixmapItem(QtGui.QImage()) + self.scene.addItem(item2) + item2.setSelected(True) + item2.setPos(-10, 40) + + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + self.scene.arrange() + + assert item2.pos() == QtCore.QPointF(-50, -30) + assert item1.pos() == QtCore.QPointF(50, -30) + + def test_arrange_vertical(self): + item1 = BeePixmapItem(QtGui.QImage()) + self.scene.addItem(item1) + item1.setSelected(True) + item1.setPos(10, -100) + item2 = BeePixmapItem(QtGui.QImage()) + self.scene.addItem(item2) + item2.setSelected(True) + item2.setPos(-10, 40) + + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + self.scene.arrange(vertical=True) + + assert item1.pos() == QtCore.QPointF(0, -70) + assert item2.pos() == QtCore.QPointF(0, 10) + + def test_arrange_when_rotated(self): + item1 = BeePixmapItem(QtGui.QImage()) + self.scene.addItem(item1) + item1.setSelected(True) + item1.setPos(10, -100) + item1.setRotation(90) + item2 = BeePixmapItem(QtGui.QImage()) + self.scene.addItem(item2) + item2.setSelected(True) + item2.setPos(-10, 40) + item2.setRotation(90) + + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + self.scene.arrange() + + assert item2.pos() == QtCore.QPointF(-40, -30) + assert item1.pos() == QtCore.QPointF(40, -30) + + def test_arrange_when_no_items(self): + self.scene.arrange() + def test_arrange_optimal(self): for i in range(4): item = BeePixmapItem(QtGui.QImage())