mirror of
https://github.com/rbreu/beeref.git
synced 2026-03-11 08:54:28 +00:00
Add scaling handles for all corners
This commit is contained in:
parent
e9444dd8e3
commit
7bb8cd200d
5 changed files with 349 additions and 84 deletions
|
|
@ -74,12 +74,15 @@ class MoveItemsBy(QtGui.QUndoCommand):
|
|||
item.moveBy(-self.delta_x, -self.delta_y)
|
||||
|
||||
|
||||
class ScaleItemsBy(QtGui.QUndoCommand):
|
||||
class ScaleItemsByDelta(QtGui.QUndoCommand):
|
||||
"""Scale Items by a given delta around the given anchor point.
|
||||
Delta will be *added* to the current scale factor."""
|
||||
|
||||
def __init__(self, items, factor, ignore_first_redo=False):
|
||||
def __init__(self, items, delta, anchor, ignore_first_redo=False):
|
||||
super().__init__('Scale items')
|
||||
self.items = items
|
||||
self.factor = factor
|
||||
self.delta = delta
|
||||
self.anchor = anchor
|
||||
self.ignore_first_redo = ignore_first_redo
|
||||
|
||||
def redo(self):
|
||||
|
|
@ -87,11 +90,15 @@ class ScaleItemsBy(QtGui.QUndoCommand):
|
|||
self.ignore_first_redo = False
|
||||
return
|
||||
for item in self.items:
|
||||
item.setScale(item.scale() + self.factor)
|
||||
item.setScale(item.scale() + self.delta)
|
||||
item.translate_for_scale_anchor(
|
||||
item.pos(), self.delta, self.anchor)
|
||||
|
||||
def undo(self):
|
||||
for item in self.items:
|
||||
item.setScale(item.scale() - self.factor)
|
||||
item.setScale(item.scale() - self.delta)
|
||||
item.translate_for_scale_anchor(
|
||||
item.pos(), -self.delta, self.anchor)
|
||||
|
||||
|
||||
class NormalizeItems(QtGui.QUndoCommand):
|
||||
|
|
|
|||
121
beeref/items.py
121
beeref/items.py
|
|
@ -35,8 +35,8 @@ class BeePixmapItem(QtWidgets.QGraphicsPixmapItem):
|
|||
select_color = QtGui.QColor(116, 234, 231, 255)
|
||||
SELECT_LINE_WIDTH = 4 # line width for the selection box
|
||||
SELECT_HANDLE_SIZE = 15 # size of selection handles for scaling
|
||||
SELECT_RESIZE_SIZE = 30 # size of hover area for scaling
|
||||
SELECT_ROTATE_SIZE = 30 # size of hover area for rotating
|
||||
SELECT_RESIZE_SIZE = 20 # size of hover area for scaling
|
||||
SELECT_ROTATE_SIZE = 20 # size of hover area for rotating
|
||||
select_debug = False # Draw debug shapes
|
||||
|
||||
def __init__(self, image, filename=None):
|
||||
|
|
@ -50,8 +50,7 @@ class BeePixmapItem(QtWidgets.QGraphicsPixmapItem):
|
|||
QtWidgets.QGraphicsItem.GraphicsItemFlags.ItemIsMovable
|
||||
| QtWidgets.QGraphicsItem.GraphicsItemFlags.ItemIsSelectable)
|
||||
|
||||
self.single_select_mode = False
|
||||
self.scale_active = False
|
||||
self.scale_active_corner = None
|
||||
self.viewport_scale = 1
|
||||
|
||||
def __str__(self):
|
||||
|
|
@ -163,14 +162,22 @@ class BeePixmapItem(QtWidgets.QGraphicsPixmapItem):
|
|||
if single_select_mode:
|
||||
pen.setWidth(self.SELECT_HANDLE_SIZE)
|
||||
painter.setPen(pen)
|
||||
painter.drawPoint(self.width, self.height)
|
||||
for corner in self.corners:
|
||||
painter.drawPoint(*corner)
|
||||
|
||||
@property
|
||||
def bottom_right_scale_bounds(self):
|
||||
"""The interactable shape of the bottom right scale handle"""
|
||||
def corners(self):
|
||||
"""The corners of the items. Used for scale and rotate handles."""
|
||||
return ((0, 0),
|
||||
(0, self.height),
|
||||
(self.width, 0),
|
||||
(self.width, self.height))
|
||||
|
||||
def get_scale_bounds(self, center):
|
||||
"""The interactable shape of the scale handles."""
|
||||
return QtCore.QRectF(
|
||||
self.width - self.select_resize_size/2,
|
||||
self.height - self.select_resize_size/2,
|
||||
center[0] - self.select_resize_size/2,
|
||||
center[1] - self.select_resize_size/2,
|
||||
self.select_resize_size,
|
||||
self.select_resize_size)
|
||||
|
||||
|
|
@ -186,6 +193,8 @@ class BeePixmapItem(QtWidgets.QGraphicsPixmapItem):
|
|||
bounds = super().boundingRect()
|
||||
if not self.isSelected():
|
||||
return bounds
|
||||
|
||||
# Add extra space for scale and rotate interactive areas
|
||||
margin = self.select_resize_size / 2 + self.select_rotate_size
|
||||
return QtCore.QRectF(
|
||||
bounds.topLeft().x() - margin,
|
||||
|
|
@ -196,8 +205,10 @@ class BeePixmapItem(QtWidgets.QGraphicsPixmapItem):
|
|||
def shape(self):
|
||||
shape_ = super().shape()
|
||||
if self.isSelected():
|
||||
# Add extra space for scale and rotate interactive areas
|
||||
path = QtGui.QPainterPath()
|
||||
path.addRect(self.bottom_right_scale_bounds)
|
||||
for corner in self.corners:
|
||||
path.addRect(self.get_scale_bounds(corner))
|
||||
path.addRect(self.bottom_right_rotate_bounds)
|
||||
shape_ = shape_ + path
|
||||
return shape_
|
||||
|
|
@ -205,48 +216,92 @@ class BeePixmapItem(QtWidgets.QGraphicsPixmapItem):
|
|||
def hoverMoveEvent(self, event):
|
||||
if not self.isSelected() or not self.scene().has_single_selection():
|
||||
return
|
||||
if self.bottom_right_scale_bounds.contains(event.pos()):
|
||||
self.setCursor(Qt.CursorShape.SizeFDiagCursor)
|
||||
elif self.bottom_right_rotate_bounds.contains(event.pos()):
|
||||
|
||||
for corner in self.corners:
|
||||
# See if we need to change the cursor for scale areas
|
||||
if self.get_scale_bounds(corner).contains(event.pos()):
|
||||
direction = self.get_scale_direction(corner)
|
||||
if direction[0] == direction[1]:
|
||||
self.setCursor(Qt.CursorShape.SizeFDiagCursor)
|
||||
else:
|
||||
self.setCursor(Qt.CursorShape.SizeBDiagCursor)
|
||||
return
|
||||
|
||||
if self.bottom_right_rotate_bounds.contains(event.pos()):
|
||||
self.setCursor(Qt.CursorShape.ForbiddenCursor)
|
||||
else:
|
||||
self.setCursor(Qt.CursorShape.ArrowCursor)
|
||||
return
|
||||
|
||||
self.setCursor(Qt.CursorShape.ArrowCursor)
|
||||
|
||||
def hoverEnterEvent(self, event):
|
||||
# Always return regular cursor when item isn't selected
|
||||
if not self.isSelected() or not self.scene().has_single_selection():
|
||||
self.setCursor(Qt.CursorShape.ArrowCursor)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if (event.button() == Qt.MouseButtons.LeftButton
|
||||
and self.bottom_right_scale_bounds.contains(event.pos())
|
||||
and self.isSelected()):
|
||||
self.scale_active = True
|
||||
self.orig_scale_factor = self.scale()
|
||||
self.scale_start = event.scenePos()
|
||||
event.accept()
|
||||
else:
|
||||
super().mousePressEvent(event)
|
||||
and self.isSelected() and self.scene().has_single_selection()):
|
||||
for corner in self.corners:
|
||||
# Check if we are in one of the corner's scale areas
|
||||
if self.get_scale_bounds(corner).contains(event.pos()):
|
||||
# Start scale action for this corner
|
||||
self.scale_active_corner = corner
|
||||
self.scale_start = event.scenePos()
|
||||
self.scale_orig_factor = self.scale()
|
||||
self.scale_orig_pos = self.pos()
|
||||
event.accept()
|
||||
return
|
||||
|
||||
def get_scale_delta(self, event):
|
||||
super().mousePressEvent(event)
|
||||
|
||||
def get_scale_delta(self, event, corner):
|
||||
imgsize = self.width + self.height
|
||||
p = event.scenePos() - self.scale_start
|
||||
return (p.x() + p.y()) / imgsize
|
||||
direction = self.get_scale_direction(corner)
|
||||
return (direction[0] * p.x() + direction[1] * p.y()) / imgsize
|
||||
|
||||
def get_scale_anchor(self, corner):
|
||||
"""Get the anchor around which the scale for this corner operates."""
|
||||
return(self.width - corner[0], self.height - corner[1])
|
||||
|
||||
def get_scale_direction(self, corner):
|
||||
"""Get the direction in which the scale for this corner increases"""
|
||||
x = 1 if corner[0] > 0 else -1
|
||||
y = 1 if corner[1] > 0 else -1
|
||||
return (x, y)
|
||||
|
||||
def translate_for_scale_anchor(self, orig_pos, scale_delta, anchor):
|
||||
"""Adjust the item's position so that a scale with the given scale
|
||||
delta appears to operate around the given anchor. ``setScale`` needs
|
||||
to be called separately with delta *added* to the current item's scale
|
||||
factor."""
|
||||
|
||||
self.setPos(
|
||||
orig_pos.x() - anchor[0] * scale_delta,
|
||||
orig_pos.y() - anchor[1] * scale_delta,
|
||||
)
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
if self.scale_active:
|
||||
delta = self.get_scale_delta(event)
|
||||
self.setScale(self.orig_scale_factor + delta)
|
||||
if self.scale_active_corner:
|
||||
delta = self.get_scale_delta(event, self.scale_active_corner)
|
||||
self.setScale(self.scale_orig_factor + delta)
|
||||
self.translate_for_scale_anchor(
|
||||
self.scale_orig_pos,
|
||||
delta,
|
||||
self.get_scale_anchor(self.scale_active_corner))
|
||||
event.accept()
|
||||
else:
|
||||
super().mouseMoveEvent(event)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if self.scale_active:
|
||||
if self.scale_active_corner:
|
||||
self.scene().undo_stack.push(
|
||||
commands.ScaleItemsBy([self],
|
||||
self.get_scale_delta(event),
|
||||
ignore_first_redo=True))
|
||||
self.scale_active = False
|
||||
commands.ScaleItemsByDelta(
|
||||
[self],
|
||||
self.get_scale_delta(event, self.scale_active_corner),
|
||||
self.get_scale_anchor(self.scale_active_corner),
|
||||
ignore_first_redo=True))
|
||||
self.scale_active_corner = None
|
||||
event.accept()
|
||||
else:
|
||||
super().mouseReleaseEvent(event)
|
||||
|
|
|
|||
|
|
@ -93,7 +93,6 @@ class SQLiteIOWriteTestCase(BeeTestCase):
|
|||
item.setPos(44, 55)
|
||||
item.setZValue(0.22)
|
||||
item.pixmap_to_bytes = MagicMock(return_value=b'abc')
|
||||
self.scene.addItem(item)
|
||||
self.io.write()
|
||||
|
||||
assert item.save_id == 1
|
||||
|
|
@ -130,7 +129,6 @@ class SQLiteIOWriteTestCase(BeeTestCase):
|
|||
item.setPos(44, 55)
|
||||
item.setZValue(0.22)
|
||||
item.save_id = 1
|
||||
self.scene.addItem(item)
|
||||
item.pixmap_to_bytes = MagicMock(return_value=b'abc')
|
||||
self.io.write()
|
||||
item.setScale(0.7)
|
||||
|
|
|
|||
|
|
@ -95,26 +95,39 @@ class ScaleItemsByTestCase(BeeTestCase):
|
|||
item1.setScale(1)
|
||||
item2 = BeePixmapItem(QtGui.QImage())
|
||||
item2.setScale(3)
|
||||
command = commands.ScaleItemsBy([item1, item2], 2)
|
||||
command = commands.ScaleItemsByDelta([item1, item2], 2, (100, 100))
|
||||
command.redo()
|
||||
assert item1.scale() == 3
|
||||
assert item1.pos().x() == -200
|
||||
assert item1.pos().y() == -200
|
||||
assert item2.scale() == 5
|
||||
assert item2.pos().x() == -200
|
||||
assert item2.pos().y() == -200
|
||||
command.undo()
|
||||
assert item1.scale() == 1
|
||||
assert item1.pos().x() == 0
|
||||
assert item1.pos().y() == 0
|
||||
assert item2.scale() == 3
|
||||
assert item2.pos().x() == 0
|
||||
assert item2.pos().y() == 0
|
||||
|
||||
def test_ignore_first_redo(self):
|
||||
item1 = BeePixmapItem(QtGui.QImage())
|
||||
item1.setScale(1)
|
||||
item2 = BeePixmapItem(QtGui.QImage())
|
||||
item2.setScale(3)
|
||||
command = commands.ScaleItemsBy([item1, item2], 2, True)
|
||||
command = commands.ScaleItemsByDelta([item1, item2], 2, (100, 100),
|
||||
ignore_first_redo=True)
|
||||
command.redo()
|
||||
assert item1.scale() == 1
|
||||
assert item2.scale() == 3
|
||||
assert item1.pos().x() == 0
|
||||
assert item1.pos().y() == 0
|
||||
command.redo()
|
||||
assert item1.scale() == 3
|
||||
assert item2.scale() == 5
|
||||
assert item1.pos().x() == -200
|
||||
assert item1.pos().y() == -200
|
||||
|
||||
|
||||
class NormalizeItemsTestCase(BeeTestCase):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from unittest.mock import patch, MagicMock, PropertyMock
|
||||
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||
from PyQt6.QtCore import Qt
|
||||
|
||||
from beeref.items import BeePixmapItem
|
||||
from beeref.scene import BeeGraphicsScene
|
||||
|
|
@ -25,10 +26,13 @@ class BeePixmapItemTestCase(BeeTestCase):
|
|||
assert item.filename == self.imgfilename3x3
|
||||
|
||||
def test_set_scale(self):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
item = BeePixmapItem(
|
||||
QtGui.QImage(self.imgfilename3x3), self.imgfilename3x3)
|
||||
item.prepareGeometryChange = MagicMock()
|
||||
item.setScale(3)
|
||||
assert item.scale() == 3
|
||||
assert item.pos().x() == 0
|
||||
assert item.pos().y() == 0
|
||||
item.prepareGeometryChange.assert_called_once()
|
||||
|
||||
def test_set_scale_ignores_zero(self):
|
||||
|
|
@ -76,18 +80,37 @@ class BeePixmapItemTestCase(BeeTestCase):
|
|||
assert item2.zValue() > item1.zValue()
|
||||
assert item2.zValue() == self.scene.max_z
|
||||
|
||||
def test_on_view_scale_change(self):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
with patch('beeref.items.BeePixmapItem.prepareGeometryChange') as m:
|
||||
item.on_view_scale_change()
|
||||
m.assert_called_once()
|
||||
|
||||
class BeePixmapItemPaintstuffTestCase(BeeTestCase):
|
||||
|
||||
class BeePixmapItemWithViewBaseTestCase(BeeTestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.scene = BeeGraphicsScene(None)
|
||||
self.item = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(self.item)
|
||||
self.view = MagicMock(get_scale=MagicMock(return_value=1))
|
||||
views_patcher = patch('beeref.scene.BeeGraphicsScene.views')
|
||||
views_mock = views_patcher.start()
|
||||
views_mock.return_value = [self.view]
|
||||
views_patcher = patch('beeref.scene.BeeGraphicsScene.views',
|
||||
return_value=[self.view])
|
||||
views_patcher.start()
|
||||
self.addCleanup(views_patcher.stop)
|
||||
width_patcher = patch('beeref.items.BeePixmapItem.width',
|
||||
new_callable=PropertyMock,
|
||||
return_value=100)
|
||||
width_patcher.start()
|
||||
self.addCleanup(width_patcher.stop)
|
||||
height_patcher = patch('beeref.items.BeePixmapItem.height',
|
||||
new_callable=PropertyMock,
|
||||
return_value=80)
|
||||
height_patcher.start()
|
||||
self.addCleanup(height_patcher.stop)
|
||||
|
||||
|
||||
class BeePixmapItemPaintstuffTestCase(BeePixmapItemWithViewBaseTestCase):
|
||||
|
||||
def test_fixed_length_for_viewport_when_default_scales(self):
|
||||
self.view.get_scale = MagicMock(return_value=1)
|
||||
|
|
@ -150,32 +173,31 @@ class BeePixmapItemPaintstuffTestCase(BeeTestCase):
|
|||
painter.drawRect.assert_called_once()
|
||||
painter.drawPoint.assert_not_called()
|
||||
|
||||
def test_bottom_right_scale_bounds(self):
|
||||
def test_corners(self):
|
||||
assert set(self.item.corners) == set((
|
||||
(0, 0),
|
||||
(100, 0),
|
||||
(0, 80),
|
||||
(100, 80)))
|
||||
|
||||
def test_get_scale_bounds(self):
|
||||
self.view.get_scale = MagicMock(return_value=1)
|
||||
self.item.SELECT_RESIZE_SIZE = 10
|
||||
with patch('beeref.items.BeePixmapItem.width',
|
||||
new_callable=PropertyMock, return_value=100):
|
||||
with patch('beeref.items.BeePixmapItem.height',
|
||||
new_callable=PropertyMock, return_value=80):
|
||||
rect = self.item.bottom_right_scale_bounds
|
||||
assert rect.topLeft().x() == 95
|
||||
assert rect.topLeft().y() == 75
|
||||
assert rect.bottomRight().x() == 105
|
||||
assert rect.bottomRight().y() == 85
|
||||
rect = self.item.get_scale_bounds((100, 100))
|
||||
assert rect.topLeft().x() == 95
|
||||
assert rect.topLeft().y() == 95
|
||||
assert rect.bottomRight().x() == 105
|
||||
assert rect.bottomRight().y() == 105
|
||||
|
||||
def test_bottom_right_rotate_bounds(self):
|
||||
self.view.get_scale = MagicMock(return_value=1)
|
||||
self.item.SELECT_RESIZE_SIZE = 10
|
||||
self.item.SELECT_ROTATE_SIZE = 10
|
||||
with patch('beeref.items.BeePixmapItem.width',
|
||||
new_callable=PropertyMock, return_value=100):
|
||||
with patch('beeref.items.BeePixmapItem.height',
|
||||
new_callable=PropertyMock, return_value=80):
|
||||
rect = self.item.bottom_right_rotate_bounds
|
||||
assert rect.topLeft().x() == 105
|
||||
assert rect.topLeft().y() == 85
|
||||
assert rect.bottomRight().x() == 115
|
||||
assert rect.bottomRight().y() == 95
|
||||
rect = self.item.bottom_right_rotate_bounds
|
||||
assert rect.topLeft().x() == 105
|
||||
assert rect.topLeft().y() == 85
|
||||
assert rect.bottomRight().x() == 115
|
||||
assert rect.bottomRight().y() == 95
|
||||
|
||||
def test_bounding_rect_when_not_selected(self):
|
||||
self.view.get_scale = MagicMock(return_value=1)
|
||||
|
|
@ -209,15 +231,11 @@ class BeePixmapItemPaintstuffTestCase(BeeTestCase):
|
|||
|
||||
with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.shape',
|
||||
return_value=path):
|
||||
with patch('beeref.items.BeePixmapItem.width',
|
||||
new_callable=PropertyMock, return_value=100):
|
||||
with patch('beeref.items.BeePixmapItem.height',
|
||||
new_callable=PropertyMock, return_value=80):
|
||||
shape = self.item.shape().boundingRect()
|
||||
assert shape.topLeft().x() == 0
|
||||
assert shape.topLeft().y() == 0
|
||||
assert shape.bottomRight().x() == 100
|
||||
assert shape.bottomRight().y() == 80
|
||||
shape = self.item.shape().boundingRect()
|
||||
assert shape.topLeft().x() == 0
|
||||
assert shape.topLeft().y() == 0
|
||||
assert shape.bottomRight().x() == 100
|
||||
assert shape.bottomRight().y() == 80
|
||||
|
||||
def test_shape_when_selected(self):
|
||||
self.item.SELECT_RESIZE_SIZE = 10
|
||||
|
|
@ -229,12 +247,186 @@ class BeePixmapItemPaintstuffTestCase(BeeTestCase):
|
|||
|
||||
with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.shape',
|
||||
return_value=path):
|
||||
with patch('beeref.items.BeePixmapItem.width',
|
||||
new_callable=PropertyMock, return_value=100):
|
||||
with patch('beeref.items.BeePixmapItem.height',
|
||||
new_callable=PropertyMock, return_value=80):
|
||||
shape = self.item.shape().boundingRect()
|
||||
assert shape.topLeft().x() == 0
|
||||
assert shape.topLeft().y() == 0
|
||||
assert shape.bottomRight().x() == 115
|
||||
assert shape.bottomRight().y() == 95
|
||||
shape = self.item.shape().boundingRect()
|
||||
assert shape.topLeft().x() == -5
|
||||
assert shape.topLeft().y() == -5
|
||||
assert shape.bottomRight().x() == 115
|
||||
assert shape.bottomRight().y() == 95
|
||||
|
||||
|
||||
class BeePixmapItemScalingTestCase(BeePixmapItemWithViewBaseTestCase):
|
||||
|
||||
def test_get_scale_delta_bottomright(self):
|
||||
self.item.scale_start = QtCore.QPoint(10, 10)
|
||||
event = MagicMock()
|
||||
event.scenePos = MagicMock(return_value=QtCore.QPoint(20, 90))
|
||||
assert self.item.get_scale_delta(event, (100, 80)) == 0.5
|
||||
|
||||
def test_get_scale_delta_topleft(self):
|
||||
self.item.scale_start = QtCore.QPoint(10, 10)
|
||||
event = MagicMock()
|
||||
event.scenePos = MagicMock(return_value=QtCore.QPoint(-10, -60))
|
||||
assert self.item.get_scale_delta(event, (0, 0)) == 0.5
|
||||
|
||||
def test_get_scale_anchor_topleft(self):
|
||||
assert self.item.get_scale_anchor((0, 0)) == (100, 80)
|
||||
|
||||
def test_get_scale_anchor_bottomright(self):
|
||||
assert self.item.get_scale_anchor((100, 80)) == (0, 0)
|
||||
|
||||
def test_get_scale_anchor_topright(self):
|
||||
assert self.item.get_scale_anchor((100, 0)) == (0, 80)
|
||||
|
||||
def test_get_scale_anchor_bottomleft(self):
|
||||
assert self.item.get_scale_anchor((0, 80)) == (100, 0)
|
||||
|
||||
def test_get_scale_direction_topleft(self):
|
||||
assert self.item.get_scale_direction((0, 0)) == (-1, -1)
|
||||
|
||||
def test_get_scale_direction_bottomright(self):
|
||||
assert self.item.get_scale_direction((100, 80)) == (1, 1)
|
||||
|
||||
def test_get_scale_direction_topright(self):
|
||||
assert self.item.get_scale_direction((100, 0)) == (1, -1)
|
||||
|
||||
def test_get_scale_direction_bottomleft(self):
|
||||
assert self.item.get_scale_direction((0, 80)) == (-1, 1)
|
||||
|
||||
def test_translate_for_scale_anchor(self):
|
||||
pos = QtCore.QPoint(50, 70)
|
||||
self.item.translate_for_scale_anchor(pos, 2, (100, 80))
|
||||
assert self.item.pos().x() == -150
|
||||
assert self.item.pos().y() == -90
|
||||
|
||||
|
||||
class BeePixmapItemEventsstuffTestCase(BeePixmapItemWithViewBaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.event = MagicMock()
|
||||
self.item.setCursor = MagicMock()
|
||||
|
||||
def test_hover_move_event_no_selection(self):
|
||||
self.event.pos = MagicMock(return_value=QtCore.QPointF(0, 0))
|
||||
self.item.hoverMoveEvent(self.event)
|
||||
self.item.setCursor.assert_not_called()
|
||||
|
||||
def test_hover_move_event_topleft_scale(self):
|
||||
self.item.setSelected(True)
|
||||
self.event.pos = MagicMock(return_value=QtCore.QPointF(0, 0))
|
||||
self.item.hoverMoveEvent(self.event)
|
||||
self.item.setCursor.assert_called_once_with(
|
||||
Qt.CursorShape.SizeFDiagCursor)
|
||||
|
||||
def test_hover_move_event_bottomright_scale(self):
|
||||
self.item.setSelected(True)
|
||||
self.event.pos = MagicMock(return_value=QtCore.QPointF(100, 80))
|
||||
self.item.hoverMoveEvent(self.event)
|
||||
self.item.setCursor.assert_called_once_with(
|
||||
Qt.CursorShape.SizeFDiagCursor)
|
||||
|
||||
def test_hover_move_event_topright_scale(self):
|
||||
self.item.setSelected(True)
|
||||
self.event.pos = MagicMock(return_value=QtCore.QPointF(100, 0))
|
||||
self.item.hoverMoveEvent(self.event)
|
||||
self.item.setCursor.assert_called_once_with(
|
||||
Qt.CursorShape.SizeBDiagCursor)
|
||||
|
||||
def test_hover_move_event_not_in_handles(self):
|
||||
self.item.setSelected(True)
|
||||
self.event.pos = MagicMock(return_value=QtCore.QPointF(50, 50))
|
||||
self.item.hoverMoveEvent(self.event)
|
||||
self.item.setCursor.assert_called_once_with(
|
||||
Qt.CursorShape.ArrowCursor)
|
||||
|
||||
def test_hover_enter_event_when_selected(self):
|
||||
self.item.setSelected(True)
|
||||
self.item.hoverEnterEvent(self.event)
|
||||
self.item.setCursor.assert_not_called()
|
||||
|
||||
def test_hover_enter_event_when_not_selected(self):
|
||||
self.item.setSelected(False)
|
||||
self.item.hoverEnterEvent(self.event)
|
||||
self.item.setCursor.assert_called_once_with(
|
||||
Qt.CursorShape.ArrowCursor)
|
||||
|
||||
def test_mouse_press_event_topleft_scale(self):
|
||||
self.item.setSelected(True)
|
||||
self.event.pos = MagicMock(return_value=QtCore.QPointF(2, 2))
|
||||
self.event.scenePos = MagicMock(return_value=QtCore.QPointF(66, 99))
|
||||
self.event.button = MagicMock(
|
||||
return_value=Qt.MouseButtons.LeftButton)
|
||||
self.item.mousePressEvent(self.event)
|
||||
assert self.item.scale_active_corner == (0, 0)
|
||||
assert self.item.scale_start == QtCore.QPointF(66, 99)
|
||||
assert self.item.scale_orig_factor == 1
|
||||
assert self.item.scale_orig_pos == QtCore.QPointF(0, 0)
|
||||
|
||||
def test_mouse_press_event_bottomright_scale(self):
|
||||
self.item.setSelected(True)
|
||||
self.event.pos = MagicMock(return_value=QtCore.QPointF(99, 79))
|
||||
self.event.scenePos = MagicMock(return_value=QtCore.QPointF(66, 99))
|
||||
self.event.button = MagicMock(
|
||||
return_value=Qt.MouseButtons.LeftButton)
|
||||
self.item.mousePressEvent(self.event)
|
||||
assert self.item.scale_active_corner == (100, 80)
|
||||
assert self.item.scale_start == QtCore.QPointF(66, 99)
|
||||
assert self.item.scale_orig_factor == 1
|
||||
assert self.item.scale_orig_pos == QtCore.QPointF(0, 0)
|
||||
|
||||
def test_mouse_press_event_not_selected(self):
|
||||
self.item.setSelected(False)
|
||||
with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mousePressEvent') as m:
|
||||
self.item.mousePressEvent(self.event)
|
||||
m.assert_called_once_with(self.event)
|
||||
assert self.item.scale_active_corner is None
|
||||
|
||||
def test_mouse_press_not_in_handles(self):
|
||||
self.item.setSelected(True)
|
||||
self.event.pos = MagicMock(return_value=QtCore.QPointF(50, 40))
|
||||
self.event.button = MagicMock(
|
||||
return_value=Qt.MouseButtons.LeftButton)
|
||||
with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mousePressEvent') as m:
|
||||
self.item.mousePressEvent(self.event)
|
||||
m.assert_called_once_with(self.event)
|
||||
assert self.item.scale_active_corner is None
|
||||
|
||||
def test_mouse_move_event_when_no_action(self):
|
||||
with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mouseMoveEvent') as m:
|
||||
self.item.mouseMoveEvent(self.event)
|
||||
m.assert_called_once_with(self.event)
|
||||
|
||||
def test_move_event_when_scale_action(self):
|
||||
self.event.scenePos = MagicMock(return_value=QtCore.QPointF(20, 90))
|
||||
self.item.scale_active_corner = (100, 80)
|
||||
self.item.scale_start = QtCore.QPointF(10, 10)
|
||||
self.item.scale_orig_factor = 1
|
||||
self.item.scale_orig_pos = QtCore.QPointF(0, 0)
|
||||
|
||||
self.item.mouseMoveEvent(self.event)
|
||||
assert self.item.scale() == 1.5
|
||||
|
||||
def test_mouse_release_event_when_no_action(self):
|
||||
with patch('PyQt6.QtWidgets.QGraphicsPixmapItem'
|
||||
'.mouseReleaseEvent') as m:
|
||||
self.item.mouseReleaseEvent(self.event)
|
||||
m.assert_called_once_with(self.event)
|
||||
|
||||
def test_mouse_release_event_when_scale_action(self):
|
||||
self.event.scenePos = MagicMock(return_value=QtCore.QPointF(20, 90))
|
||||
self.item.scale_active_corner = (100, 80)
|
||||
self.item.scale_start = QtCore.QPointF(10, 10)
|
||||
self.item.scale_orig_factor = 1
|
||||
self.item.scale_orig_pos = QtCore.QPointF(0, 0)
|
||||
self.scene.undo_stack = MagicMock()
|
||||
self.scene.undo_stack.push = MagicMock()
|
||||
|
||||
self.item.mouseReleaseEvent(self.event)
|
||||
self.scene.undo_stack.push.assert_called_once()
|
||||
args = self.scene.undo_stack.push.call_args_list[0][0]
|
||||
cmd = args[0]
|
||||
assert cmd.items == [self.item]
|
||||
assert cmd.delta == 0.5
|
||||
assert cmd.anchor == (0, 0)
|
||||
assert cmd.ignore_first_redo is True
|
||||
assert self.item.scale_active_corner is None
|
||||
|
|
|
|||
Loading…
Reference in a new issue