mirror of
https://github.com/rbreu/beeref.git
synced 2026-03-11 08:54:28 +00:00
Add rotation
This commit is contained in:
parent
8204d3141f
commit
d58b46b08f
11 changed files with 198 additions and 44 deletions
|
|
@ -16,11 +16,10 @@
|
|||
# along with BeeRef. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
import os.path
|
||||
import signal
|
||||
import sys
|
||||
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||
from PyQt6 import QtCore, QtWidgets
|
||||
|
||||
from beeref.assets import BeeAssets
|
||||
from beeref.config import CommandlineArgs
|
||||
|
|
|
|||
|
|
@ -18,8 +18,7 @@
|
|||
import logging
|
||||
import os.path
|
||||
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6 import QtGui
|
||||
|
||||
|
||||
logger = logging.getLogger('BeeRef')
|
||||
|
|
@ -27,7 +26,7 @@ logger = logging.getLogger('BeeRef')
|
|||
|
||||
class BeeAssets:
|
||||
_instance = None
|
||||
PATH = os.path.join( os.path.dirname(__file__), 'assets')
|
||||
PATH = os.path.join(os.path.dirname(__file__), 'assets')
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if not cls._instance:
|
||||
|
|
@ -36,7 +35,6 @@ class BeeAssets:
|
|||
return cls._instance
|
||||
|
||||
def on_new(self):
|
||||
PATH = os.path.join(os.path.dirname(__file__), 'assets')
|
||||
logger.debug(f'Assets path: {self.PATH}')
|
||||
|
||||
self.logo = QtGui.QIcon(os.path.join(self.PATH, 'logo.png'))
|
||||
|
|
|
|||
|
|
@ -103,6 +103,35 @@ class ScaleItemsBy(QtGui.QUndoCommand):
|
|||
item.setPos(data['orig_pos'])
|
||||
|
||||
|
||||
class RotateItemsBy(QtGui.QUndoCommand):
|
||||
"""Rotate items by a given deltan around the given anchor."""
|
||||
|
||||
def __init__(self, items, delta, anchor, ignore_first_redo=False):
|
||||
super().__init__('Scale items')
|
||||
self.ignore_first_redo = ignore_first_redo
|
||||
self.items = items
|
||||
self.delta = delta
|
||||
self.anchor = anchor
|
||||
|
||||
def redo(self):
|
||||
if self.ignore_first_redo:
|
||||
self.ignore_first_redo = False
|
||||
return
|
||||
for item in self.items:
|
||||
item.setRotation(item.rotation() + self.delta,
|
||||
item.mapFromScene(self.anchor))
|
||||
item.scene().on_selection_change()
|
||||
|
||||
def undo(self):
|
||||
if self.ignore_first_redo:
|
||||
self.ignore_first_redo = False
|
||||
return
|
||||
for item in self.items:
|
||||
item.setRotation(item.rotation() - self.delta,
|
||||
item.mapFromScene(self.anchor))
|
||||
item.scene().on_selection_change()
|
||||
|
||||
|
||||
class NormalizeItems(QtGui.QUndoCommand):
|
||||
|
||||
def __init__(self, items, scale_factors):
|
||||
|
|
|
|||
|
|
@ -127,20 +127,21 @@ class SQLiteIO:
|
|||
@handle_sqlite_errors
|
||||
def read(self):
|
||||
rows = self.fetchall(
|
||||
'SELECT items.id, x, y, z, scale, filename, sqlar.data '
|
||||
'SELECT items.id, x, y, z, scale, rotation, filename, sqlar.data '
|
||||
'FROM items '
|
||||
'INNER JOIN sqlar on sqlar.item_id = items.id')
|
||||
if self.progress:
|
||||
self.progress.setMaximum(len(rows))
|
||||
|
||||
for i, row in enumerate(rows):
|
||||
item = BeePixmapItem(QtGui.QImage(), filename=row[5])
|
||||
item = BeePixmapItem(QtGui.QImage(), filename=row[6])
|
||||
item.save_id = row[0]
|
||||
item.pixmap_from_bytes(row[6])
|
||||
item.pixmap_from_bytes(row[7])
|
||||
item.setPos(row[1], row[2])
|
||||
self.scene.addItem(item)
|
||||
item.setZValue(row[3])
|
||||
item.setScale(row[4])
|
||||
item.setRotation(row[5])
|
||||
if self.progress:
|
||||
self.progress.setValue(i)
|
||||
if self.progress.wasCanceled():
|
||||
|
|
@ -187,10 +188,10 @@ class SQLiteIO:
|
|||
|
||||
def insert_item(self, item):
|
||||
self.ex(
|
||||
'INSERT INTO items (type, x, y, z, scale, filename) '
|
||||
'VALUES (?, ?, ?, ?, ?, ?) ',
|
||||
'INSERT INTO items (type, x, y, z, scale, rotation, filename) '
|
||||
'VALUES (?, ?, ?, ?, ?, ?, ?) ',
|
||||
('pixmap', item.pos().x(), item.pos().y(), item.zValue(),
|
||||
item.scale(), item.filename))
|
||||
item.scale(), item.rotation(), item.filename))
|
||||
item.save_id = self.cursor.lastrowid
|
||||
pixmap = item.pixmap_to_bytes()
|
||||
|
||||
|
|
@ -213,8 +214,8 @@ class SQLiteIO:
|
|||
data never changes and is also time-consuming to save.
|
||||
"""
|
||||
self.ex(
|
||||
'UPDATE items SET x=?, y=?, z=?, scale=?, filename=? '
|
||||
'UPDATE items SET x=?, y=?, z=?, scale=?, rotation=?, filename=? '
|
||||
'WHERE id=?',
|
||||
(item.pos().x(), item.pos().y(), item.zValue(), item.scale(),
|
||||
item.filename, item.save_id))
|
||||
item.rotation(), item.filename, item.save_id))
|
||||
self.connection.commit()
|
||||
|
|
|
|||
|
|
@ -40,11 +40,11 @@ class BeePixmapItem(SelectableMixin, QtWidgets.QGraphicsPixmapItem):
|
|||
def __str__(self):
|
||||
return (f'Image "{self.filename}" {self.width} x {self.height}')
|
||||
|
||||
def set_pos_center(self, x, y):
|
||||
def set_pos_center(self, pos):
|
||||
"""Sets the position using the item's center as the origin point."""
|
||||
|
||||
self.setPos(x - self.width * self.scale() / 2,
|
||||
y - self.height * self.scale() / 2)
|
||||
center = self.mapToScene(QtCore.QPointF(self.width/2, self.height/2))
|
||||
self.setPos(pos - center)
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
"""Classes for items that draw and handle selection stuff."""
|
||||
|
||||
import logging
|
||||
import math
|
||||
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||
from PyQt6.QtCore import Qt
|
||||
|
|
@ -50,6 +51,16 @@ class BaseItemMixin:
|
|||
def bring_to_front(self):
|
||||
self.setZValue(self.scene().max_z + 0.001)
|
||||
|
||||
def setRotation(self, rotation, anchor=None):
|
||||
anchor = anchor or QtCore.QPointF(0, 0)
|
||||
prev = self.mapToScene(anchor)
|
||||
super().setRotation(rotation)
|
||||
|
||||
# Calculate where the anchor moved to so that we can shift the
|
||||
# item back to keep the anchor fixed
|
||||
diff = self.mapToScene(anchor) - prev
|
||||
self.setPos(self.pos() - diff)
|
||||
|
||||
|
||||
class SelectableMixin(BaseItemMixin):
|
||||
"""Common code for selectable items: Selection outline, handles etc."""
|
||||
|
|
@ -66,6 +77,7 @@ class SelectableMixin(BaseItemMixin):
|
|||
| QtWidgets.QGraphicsItem.GraphicsItemFlags.ItemIsSelectable)
|
||||
|
||||
self.scale_active = False
|
||||
self.rotate_active = False
|
||||
self.viewport_scale = 1
|
||||
self.conf_debug_shapes = commandline_args.draw_debug_shapes
|
||||
|
||||
|
|
@ -223,7 +235,7 @@ class SelectableMixin(BaseItemMixin):
|
|||
if self.get_scale_bounds(corner).contains(event.pos()):
|
||||
# Start scale action for this corner
|
||||
self.scale_active = True
|
||||
self.scale_start = event.scenePos()
|
||||
self.event_start = event.scenePos()
|
||||
self.scale_direction = self.get_corner_direction(corner)
|
||||
for item in self.selection_action_items():
|
||||
item.scale_anchor = self.get_scale_anchor(item, corner)
|
||||
|
|
@ -231,12 +243,25 @@ class SelectableMixin(BaseItemMixin):
|
|||
item.scale_orig_pos = item.pos()
|
||||
event.accept()
|
||||
return
|
||||
# Check if we are in one of the corner's rotate areas
|
||||
if self.get_rotate_bounds(corner).contains(event.pos()):
|
||||
# Start rotate action
|
||||
self.rotate_active = True
|
||||
self.rotate_anchor = self.mapToScene(
|
||||
QtCore.QPointF(self.width/2, self.height/2))
|
||||
self.rotate_start_angle = self.get_rotate_angle(event)
|
||||
for item in self.selection_action_items():
|
||||
item.rotate_anchor = self.rotate_anchor
|
||||
item.rotate_orig_degrees = item.rotation()
|
||||
event.accept()
|
||||
return
|
||||
|
||||
super().mousePressEvent(event)
|
||||
|
||||
def get_scale_factor(self, event):
|
||||
"""Get the scale factor for the current mouse movement."""
|
||||
imgsize = self.width + self.height
|
||||
p = event.scenePos() - self.scale_start
|
||||
p = event.scenePos() - self.event_start
|
||||
direction = self.scale_direction
|
||||
delta = QtCore.QPointF.dotProduct(direction, p) / imgsize
|
||||
return (self.scale_orig_factor + delta) / self.scale_orig_factor
|
||||
|
|
@ -265,6 +290,17 @@ class SelectableMixin(BaseItemMixin):
|
|||
self.scale_orig_pos.y() - self.scale_anchor.y() * factor,
|
||||
)
|
||||
|
||||
def get_rotate_angle(self, event):
|
||||
"""Get the angle of the current mouse position towards the
|
||||
scale center."""
|
||||
|
||||
diff = event.scenePos() - self.rotate_anchor
|
||||
return -math.degrees(math.atan2(diff.x(), diff.y()))
|
||||
|
||||
def get_rotate_delta(self, event):
|
||||
"""Get the rotate delta for the current mouse movement"""
|
||||
return self.get_rotate_angle(event) - self.rotate_start_angle
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
if self.scale_active:
|
||||
factor = self.get_scale_factor(event)
|
||||
|
|
@ -272,6 +308,11 @@ class SelectableMixin(BaseItemMixin):
|
|||
item.setScale(item.scale_orig_factor * factor)
|
||||
item.translate_for_scale_anchor(factor)
|
||||
event.accept()
|
||||
elif self.rotate_active:
|
||||
delta = self.get_rotate_delta(event)
|
||||
for item in self.selection_action_items():
|
||||
item.setRotation(item.rotate_orig_degrees + delta,
|
||||
item.mapFromScene(self.rotate_anchor))
|
||||
else:
|
||||
super().mouseMoveEvent(event)
|
||||
|
||||
|
|
@ -284,6 +325,16 @@ class SelectableMixin(BaseItemMixin):
|
|||
ignore_first_redo=True))
|
||||
self.scale_active = False
|
||||
event.accept()
|
||||
elif self.rotate_active:
|
||||
self.scene().on_selection_change()
|
||||
self.scene().undo_stack.push(
|
||||
commands.RotateItemsBy(
|
||||
self.selection_action_items(),
|
||||
self.get_rotate_delta(event),
|
||||
self.rotate_anchor,
|
||||
ignore_first_redo=True))
|
||||
self.rotate_active = False
|
||||
event.accept()
|
||||
else:
|
||||
super().mouseReleaseEvent(event)
|
||||
|
||||
|
|
|
|||
|
|
@ -323,7 +323,7 @@ class BeeGraphicsView(QtWidgets.QGraphicsView):
|
|||
errors.append(filename)
|
||||
continue
|
||||
item = BeePixmapItem(img, filename)
|
||||
item.set_pos_center(pos.x(), pos.y())
|
||||
item.set_pos_center(pos)
|
||||
items.append(item)
|
||||
pos.setX(pos.x() + 50)
|
||||
pos.setY(pos.y() + 50)
|
||||
|
|
|
|||
|
|
@ -92,12 +92,13 @@ class SQLiteIOWriteTestCase(BeeTestCase):
|
|||
item.setScale(1.3)
|
||||
item.setPos(44, 55)
|
||||
item.setZValue(0.22)
|
||||
item.setRotation(33)
|
||||
item.pixmap_to_bytes = MagicMock(return_value=b'abc')
|
||||
self.io.write()
|
||||
|
||||
assert item.save_id == 1
|
||||
result = self.io.fetchone(
|
||||
'SELECT x, y, z, scale, filename, type, '
|
||||
'SELECT x, y, z, scale, rotation, filename, type, '
|
||||
'sqlar.data, sqlar.name '
|
||||
'FROM items '
|
||||
'INNER JOIN sqlar on sqlar.item_id = items.id')
|
||||
|
|
@ -105,10 +106,11 @@ class SQLiteIOWriteTestCase(BeeTestCase):
|
|||
assert result[1] == 55.0
|
||||
assert result[2] == 0.22
|
||||
assert result[3] == 1.3
|
||||
assert result[4] == 'bee.jpg'
|
||||
assert result[5] == 'pixmap'
|
||||
assert result[6] == b'abc'
|
||||
assert result[7] == '0001-bee.png'
|
||||
assert result[4] == 33
|
||||
assert result[5] == 'bee.jpg'
|
||||
assert result[6] == 'pixmap'
|
||||
assert result[7] == b'abc'
|
||||
assert result[8] == '0001-bee.png'
|
||||
|
||||
def test_inserts_new_item_without_filename(self):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
|
|
@ -128,12 +130,14 @@ class SQLiteIOWriteTestCase(BeeTestCase):
|
|||
item.setScale(1.3)
|
||||
item.setPos(44, 55)
|
||||
item.setZValue(0.22)
|
||||
item.setRotation(33)
|
||||
item.save_id = 1
|
||||
item.pixmap_to_bytes = MagicMock(return_value=b'abc')
|
||||
self.io.write()
|
||||
item.setScale(0.7)
|
||||
item.setPos(20, 30)
|
||||
item.setZValue(0.33)
|
||||
item.setRotation(100)
|
||||
item.filename = 'new.png'
|
||||
item.pixmap_to_bytes.return_value = b'updated'
|
||||
self.io.create_new = False
|
||||
|
|
@ -141,15 +145,16 @@ class SQLiteIOWriteTestCase(BeeTestCase):
|
|||
|
||||
assert self.io.fetchone('SELECT COUNT(*) from items') == (1,)
|
||||
result = self.io.fetchone(
|
||||
'SELECT x, y, z, scale, filename, sqlar.data '
|
||||
'SELECT x, y, z, scale, rotation, filename, sqlar.data '
|
||||
'FROM items '
|
||||
'INNER JOIN sqlar on sqlar.item_id = items.id')
|
||||
assert result[0] == 20
|
||||
assert result[1] == 30
|
||||
assert result[2] == 0.33
|
||||
assert result[3] == 0.7
|
||||
assert result[4] == 'new.png'
|
||||
assert result[5] == b'abc'
|
||||
assert result[4] == 100
|
||||
assert result[5] == 'new.png'
|
||||
assert result[6] == b'abc'
|
||||
|
||||
def test_removes_nonexisting_item(self):
|
||||
item = BeePixmapItem(QtGui.QImage(), filename='bee.png')
|
||||
|
|
@ -200,9 +205,10 @@ class SQLiteIOReadTestCase(BeeTestCase):
|
|||
fname = os.path.join(dirname, 'test.bee')
|
||||
io = SQLiteIO(fname, self.scene, create_new=True)
|
||||
io.create_schema_on_new()
|
||||
io.ex('INSERT INTO items (type, x, y, z, scale, filename) '
|
||||
'VALUES (?, ?, ?, ?, ?, ?) ',
|
||||
('pixmap', 22.2, 33.3, 0.22, 3.4, 'bee.png'))
|
||||
io.ex('INSERT INTO items '
|
||||
'(type, x, y, z, scale, rotation, filename) '
|
||||
'VALUES (?, ?, ?, ?, ?, ?, ?) ',
|
||||
('pixmap', 22.2, 33.3, 0.22, 3.4, 45, 'bee.png'))
|
||||
io.ex('INSERT INTO sqlar (item_id, data) VALUES (?, ?)',
|
||||
(1, self.imgdata3x3))
|
||||
io.connection.commit()
|
||||
|
|
@ -217,6 +223,7 @@ class SQLiteIOReadTestCase(BeeTestCase):
|
|||
assert item.pos().y() == 33.3
|
||||
assert item.zValue() == 0.22
|
||||
assert item.scale() == 3.4
|
||||
assert item.rotation() == 45
|
||||
assert item.filename == 'bee.png'
|
||||
assert item.width == 3
|
||||
assert item.height == 3
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from unittest.mock import patch
|
||||
|
||||
from beeref.config import CommandlineArgs
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from unittest.mock import patch, MagicMock, PropertyMock
|
||||
|
||||
from PyQt6 import QtGui
|
||||
from PyQt6 import QtCore, QtGui
|
||||
|
||||
from beeref.items import BeePixmapItem
|
||||
from beeref.scene import BeeGraphicsScene
|
||||
|
|
@ -29,7 +29,7 @@ class BeePixmapItemTestCase(BeeTestCase):
|
|||
new_callable=PropertyMock, return_value=200):
|
||||
with patch('beeref.items.BeePixmapItem.height',
|
||||
new_callable=PropertyMock, return_value=100):
|
||||
item.set_pos_center(0, 0)
|
||||
item.set_pos_center(QtCore.QPointF(0, 0))
|
||||
assert item.pos().x() == -100
|
||||
assert item.pos().y() == -50
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,27 @@ class BaseItemMixinTestCase(BeeTestCase):
|
|||
assert item2.zValue() > item1.zValue()
|
||||
assert item2.zValue() == self.scene.max_z
|
||||
|
||||
def test_set_rotation_anchor_topleft(self):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
item.setRotation(45)
|
||||
assert item.rotation() == 45
|
||||
assert item.pos().x() == 0
|
||||
assert item.pos().y() == 0
|
||||
|
||||
def test_set_rotation_anchor_bottomright_cw(self):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
item.setRotation(90, QtCore.QPointF(100, 100))
|
||||
assert item.rotation() == 90
|
||||
assert item.pos().x() == 200
|
||||
assert item.pos().y() == 0
|
||||
|
||||
def test_set_rotation_anchor_bottomright_ccw(self):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
item.setRotation(-90, QtCore.QPointF(100, 100))
|
||||
assert item.rotation() == -90
|
||||
assert item.pos().x() == 0
|
||||
assert item.pos().y() == 200
|
||||
|
||||
|
||||
class SelectableMixinBaseTestCase(BeeTestCase):
|
||||
|
||||
|
|
@ -307,7 +328,7 @@ class SelectableMixinTestCase(SelectableMixinBaseTestCase):
|
|||
class SelectableMixinScalingTestCase(SelectableMixinBaseTestCase):
|
||||
|
||||
def test_get_scale_factor_bottomright(self):
|
||||
self.item.scale_start = QtCore.QPointF(10, 10)
|
||||
self.item.event_start = QtCore.QPointF(10, 10)
|
||||
self.item.scale_direction = QtCore.QPointF(1, 1)
|
||||
self.item.scale_orig_factor = 1
|
||||
event = MagicMock()
|
||||
|
|
@ -315,7 +336,7 @@ class SelectableMixinScalingTestCase(SelectableMixinBaseTestCase):
|
|||
assert self.item.get_scale_factor(event) == 1.5
|
||||
|
||||
def test_get_scale_factor_topleft(self):
|
||||
self.item.scale_start = QtCore.QPointF(10, 10)
|
||||
self.item.event_start = QtCore.QPointF(10, 10)
|
||||
self.item.scale_direction = QtCore.QPointF(-1, -1)
|
||||
self.item.scale_orig_factor = 0.5
|
||||
event = MagicMock()
|
||||
|
|
@ -373,6 +394,19 @@ class SelectableMixinScalingTestCase(SelectableMixinBaseTestCase):
|
|||
assert self.item.pos().x() == -150
|
||||
assert self.item.pos().y() == -90
|
||||
|
||||
def test_get_rotate_angle(self):
|
||||
self.item.rotate_anchor = QtCore.QPointF(10, 20)
|
||||
event = MagicMock()
|
||||
event.scenePos = MagicMock(return_value=QtCore.QPointF(15, 25))
|
||||
assert self.item.get_rotate_angle(event) == -45
|
||||
|
||||
def test_get_rotate_delta(self):
|
||||
self.item.rotate_anchor = QtCore.QPointF(10, 20)
|
||||
self.item.rotate_start_angle = -3
|
||||
event = MagicMock()
|
||||
event.scenePos = MagicMock(return_value=QtCore.QPointF(15, 25))
|
||||
assert self.item.get_rotate_delta(event) == -42
|
||||
|
||||
|
||||
class SelectableMixinMouseEventsTestCase(SelectableMixinBaseTestCase):
|
||||
|
||||
|
|
@ -380,6 +414,8 @@ class SelectableMixinMouseEventsTestCase(SelectableMixinBaseTestCase):
|
|||
super().setUp()
|
||||
self.event = MagicMock()
|
||||
self.item.setCursor = MagicMock()
|
||||
self.item.SELECT_RESIZE_SIZE = 10
|
||||
self.item.SELECT_ROTATE_SIZE = 10
|
||||
|
||||
def test_hover_move_event_no_selection(self):
|
||||
self.event.pos = MagicMock(return_value=QtCore.QPointF(0, 0))
|
||||
|
|
@ -409,8 +445,6 @@ class SelectableMixinMouseEventsTestCase(SelectableMixinBaseTestCase):
|
|||
|
||||
def test_hover_move_event_rotate(self):
|
||||
self.item.setSelected(True)
|
||||
self.item.SELECT_RESIZE_SIZE = 10
|
||||
self.item.SELECT_ROTATE_SIZE = 10
|
||||
self.event.pos = MagicMock(return_value=QtCore.QPointF(110, 90))
|
||||
self.item.hoverMoveEvent(self.event)
|
||||
self.item.setCursor.assert_called_once_with(BeeAssets().cursor_rotate)
|
||||
|
|
@ -441,7 +475,7 @@ class SelectableMixinMouseEventsTestCase(SelectableMixinBaseTestCase):
|
|||
return_value=Qt.MouseButtons.LeftButton)
|
||||
self.item.mousePressEvent(self.event)
|
||||
assert self.item.scale_active is True
|
||||
assert self.item.scale_start == QtCore.QPointF(66, 99)
|
||||
assert self.item.event_start == QtCore.QPointF(66, 99)
|
||||
assert self.item.scale_direction == QtCore.QPointF(-1, -1)
|
||||
assert self.item.scale_orig_factor == 1
|
||||
assert self.item.scale_orig_pos == QtCore.QPointF(0, 0)
|
||||
|
|
@ -455,10 +489,21 @@ class SelectableMixinMouseEventsTestCase(SelectableMixinBaseTestCase):
|
|||
self.item.mousePressEvent(self.event)
|
||||
assert self.item.scale_active is True
|
||||
assert self.item.scale_direction == QtCore.QPointF(1, 1)
|
||||
assert self.item.scale_start == QtCore.QPointF(66, 99)
|
||||
assert self.item.event_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_rotate(self):
|
||||
self.item.setSelected(True)
|
||||
self.event.pos = MagicMock(return_value=QtCore.QPointF(111, 91))
|
||||
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.rotate_active is True
|
||||
assert self.item.rotate_anchor == QtCore.QPointF(50, 40)
|
||||
assert self.item.rotate_orig_degrees == 0
|
||||
|
||||
def test_mouse_press_event_not_selected(self):
|
||||
self.item.setSelected(False)
|
||||
with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mousePressEvent') as m:
|
||||
|
|
@ -481,18 +526,27 @@ class SelectableMixinMouseEventsTestCase(SelectableMixinBaseTestCase):
|
|||
self.item.mouseMoveEvent(self.event)
|
||||
m.assert_called_once_with(self.event)
|
||||
|
||||
def test_move_event_when_scale_action(self):
|
||||
def test_mouse_move_event_when_scale_action(self):
|
||||
self.event.scenePos = MagicMock(return_value=QtCore.QPointF(20, 90))
|
||||
self.item.scale_active = True
|
||||
self.item.scale_direction = QtCore.QPointF(1, 1)
|
||||
self.item.scale_anchor = QtCore.QPointF(100, 80)
|
||||
self.item.scale_start = QtCore.QPointF(10, 10)
|
||||
self.item.event_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_move_event_when_rotate_action(self):
|
||||
self.event.scenePos = MagicMock(return_value=QtCore.QPointF(15, 25))
|
||||
self.item.rotate_active = True
|
||||
self.item.rotate_orig_degrees = 0
|
||||
self.item.rotate_start_angle = -3
|
||||
self.item.rotate_anchor = QtCore.QPointF(10, 20)
|
||||
self.item.mouseMoveEvent(self.event)
|
||||
assert self.item.rotation() == -42
|
||||
|
||||
def test_mouse_release_event_when_no_action(self):
|
||||
with patch('PyQt6.QtWidgets.QGraphicsPixmapItem'
|
||||
'.mouseReleaseEvent') as m:
|
||||
|
|
@ -504,7 +558,7 @@ class SelectableMixinMouseEventsTestCase(SelectableMixinBaseTestCase):
|
|||
self.item.scale_active = True
|
||||
self.item.scale_direction = QtCore.QPointF(1, 1)
|
||||
self.item.scale_anchor = QtCore.QPointF(100, 80)
|
||||
self.item.scale_start = QtCore.QPointF(10, 10)
|
||||
self.item.event_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(push=MagicMock())
|
||||
|
|
@ -522,6 +576,23 @@ class SelectableMixinMouseEventsTestCase(SelectableMixinBaseTestCase):
|
|||
assert cmd.ignore_first_redo is True
|
||||
assert self.item.scale_active is False
|
||||
|
||||
def test_mouse_release_event_when_rotate_action(self):
|
||||
self.event.scenePos = MagicMock(return_value=QtCore.QPointF(15, 25))
|
||||
self.item.rotate_active = True
|
||||
self.item.rotate_orig_degrees = 0
|
||||
self.item.rotate_start_angle = -3
|
||||
self.item.rotate_anchor = QtCore.QPointF(10, 20)
|
||||
self.scene.undo_stack = MagicMock(push=MagicMock())
|
||||
|
||||
self.item.mouseReleaseEvent(self.event)
|
||||
args = self.scene.undo_stack.push.call_args_list[0][0]
|
||||
cmd = args[0]
|
||||
assert cmd.items == [self.item]
|
||||
assert cmd.delta == -42
|
||||
assert cmd.anchor == QtCore.QPointF(10, 20)
|
||||
assert cmd.ignore_first_redo is True
|
||||
assert self.item.rotate_active is False
|
||||
|
||||
|
||||
class MultiSelectItemTestCase(BeeTestCase):
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue