mirror of
https://github.com/rbreu/beeref.git
synced 2026-03-11 08:54:28 +00:00
Unittests for multi selections
This commit is contained in:
parent
511848146e
commit
165283d56c
6 changed files with 1192 additions and 749 deletions
312
beeref/items.py
312
beeref/items.py
|
|
@ -20,247 +20,13 @@ text).
|
|||
import logging
|
||||
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtWidgets import QGraphicsItem
|
||||
|
||||
from beeref import commands
|
||||
from beeref.config import CommandlineArgs
|
||||
from beeref.selection import SelectableMixin
|
||||
|
||||
|
||||
commandline_args = CommandlineArgs()
|
||||
logger = logging.getLogger('BeeRef')
|
||||
|
||||
|
||||
class SelectableMixin:
|
||||
"""Common code for selectable items: Selection outline, handles etc."""
|
||||
|
||||
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 = 20 # size of hover area for scaling
|
||||
SELECT_ROTATE_SIZE = 20 # size of hover area for rotating
|
||||
|
||||
def init_selectable(self):
|
||||
self.setAcceptHoverEvents(True)
|
||||
self.setFlags(
|
||||
QtWidgets.QGraphicsItem.GraphicsItemFlags.ItemIsMovable
|
||||
| QtWidgets.QGraphicsItem.GraphicsItemFlags.ItemIsSelectable)
|
||||
|
||||
self.scale_active = False
|
||||
self.viewport_scale = 1
|
||||
self.conf_debug_shapes = commandline_args.draw_debug_shapes
|
||||
|
||||
def bring_to_front(self):
|
||||
self.setZValue(self.scene().max_z + 0.001)
|
||||
|
||||
def fixed_length_for_viewport(self, value):
|
||||
"""The interactable areas need to stay the same size on the
|
||||
screen so we need to adjust the values according to the scale
|
||||
factor sof the view and the item."""
|
||||
|
||||
if self.scene():
|
||||
scale = self.scene().views()[0].get_scale()
|
||||
self._view_scale = scale
|
||||
|
||||
# It can happen that the item is already removed from
|
||||
# the scene but its boundingRect is still needed. Keep the
|
||||
# last known scaling factor for that case
|
||||
return value / getattr(self, '_view_scale', 1) / self.scale()
|
||||
|
||||
@property
|
||||
def select_resize_size(self):
|
||||
return self.fixed_length_for_viewport(self.SELECT_RESIZE_SIZE)
|
||||
|
||||
@property
|
||||
def select_rotate_size(self):
|
||||
return self.fixed_length_for_viewport(self.SELECT_ROTATE_SIZE)
|
||||
|
||||
def draw_debug_shape(self, painter, shape, r, g, b):
|
||||
color = QtGui.QColor(r, g, b, 50)
|
||||
if isinstance(shape, QtCore.QRectF):
|
||||
painter.fillRect(shape, color)
|
||||
else:
|
||||
painter.fillPath(shape, color)
|
||||
|
||||
def paint_selectable(self, painter, option, widget):
|
||||
if self.conf_debug_shapes:
|
||||
self.draw_debug_shape(painter, self.boundingRect(), 0, 255, 0)
|
||||
self.draw_debug_shape(painter, self.shape(), 255, 0, 0)
|
||||
|
||||
if not self.has_selection_outline():
|
||||
return
|
||||
|
||||
pen = QtGui.QPen(self.select_color)
|
||||
pen.setWidth(self.SELECT_LINE_WIDTH)
|
||||
pen.setCosmetic(True)
|
||||
painter.setPen(pen)
|
||||
|
||||
# Draw the main selection rectangle
|
||||
painter.drawRect(0, 0, self.width, self.height)
|
||||
|
||||
# If it's a single selection, draw the handles:
|
||||
if self.has_selection_handles():
|
||||
pen.setWidth(self.SELECT_HANDLE_SIZE)
|
||||
painter.setPen(pen)
|
||||
for corner in self.corners:
|
||||
painter.drawPoint(*corner)
|
||||
|
||||
@property
|
||||
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(
|
||||
center[0] - self.select_resize_size/2,
|
||||
center[1] - self.select_resize_size/2,
|
||||
self.select_resize_size,
|
||||
self.select_resize_size)
|
||||
|
||||
@property
|
||||
def bottom_right_rotate_bounds(self):
|
||||
"""The interactable shape of the bottom right rotate handle"""
|
||||
return QtCore.QRectF(
|
||||
self.width + self.select_resize_size / 2,
|
||||
self.height + self.select_resize_size / 2,
|
||||
self.select_rotate_size, self.select_rotate_size)
|
||||
|
||||
def boundingRect(self):
|
||||
bounds = super().boundingRect()
|
||||
if not self.has_selection_outline():
|
||||
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,
|
||||
bounds.topLeft().y() - margin,
|
||||
bounds.bottomRight().x() + 2 * margin,
|
||||
bounds.bottomRight().y() + 2 * margin)
|
||||
|
||||
def shape(self):
|
||||
shape_ = super().shape()
|
||||
if self.has_selection_outline():
|
||||
# Add extra space for scale and rotate interactive areas
|
||||
path = QtGui.QPainterPath()
|
||||
for corner in self.corners:
|
||||
path.addRect(self.get_scale_bounds(corner))
|
||||
path.addRect(self.bottom_right_rotate_bounds)
|
||||
shape_ = shape_ + path
|
||||
return shape_
|
||||
|
||||
def hoverMoveEvent(self, event):
|
||||
if not self.has_selection_handles():
|
||||
return
|
||||
|
||||
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)
|
||||
return
|
||||
|
||||
self.setCursor(Qt.CursorShape.ArrowCursor)
|
||||
|
||||
def hoverEnterEvent(self, event):
|
||||
# Always return regular cursor when there aren't any selection handles
|
||||
if not self.has_selection_handles():
|
||||
self.setCursor(Qt.CursorShape.ArrowCursor)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if (event.button() == Qt.MouseButtons.LeftButton
|
||||
and self.has_selection_handles()):
|
||||
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 = True
|
||||
self.scale_start = event.scenePos()
|
||||
self.scale_direction = self.get_scale_direction(corner)
|
||||
for item in self.selection_action_items():
|
||||
item.scale_anchor = self.get_scale_anchor(item, corner)
|
||||
item.scale_orig_factor = item.scale()
|
||||
item.scale_orig_pos = item.pos()
|
||||
event.accept()
|
||||
return
|
||||
|
||||
super().mousePressEvent(event)
|
||||
|
||||
def get_scale_factor(self, event):
|
||||
imgsize = self.width + self.height
|
||||
p = event.scenePos() - self.scale_start
|
||||
direction = self.scale_direction
|
||||
delta = (direction[0] * p.x() + direction[1] * p.y()) / imgsize
|
||||
return (self.scale_orig_factor + delta) / self.scale_orig_factor
|
||||
|
||||
def get_scale_anchor(self, item, corner):
|
||||
"""Get the anchor around which the scale for this corner operates."""
|
||||
return item.mapFromScene(
|
||||
self.mapToScene(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, scale_factor):
|
||||
"""Adjust the item's position so that a scale with the given scale
|
||||
factor appears to operate around the scale anchor. ``setScale``
|
||||
needs to be called separately with ``scale_factor`` multiplied by
|
||||
the item's current scale factor.
|
||||
"""
|
||||
|
||||
factor = self.scale_orig_factor * (scale_factor - 1)
|
||||
self.setPos(
|
||||
self.scale_orig_pos.x() - self.scale_anchor.x() * factor,
|
||||
self.scale_orig_pos.y() - self.scale_anchor.y() * factor,
|
||||
)
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
if self.scale_active:
|
||||
factor = self.get_scale_factor(event)
|
||||
for item in self.selection_action_items():
|
||||
item.setScale(item.scale_orig_factor * factor)
|
||||
item.translate_for_scale_anchor(factor)
|
||||
event.accept()
|
||||
else:
|
||||
super().mouseMoveEvent(event)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if self.scale_active:
|
||||
self.scene().undo_stack.push(
|
||||
commands.ScaleItemsBy(
|
||||
self.selection_action_items(),
|
||||
self.get_scale_factor(event),
|
||||
ignore_first_redo=True))
|
||||
self.scale_active = False
|
||||
event.accept()
|
||||
else:
|
||||
super().mouseReleaseEvent(event)
|
||||
|
||||
def on_view_scale_change(self):
|
||||
self.prepareGeometryChange()
|
||||
|
||||
def itemChange(self, change, value):
|
||||
if change == QGraphicsItem.GraphicsItemChange.ItemSelectedChange:
|
||||
self.prepareGeometryChange()
|
||||
if hasattr(self, 'on_selected_change'):
|
||||
self.on_selected_change(value)
|
||||
return super().itemChange(change, value)
|
||||
|
||||
|
||||
class BeePixmapItem(SelectableMixin, QtWidgets.QGraphicsPixmapItem):
|
||||
"""Class for images added by the user."""
|
||||
|
||||
|
|
@ -272,21 +38,7 @@ class BeePixmapItem(SelectableMixin, QtWidgets.QGraphicsPixmapItem):
|
|||
self.init_selectable()
|
||||
|
||||
def __str__(self):
|
||||
return (f'Image "{self.filename}" '
|
||||
f'with dimensions {self.width} x {self.height}')
|
||||
|
||||
def setScale(self, factor):
|
||||
if factor <= 0:
|
||||
return
|
||||
|
||||
logger.debug(f'Setting scale for image "{self.filename}" to {factor}')
|
||||
self.prepareGeometryChange()
|
||||
super().setScale(factor)
|
||||
|
||||
def setZValue(self, value):
|
||||
logger.debug(f'Setting z-value for image "{self.filename}" to {value}')
|
||||
super().setZValue(value)
|
||||
self.scene().max_z = max(self.scene().max_z, value)
|
||||
return (f'Image "{self.filename}" {self.width} x {self.height}')
|
||||
|
||||
def set_pos_center(self, x, y):
|
||||
"""Sets the position using the item's center as the origin point."""
|
||||
|
|
@ -302,11 +54,6 @@ class BeePixmapItem(SelectableMixin, QtWidgets.QGraphicsPixmapItem):
|
|||
def height(self):
|
||||
return self.pixmap().size().height()
|
||||
|
||||
@property
|
||||
def scene_bottomright(self):
|
||||
"""The bottom right corner in scene coordinates."""
|
||||
return self.mapToScene(QtCore.QPointF(self.width, self.height))
|
||||
|
||||
def pixmap_to_bytes(self):
|
||||
"""Convert the pixmap data to PNG bytestring."""
|
||||
barray = QtCore.QByteArray()
|
||||
|
|
@ -340,58 +87,3 @@ class BeePixmapItem(SelectableMixin, QtWidgets.QGraphicsPixmapItem):
|
|||
def on_selected_change(self, value):
|
||||
if(value and self.scene() and not self.scene().has_selection()):
|
||||
self.bring_to_front()
|
||||
|
||||
|
||||
class MultiSelectItem(SelectableMixin, QtWidgets.QGraphicsRectItem):
|
||||
"""Class for images added by the user."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.init_selectable()
|
||||
|
||||
def __str__(self):
|
||||
return (f'MultiSelectItem '
|
||||
f'with dimensions {self.width} x {self.height}')
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return self.rect().width()
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
return self.rect().height()
|
||||
|
||||
def paint(self, painter, option, widget):
|
||||
self.paint_selectable(painter, option, widget)
|
||||
|
||||
def has_selection_outline(self):
|
||||
return True
|
||||
|
||||
def has_selection_handles(self):
|
||||
return True
|
||||
|
||||
def selection_action_items(self):
|
||||
"""The items affected by selection actions like scaling and rotating.
|
||||
"""
|
||||
return list(self.scene().selectedItems())
|
||||
|
||||
def fit_selection_area(self, rect):
|
||||
"""Updates itself to fit the given selection area."""
|
||||
|
||||
logging.debug(f'Fit selection area to {rect}')
|
||||
self.setRect(0, 0, rect.width(), rect.height())
|
||||
self.setPos(rect.topLeft())
|
||||
self.setScale(1)
|
||||
self.setRotation(0)
|
||||
self.setSelected(True)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if (event.button() == Qt.MouseButtons.LeftButton
|
||||
and event.modifiers() == Qt.KeyboardModifiers.ControlModifier):
|
||||
# We still need to be able to select additional images
|
||||
# within/"under" the multi select rectangle, so let ctrl+click
|
||||
# events pass through
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
super().mousePressEvent(event)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ from PyQt6 import QtCore, QtWidgets
|
|||
from PyQt6.QtCore import Qt
|
||||
|
||||
from beeref import commands
|
||||
from beeref.items import MultiSelectItem
|
||||
from beeref.selection import MultiSelectItem
|
||||
|
||||
|
||||
logger = logging.getLogger('BeeRef')
|
||||
|
|
@ -119,13 +119,13 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene):
|
|||
commands.MoveItemsBy(self.selectedItems(),
|
||||
delta.x(), delta.y(),
|
||||
ignore_first_redo=True))
|
||||
self.move_active = False
|
||||
self.move_active = False
|
||||
super().mouseReleaseEvent(event)
|
||||
|
||||
def items_for_save(self):
|
||||
"""Returns the items that are to be saved.
|
||||
|
||||
Items to be saved are items that have an save_id attribute.
|
||||
Items to be saved are items that have a save_id attribute.
|
||||
"""
|
||||
|
||||
return filter(lambda i: hasattr(i, 'save_id'),
|
||||
|
|
@ -144,19 +144,17 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene):
|
|||
|
||||
items = list(filter(lambda i: hasattr(i, 'save_id'),
|
||||
self.selectedItems()))
|
||||
x = []
|
||||
y = []
|
||||
|
||||
topleft = items[0].pos()
|
||||
bottomright = items[0].scene_bottomright
|
||||
for item in items:
|
||||
for corner in item.corners_scene_coords:
|
||||
x.append(corner.x())
|
||||
y.append(corner.y())
|
||||
|
||||
for item in items[1:]:
|
||||
tl = item.pos()
|
||||
br = item.scene_bottomright
|
||||
topleft.setX(min(topleft.x(), tl.x(), br.x()))
|
||||
topleft.setY(min(topleft.y(), tl.y(), br.y()))
|
||||
bottomright.setX(max(bottomright.x(), tl.x(), br.x()))
|
||||
bottomright.setY(max(bottomright.y(), tl.y(), br.y()))
|
||||
|
||||
return QtCore.QRectF(topleft, bottomright)
|
||||
return QtCore.QRectF(
|
||||
QtCore.QPointF(min(x), min(y)),
|
||||
QtCore.QPointF(max(x), max(y)))
|
||||
|
||||
def on_selection_change(self):
|
||||
if self.has_multi_selection():
|
||||
|
|
|
|||
332
beeref/selection.py
Normal file
332
beeref/selection.py
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
# This file is part of BeeRef.
|
||||
#
|
||||
# BeeRef is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BeeRef is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BeeRef. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
"""Classes for items that draw and handle selection stuff."""
|
||||
|
||||
import logging
|
||||
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtWidgets import QGraphicsItem
|
||||
|
||||
from beeref import commands
|
||||
from beeref.config import CommandlineArgs
|
||||
|
||||
|
||||
commandline_args = CommandlineArgs()
|
||||
logger = logging.getLogger('BeeRef')
|
||||
|
||||
|
||||
class SelectableMixin:
|
||||
"""Common code for selectable items: Selection outline, handles etc."""
|
||||
|
||||
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 = 20 # size of hover area for scaling
|
||||
SELECT_ROTATE_SIZE = 20 # size of hover area for rotating
|
||||
|
||||
def init_selectable(self):
|
||||
self.setAcceptHoverEvents(True)
|
||||
self.setFlags(
|
||||
QtWidgets.QGraphicsItem.GraphicsItemFlags.ItemIsMovable
|
||||
| QtWidgets.QGraphicsItem.GraphicsItemFlags.ItemIsSelectable)
|
||||
|
||||
self.scale_active = False
|
||||
self.viewport_scale = 1
|
||||
self.conf_debug_shapes = commandline_args.draw_debug_shapes
|
||||
|
||||
def setScale(self, factor):
|
||||
if factor <= 0:
|
||||
return
|
||||
|
||||
logger.debug(f'Setting scale for {self} to {factor}')
|
||||
self.prepareGeometryChange()
|
||||
super().setScale(factor)
|
||||
|
||||
def setZValue(self, value):
|
||||
logger.debug(f'Setting z-value for image {self} to {value}')
|
||||
super().setZValue(value)
|
||||
self.scene().max_z = max(self.scene().max_z, value)
|
||||
|
||||
def bring_to_front(self):
|
||||
self.setZValue(self.scene().max_z + 0.001)
|
||||
|
||||
def fixed_length_for_viewport(self, value):
|
||||
"""The interactable areas need to stay the same size on the
|
||||
screen so we need to adjust the values according to the scale
|
||||
factor sof the view and the item."""
|
||||
|
||||
if self.scene():
|
||||
scale = self.scene().views()[0].get_scale()
|
||||
self._view_scale = scale
|
||||
|
||||
# It can happen that the item is already removed from
|
||||
# the scene but its boundingRect is still needed. Keep the
|
||||
# last known scaling factor for that case
|
||||
return value / getattr(self, '_view_scale', 1) / self.scale()
|
||||
|
||||
@property
|
||||
def select_resize_size(self):
|
||||
return self.fixed_length_for_viewport(self.SELECT_RESIZE_SIZE)
|
||||
|
||||
@property
|
||||
def select_rotate_size(self):
|
||||
return self.fixed_length_for_viewport(self.SELECT_ROTATE_SIZE)
|
||||
|
||||
def draw_debug_shape(self, painter, shape, r, g, b):
|
||||
color = QtGui.QColor(r, g, b, 50)
|
||||
if isinstance(shape, QtCore.QRectF):
|
||||
painter.fillRect(shape, color)
|
||||
else:
|
||||
painter.fillPath(shape, color)
|
||||
|
||||
def paint_selectable(self, painter, option, widget):
|
||||
if self.conf_debug_shapes:
|
||||
self.draw_debug_shape(painter, self.boundingRect(), 0, 255, 0)
|
||||
self.draw_debug_shape(painter, self.shape(), 255, 0, 0)
|
||||
|
||||
if not self.has_selection_outline():
|
||||
return
|
||||
|
||||
pen = QtGui.QPen(self.select_color)
|
||||
pen.setWidth(self.SELECT_LINE_WIDTH)
|
||||
pen.setCosmetic(True)
|
||||
painter.setPen(pen)
|
||||
|
||||
# Draw the main selection rectangle
|
||||
painter.drawRect(0, 0, self.width, self.height)
|
||||
|
||||
# If it's a single selection, draw the handles:
|
||||
if self.has_selection_handles():
|
||||
pen.setWidth(self.SELECT_HANDLE_SIZE)
|
||||
painter.setPen(pen)
|
||||
for corner in self.corners:
|
||||
painter.drawPoint(corner)
|
||||
|
||||
@property
|
||||
def corners(self):
|
||||
"""The corners of the item. Used for scale and rotate handles."""
|
||||
return (QtCore.QPointF(0, 0),
|
||||
QtCore.QPointF(0, self.height),
|
||||
QtCore.QPointF(self.width, 0),
|
||||
QtCore.QPointF(self.width, self.height))
|
||||
|
||||
@property
|
||||
def corners_scene_coords(self):
|
||||
"""The corners of the item mapped to scene coordinates."""
|
||||
|
||||
return [self.mapToScene(corner) for corner in self.corners]
|
||||
|
||||
def get_scale_bounds(self, center):
|
||||
"""The interactable shape of the scale handles."""
|
||||
return QtCore.QRectF(
|
||||
center.x() - self.select_resize_size/2,
|
||||
center.y() - self.select_resize_size/2,
|
||||
self.select_resize_size,
|
||||
self.select_resize_size)
|
||||
|
||||
@property
|
||||
def bottom_right_rotate_bounds(self):
|
||||
"""The interactable shape of the bottom right rotate handle"""
|
||||
return QtCore.QRectF(
|
||||
self.width + self.select_resize_size / 2,
|
||||
self.height + self.select_resize_size / 2,
|
||||
self.select_rotate_size, self.select_rotate_size)
|
||||
|
||||
def boundingRect(self):
|
||||
bounds = super().boundingRect()
|
||||
if not self.has_selection_outline():
|
||||
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,
|
||||
bounds.topLeft().y() - margin,
|
||||
bounds.bottomRight().x() + 2 * margin,
|
||||
bounds.bottomRight().y() + 2 * margin)
|
||||
|
||||
def shape(self):
|
||||
shape_ = super().shape()
|
||||
if self.has_selection_outline():
|
||||
# Add extra space for scale and rotate interactive areas
|
||||
path = QtGui.QPainterPath()
|
||||
for corner in self.corners:
|
||||
path.addRect(self.get_scale_bounds(corner))
|
||||
path.addRect(self.bottom_right_rotate_bounds)
|
||||
shape_ = shape_ + path
|
||||
return shape_
|
||||
|
||||
def hoverMoveEvent(self, event):
|
||||
if not self.has_selection_handles():
|
||||
return
|
||||
|
||||
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)
|
||||
return
|
||||
|
||||
self.setCursor(Qt.CursorShape.ArrowCursor)
|
||||
|
||||
def hoverEnterEvent(self, event):
|
||||
# Always return regular cursor when there aren't any selection handles
|
||||
if not self.has_selection_handles():
|
||||
self.setCursor(Qt.CursorShape.ArrowCursor)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if (event.button() == Qt.MouseButtons.LeftButton
|
||||
and self.has_selection_handles()):
|
||||
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 = True
|
||||
self.scale_start = event.scenePos()
|
||||
self.scale_direction = self.get_scale_direction(corner)
|
||||
for item in self.selection_action_items():
|
||||
item.scale_anchor = self.get_scale_anchor(item, corner)
|
||||
item.scale_orig_factor = item.scale()
|
||||
item.scale_orig_pos = item.pos()
|
||||
event.accept()
|
||||
return
|
||||
|
||||
super().mousePressEvent(event)
|
||||
|
||||
def get_scale_factor(self, event):
|
||||
imgsize = self.width + self.height
|
||||
p = event.scenePos() - self.scale_start
|
||||
direction = self.scale_direction
|
||||
delta = (direction[0] * p.x() + direction[1] * p.y()) / imgsize
|
||||
return (self.scale_orig_factor + delta) / self.scale_orig_factor
|
||||
|
||||
def get_scale_anchor(self, item, corner):
|
||||
"""Get the anchor around which the scale for this corner operates."""
|
||||
return item.mapFromScene(
|
||||
self.mapToScene(self.width - corner.x(), self.height - corner.y()))
|
||||
|
||||
def get_scale_direction(self, corner):
|
||||
"""Get the direction in which the scale for this corner increases"""
|
||||
x = 1 if corner.x() > 0 else -1
|
||||
y = 1 if corner.y() > 0 else -1
|
||||
return (x, y)
|
||||
|
||||
def translate_for_scale_anchor(self, scale_factor):
|
||||
"""Adjust the item's position so that a scale with the given scale
|
||||
factor appears to operate around the scale anchor. ``setScale``
|
||||
needs to be called separately with ``scale_factor`` multiplied by
|
||||
the item's current scale factor.
|
||||
"""
|
||||
|
||||
factor = self.scale_orig_factor * (scale_factor - 1)
|
||||
self.setPos(
|
||||
self.scale_orig_pos.x() - self.scale_anchor.x() * factor,
|
||||
self.scale_orig_pos.y() - self.scale_anchor.y() * factor,
|
||||
)
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
if self.scale_active:
|
||||
factor = self.get_scale_factor(event)
|
||||
for item in self.selection_action_items():
|
||||
item.setScale(item.scale_orig_factor * factor)
|
||||
item.translate_for_scale_anchor(factor)
|
||||
event.accept()
|
||||
else:
|
||||
super().mouseMoveEvent(event)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if self.scale_active:
|
||||
self.scene().undo_stack.push(
|
||||
commands.ScaleItemsBy(
|
||||
self.selection_action_items(),
|
||||
self.get_scale_factor(event),
|
||||
ignore_first_redo=True))
|
||||
self.scale_active = False
|
||||
event.accept()
|
||||
else:
|
||||
super().mouseReleaseEvent(event)
|
||||
|
||||
def on_view_scale_change(self):
|
||||
self.prepareGeometryChange()
|
||||
|
||||
def itemChange(self, change, value):
|
||||
if change == QGraphicsItem.GraphicsItemChange.ItemSelectedChange:
|
||||
self.prepareGeometryChange()
|
||||
if hasattr(self, 'on_selected_change'):
|
||||
self.on_selected_change(value)
|
||||
return super().itemChange(change, value)
|
||||
|
||||
|
||||
class MultiSelectItem(SelectableMixin, QtWidgets.QGraphicsRectItem):
|
||||
"""Class for images added by the user."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.init_selectable()
|
||||
|
||||
def __str__(self):
|
||||
return (f'MultiSelectItem {self.width} x {self.height}')
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return self.rect().width()
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
return self.rect().height()
|
||||
|
||||
def paint(self, painter, option, widget):
|
||||
self.paint_selectable(painter, option, widget)
|
||||
|
||||
def has_selection_outline(self):
|
||||
return True
|
||||
|
||||
def has_selection_handles(self):
|
||||
return True
|
||||
|
||||
def selection_action_items(self):
|
||||
"""The items affected by selection actions like scaling and rotating.
|
||||
"""
|
||||
return list(self.scene().selectedItems())
|
||||
|
||||
def fit_selection_area(self, rect):
|
||||
"""Updates itself to fit the given selection area."""
|
||||
|
||||
logging.debug(f'Fit selection area to {rect}')
|
||||
self.setRect(0, 0, rect.width(), rect.height())
|
||||
self.setPos(rect.topLeft())
|
||||
self.setScale(1)
|
||||
self.setRotation(0)
|
||||
self.setSelected(True)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if (event.button() == Qt.MouseButtons.LeftButton
|
||||
and event.modifiers() == Qt.KeyboardModifiers.ControlModifier):
|
||||
# We still need to be able to select additional images
|
||||
# within/"under" the multi select rectangle, so let ctrl+click
|
||||
# events pass through
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
super().mousePressEvent(event)
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
from unittest.mock import patch, MagicMock, PropertyMock
|
||||
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6 import QtGui
|
||||
|
||||
from beeref.items import BeePixmapItem
|
||||
from beeref.scene import BeeGraphicsScene
|
||||
|
|
@ -13,37 +12,16 @@ class BeePixmapItemTestCase(BeeTestCase):
|
|||
def setUp(self):
|
||||
self.scene = BeeGraphicsScene(None)
|
||||
|
||||
def test_init(self):
|
||||
@patch('beeref.selection.SelectableMixin.init_selectable')
|
||||
def test_init(self, selectable_mock):
|
||||
item = BeePixmapItem(
|
||||
QtGui.QImage(self.imgfilename3x3), self.imgfilename3x3)
|
||||
assert item.save_id is None
|
||||
assert item.width == 3
|
||||
assert item.height == 3
|
||||
assert item.scale() == 1
|
||||
assert item.flags() == (
|
||||
QtWidgets.QGraphicsItem.GraphicsItemFlags.ItemIsMovable
|
||||
| QtWidgets.QGraphicsItem.GraphicsItemFlags.ItemIsSelectable)
|
||||
assert item.filename == self.imgfilename3x3
|
||||
|
||||
def test_set_scale(self):
|
||||
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):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
item.setScale(0)
|
||||
assert item.scale() == 1
|
||||
|
||||
def test_set_scale_ignores_negative(self):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
item.setScale(-0.1)
|
||||
assert item.scale() == 1
|
||||
selectable_mock.assert_called_once()
|
||||
|
||||
def test_set_pos_center(self):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
|
|
@ -55,411 +33,69 @@ class BeePixmapItemTestCase(BeeTestCase):
|
|||
assert item.pos().x() == -100
|
||||
assert item.pos().y() == -50
|
||||
|
||||
def test_set_zvalue_sets_new_max(self):
|
||||
def test_pixmap_to_bytes(self):
|
||||
item = BeePixmapItem(QtGui.QImage(self.imgfilename3x3))
|
||||
assert item.pixmap_to_bytes().startswith(b'\x89PNG')
|
||||
|
||||
def test_pixmap_from_bytes(self):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
with open(self.imgfilename3x3, 'rb') as f:
|
||||
imgdata = f.read()
|
||||
item.pixmap_from_bytes(imgdata)
|
||||
assert item.width == 3
|
||||
assert item.height == 3
|
||||
|
||||
def test_paint(self):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
item.pixmap = MagicMock(return_value='bee')
|
||||
item.paint_selectable = MagicMock()
|
||||
painter = MagicMock()
|
||||
item.paint(painter, None, None)
|
||||
item.paint_selectable.assert_called_once()
|
||||
painter.drawPixmap.assert_called_with(0, 0, 'bee')
|
||||
|
||||
def test_has_selection_outline_when_not_selected(self):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item)
|
||||
item.setZValue(1.1)
|
||||
assert item.zValue() == 1.1
|
||||
assert self.scene.max_z == 1.1
|
||||
item.setSelected(False)
|
||||
item.has_selection_outline() is False
|
||||
|
||||
def test_set_zvalue_keeps_old_max(self):
|
||||
def test_has_selection_outline_when_selected(self):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item)
|
||||
self.scene.max_z = 3.3
|
||||
item.setZValue(1.1)
|
||||
assert item.zValue() == 1.1
|
||||
assert self.scene.max_z == 3.3
|
||||
item.setSelected(True)
|
||||
item.has_selection_outline() is True
|
||||
|
||||
def test_bring_to_front(self):
|
||||
def test_has_selection_handles_when_not_selected(self):
|
||||
item1 = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item1)
|
||||
item1.setZValue(3.3)
|
||||
item1.setSelected(False)
|
||||
item2 = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item2)
|
||||
item2.bring_to_front()
|
||||
assert item2.zValue() > item1.zValue()
|
||||
assert item2.zValue() == self.scene.max_z
|
||||
item2.setSelected(False)
|
||||
item1.has_selection_handles() is False
|
||||
|
||||
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 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',
|
||||
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)
|
||||
assert self.item.fixed_length_for_viewport(100) == 100
|
||||
assert self.item._view_scale == 1
|
||||
|
||||
def test_fixed_length_for_viewport_when_viewport_scaled(self):
|
||||
self.view.get_scale = MagicMock(return_value=2)
|
||||
assert self.item.fixed_length_for_viewport(100) == 50
|
||||
assert self.item._view_scale == 2
|
||||
|
||||
def test_fixed_length_for_viewport_when_item_scaled(self):
|
||||
self.view.get_scale = MagicMock(return_value=1)
|
||||
self.item.setScale(5)
|
||||
assert self.item.fixed_length_for_viewport(100) == 20
|
||||
assert self.item._view_scale == 1
|
||||
|
||||
def test_fixed_length_for_viewport_when_no_scene(self):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
item._view_scale = 0.5
|
||||
assert item.fixed_length_for_viewport(100) == 200
|
||||
|
||||
def test_resize_size_when_scaled(self):
|
||||
self.view.get_scale = MagicMock(return_value=2)
|
||||
self.item.setScale(2)
|
||||
self.item.SELECT_RESIZE_SIZE = 100
|
||||
assert self.item.select_resize_size == 25
|
||||
|
||||
def test_rotate_size_when_scaled(self):
|
||||
self.view.get_scale = MagicMock(return_value=2)
|
||||
self.item.setScale(2)
|
||||
self.item.SELECT_ROTATE_SIZE = 100
|
||||
assert self.item.select_rotate_size == 25
|
||||
|
||||
@patch('beeref.items.BeePixmapItem.draw_debug_shape')
|
||||
def test_paint_when_not_selected(self, debug_mock):
|
||||
painter = MagicMock()
|
||||
self.item.setSelected(False)
|
||||
self.item.pixmap = MagicMock(return_value='pixmap')
|
||||
self.item.paint(painter, None, None)
|
||||
painter.drawPixmap.assert_called_once_with(0, 0, 'pixmap')
|
||||
painter.drawRect.assert_not_called()
|
||||
painter.drawPoint.assert_not_called()
|
||||
debug_mock.assert_not_called()
|
||||
|
||||
def test_paint_when_selected_single_selection(self):
|
||||
painter = MagicMock()
|
||||
self.item.setSelected(True)
|
||||
self.item.paint(painter, None, None)
|
||||
painter.drawPixmap.assert_called_once()
|
||||
painter.drawRect.assert_called_once()
|
||||
assert painter.drawPoint.call_count == 4
|
||||
|
||||
def test_paint_when_selected_multi_selection(self):
|
||||
def test_has_selection_handles_when_selected_single(self):
|
||||
item1 = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item1)
|
||||
item1.setSelected(True)
|
||||
item2 = BeePixmapItem(QtGui.QImage())
|
||||
item2.setSelected(True)
|
||||
self.scene.addItem(item2)
|
||||
painter = MagicMock()
|
||||
self.item.setSelected(True)
|
||||
self.item.paint(painter, None, None)
|
||||
painter.drawPixmap.assert_called_once()
|
||||
painter.drawRect.assert_called_once()
|
||||
painter.drawPoint.assert_not_called()
|
||||
item2.setSelected(False)
|
||||
item1.has_selection_handles() is True
|
||||
|
||||
def test_paint_when_draw_debug_shapes(self):
|
||||
with patch('beeref.items.commandline_args') as args_mock:
|
||||
with patch('beeref.items.BeePixmapItem.draw_debug_shape') as m:
|
||||
args_mock.draw_debug_shapes = True
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
item.paint(MagicMock(), None, None)
|
||||
m.assert_called()
|
||||
def test_has_selection_handles_when_selected_multi(self):
|
||||
view = MagicMock(get_scale=MagicMock(return_value=1))
|
||||
with patch('beeref.scene.BeeGraphicsScene.views',
|
||||
return_value=[view]):
|
||||
item1 = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item1)
|
||||
item1.setSelected(True)
|
||||
item2 = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item2)
|
||||
item2.setSelected(True)
|
||||
item1.has_selection_handles() is False
|
||||
|
||||
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
|
||||
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
|
||||
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)
|
||||
self.item.setSelected(False)
|
||||
with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.boundingRect',
|
||||
return_value=QtCore.QRectF(0, 0, 100, 80)):
|
||||
rect = self.item.boundingRect()
|
||||
assert rect.topLeft().x() == 0
|
||||
assert rect.topLeft().y() == 0
|
||||
assert rect.bottomRight().x() == 100
|
||||
assert rect.bottomRight().y() == 80
|
||||
|
||||
def test_bounding_rect_when_selected(self):
|
||||
self.item.SELECT_RESIZE_SIZE = 10
|
||||
self.item.SELECT_ROTATE_SIZE = 10
|
||||
self.view.get_scale = MagicMock(return_value=1)
|
||||
self.item.setSelected(True)
|
||||
with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.boundingRect',
|
||||
return_value=QtCore.QRectF(0, 0, 100, 80)):
|
||||
rect = self.item.boundingRect()
|
||||
assert rect.topLeft().x() == -15
|
||||
assert rect.topLeft().y() == -15
|
||||
assert rect.bottomRight().x() == 115
|
||||
assert rect.bottomRight().y() == 95
|
||||
|
||||
def test_shape_when_not_selected(self):
|
||||
self.view.get_scale = MagicMock(return_value=1)
|
||||
self.item.setSelected(False)
|
||||
path = QtGui.QPainterPath()
|
||||
path.addRect(QtCore.QRectF(0, 0, 100, 80))
|
||||
|
||||
with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.shape',
|
||||
return_value=path):
|
||||
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
|
||||
self.item.SELECT_ROTATE_SIZE = 10
|
||||
self.view.get_scale = MagicMock(return_value=1)
|
||||
self.item.setSelected(True)
|
||||
path = QtGui.QPainterPath()
|
||||
path.addRect(QtCore.QRectF(0, 0, 100, 80))
|
||||
|
||||
with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.shape',
|
||||
return_value=path):
|
||||
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_factor_bottomright(self):
|
||||
self.item.scale_start = QtCore.QPoint(10, 10)
|
||||
self.item.scale_direction = (1, 1)
|
||||
self.item.scale_orig_factor = 1
|
||||
event = MagicMock()
|
||||
event.scenePos = MagicMock(return_value=QtCore.QPoint(20, 90))
|
||||
assert self.item.get_scale_factor(event) == 1.5
|
||||
|
||||
def test_get_scale_delta_topleft(self):
|
||||
self.item.scale_start = QtCore.QPoint(10, 10)
|
||||
self.item.scale_direction = (-1, -1)
|
||||
self.item.scale_orig_factor = 0.5
|
||||
event = MagicMock()
|
||||
event.scenePos = MagicMock(return_value=QtCore.QPoint(-10, -60))
|
||||
assert self.item.get_scale_factor(event) == 2
|
||||
|
||||
def test_get_scale_anchor_topleft(self):
|
||||
anchor = self.item.get_scale_anchor(self.item, (0, 0))
|
||||
assert anchor.x() == 100
|
||||
assert anchor.y() == 80
|
||||
|
||||
def test_get_scale_anchor_bottomright(self):
|
||||
anchor = self.item.get_scale_anchor(self.item, (100, 80))
|
||||
assert anchor.x() == 0
|
||||
assert anchor.y() == 0
|
||||
|
||||
def test_get_scale_anchor_topright(self):
|
||||
anchor = self.item.get_scale_anchor(self.item, (100, 0))
|
||||
assert anchor.x() == 0
|
||||
assert anchor.y() == 80
|
||||
|
||||
def test_get_scale_anchor_bottomleft(self):
|
||||
anchor = self.item.get_scale_anchor(self.item, (0, 80))
|
||||
assert anchor.x() == 100
|
||||
assert anchor.y() == 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):
|
||||
self.item.scale_orig_pos = QtCore.QPoint(50, 70)
|
||||
self.item.scale_anchor = QtCore.QPoint(100, 80)
|
||||
self.item.scale_orig_factor = 1
|
||||
self.item.translate_for_scale_anchor(3)
|
||||
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 is True
|
||||
assert self.item.scale_start == QtCore.QPointF(66, 99)
|
||||
assert self.item.scale_direction == (-1, -1)
|
||||
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 is True
|
||||
assert self.item.scale_direction == (1, 1)
|
||||
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 is False
|
||||
|
||||
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 is False
|
||||
|
||||
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 = True
|
||||
self.item.scale_direction = (1, 1)
|
||||
self.item.scale_anchor = QtCore.QPointF(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 = True
|
||||
self.item.scale_direction = (1, 1)
|
||||
self.item.scale_anchor = QtCore.QPointF(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.factor == 1.5
|
||||
assert cmd.item_data == [{
|
||||
'anchor': QtCore.QPointF(100, 80),
|
||||
'orig_factor': 1,
|
||||
'orig_pos': QtCore.QPointF(0, 0)}]
|
||||
assert cmd.ignore_first_redo is True
|
||||
assert self.item.scale_active is False
|
||||
def test_selection_action_items(self):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
assert item.selection_action_items() == [item]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import math
|
||||
from unittest.mock import patch, MagicMock
|
||||
from unittest.mock import patch, MagicMock, PropertyMock
|
||||
|
||||
from PyQt6 import QtGui, QtWidgets
|
||||
from pytest import approx
|
||||
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||
from PyQt6.QtCore import Qt
|
||||
|
||||
from beeref.items import BeePixmapItem
|
||||
from beeref.scene import BeeGraphicsScene
|
||||
|
|
@ -13,6 +16,10 @@ class BeeGraphicsSceneNormalizeTestCase(BeeTestCase):
|
|||
def setUp(self):
|
||||
self.undo_stack = QtGui.QUndoStack()
|
||||
self.scene = BeeGraphicsScene(self.undo_stack)
|
||||
self.view = MagicMock(get_scale=MagicMock(return_value=1))
|
||||
views_patcher = patch('beeref.scene.BeeGraphicsScene.views',
|
||||
return_value=[self.view])
|
||||
views_patcher.start()
|
||||
|
||||
def test_normalize_height(self):
|
||||
item1 = MagicMock(height=100, scale_factor=1)
|
||||
|
|
@ -56,6 +63,144 @@ class BeeGraphicsSceneNormalizeTestCase(BeeTestCase):
|
|||
def test_normalize_size_when_no_items(self):
|
||||
self.scene.normalize_size()
|
||||
|
||||
def test_has_selection_when_no_selection(self):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item)
|
||||
assert self.scene.has_selection() is False
|
||||
|
||||
def test_has_selection_when_selection(self):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item)
|
||||
item.setSelected(True)
|
||||
assert self.scene.has_selection() is True
|
||||
|
||||
def test_has_single_selection_when_no_selection(self):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item)
|
||||
assert self.scene.has_single_selection() is False
|
||||
|
||||
def test_has_single_selection_when_single_selection(self):
|
||||
item1 = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item1)
|
||||
item1.setSelected(True)
|
||||
item2 = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item2)
|
||||
assert self.scene.has_single_selection() is True
|
||||
|
||||
def test_has_single_selection_when_multi_selection(self):
|
||||
item1 = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item1)
|
||||
item1.setSelected(True)
|
||||
item2 = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item2)
|
||||
item2.setSelected(True)
|
||||
assert self.scene.has_single_selection() is False
|
||||
|
||||
def test_has_multi_selection_when_no_selection(self):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item)
|
||||
assert self.scene.has_multi_selection() is False
|
||||
|
||||
def test_has_multi_selection_when_single_selection(self):
|
||||
item1 = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item1)
|
||||
item1.setSelected(True)
|
||||
item2 = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item2)
|
||||
assert self.scene.has_multi_selection() is False
|
||||
|
||||
def test_has_multi_selection_when_multi_selection(self):
|
||||
item1 = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item1)
|
||||
item1.setSelected(True)
|
||||
item2 = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item2)
|
||||
item2.setSelected(True)
|
||||
assert self.scene.has_multi_selection() is True
|
||||
|
||||
@patch('PyQt6.QtWidgets.QGraphicsScene.mousePressEvent')
|
||||
def test_mouse_press_event_when_right_click(self, mouse_mock):
|
||||
event = MagicMock(
|
||||
button=MagicMock(return_value=Qt.MouseButtons.RightButton))
|
||||
self.scene.mousePressEvent(event)
|
||||
event.accept.assert_not_called()
|
||||
mouse_mock.assert_not_called()
|
||||
|
||||
@patch('PyQt6.QtWidgets.QGraphicsScene.mousePressEvent')
|
||||
def test_mouse_press_event_when_left_click(self, mouse_mock):
|
||||
event = MagicMock(
|
||||
button=MagicMock(return_value=Qt.MouseButtons.LeftButton),
|
||||
scenePos=MagicMock(return_value=QtCore.QPoint(10, 20)),
|
||||
)
|
||||
self.scene.mousePressEvent(event)
|
||||
event.accept.assert_not_called()
|
||||
mouse_mock.assert_called_once_with(event)
|
||||
assert self.scene.move_active is True
|
||||
assert self.scene.move_start == QtCore.QPoint(10, 20)
|
||||
|
||||
@patch('PyQt6.QtWidgets.QGraphicsScene.mouseReleaseEvent')
|
||||
def test_mouse_release_event_when_move_active(self, mouse_mock):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item)
|
||||
item.setSelected(True)
|
||||
event = MagicMock(
|
||||
scenePos=MagicMock(return_value=QtCore.QPoint(10, 20)))
|
||||
self.scene.move_active = True
|
||||
self.scene.move_start = QtCore.QPoint(0, 0)
|
||||
self.scene.undo_stack = MagicMock(push=MagicMock())
|
||||
|
||||
self.scene.mouseReleaseEvent(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 == [item]
|
||||
assert cmd.ignore_first_redo is True
|
||||
assert cmd.delta_x == 10
|
||||
assert cmd.delta_y == 20
|
||||
mouse_mock.assert_called_once_with(event)
|
||||
assert self.scene.move_active is False
|
||||
|
||||
@patch('PyQt6.QtWidgets.QGraphicsScene.mouseReleaseEvent')
|
||||
def test_mouse_release_event_when_move_not_active(self, mouse_mock):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item)
|
||||
item.setSelected(True)
|
||||
event = MagicMock(
|
||||
scenePos=MagicMock(return_value=QtCore.QPoint(10, 20)))
|
||||
self.scene.move_active = False
|
||||
self.scene.undo_stack = MagicMock(push=MagicMock())
|
||||
|
||||
self.scene.mouseReleaseEvent(event)
|
||||
self.scene.undo_stack.push.assert_not_called()
|
||||
mouse_mock.assert_called_once_with(event)
|
||||
assert self.scene.move_active is False
|
||||
|
||||
@patch('PyQt6.QtWidgets.QGraphicsScene.mouseReleaseEvent')
|
||||
def test_mouse_release_event_when_no_selection(self, mouse_mock):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item)
|
||||
item.setSelected(False)
|
||||
event = MagicMock(
|
||||
scenePos=MagicMock(return_value=QtCore.QPoint(10, 20)))
|
||||
self.scene.move_active = True
|
||||
self.scene.undo_stack = MagicMock(push=MagicMock())
|
||||
|
||||
self.scene.mouseReleaseEvent(event)
|
||||
self.scene.undo_stack.push.assert_not_called()
|
||||
mouse_mock.assert_called_once_with(event)
|
||||
assert self.scene.move_active is False
|
||||
|
||||
def test_items_for_save(self):
|
||||
item1 = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item1)
|
||||
item2 = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item2)
|
||||
item3 = QtWidgets.QGraphicsRectItem()
|
||||
self.scene.addItem(item3)
|
||||
|
||||
items = list(self.scene.items_for_save())
|
||||
assert items == [item1, item2]
|
||||
|
||||
def test_clear_save_ids(self):
|
||||
item1 = BeePixmapItem(QtGui.QImage())
|
||||
item1.save_id = 5
|
||||
|
|
@ -70,13 +215,100 @@ class BeeGraphicsSceneNormalizeTestCase(BeeTestCase):
|
|||
assert item2.save_id is None
|
||||
assert hasattr(item3, 'save_id') is False
|
||||
|
||||
def test_items_for_save(self):
|
||||
def test_on_view_scale_change(self):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item)
|
||||
item.setSelected(True)
|
||||
item.on_view_scale_change = MagicMock()
|
||||
self.scene.on_view_scale_change()
|
||||
item.on_view_scale_change.assert_called_once()
|
||||
|
||||
def test_get_selection_rect_two_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)
|
||||
item3 = QtWidgets.QGraphicsRectItem()
|
||||
item2.setSelected(True)
|
||||
item2.setPos(-33, 22)
|
||||
item3 = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item3)
|
||||
item3.setSelected(False)
|
||||
item3.setPos(1000, 1000)
|
||||
|
||||
items = list(self.scene.items_for_save())
|
||||
assert items == [item1, item2]
|
||||
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.get_selection_rect()
|
||||
|
||||
assert rect.topLeft().x() == -33
|
||||
assert rect.topLeft().y() == -6
|
||||
assert rect.bottomRight().x() == 104
|
||||
assert rect.bottomRight().y() == 122
|
||||
|
||||
def test_get_selection_rect_rotated_item(self):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item)
|
||||
item.setSelected(True)
|
||||
item.setRotation(-45)
|
||||
|
||||
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.get_selection_rect()
|
||||
|
||||
assert rect.topLeft().x() == 0
|
||||
assert rect.topLeft().y() == approx(-math.sqrt(2) * 50)
|
||||
assert rect.bottomRight().x() == approx(math.sqrt(2) * 100)
|
||||
assert rect.bottomRight().y() == approx(math.sqrt(2) * 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()
|
||||
self.scene.multi_select_item.bring_to_front = MagicMock()
|
||||
self.scene.get_selection_rect = MagicMock(
|
||||
return_value=QtCore.QRectF(0, 0, 100, 80))
|
||||
self.scene.addItem = MagicMock()
|
||||
|
||||
self.scene.on_selection_change()
|
||||
|
||||
m_item = self.scene.multi_select_item
|
||||
m_item.fit_selection_area.assert_called_once_with(
|
||||
QtCore.QRectF(0, 0, 100, 80))
|
||||
m_item.bring_to_front.assert_called_once()
|
||||
self.scene.addItem.assert_called_once_with(m_item)
|
||||
|
||||
def test_on_selection_change_when_multi_selection_existing(self):
|
||||
self.scene.addItem(self.scene.multi_select_item)
|
||||
self.scene.has_multi_selection = MagicMock(return_value=True)
|
||||
self.scene.multi_select_item.fit_selection_area = MagicMock()
|
||||
self.scene.multi_select_item.bring_to_front = MagicMock()
|
||||
self.scene.get_selection_rect = MagicMock(
|
||||
return_value=QtCore.QRectF(0, 0, 100, 80))
|
||||
self.scene.addItem = MagicMock()
|
||||
|
||||
self.scene.on_selection_change()
|
||||
|
||||
m_item = self.scene.multi_select_item
|
||||
m_item.fit_selection_area.assert_called_once_with(
|
||||
QtCore.QRectF(0, 0, 100, 80))
|
||||
m_item.bring_to_front.assert_not_called()
|
||||
self.scene.addItem.assert_not_called()
|
||||
|
||||
def test_on_selection_change_when_multi_selection_ended(self):
|
||||
self.scene.addItem(self.scene.multi_select_item)
|
||||
self.scene.has_multi_selection = MagicMock(return_value=False)
|
||||
self.scene.multi_select_item.fit_selection_area = MagicMock()
|
||||
self.scene.multi_select_item.bring_to_front = MagicMock()
|
||||
self.scene.get_selection_rect = MagicMock(
|
||||
return_value=QtCore.QRectF(0, 0, 100, 80))
|
||||
self.scene.removeItem = MagicMock()
|
||||
|
||||
self.scene.on_selection_change()
|
||||
|
||||
m_item = self.scene.multi_select_item
|
||||
m_item.fit_selection_area.assert_not_called()
|
||||
self.scene.removeItem.assert_called_once_with(m_item)
|
||||
|
|
|
|||
553
tests/test_selection.py
Normal file
553
tests/test_selection.py
Normal file
|
|
@ -0,0 +1,553 @@
|
|||
from unittest.mock import patch, MagicMock, PropertyMock
|
||||
|
||||
from PyQt6 import QtCore, QtGui
|
||||
from PyQt6.QtCore import Qt
|
||||
|
||||
from beeref.items import BeePixmapItem
|
||||
from beeref.scene import BeeGraphicsScene
|
||||
from beeref.selection import MultiSelectItem
|
||||
from .base import BeeTestCase
|
||||
|
||||
|
||||
class SelectableMixinBaseTestCase(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',
|
||||
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 SelectableMixinTestCase(SelectableMixinBaseTestCase):
|
||||
|
||||
def test_set_scale(self):
|
||||
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):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
item.setScale(0)
|
||||
assert item.scale() == 1
|
||||
|
||||
def test_set_scale_ignores_negative(self):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
item.setScale(-0.1)
|
||||
assert item.scale() == 1
|
||||
|
||||
def test_set_zvalue_sets_new_max(self):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item)
|
||||
item.setZValue(1.1)
|
||||
assert item.zValue() == 1.1
|
||||
assert self.scene.max_z == 1.1
|
||||
|
||||
def test_set_zvalue_keeps_old_max(self):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item)
|
||||
self.scene.max_z = 3.3
|
||||
item.setZValue(1.1)
|
||||
assert item.zValue() == 1.1
|
||||
assert self.scene.max_z == 3.3
|
||||
|
||||
def test_bring_to_front(self):
|
||||
item1 = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item1)
|
||||
item1.setZValue(3.3)
|
||||
item2 = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item2)
|
||||
item2.bring_to_front()
|
||||
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()
|
||||
|
||||
def test_fixed_length_for_viewport_when_default_scales(self):
|
||||
self.view.get_scale = MagicMock(return_value=1)
|
||||
assert self.item.fixed_length_for_viewport(100) == 100
|
||||
assert self.item._view_scale == 1
|
||||
|
||||
def test_fixed_length_for_viewport_when_viewport_scaled(self):
|
||||
self.view.get_scale = MagicMock(return_value=2)
|
||||
assert self.item.fixed_length_for_viewport(100) == 50
|
||||
assert self.item._view_scale == 2
|
||||
|
||||
def test_fixed_length_for_viewport_when_item_scaled(self):
|
||||
self.view.get_scale = MagicMock(return_value=1)
|
||||
self.item.setScale(5)
|
||||
assert self.item.fixed_length_for_viewport(100) == 20
|
||||
assert self.item._view_scale == 1
|
||||
|
||||
def test_fixed_length_for_viewport_when_no_scene(self):
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
item._view_scale = 0.5
|
||||
assert item.fixed_length_for_viewport(100) == 200
|
||||
|
||||
def test_resize_size_when_scaled(self):
|
||||
self.view.get_scale = MagicMock(return_value=2)
|
||||
self.item.setScale(2)
|
||||
self.item.SELECT_RESIZE_SIZE = 100
|
||||
assert self.item.select_resize_size == 25
|
||||
|
||||
def test_rotate_size_when_scaled(self):
|
||||
self.view.get_scale = MagicMock(return_value=2)
|
||||
self.item.setScale(2)
|
||||
self.item.SELECT_ROTATE_SIZE = 100
|
||||
assert self.item.select_rotate_size == 25
|
||||
|
||||
@patch('beeref.items.BeePixmapItem.draw_debug_shape')
|
||||
def test_paint_when_not_selected(self, debug_mock):
|
||||
painter = MagicMock()
|
||||
self.item.setSelected(False)
|
||||
self.item.pixmap = MagicMock(return_value='pixmap')
|
||||
self.item.paint(painter, None, None)
|
||||
painter.drawPixmap.assert_called_once_with(0, 0, 'pixmap')
|
||||
painter.drawRect.assert_not_called()
|
||||
painter.drawPoint.assert_not_called()
|
||||
debug_mock.assert_not_called()
|
||||
|
||||
def test_paint_when_selected_single_selection(self):
|
||||
painter = MagicMock()
|
||||
self.item.setSelected(True)
|
||||
self.item.paint(painter, None, None)
|
||||
painter.drawPixmap.assert_called_once()
|
||||
painter.drawRect.assert_called_once()
|
||||
assert painter.drawPoint.call_count == 4
|
||||
|
||||
def test_paint_when_selected_multi_selection(self):
|
||||
item2 = BeePixmapItem(QtGui.QImage())
|
||||
item2.setSelected(True)
|
||||
self.scene.addItem(item2)
|
||||
painter = MagicMock()
|
||||
self.item.setSelected(True)
|
||||
self.item.paint(painter, None, None)
|
||||
painter.drawPixmap.assert_called_once()
|
||||
painter.drawRect.assert_called_once()
|
||||
painter.drawPoint.assert_not_called()
|
||||
|
||||
def test_paint_when_draw_debug_shapes(self):
|
||||
with patch('beeref.selection.commandline_args') as args_mock:
|
||||
with patch('beeref.items.BeePixmapItem.draw_debug_shape') as m:
|
||||
args_mock.draw_debug_shapes = True
|
||||
item = BeePixmapItem(QtGui.QImage())
|
||||
item.paint(MagicMock(), None, None)
|
||||
m.assert_called()
|
||||
|
||||
def test_corners(self):
|
||||
assert len(self.item.corners) == 4
|
||||
assert QtCore.QPointF(0, 0) in self.item.corners
|
||||
assert QtCore.QPointF(100, 0) in self.item.corners
|
||||
assert QtCore.QPointF(0, 80) in self.item.corners
|
||||
assert QtCore.QPointF(100, 80) in self.item.corners
|
||||
|
||||
def test_corners_scene_coords_translated(self):
|
||||
self.item.setPos(5, 5)
|
||||
corners = self.item.corners_scene_coords
|
||||
assert len(self.item.corners) == 4
|
||||
assert QtCore.QPointF(5, 5) in corners
|
||||
assert QtCore.QPointF(105, 5) in corners
|
||||
assert QtCore.QPointF(5, 85) in corners
|
||||
assert QtCore.QPointF(105, 85) in corners
|
||||
|
||||
def test_corners_scene_coords_scaled(self):
|
||||
self.item.setScale(2)
|
||||
corners = self.item.corners_scene_coords
|
||||
assert len(self.item.corners) == 4
|
||||
assert QtCore.QPointF(0, 0) in corners
|
||||
assert QtCore.QPointF(200, 0) in corners
|
||||
assert QtCore.QPointF(0, 160) in corners
|
||||
assert QtCore.QPointF(200, 160) in corners
|
||||
|
||||
def test_get_scale_bounds(self):
|
||||
self.view.get_scale = MagicMock(return_value=1)
|
||||
self.item.SELECT_RESIZE_SIZE = 10
|
||||
rect = self.item.get_scale_bounds(QtCore.QPointF(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
|
||||
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)
|
||||
self.item.setSelected(False)
|
||||
with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.boundingRect',
|
||||
return_value=QtCore.QRectF(0, 0, 100, 80)):
|
||||
rect = self.item.boundingRect()
|
||||
assert rect.topLeft().x() == 0
|
||||
assert rect.topLeft().y() == 0
|
||||
assert rect.bottomRight().x() == 100
|
||||
assert rect.bottomRight().y() == 80
|
||||
|
||||
def test_bounding_rect_when_selected(self):
|
||||
self.item.SELECT_RESIZE_SIZE = 10
|
||||
self.item.SELECT_ROTATE_SIZE = 10
|
||||
self.view.get_scale = MagicMock(return_value=1)
|
||||
self.item.setSelected(True)
|
||||
with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.boundingRect',
|
||||
return_value=QtCore.QRectF(0, 0, 100, 80)):
|
||||
rect = self.item.boundingRect()
|
||||
assert rect.topLeft().x() == -15
|
||||
assert rect.topLeft().y() == -15
|
||||
assert rect.bottomRight().x() == 115
|
||||
assert rect.bottomRight().y() == 95
|
||||
|
||||
def test_shape_when_not_selected(self):
|
||||
self.view.get_scale = MagicMock(return_value=1)
|
||||
self.item.setSelected(False)
|
||||
path = QtGui.QPainterPath()
|
||||
path.addRect(QtCore.QRectF(0, 0, 100, 80))
|
||||
|
||||
with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.shape',
|
||||
return_value=path):
|
||||
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
|
||||
self.item.SELECT_ROTATE_SIZE = 10
|
||||
self.view.get_scale = MagicMock(return_value=1)
|
||||
self.item.setSelected(True)
|
||||
path = QtGui.QPainterPath()
|
||||
path.addRect(QtCore.QRectF(0, 0, 100, 80))
|
||||
|
||||
with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.shape',
|
||||
return_value=path):
|
||||
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 SelectableMixinScalingTestCase(SelectableMixinBaseTestCase):
|
||||
|
||||
def test_get_scale_factor_bottomright(self):
|
||||
self.item.scale_start = QtCore.QPoint(10, 10)
|
||||
self.item.scale_direction = (1, 1)
|
||||
self.item.scale_orig_factor = 1
|
||||
event = MagicMock()
|
||||
event.scenePos = MagicMock(return_value=QtCore.QPoint(20, 90))
|
||||
assert self.item.get_scale_factor(event) == 1.5
|
||||
|
||||
def test_get_scale_factor_topleft(self):
|
||||
self.item.scale_start = QtCore.QPoint(10, 10)
|
||||
self.item.scale_direction = (-1, -1)
|
||||
self.item.scale_orig_factor = 0.5
|
||||
event = MagicMock()
|
||||
event.scenePos = MagicMock(return_value=QtCore.QPoint(-10, -60))
|
||||
assert self.item.get_scale_factor(event) == 2
|
||||
|
||||
def test_get_scale_anchor_topleft(self):
|
||||
anchor = self.item.get_scale_anchor(self.item, QtCore.QPointF(0, 0))
|
||||
assert anchor.x() == 100
|
||||
assert anchor.y() == 80
|
||||
|
||||
def test_get_scale_anchor_bottomright(self):
|
||||
anchor = self.item.get_scale_anchor(self.item, QtCore.QPointF(100, 80))
|
||||
assert anchor.x() == 0
|
||||
assert anchor.y() == 0
|
||||
|
||||
def test_get_scale_anchor_topright(self):
|
||||
anchor = self.item.get_scale_anchor(self.item, QtCore.QPointF(100, 0))
|
||||
assert anchor.x() == 0
|
||||
assert anchor.y() == 80
|
||||
|
||||
def test_get_scale_anchor_bottomleft(self):
|
||||
anchor = self.item.get_scale_anchor(self.item, QtCore.QPointF(0, 80))
|
||||
assert anchor.x() == 100
|
||||
assert anchor.y() == 0
|
||||
|
||||
def test_get_scale_anchor_topleft_when_anchor_other_item(self):
|
||||
item2 = BeePixmapItem(QtGui.QImage())
|
||||
item2.setPos(22, 33)
|
||||
anchor = self.item.get_scale_anchor(item2, QtCore.QPointF(0, 0))
|
||||
assert anchor.x() == 78
|
||||
assert anchor.y() == 47
|
||||
|
||||
def test_get_scale_direction_topleft(self):
|
||||
assert self.item.get_scale_direction(QtCore.QPointF(0, 0)) == (-1, -1)
|
||||
|
||||
def test_get_scale_direction_bottomright(self):
|
||||
assert self.item.get_scale_direction(QtCore.QPointF(100, 80)) == (1, 1)
|
||||
|
||||
def test_get_scale_direction_topright(self):
|
||||
assert self.item.get_scale_direction(QtCore.QPointF(100, 0)) == (1, -1)
|
||||
|
||||
def test_get_scale_direction_bottomleft(self):
|
||||
assert self.item.get_scale_direction(QtCore.QPointF(0, 80)) == (-1, 1)
|
||||
|
||||
def test_translate_for_scale_anchor(self):
|
||||
self.item.scale_orig_pos = QtCore.QPoint(50, 70)
|
||||
self.item.scale_anchor = QtCore.QPoint(100, 80)
|
||||
self.item.scale_orig_factor = 1
|
||||
self.item.translate_for_scale_anchor(3)
|
||||
assert self.item.pos().x() == -150
|
||||
assert self.item.pos().y() == -90
|
||||
|
||||
|
||||
class SelectableMixinMouseEventsTestCase(SelectableMixinBaseTestCase):
|
||||
|
||||
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 is True
|
||||
assert self.item.scale_start == QtCore.QPointF(66, 99)
|
||||
assert self.item.scale_direction == (-1, -1)
|
||||
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 is True
|
||||
assert self.item.scale_direction == (1, 1)
|
||||
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 is False
|
||||
|
||||
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 is False
|
||||
|
||||
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 = True
|
||||
self.item.scale_direction = (1, 1)
|
||||
self.item.scale_anchor = QtCore.QPointF(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 = True
|
||||
self.item.scale_direction = (1, 1)
|
||||
self.item.scale_anchor = QtCore.QPointF(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(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.factor == 1.5
|
||||
assert cmd.item_data == [{
|
||||
'anchor': QtCore.QPointF(100, 80),
|
||||
'orig_factor': 1,
|
||||
'orig_pos': QtCore.QPointF(0, 0)}]
|
||||
assert cmd.ignore_first_redo is True
|
||||
assert self.item.scale_active is False
|
||||
|
||||
|
||||
class MultiSelectItemItemTestCase(BeeTestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.scene = BeeGraphicsScene(None)
|
||||
|
||||
@patch('beeref.selection.SelectableMixin.init_selectable')
|
||||
def test_init(self, selectable_mock):
|
||||
item = MultiSelectItem()
|
||||
assert hasattr(item, 'save_id') is False
|
||||
selectable_mock.assert_called_once()
|
||||
|
||||
def test_width(self):
|
||||
item = MultiSelectItem()
|
||||
item.setRect(0, 0, 50, 100)
|
||||
assert item.width == 50
|
||||
|
||||
def test_height(self):
|
||||
item = MultiSelectItem()
|
||||
item.setRect(0, 0, 50, 100)
|
||||
assert item.height == 100
|
||||
|
||||
def test_paint(self):
|
||||
item = MultiSelectItem()
|
||||
item.paint_selectable = MagicMock()
|
||||
painter = MagicMock()
|
||||
item.paint(painter, None, None)
|
||||
item.paint_selectable.assert_called_once()
|
||||
painter.drawRect.assert_not_called()
|
||||
|
||||
def test_has_selection_outline(self):
|
||||
item = MultiSelectItem()
|
||||
item.has_selection_outline() is True
|
||||
|
||||
def test_has_selection_handles(self):
|
||||
item = MultiSelectItem()
|
||||
item.has_selection_handles() is True
|
||||
|
||||
def test_selection_action_items(self):
|
||||
view = MagicMock(get_scale=MagicMock(return_value=1))
|
||||
with patch('beeref.scene.BeeGraphicsScene.views',
|
||||
return_value=[view]):
|
||||
img1 = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(img1)
|
||||
img1.setSelected(True)
|
||||
img2 = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(img2)
|
||||
img2.setSelected(False)
|
||||
item = MultiSelectItem()
|
||||
self.scene.addItem(item)
|
||||
item.setSelected(True)
|
||||
action_items = item.selection_action_items()
|
||||
assert img1 in action_items
|
||||
assert item in action_items
|
||||
assert img2 not in action_items
|
||||
|
||||
def test_fit_selection_area(self):
|
||||
item = MultiSelectItem()
|
||||
item.setScale(5)
|
||||
item.setRotation(-20)
|
||||
item.fit_selection_area(QtCore.QRectF(-10, -20, 100, 80))
|
||||
assert item.pos().x() == -10
|
||||
assert item.pos().y() == -20
|
||||
assert item.width == 100
|
||||
assert item.height == 80
|
||||
assert item.scale() == 1
|
||||
assert item.rotation() == 0
|
||||
assert item.isSelected() is True
|
||||
|
||||
@patch('PyQt6.QtWidgets.QGraphicsRectItem.mousePressEvent')
|
||||
def test_mouse_press_event_when_ctrl_leftclick(self, mouse_mock):
|
||||
item = MultiSelectItem()
|
||||
item.fit_selection_area(QtCore.QRectF(0, 0, 100, 80))
|
||||
event = MagicMock(
|
||||
button=MagicMock(return_value=Qt.MouseButtons.LeftButton),
|
||||
modifiers=MagicMock(
|
||||
return_value=Qt.KeyboardModifiers.ControlModifier))
|
||||
item.mousePressEvent(event)
|
||||
event.ignore.assert_called_once()
|
||||
mouse_mock.assert_not_called()
|
||||
|
||||
@patch('beeref.selection.SelectableMixin.mousePressEvent')
|
||||
def test_mouse_press_event_when_leftclick(self, mouse_mock):
|
||||
item = MultiSelectItem()
|
||||
item.fit_selection_area(QtCore.QRectF(0, 0, 100, 80))
|
||||
event = MagicMock(
|
||||
button=MagicMock(return_value=Qt.MouseButtons.LeftButton))
|
||||
item.mousePressEvent(event)
|
||||
event.ignore.assert_not_called()
|
||||
mouse_mock.assert_called_once_with(event)
|
||||
Loading…
Reference in a new issue