diff --git a/beeref/commands.py b/beeref/commands.py index 23f0f4e..fb763b8 100644 --- a/beeref/commands.py +++ b/beeref/commands.py @@ -137,7 +137,7 @@ class NormalizeItems(QtGui.QUndoCommand): self.old_scale_factors = [] for item, factor in zip(self.items, self.scale_factors): self.old_scale_factors.append(item.scale()) - item.setScale(factor, + item.setScale(item.scale() * factor, QtCore.QPointF(item.width, item.height) / 2) def undo(self): diff --git a/beeref/scene.py b/beeref/scene.py index 8f62c21..934808b 100644 --- a/beeref/scene.py +++ b/beeref/scene.py @@ -48,7 +48,10 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene): :param mode: "width" or "height". """ - values = [getattr(i, mode) for i in self.selectedItems(user_only=True)] + values = [] + for item in self.selectedItems(user_only=True): + rect = self.itemsBoundingRect(items=[item]) + values.append(getattr(rect, mode)()) if not values: return avg = sum(values) / len(values) @@ -57,7 +60,8 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene): scale_factors = [] for item in self.selectedItems(user_only=True): - scale_factors.append(avg / getattr(item, mode)) + rect = self.itemsBoundingRect(items=[item]) + scale_factors.append(avg / getattr(rect, mode)()) self.undo_stack.push( commands.NormalizeItems( self.selectedItems(user_only=True), scale_factors)) @@ -75,8 +79,11 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene): Size meaning the area = widh * height. """ - sizes = [i.width * i.height - for i in self.selectedItems(user_only=True)] + + sizes = [] + for item in self.selectedItems(user_only=True): + rect = self.itemsBoundingRect(items=[item]) + sizes.append(rect.width() * rect.height()) if not sizes: return @@ -86,7 +93,8 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene): scale_factors = [] for item in self.selectedItems(user_only=True): - scale_factors.append(math.sqrt(avg / item.width / item.height)) + rect = self.itemsBoundingRect(items=[item]) + scale_factors.append(math.sqrt(avg / rect.width() / rect.height())) self.undo_stack.push( commands.NormalizeItems( self.selectedItems(user_only=True), scale_factors)) @@ -140,7 +148,7 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene): if not item.isSelected(): item.setSelected(True) self.views()[0].fit_rect( - self.itemsBoundingRect(selection_only=True), + self.itemsBoundingRect(items=[item]), toggle_item=item) return super().mouseDoubleClickEvent(event) @@ -202,23 +210,30 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene): for item in self.selectedItems(): item.on_view_scale_change() - def itemsBoundingRect(self, selection_only=False): + def itemsBoundingRect(self, selection_only=False, items=None): """Returns the bounding rect of the scene's items; either all of them - or only selected ones. + or only selected ones, or the items givin in ``items``. Re-implemented to not include the items's selection handles. """ - base = self.selectedItems() if selection_only else self.items() - items = list(filter(lambda i: hasattr(i, 'save_id'), base)) + def filter_user_items(ilist): + return list(filter(lambda i: hasattr(i, 'save_id'), ilist)) - if not items: + if selection_only: + base = filter_user_items(self.selectedItems()) + elif items: + base = items + else: + base = filter_user_items(self.items()) + + if not base: return QtCore.QRectF(0, 0, 0, 0) x = [] y = [] - for item in items: + for item in base: for corner in item.corners_scene_coords: x.append(corner.x()) y.append(corner.y()) diff --git a/tests/test_commands.py b/tests/test_commands.py index fd6973d..416cb58 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -229,8 +229,8 @@ class NormalizeItemsTestCase(BeeTestCase): command.redo() assert item1.scale() == 2 assert item1.pos() == QtCore.QPointF(-50, -40) - assert item2.scale() == 0.5 - assert item2.pos() == QtCore.QPointF(125, 100) + assert item2.scale() == 1.5 + assert item2.pos() == QtCore.QPointF(75, 60) command.undo() assert item1.scale() == 1 assert item1.pos() == QtCore.QPointF(0, 0) diff --git a/tests/test_scene.py b/tests/test_scene.py index 59ea738..a6e4729 100644 --- a/tests/test_scene.py +++ b/tests/test_scene.py @@ -24,45 +24,122 @@ class BeeGraphicsSceneTestCase(BeeTestCase): self.addCleanup(views_patcher.stop) def test_normalize_height(self): - item1 = MagicMock(width=200, height=100, scale_factor=1) - item2 = MagicMock(width=300, height=200, scale_factor=3) + item1 = BeePixmapItem(QtGui.QImage()) + self.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + self.scene.addItem(item2) + item2.setSelected(True) + item2.setScale(3) - with patch.object(self.scene, 'selectedItems', - return_value=[item1, item2]): - self.scene.normalize_height() + 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.normalize_height() - item1.setScale.assert_called_once_with(1.5, QtCore.QPointF(100, 50)) - item2.setScale.assert_called_once_with(0.75, QtCore.QPointF(150, 100)) + assert item1.scale() == 2 + assert item1.pos() == QtCore.QPointF(-50, -40) + assert item2.scale() == 2 + assert item2.pos() == QtCore.QPointF(50, 40) + + def test_normalize_height_with_rotation(self): + item1 = BeePixmapItem(QtGui.QImage()) + self.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + self.scene.addItem(item2) + item2.setSelected(True) + 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=200): + self.scene.normalize_height() + + assert item1.scale() == 0.75 + assert item2.scale() == 1.5 def test_normalize_height_when_no_items(self): self.scene.normalize_height() def test_normalize_width(self): - item1 = MagicMock(width=100, height=200, scale_factor=1) - item2 = MagicMock(width=200, height=300, scale_factor=3) + item1 = BeePixmapItem(QtGui.QImage()) + self.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + self.scene.addItem(item2) + item2.setSelected(True) + item2.setScale(3) - with patch.object(self.scene, 'selectedItems', - return_value=[item1, item2]): - self.scene.normalize_width() + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=80): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=100): + self.scene.normalize_width() - item1.setScale.assert_called_once_with(1.5, QtCore.QPointF(50, 100)) - item2.setScale.assert_called_once_with(0.75, QtCore.QPointF(100, 150)) + assert item1.scale() == 2 + assert item1.pos() == QtCore.QPointF(-40, -50) + assert item2.scale() == 2 + assert item2.pos() == QtCore.QPointF(40, 50) + + def test_normalize_width_with_rotation(self): + item1 = BeePixmapItem(QtGui.QImage()) + self.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + self.scene.addItem(item2) + item2.setSelected(True) + item2.setRotation(90) + + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=200): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=100): + self.scene.normalize_height() + + assert item1.scale() == 1.5 + assert item2.scale() == 0.75 def test_normalize_width_when_no_items(self): self.scene.normalize_width() def test_normalize_size(self): - item1 = MagicMock(width=100, height=200, scale_factor=1) - item2 = MagicMock(width=400, height=100, scale_factor=3) + item1 = BeePixmapItem(QtGui.QImage()) + self.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + self.scene.addItem(item2) + item2.setSelected(True) + item2.setScale(2) - with patch.object(self.scene, 'selectedItems', - return_value=[item1, item2]): - self.scene.normalize_size() + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=100): + self.scene.normalize_size() - item1.setScale.assert_called_once_with( - math.sqrt(1.5), QtCore.QPointF(50, 100)) - item2.setScale.assert_called_once_with( - math.sqrt(0.75), QtCore.QPointF(200, 50)) + assert item1.scale() == approx(math.sqrt(2.5)) + assert item2.scale() == approx(math.sqrt(2.5)) + + def test_normalize_size_with_rotation(self): + item1 = BeePixmapItem(QtGui.QImage()) + self.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + self.scene.addItem(item2) + item2.setSelected(True) + 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=200): + self.scene.normalize_size() + + assert item1.scale() == 1 + assert item2.scale() == 1 def test_normalize_size_when_no_items(self): self.scene.normalize_size() @@ -459,6 +536,31 @@ class BeeGraphicsSceneTestCase(BeeTestCase): self.scene.on_view_scale_change() item.on_view_scale_change.assert_called_once() + def test_items_bounding_rect_given_items(self): + item1 = BeePixmapItem(QtGui.QImage()) + self.scene.addItem(item1) + item1.setSelected(True) + item1.setPos(4, -6) + item2 = BeePixmapItem(QtGui.QImage()) + self.scene.addItem(item2) + item2.setSelected(True) + item2.setPos(-33, 22) + item3 = BeePixmapItem(QtGui.QImage()) + self.scene.addItem(item3) + item3.setSelected(True) + item3.setPos(1000, 1000) + + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=100): + rect = self.scene.itemsBoundingRect(items=[item1, item2]) + + assert rect.topLeft().x() == -33 + assert rect.topLeft().y() == -6 + assert rect.bottomRight().x() == 104 + assert rect.bottomRight().y() == 122 + def test_items_bounding_rect_two_items_selection_only(self): item1 = BeePixmapItem(QtGui.QImage()) self.scene.addItem(item1)