Image cropping

This commit is contained in:
Rebecca Breu 2021-10-07 15:23:37 +02:00
parent 235b03951b
commit 3345efaacf
19 changed files with 1687 additions and 636 deletions

View file

@ -4,6 +4,8 @@
Added
-----
* Image cropping (Go to "Transform -> Crop", or press Shift + C)
Note that older version of BeeRef will display the images uncropped.
* Show list of recent files on welcome screen
* Keyboard shortcuts can now be configured via a settings file.
Go to "Settings -> Open Settings Folder" and edit KeyboardSettings.ini

View file

@ -145,6 +145,13 @@ actions = [
'callback': 'on_action_arrange_vertical',
'group': 'active_when_selection',
},
{
'id': 'crop',
'text': '&Crop',
'shortcuts': ['Shift+C'],
'callback': 'on_action_crop',
'group': 'active_when_croppable',
},
{
'id': 'flip_horizontally',
'text': 'Flip &Horizontally',
@ -196,6 +203,12 @@ actions = [
'callback': 'on_action_reset_flip',
'group': 'active_when_selection',
},
{
'id': 'reset_crop',
'text': 'Reset Cro&p',
'callback': 'on_action_reset_crop',
'group': 'active_when_selection',
},
{
'id': 'reset_transforms',
'text': 'Reset &All',

View file

@ -73,12 +73,14 @@ menu_structure = [
{
'menu': '&Transform',
'items': [
'crop',
'flip_horizontally',
'flip_vertically',
MENU_SEPARATOR,
'reset_scale',
'reset_rotation',
'reset_flip',
'reset_crop',
'reset_transforms',
],
},

View file

@ -32,10 +32,9 @@ class InsertItems(QtGui.QUndoCommand):
return
if self.position:
rect = self.scene.itemsBoundingRect(items=self.items)
center = (rect.topLeft() + rect.bottomRight()) / 2
for item in self.items:
self.old_positions.append(item.pos())
item.setPos(item.pos() + self.position - center)
item.setPos(item.pos() + self.position - rect.center())
self.scene.clearSelection()
for item in self.items:
self.scene.addItem(item)
@ -148,13 +147,11 @@ class NormalizeItems(QtGui.QUndoCommand):
self.old_scale_factors = []
for item, factor in zip(self.items, self.scale_factors):
self.old_scale_factors.append(item.scale())
item.setScale(item.scale() * factor,
QtCore.QPointF(item.width, item.height) / 2)
item.setScale(item.scale() * factor, item.center)
def undo(self):
for item, factor in zip(self.items, self.old_scale_factors):
item.setScale(factor,
QtCore.QPointF(item.width, item.height) / 2)
item.setScale(factor, item.center)
class FlipItems(QtGui.QUndoCommand):
@ -226,6 +223,23 @@ class ResetFlip(QtGui.QUndoCommand):
item.do_flip(anchor=item.center)
class ResetCrop(QtGui.QUndoCommand):
def __init__(self, items):
super().__init__('Reset Crop')
self.items = [item for item in items if item.is_croppable]
def redo(self):
self.old_crops = []
for item in self.items:
self.old_crops.append(item.crop)
item.reset_crop()
def undo(self):
for item, crop in zip(self.items, self.old_crops):
item.crop = crop
class ResetTransforms(QtGui.QUndoCommand):
def __init__(self, items):
@ -235,11 +249,15 @@ class ResetTransforms(QtGui.QUndoCommand):
def redo(self):
self.old_values = []
for item in self.items:
self.old_values.append({
values = {
'scale': item.scale(),
'rotation': item.rotation(),
'flip': item.flip(),
})
}
if item.is_croppable:
values['crop'] = item.crop
item.reset_crop()
self.old_values.append(values)
item.setScale(1, anchor=item.center)
item.setRotation(0, anchor=item.center)
@ -252,6 +270,8 @@ class ResetTransforms(QtGui.QUndoCommand):
item.setRotation(old['rotation'], anchor=item.center)
if old['flip'] == -1:
item.do_flip(anchor=item.center)
if item.is_croppable:
item.crop = old['crop']
class ArrangeItems(QtGui.QUndoCommand):
@ -266,7 +286,7 @@ class ArrangeItems(QtGui.QUndoCommand):
self.old_positions = []
for item, pos in zip(self.items, self.positions):
self.old_positions.append(item.pos())
orig_topleft = item.corners_scene_coords[0]
orig_topleft = item.mapToScene(QtCore.QPointF(0, 0))
rect_topleft = self.scene.itemsBoundingRect(
items=[item]).topLeft()
item.setPos(pos + orig_topleft - rect_topleft)
@ -274,3 +294,17 @@ class ArrangeItems(QtGui.QUndoCommand):
def undo(self):
for item, pos in zip(self.items, self.old_positions):
item.setPos(pos)
class CropItem(QtGui.QUndoCommand):
def __init__(self, item, crop):
super().__init__('Crop item')
self.item = item
self.crop = crop
def redo(self):
self.old_crop = self.item.crop
self.item.crop = self.crop
def undo(self):
self.item.crop = self.old_crop

View file

@ -204,7 +204,7 @@ class SQLiteIO:
}
if data['type'] == 'pixmap':
data['item'] = BeePixmapItem(QtGui.QImage(), **data['data'])
data['item'] = BeePixmapItem(QtGui.QImage())
data['item'].pixmap_from_bytes(row[9])
self.scene.add_item_later(data)

View file

@ -22,6 +22,7 @@ import logging
from PyQt6 import QtCore, QtGui, QtWidgets
from PyQt6.QtCore import Qt
from beeref import commands
from beeref.constants import COLORS
from beeref.selection import SelectableMixin
@ -79,12 +80,16 @@ class BeePixmapItem(BeeItemMixin, QtWidgets.QGraphicsPixmapItem):
"""Class for images added by the user."""
TYPE = 'pixmap'
CROP_HANDLE_SIZE = 15
def __init__(self, image, filename=None):
super().__init__(QtGui.QPixmap.fromImage(image))
self.save_id = None
self.filename = filename
self.reset_crop()
logger.debug(f'Initialized {self}')
self.is_croppable = True
self.crop_mode = False
self.init_selectable()
@classmethod
@ -92,21 +97,37 @@ class BeePixmapItem(BeeItemMixin, QtWidgets.QGraphicsPixmapItem):
item = kwargs.pop('item')
data = kwargs.pop('data', {})
item.filename = item.filename or data.get('filename')
if 'crop' in data:
item.crop = QtCore.QRectF(*data['crop'])
return item
def __str__(self):
return (f'Image "{self.filename}" {self.width} x {self.height}')
size = self.pixmap().size()
return (f'Image "{self.filename}" {size.width()} x {size.height()}')
@property
def width(self):
return self.pixmap().size().width()
def crop(self):
return self._crop
@property
def height(self):
return self.pixmap().size().height()
@crop.setter
def crop(self, value):
logger.debug(f'Setting crop for {self} to {value}')
self.prepareGeometryChange()
self._crop = value
self.update()
def bounding_rect_unselected(self):
if self.crop_mode:
return QtWidgets.QGraphicsPixmapItem.boundingRect(self)
else:
return self.crop
def get_extra_save_data(self):
return {'filename': self.filename}
return {'filename': self.filename,
'crop': [self.crop.topLeft().x(),
self.crop.topLeft().y(),
self.crop.width(),
self.crop.height()]}
def pixmap_to_bytes(self):
"""Convert the pixmap data to PNG bytestring."""
@ -117,16 +138,16 @@ class BeePixmapItem(BeeItemMixin, QtWidgets.QGraphicsPixmapItem):
img.save(buffer, 'PNG')
return barray.data()
def setPixmap(self, pixmap):
super().setPixmap(pixmap)
self.reset_crop()
def pixmap_from_bytes(self, data):
"""Set image pimap from a bytestring."""
pixmap = QtGui.QPixmap()
pixmap.loadFromData(data)
self.setPixmap(pixmap)
def paint(self, painter, option, widget):
painter.drawPixmap(0, 0, self.pixmap())
self.paint_selectable(painter, option, widget)
def create_copy(self):
item = BeePixmapItem(QtGui.QImage(), self.filename)
item.setPixmap(self.pixmap())
@ -136,11 +157,196 @@ class BeePixmapItem(BeeItemMixin, QtWidgets.QGraphicsPixmapItem):
item.setRotation(self.rotation())
if self.flip() == -1:
item.do_flip()
item.crop = self.crop
return item
def copy_to_clipboard(self, clipboard):
clipboard.setPixmap(self.pixmap())
def reset_crop(self):
self.crop = QtCore.QRectF(
0, 0, self.pixmap().size().width(), self.pixmap().size().height())
@property
def crop_handle_size(self):
return self.fixed_length_for_viewport(self.CROP_HANDLE_SIZE)
def crop_handle_topleft(self):
topleft = self.crop_temp.topLeft()
return QtCore.QRectF(
topleft.x(),
topleft.y(),
self.crop_handle_size,
self.crop_handle_size)
def crop_handle_bottomleft(self):
bottomleft = self.crop_temp.bottomLeft()
return QtCore.QRectF(
bottomleft.x(),
bottomleft.y() - self.crop_handle_size,
self.crop_handle_size,
self.crop_handle_size)
def crop_handle_bottomright(self):
bottomright = self.crop_temp.bottomRight()
return QtCore.QRectF(
bottomright.x() - self.crop_handle_size,
bottomright.y() - self.crop_handle_size,
self.crop_handle_size,
self.crop_handle_size)
def crop_handle_topright(self):
topright = self.crop_temp.topRight()
return QtCore.QRectF(
topright.x() - self.crop_handle_size,
topright.y(),
self.crop_handle_size,
self.crop_handle_size)
def crop_handles(self):
return (self.crop_handle_topleft,
self.crop_handle_bottomleft,
self.crop_handle_bottomright,
self.crop_handle_topright)
def get_crop_handle_cursor(self, handle):
"""Gets the crop cursor for the given handle."""
is_topleft_or_bottomright = handle in (
self.crop_handle_topleft, self.crop_handle_bottomright)
return self.get_diag_cursor(is_topleft_or_bottomright)
def draw_crop_rect(self, painter, rect):
"""Paint a dotted rectangle for the cropping UI."""
pen = QtGui.QPen(QtGui.QColor(255, 255, 255))
pen.setWidth(2)
pen.setCosmetic(True)
painter.setPen(pen)
painter.drawRect(rect)
pen.setColor(QtGui.QColor(0, 0, 0))
pen.setStyle(Qt.PenStyle.DotLine)
painter.setPen(pen)
painter.drawRect(rect)
def paint(self, painter, option, widget):
if self.crop_mode:
self.paint_debug(painter, option, widget)
# Darken image outside of cropped area
painter.drawPixmap(0, 0, self.pixmap())
path = QtWidgets.QGraphicsPixmapItem.shape(self)
path.addRect(self.crop_temp)
color = QtGui.QColor(0, 0, 0)
color.setAlpha(100)
painter.setBrush(QtGui.QBrush(color))
painter.setPen(QtGui.QPen())
painter.drawPath(path)
painter.setBrush(QtGui.QBrush())
for handle in self.crop_handles():
self.draw_crop_rect(painter, handle())
self.draw_crop_rect(painter, self.crop_temp)
else:
painter.drawPixmap(self.crop, self.pixmap(), self.crop)
self.paint_selectable(painter, option, widget)
def enter_crop_mode(self):
logger.debug(f'Entering crop mode on {self}')
self.prepareGeometryChange()
self.crop_mode = True
self.crop_temp = QtCore.QRectF(self.crop)
self.crop_mode_move = None
self.crop_mode_event_start = None
self.grabKeyboard()
self.update()
self.scene().crop_item = self
def exit_crop_mode(self, confirm):
logger.debug(f'Exiting crop mode with {confirm} on {self}')
if confirm and self.crop != self.crop_temp:
self.scene().undo_stack.push(
commands.CropItem(self, self.crop_temp))
self.prepareGeometryChange()
self.crop_mode = False
self.crop_temp = None
self.crop_mode_move = None
self.crop_mode_event_start = None
self.ungrabKeyboard()
self.update()
self.scene().crop_item = None
def keyPressEvent(self, event):
if event.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
self.exit_crop_mode(confirm=True)
elif event.key() == Qt.Key.Key_Escape:
self.exit_crop_mode(confirm=False)
else:
super().keyPressEvent(event)
def hoverMoveEvent(self, event):
if not self.crop_mode:
return super().hoverMoveEvent(event)
for handle in self.crop_handles():
if handle().contains(event.pos()):
self.setCursor(self.get_crop_handle_cursor(handle))
return
self.setCursor(Qt.CursorShape.ArrowCursor)
def mousePressEvent(self, event):
if not self.crop_mode:
return super().mousePressEvent(event)
event.accept()
for handle in self.crop_handles():
# Click into a handle?
if handle().contains(event.pos()):
self.crop_mode_event_start = event.pos()
self.crop_mode_move = handle
return
# Click not in handle, end cropping mode:
self.exit_crop_mode(
confirm=self.crop_temp.contains(event.pos()))
def ensure_point_within_pixmap_bounds(self, point):
"""Returns the point, or the nearest point within the pixmap."""
point.setX(min(self.pixmap().size().width(), max(0, point.x())))
point.setY(min(self.pixmap().size().height(), max(0, point.y())))
return point
def mouseMoveEvent(self, event):
if self.crop_mode:
diff = event.pos() - self.crop_mode_event_start
if self.crop_mode_move == self.crop_handle_topleft:
new = self.ensure_point_within_pixmap_bounds(
self.crop_temp.topLeft() + diff)
self.crop_temp.setTopLeft(new)
if self.crop_mode_move == self.crop_handle_bottomleft:
new = self.ensure_point_within_pixmap_bounds(
self.crop_temp.bottomLeft() + diff)
self.crop_temp.setBottomLeft(new)
if self.crop_mode_move == self.crop_handle_bottomright:
new = self.ensure_point_within_pixmap_bounds(
self.crop_temp.bottomRight() + diff)
self.crop_temp.setBottomRight(new)
if self.crop_mode_move == self.crop_handle_topright:
new = self.ensure_point_within_pixmap_bounds(
self.crop_temp.topRight() + diff)
self.crop_temp.setTopRight(new)
self.update()
self.crop_mode_event_start = event.pos()
event.accept()
else:
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
if self.crop_mode:
self.crop_mode_move = None
self.crop_mode_event_start = None
event.accept()
else:
super().mouseReleaseEvent(event)
@register_item
class BeeTextItem(BeeItemMixin, QtWidgets.QGraphicsTextItem):
@ -152,6 +358,7 @@ class BeeTextItem(BeeItemMixin, QtWidgets.QGraphicsTextItem):
super().__init__(text or "Text")
self.save_id = None
logger.debug(f'Initialized {self}')
self.is_croppable = False
self.init_selectable()
self.is_editable = True
self.edit_mode = False
@ -167,14 +374,6 @@ class BeeTextItem(BeeItemMixin, QtWidgets.QGraphicsTextItem):
txt = self.toPlainText()[:40]
return (f'Text "{txt}"')
@property
def width(self):
return QtWidgets.QGraphicsTextItem.boundingRect(self).width()
@property
def height(self):
return QtWidgets.QGraphicsTextItem.boundingRect(self).height()
def get_extra_save_data(self):
return {'text': self.toPlainText()}
@ -203,15 +402,19 @@ class BeeTextItem(BeeItemMixin, QtWidgets.QGraphicsTextItem):
return item
def enter_edit_mode(self):
logger.debug(f'Entering edit mode on {self}')
self.edit_mode = True
self.setTextInteractionFlags(
Qt.TextInteractionFlag.TextEditorInteraction)
self.scene().edit_item = self
def exit_edit_mode(self):
logger.debug(f'Exiting edit mode on {self}')
self.edit_mode = False
# reset selection:
self.setTextCursor(QtGui.QTextCursor(self.document()))
self.setTextInteractionFlags(Qt.TextInteractionFlag.NoTextInteraction)
self.scene().edit_item = None
def has_selection_handles(self):
return super().has_selection_handles() and not self.edit_mode

View file

@ -47,6 +47,7 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene):
self.items_to_add = Queue()
self.internal_clipboard = []
self.edit_item = None
self.crop_item = None
def addItem(self, item):
logger.debug(f'Adding item {item}')
@ -56,6 +57,11 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene):
logger.debug(f'Removing item {item}')
super().removeItem(item)
def cancel_crop_mode(self):
"""Cancels an ongoing crop mode, if there is any."""
if self.crop_item:
self.crop_item.exit_crop_mode(confirm=False)
def copy_selection_to_internal_clipboard(self):
self.internal_clipboard = []
for item in self.selectedItems(user_only=True):
@ -70,6 +76,7 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene):
self.undo_stack.push(commands.InsertItems(self, copies, position))
def raise_to_top(self):
self.cancel_crop_mode()
items = self.selectedItems(user_only=True)
z_values = map(lambda i: i.zValue(), items)
delta = self.max_z + self.Z_STEP - min(z_values)
@ -78,6 +85,7 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene):
item.setZValue(item.zValue() + delta)
def lower_to_bottom(self):
self.cancel_crop_mode()
items = self.selectedItems(user_only=True)
z_values = map(lambda i: i.zValue(), items)
delta = self.min_z - self.Z_STEP - max(z_values)
@ -93,6 +101,7 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene):
:param mode: "width" or "height".
"""
self.cancel_crop_mode()
values = []
items = self.selectedItems(user_only=True)
for item in items:
@ -124,6 +133,7 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene):
Size meaning the area = widh * height.
"""
self.cancel_crop_mode()
sizes = []
items = self.selectedItems(user_only=True)
for item in items:
@ -146,6 +156,8 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene):
def arrange(self, vertical=False):
"""Arrange items in a line (horizontally or vertically)."""
self.cancel_crop_mode()
items = self.selectedItems(user_only=True)
if len(items) < 2:
return
@ -184,6 +196,8 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene):
positions))
def arrange_optimal(self):
self.cancel_crop_mode()
items = self.selectedItems(user_only=True)
if len(items) < 2:
return
@ -218,13 +232,25 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene):
def flip_items(self, vertical=False):
"""Flip selected items."""
self.cancel_crop_mode()
self.undo_stack.push(
commands.FlipItems(self.selectedItems(user_only=True),
self.get_selection_center(),
vertical=vertical))
def crop_items(self):
"""Crop selected item."""
if self.crop_item:
return
if self.has_croppable_selection():
item = self.selectedItems(user_only=True)[0]
if item.is_croppable:
item.enter_crop_mode()
def set_selected_all_items(self, value):
"""Sets the selection mode of all items to ``value``."""
self.cancel_crop_mode()
for item in self.items():
item.setSelected(value)
@ -243,6 +269,14 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene):
return len(self.selectedItems(user_only=True)) > 1
def has_croppable_selection(self):
"""Checks whether the current selection is croppable, i.e. a
single selection whose item is croppable."""
if self.has_single_selection():
return self.selectedItems(user_only=True)[0].is_croppable
return False
def mousePressEvent(self, event):
if event.button() == Qt.MouseButton.RightButton:
# Right-click invokes the context menu on the
@ -257,7 +291,12 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene):
if self.edit_item:
if item_at_pos != self.edit_item:
self.edit_item.exit_edit_mode()
self.edit_item = None
else:
super().mousePressEvent(event)
return
if self.crop_item:
if item_at_pos != self.crop_item:
self.cancel_crop_mode()
else:
super().mousePressEvent(event)
return
@ -275,7 +314,6 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene):
if not item.isSelected():
item.setSelected(True)
if item.is_editable:
self.edit_item = item
item.enter_edit_mode()
self.mousePressEvent(event)
else:

View file

@ -13,7 +13,7 @@
# 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."""
"""Classes that draw and handle selection stuff for items."""
import logging
import math
@ -98,9 +98,20 @@ class BaseItemMixin:
if vertical:
self.setRotation(self.rotation() + 180)
def bounding_rect_unselected(self):
return super().boundingRect()
@property
def width(self):
return self.bounding_rect_unselected().width()
@property
def height(self):
return self.bounding_rect_unselected().height()
@property
def center(self):
return QtCore.QPointF(self.width, self.height) / 2
return self.bounding_rect_unselected().center()
@property
def center_scene_coords(self):
@ -166,7 +177,7 @@ class SelectableMixin(BaseItemMixin):
else:
painter.fillPath(shape, color)
def paint_selectable(self, painter, option, widget):
def paint_debug(self, painter, option, widget):
if commandline_args.debug_shapes:
self.draw_debug_shape(painter, self.shape(), 255, 0, 0)
if commandline_args.debug_boundingrects:
@ -180,6 +191,9 @@ class SelectableMixin(BaseItemMixin):
for edge in self.get_flip_bounds():
self.draw_debug_shape(painter, edge['rect'], 255, 255, 0)
def paint_selectable(self, painter, option, widget):
self.paint_debug(painter, option, widget)
if not self.has_selection_outline():
return
@ -189,7 +203,7 @@ class SelectableMixin(BaseItemMixin):
painter.setPen(pen)
# Draw the main selection rectangle
painter.drawRect(0, 0, self.width, self.height)
painter.drawRect(self.bounding_rect_unselected())
# If it's a single selection, draw the handles:
if self.has_selection_handles():
@ -202,10 +216,10 @@ class SelectableMixin(BaseItemMixin):
@property
def corners(self):
"""The corners of the item. Used for scale and rotate handles."""
return (QtCore.QPointF(0, 0),
QtCore.QPointF(self.width, 0),
QtCore.QPointF(self.width, self.height),
QtCore.QPointF(0, self.height))
return (self.bounding_rect_unselected().topLeft(),
self.bounding_rect_unselected().topRight(),
self.bounding_rect_unselected().bottomRight(),
self.bounding_rect_unselected().bottomLeft())
@property
def corners_scene_coords(self):
@ -264,65 +278,66 @@ class SelectableMixin(BaseItemMixin):
outer_margin = self.select_resize_size / 2
inner_margin = self.select_resize_size / 2
origin = self.bounding_rect_unselected().topLeft()
return [
# top:
{
'rect': QtCore.QRectF(inner_margin,
-outer_margin,
self.width - 2 * inner_margin,
outer_margin + inner_margin),
'rect': QtCore.QRectF(
origin.x() + inner_margin,
origin.y() - outer_margin,
self.width - 2 * inner_margin,
outer_margin + inner_margin),
'flip_v': True,
},
# bottom:
{
'rect': QtCore.QRectF(inner_margin,
self.height - inner_margin,
self.width - 2 * inner_margin,
outer_margin + inner_margin),
'rect': QtCore.QRectF(
origin.x() + inner_margin,
origin.y() + self.height - inner_margin,
self.width - 2 * inner_margin,
outer_margin + inner_margin),
'flip_v': True,
},
# left:
{
'rect': QtCore.QRectF(-outer_margin,
inner_margin,
outer_margin + inner_margin,
self.height - 2 * inner_margin),
'rect': QtCore.QRectF(
origin.x() - outer_margin,
origin.y() + inner_margin,
outer_margin + inner_margin,
self.height - 2 * inner_margin),
'flip_v': False,
},
# right:
{
'rect': QtCore.QRectF(self.width - inner_margin,
inner_margin,
outer_margin + inner_margin,
self.height - 2 * inner_margin),
'rect': QtCore.QRectF(
origin.x() + self.width - inner_margin,
origin.y() + inner_margin,
outer_margin + inner_margin,
self.height - 2 * inner_margin),
'flip_v': False,
}
]
def boundingRect(self):
if not self.has_selection_outline():
return super().boundingRect()
return self.bounding_rect_unselected()
# Add extra space for the interactive areas
margin = self.select_resize_size / 2 + self.select_rotate_size
return QtCore.QRectF(
-margin, -margin,
self.width + 2 * margin,
self.height + 2 * margin)
return self.bounding_rect_unselected().marginsAdded(
QtCore.QMarginsF(margin, margin, margin, margin))
def shape(self):
path = QtGui.QPainterPath()
if self.has_selection_handles():
margin = self.select_resize_size / 2
rect = QtCore.QRectF(
-margin, -margin,
self.width + 2 * margin,
self.height + 2 * margin)
rect = self.bounding_rect_unselected().marginsAdded(
QtCore.QMarginsF(margin, margin, margin, margin))
path.addRect(rect)
for corner in self.corners:
path.addPath(self.get_rotate_bounds(corner))
else:
rect = super().boundingRect()
rect = self.bounding_rect_unselected()
path.addRect(rect)
return path
@ -408,14 +423,15 @@ class SelectableMixin(BaseItemMixin):
def get_scale_anchor(self, corner):
"""Get the anchor around which the scale for this corner operates."""
return QtCore.QPointF(self.width - corner.x(),
self.height - corner.y())
origin = self.bounding_rect_unselected().topLeft()
return QtCore.QPointF(self.width - corner.x() + 2*origin.x(),
self.height - corner.y() + 2*origin.y())
def get_corner_direction(self, corner):
"""Get the direction facing away from the center, e.g. the direction
in which the scale for this corner increases."""
return QtCore.QPointF(1 if corner.x() > 0 else -1,
1 if corner.y() > 0 else -1)
return QtCore.QPointF(1 if corner.x() > self.center.x() else -1,
1 if corner.y() > self.center.y() else -1)
def get_direction_from_center(self, pos):
"""The direction of a point in relation to the item's center."""
@ -444,11 +460,16 @@ class SelectableMixin(BaseItemMixin):
def get_corner_scale_cursor(self, corner):
"""Gets the scale cursor for the given corner."""
is_topleft_or_bottomright = corner in (
self.bounding_rect_unselected().topLeft(),
self.bounding_rect_unselected().bottomRight())
return self.get_diag_cursor(is_topleft_or_bottomright)
def get_diag_cursor(self, is_topleft_or_bottomright):
rotation = self.rotation() % 180
flipped = self.flip() == -1
if corner in (QtCore.QPointF(0, 0),
QtCore.QPointF(self.width, self.height)):
if is_topleft_or_bottomright:
if 22.5 < rotation < 67.5:
return Qt.CursorShape.SizeVerCursor
elif 67.5 < rotation < 112.5:
@ -571,14 +592,6 @@ class MultiSelectItem(SelectableMixin,
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)
@ -641,14 +654,6 @@ class RubberbandItem(BaseItemMixin, QtWidgets.QGraphicsRectItem):
def __str__(self):
return (f'RubberbandItem {self.width} x {self.height}')
@property
def width(self):
return self.rect().width()
@property
def height(self):
return self.rect().height()
def fit(self, point1, point2):
"""Updates itself to fit the two given points."""

View file

@ -215,10 +215,12 @@ class BeeGraphicsView(MainControlsMixin,
def on_action_undo(self):
logger.debug('Undo: %s' % self.undo_stack.undoText())
self.scene.cancel_crop_mode()
self.undo_stack.undo()
def on_action_redo(self):
logger.debug('Redo: %s' % self.undo_stack.redoText())
self.scene.cancel_crop_mode()
self.undo_stack.redo()
def on_action_select_all(self):
@ -229,6 +231,7 @@ class BeeGraphicsView(MainControlsMixin,
def on_action_delete_items(self):
logger.debug('Deleting items...')
self.scene.cancel_crop_mode()
self.undo_stack.push(
commands.DeleteItems(
self.scene, self.scene.selectedItems(user_only=True)))
@ -264,6 +267,9 @@ class BeeGraphicsView(MainControlsMixin,
def on_action_arrange_optimal(self):
self.scene.arrange_optimal()
def on_action_crop(self):
self.scene.crop_items()
def on_action_flip_horizontally(self):
self.scene.flip_items(vertical=False)
@ -271,18 +277,27 @@ class BeeGraphicsView(MainControlsMixin,
self.scene.flip_items(vertical=True)
def on_action_reset_scale(self):
self.scene.cancel_crop_mode()
self.undo_stack.push(commands.ResetScale(
self.scene.selectedItems(user_only=True)))
def on_action_reset_rotation(self):
self.scene.cancel_crop_mode()
self.undo_stack.push(commands.ResetRotation(
self.scene.selectedItems(user_only=True)))
def on_action_reset_flip(self):
self.scene.cancel_crop_mode()
self.undo_stack.push(commands.ResetFlip(
self.scene.selectedItems(user_only=True)))
def on_action_reset_crop(self):
self.scene.cancel_crop_mode()
self.undo_stack.push(commands.ResetCrop(
self.scene.selectedItems(user_only=True)))
def on_action_reset_transforms(self):
self.scene.cancel_crop_mode()
self.undo_stack.push(commands.ResetTransforms(
self.scene.selectedItems(user_only=True)))
@ -316,6 +331,7 @@ class BeeGraphicsView(MainControlsMixin,
self.worker.start()
def on_action_open(self):
self.scene.cancel_crop_mode()
filename, f = QtWidgets.QFileDialog.getOpenFileName(
parent=self,
caption='Open file',
@ -349,6 +365,7 @@ class BeeGraphicsView(MainControlsMixin,
self.worker.start()
def on_action_save_as(self):
self.scene.cancel_crop_mode()
filename, f = QtWidgets.QFileDialog.getSaveFileName(
parent=self,
caption='Save file',
@ -357,6 +374,7 @@ class BeeGraphicsView(MainControlsMixin,
self.do_save(filename, create_new=True)
def on_action_save(self):
self.scene.cancel_crop_mode()
if not self.filename:
self.on_action_save_as()
else:
@ -429,6 +447,7 @@ class BeeGraphicsView(MainControlsMixin,
self.worker.start()
def on_action_insert_images(self):
self.scene.cancel_crop_mode()
formats = self.get_supported_image_formats(QtGui.QImageReader)
logger.debug(f'Supported image types for reading: {formats}')
filenames, f = QtWidgets.QFileDialog.getOpenFileNames(
@ -438,6 +457,7 @@ class BeeGraphicsView(MainControlsMixin,
self.do_insert_images(filenames)
def on_action_insert_text(self):
self.scene.cancel_crop_mode()
item = BeeTextItem()
pos = self.mapToScene(self.mapFromGlobal(self.cursor().pos()))
item.setScale(1 / self.get_scale())
@ -445,6 +465,7 @@ class BeeGraphicsView(MainControlsMixin,
def on_action_copy(self):
logger.debug('Copying to clipboard...')
self.scene.cancel_crop_mode()
clipboard = QtWidgets.QApplication.clipboard()
items = self.scene.selectedItems(user_only=True)
@ -462,6 +483,7 @@ class BeeGraphicsView(MainControlsMixin,
'beeref/items', QtCore.QByteArray.number(len(items)))
def on_action_paste(self):
self.scene.cancel_crop_mode()
logger.debug('Pasting from clipboard...')
clipboard = QtWidgets.QApplication.clipboard()
pos = self.mapToScene(self.mapFromGlobal(self.cursor().pos()))
@ -499,6 +521,8 @@ class BeeGraphicsView(MainControlsMixin,
len(self.scene.selectedItems(user_only=True)))
self.actiongroup_set_enabled('active_when_selection',
self.scene.has_selection())
self.actiongroup_set_enabled('active_when_croppable',
self.scene.has_croppable_selection())
self.viewport().repaint()
def recalc_scene_rect(self):

View file

@ -4,7 +4,7 @@ import os.path
import stat
from unittest.mock import MagicMock, patch
from PyQt6 import QtGui
from PyQt6 import QtCore, QtGui
import pytest
from beeref.fileio import schema, is_bee_file
@ -233,6 +233,7 @@ def test_sqliteio_write_inserts_new_pixmap_item(tmpfile, view):
item.setZValue(0.22)
item.setRotation(33)
item.do_flip()
item.crop = QtCore.QRectF(5, 5, 100, 80)
item.pixmap_to_bytes = MagicMock(return_value=b'abc')
io = SQLiteIO(tmpfile, view.scene, create_new=True)
io.write()
@ -249,7 +250,10 @@ def test_sqliteio_write_inserts_new_pixmap_item(tmpfile, view):
assert result[3] == 1.3
assert result[4] == 33
assert result[5] == -1
assert json.loads(result[6]) == {'filename': 'bee.jpg'}
assert json.loads(result[6]) == {
'filename': 'bee.jpg',
'crop': [5, 5, 100, 80],
}
assert result[7] == 'pixmap'
assert result[8] == b'abc'
assert result[9] == '0001-bee.png'
@ -265,7 +269,7 @@ def test_sqliteio_write_inserts_new_pixmap_item_without_filename(
result = io.fetchone(
'SELECT items.data, sqlar.name FROM items '
'INNER JOIN sqlar on sqlar.item_id = items.id')
assert json.loads(result[0]) == {'filename': None}
assert json.loads(result[0])['filename'] is None
assert result[1] == '0001.png'
@ -311,6 +315,7 @@ def test_sqliteio_write_updates_existing_pixmap_item(tmpfile, view):
item.setZValue(0.22)
item.setRotation(33)
item.save_id = 1
item.crop = QtCore.QRectF(5, 5, 80, 100)
item.pixmap_to_bytes = MagicMock(return_value=b'abc')
io = SQLiteIO(tmpfile, view.scene, create_new=True)
io.write()
@ -319,6 +324,7 @@ def test_sqliteio_write_updates_existing_pixmap_item(tmpfile, view):
item.setZValue(0.33)
item.setRotation(100)
item.do_flip()
item.crop = QtCore.QRectF(1, 2, 30, 40)
item.filename = 'new.png'
item.pixmap_to_bytes.return_value = b'updated'
io.create_new = False
@ -335,7 +341,10 @@ def test_sqliteio_write_updates_existing_pixmap_item(tmpfile, view):
assert result[3] == 0.7
assert result[4] == 100
assert result[5] == -1
assert json.loads(result[6]) == {'filename': 'new.png'}
assert json.loads(result[6]) == {
'filename': 'new.png',
'crop': [1, 2, 30, 40],
}
assert result[7] == b'abc'

View file

@ -1,6 +1,8 @@
from unittest.mock import patch, MagicMock, PropertyMock
import pytest
from unittest.mock import patch, MagicMock
from PyQt6 import QtCore, QtGui, QtWidgets
from PyQt6.QtCore import Qt
from beeref.items import BeePixmapItem, item_registry
@ -17,44 +19,66 @@ def test_init(selectable_mock, qapp, imgfilename3x3):
assert item.height == 3
assert item.scale() == 1
assert item.filename == imgfilename3x3
assert item.crop == QtCore.QRectF(0, 0, 3, 3)
assert item.is_croppable is True
assert item.crop_mode is False
selectable_mock.assert_called_once()
def test_set_pos_center(qapp, item):
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=200):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=100):
item.set_pos_center(QtCore.QPointF(0, 0))
assert item.pos().x() == -100
assert item.pos().y() == -50
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 200, 100)):
item.set_pos_center(QtCore.QPointF(0, 0))
assert item.pos().x() == -100
assert item.pos().y() == -50
def test_set_pos_center_when_scaled(qapp, item):
item.setScale(2)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=200):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=100):
item.set_pos_center(QtCore.QPointF(0, 0))
assert item.pos().x() == -200
assert item.pos().y() == -100
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 200, 100)):
item.set_pos_center(QtCore.QPointF(0, 0))
assert item.pos().x() == -200
assert item.pos().y() == -100
def test_set_pos_center_when_rotated(qapp, item):
item.setRotation(90)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=200):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=100):
item.set_pos_center(QtCore.QPointF(0, 0))
assert item.pos().x() == 50
assert item.pos().y() == -100
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 200, 100)):
item.set_pos_center(QtCore.QPointF(0, 0))
assert item.pos().x() == 50
assert item.pos().y() == -100
def test_set_crop(qapp, item):
item.update = MagicMock()
item.prepareGeometryChange = MagicMock()
item.crop = QtCore.QRectF(10, 20, 30, 40)
item.update.assert_called_once_with()
item.prepareGeometryChange.assert_called_once_with()
def test_bounding_rect_unselected(qapp, imgfilename3x3):
item = BeePixmapItem(QtGui.QImage(imgfilename3x3))
item.crop = QtCore.QRectF(1, 1, 2, 2)
assert item.bounding_rect_unselected() == QtCore.QRectF(1, 1, 2, 2)
def test_bounding_rect_unselected_in_crop_mode(qapp, imgfilename3x3):
item = BeePixmapItem(QtGui.QImage(imgfilename3x3))
item.crop = QtCore.QRectF(1, 1, 2, 2)
item.crop_mode = True
assert item.bounding_rect_unselected() == QtCore.QRectF(-0.5, -0.5, 4, 4)
def test_get_extra_save_data(item):
item.filename = 'foobar.png'
assert item.get_extra_save_data() == {'filename': 'foobar.png'}
item.crop = QtCore.QRectF(10, 20, 30, 40)
assert item.get_extra_save_data() == {
'filename': 'foobar.png',
'crop': [10, 20, 30, 40],
}
def test_pixmap_to_bytes(qapp, imgfilename3x3):
@ -68,15 +92,7 @@ def test_pixmap_from_bytes(qapp, item, imgfilename3x3):
item.pixmap_from_bytes(imgdata)
assert item.width == 3
assert item.height == 3
def test_paint(qapp, item):
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')
assert item.crop == QtCore.QRectF(0, 0, 3, 3)
def test_has_selection_outline_when_not_selected(view, item):
@ -159,6 +175,14 @@ def test_create_from_data(item):
assert item.filename == 'foobar.png'
def test_create_from_data_with_crop(item):
new_item = BeePixmapItem.create_from_data(
item=item, data={'filename': 'foobar.png', 'crop': [10, 20, 30, 40]})
assert new_item is item
assert item.filename == 'foobar.png'
assert item.crop == QtCore.QRectF(10, 20, 30, 40)
def test_create_copy(qapp, imgfilename3x3):
item = BeePixmapItem(QtGui.QImage(imgfilename3x3), 'foo.png')
item.setPos(20, 30)
@ -166,19 +190,401 @@ def test_create_copy(qapp, imgfilename3x3):
item.do_flip()
item.setZValue(0.5)
item.setScale(2.2)
item.crop = QtCore.QRectF(10, 20, 30, 40)
copy = item.create_copy()
assert copy.pixmap_to_bytes() == item.pixmap_to_bytes()
assert copy.filename == 'foo.png'
assert copy.pos() == QtCore.QPointF(20, 30)
assert copy.rotation() == 33
assert item.flip() == -1
assert item.zValue() == 0.5
assert item.scale() == 2.2
assert copy.flip() == -1
assert copy.zValue() == 0.5
assert copy.scale() == 2.2
assert copy.crop == QtCore.QRectF(10, 20, 30, 40)
def test_item_to_clipboard(qapp, imgfilename3x3):
def test_copy_to_clipboard(qapp, imgfilename3x3):
clipboard = QtWidgets.QApplication.clipboard()
item = BeePixmapItem(QtGui.QImage(imgfilename3x3), 'foo.png')
item.copy_to_clipboard(clipboard)
assert clipboard.pixmap().size() == item.pixmap().size()
def test_reset_crop(qapp, imgfilename3x3):
item = BeePixmapItem(QtGui.QImage(imgfilename3x3))
item.crop = QtCore.QRectF(10, 20, 30, 40)
item.reset_crop()
assert item.crop == QtCore.QRectF(0, 0, 3, 3)
def test_crop_handle_topleft(qapp, item):
item.crop_temp = QtCore.QRectF(100, 200, 300, 400)
assert item.crop_handle_topleft() == QtCore.QRectF(100, 200, 15, 15)
def test_crop_handle_bottomleft(qapp, item):
item.crop_temp = QtCore.QRectF(100, 200, 300, 400)
assert item.crop_handle_bottomleft() == QtCore.QRectF(100, 585, 15, 15)
def test_crop_handle_bottomright(qapp, item):
item.crop_temp = QtCore.QRectF(100, 200, 300, 400)
assert item.crop_handle_bottomright() == QtCore.QRectF(385, 585, 15, 15)
def test_crop_handle_topright(qapp, item):
item.crop_temp = QtCore.QRectF(100, 200, 300, 400)
assert item.crop_handle_topright() == QtCore.QRectF(385, 200, 15, 15)
def test_paint(qapp, item):
item.pixmap = MagicMock()
item.paint_selectable = MagicMock()
item.crop = QtCore.QRectF(10, 20, 30, 40)
painter = MagicMock()
item.paint(painter, None, None)
item.paint_selectable.assert_called_once()
painter.drawPixmap.assert_called_with(
QtCore.QRectF(10, 20, 30, 40),
item.pixmap(),
QtCore.QRectF(10, 20, 30, 40))
def test_paint_when_crop_mode(qapp, item):
item.pixmap = MagicMock()
item.paint_selectable = MagicMock()
item.crop = QtCore.QRectF(10, 20, 30, 40)
item.crop_mode = True
item.crop_temp = QtCore.QRectF(11, 22, 29, 39)
painter = MagicMock()
item.paint(painter, None, None)
item.paint_selectable.assert_not_called()
painter.drawPixmap.assert_called_with(0, 0, item.pixmap())
def test_enter_crop_mode(view, item):
view.scene.addItem(item)
item.crop = QtCore.QRectF(10, 20, 30, 40)
item.update = MagicMock()
item.prepareGeometryChange = MagicMock()
item.grabKeyboard = MagicMock()
item.enter_crop_mode()
assert item.crop_mode is True
assert item.crop_temp == QtCore.QRectF(10, 20, 30, 40)
assert item.crop_mode_move is None
assert item.crop_mode_event_start is None
item.update.assert_called_once_with()
item.prepareGeometryChange.assert_called_once_with()
item.grabKeyboard.assert_called_once_with()
assert view.scene.crop_item == item
def test_exit_crop_mode_confirmed(view, item):
view.scene.addItem(item)
item.update = MagicMock()
item.prepareGeometryChange = MagicMock()
item.ungrabKeyboard = MagicMock()
item.crop = QtCore.QRectF(0, 0, 100, 80)
item.crop_temp = QtCore.QRectF(10, 20, 30, 40)
item.crop_mode = True
item.crop_mode_move = 'topleft'
item.crop_mode_event_start = QtCore.QRectF(1, 1, 1, 1)
item.exit_crop_mode(confirm=True)
item.crop == QtCore.QRectF(10, 20, 30, 40)
assert item.crop_mode is False
assert item.crop_temp is None
assert item.crop_mode_move is None
assert item.crop_mode_event_start is None
item.update.assert_called()
item.prepareGeometryChange.assert_called()
item.ungrabKeyboard.assert_called_once_with()
assert view.scene.crop_item is None
view.scene.undo_stack.canUndo() is True
def test_exit_crop_mode_confirmed_no_change(view, item):
view.scene.addItem(item)
item.crop = QtCore.QRectF(0, 0, 100, 80)
item.crop_temp = QtCore.QRectF(0, 0, 100, 80)
item.crop_mode = True
item.exit_crop_mode(confirm=True)
assert item.crop == QtCore.QRectF(0, 0, 100, 80)
view.scene.undo_stack.canUndo() is False
def test_exit_crop_mode_not_confirmed(view, item):
view.scene.addItem(item)
item.update = MagicMock()
item.prepareGeometryChange = MagicMock()
item.ungrabKeyboard = MagicMock()
item.crop = QtCore.QRectF(0, 0, 100, 80)
item.crop_temp = QtCore.QRectF(10, 20, 30, 40)
item.crop_mode = True
item.crop_mode_move = 'topleft'
item.crop_mode_event_start = QtCore.QRectF(1, 1, 1, 1)
item.exit_crop_mode(confirm=False)
item.crop == QtCore.QRectF(0, 0, 100, 80)
assert item.crop_mode is False
assert item.crop_temp is None
assert item.crop_mode_move is None
assert item.crop_mode_event_start is None
item.update.assert_called()
item.prepareGeometryChange.assert_called()
item.ungrabKeyboard.assert_called_once_with()
assert view.scene.crop_item is None
view.scene.undo_stack.canUndo() is False
@patch('PyQt6.QtWidgets.QGraphicsPixmapItem.keyPressEvent')
def test_key_press_event_return(key_mock, qapp, item):
item.exit_crop_mode = MagicMock()
event = MagicMock()
event.key.return_value = Qt.Key.Key_Return
item.keyPressEvent(event)
item.exit_crop_mode.assert_called_once_with(confirm=True)
key_mock.assert_not_called()
@patch('PyQt6.QtWidgets.QGraphicsPixmapItem.keyPressEvent')
def test_key_press_event_escape(key_mock, qapp, item):
item.exit_crop_mode = MagicMock()
event = MagicMock()
event.key.return_value = Qt.Key.Key_Escape
item.keyPressEvent(event)
item.exit_crop_mode.assert_called_once_with(confirm=False)
key_mock.assert_not_called()
@patch('PyQt6.QtWidgets.QGraphicsPixmapItem.keyPressEvent')
def test_key_press_event_other(key_mock, qapp, item):
item.exit_crop_mode = MagicMock()
event = MagicMock()
event.key.return_value = Qt.Key.Key_Space
item.keyPressEvent(event)
item.exit_crop_mode.assert_not_called()
key_mock.assert_called_once_with(event)
@patch('beeref.selection.SelectableMixin.hoverMoveEvent')
def test_hover_move_event_when_not_crop_mode(hover_mock, qapp, item):
item.crop_mode = False
event = MagicMock()
item.hoverMoveEvent(event)
hover_mock.assert_called_once_with(event)
@patch('beeref.selection.SelectableMixin.hoverMoveEvent')
def test_hover_move_event_crop_mode_inside_handle(hover_mock, qapp, item):
item.crop_mode = True
item.crop_temp = QtCore.QRectF(0, 0, 100, 80)
event = MagicMock()
event.pos.return_value = QtCore.QPointF(5, 5)
item.hoverMoveEvent(event)
item.cursor() == Qt.CursorShape.SizeFDiagCursor
hover_mock.assert_not_called()
@patch('beeref.selection.SelectableMixin.hoverMoveEvent')
def test_hover_move_event_crop_mode_outside_handle(hover_mock, qapp, item):
item.crop_mode = True
item.crop_temp = QtCore.QRectF(0, 0, 100, 80)
item.setCursor(Qt.CursorShape.SizeFDiagCursor)
event = MagicMock()
event.pos.return_value = QtCore.QPointF(50, 50)
item.hoverMoveEvent(event)
item.cursor() == Qt.CursorShape.ArrowCursor
hover_mock.assert_not_called()
@patch('beeref.selection.SelectableMixin.mousePressEvent')
def test_mouse_press_event_when_not_crop_mode(mouse_mock, qapp, item):
item.crop_mode = False
item.crop_mode_move = None
item.exit_crop_mode = MagicMock()
event = MagicMock()
item.mousePressEvent(event)
assert item.crop_mode_move is None
item.exit_crop_mode.assert_not_called()
mouse_mock.assert_called_once_with(event)
event.accept.assert_not_called()
@patch('beeref.selection.SelectableMixin.mousePressEvent')
def test_mouse_press_event_crop_mode_inside_handle(mouse_mock, qapp, item):
item.crop_mode = True
item.crop_temp = QtCore.QRectF(0, 0, 100, 80)
item.crop_mode_move = None
item.exit_crop_mode = MagicMock()
event = MagicMock()
event.pos.return_value = QtCore.QPointF(5, 5)
item.mousePressEvent(event)
assert item.crop_mode_move == item.crop_handle_topleft
assert item.crop_mode_event_start == QtCore.QPointF(5, 5)
item.exit_crop_mode.assert_not_called()
mouse_mock.assert_not_called()
event.accept.assert_called_once_with()
@patch('beeref.selection.SelectableMixin.mousePressEvent')
def test_mouse_press_event_crop_mode_outside_handle_inside_crop(
mouse_mock, qapp, item):
item.crop_mode = True
item.crop_temp = QtCore.QRectF(0, 0, 100, 80)
item.crop_mode_move = None
item.exit_crop_mode = MagicMock()
event = MagicMock()
event.pos.return_value = QtCore.QPointF(50, 50)
item.mousePressEvent(event)
assert item.crop_mode_move is None
item.exit_crop_mode.assert_called_once_with(confirm=True)
mouse_mock.assert_not_called()
event.accept.assert_called_once_with()
@patch('beeref.selection.SelectableMixin.mousePressEvent')
def test_mouse_press_event_crop_mode_outside_handle_outside_crop(
mouse_mock, qapp, item):
item.crop_mode = True
item.crop_temp = QtCore.QRectF(0, 0, 100, 80)
item.crop_mode_move = None
item.exit_crop_mode = MagicMock()
event = MagicMock()
event.pos.return_value = QtCore.QPointF(150, 150)
item.mousePressEvent(event)
assert item.crop_mode_move is None
item.exit_crop_mode.assert_called_once_with(confirm=False)
mouse_mock.assert_not_called()
event.accept.assert_called_once_with()
@pytest.mark.parametrize('point,expected',
[((45, 56), (45, 56)),
((0, 0), (0, 0)),
((-5, -5), (0, 0)),
((100, 80), (100, 80)),
((105, 85), (100, 80))])
def test_ensure_point_within_pixmap_bounds_inside(point, expected, qapp, item):
pixmap = MagicMock()
pixmap.size.return_value = QtCore.QRectF(0, 0, 100, 80)
item.pixmap = MagicMock(return_value=pixmap)
result = item.ensure_point_within_pixmap_bounds(QtCore.QPointF(*point))
assert result == QtCore.QPointF(*expected)
@patch('beeref.selection.SelectableMixin.mouseMoveEvent')
def test_mouse_move_when_crop_mode_bottomleft(mouse_mock, qapp, item):
pixmap = MagicMock()
pixmap.size.return_value = QtCore.QRectF(0, 0, 100, 80)
item.crop_mode = True
item.pixmap = MagicMock(return_value=pixmap)
item.crop_temp = QtCore.QRectF(10, 20, 30, 40)
item.crop_mode_event_start = QtCore.QPointF(15, 75)
item.crop_mode_move = item.crop_handle_bottomleft
event = MagicMock()
event.pos.return_value = QtCore.QPointF(10, 70)
item.mouseMoveEvent(event)
assert item.crop_temp == QtCore.QRectF(5, 20, 35, 35)
event.accept.assert_called_once()
mouse_mock.assert_not_called()
@patch('beeref.selection.SelectableMixin.mouseMoveEvent')
def test_mouse_move_when_crop_mode_bottomright(mouse_mock, qapp, item):
pixmap = MagicMock()
pixmap.size.return_value = QtCore.QRectF(0, 0, 100, 80)
item.crop_mode = True
item.pixmap = MagicMock(return_value=pixmap)
item.crop_temp = QtCore.QRectF(10, 20, 30, 40)
item.crop_mode_event_start = QtCore.QPointF(95, 75)
item.crop_mode_move = item.crop_handle_bottomright
event = MagicMock()
event.pos.return_value = QtCore.QPointF(90, 70)
item.mouseMoveEvent(event)
assert item.crop_temp == QtCore.QRectF(10, 20, 25, 35)
event.accept.assert_called_once()
mouse_mock.assert_not_called()
@patch('beeref.selection.SelectableMixin.mouseMoveEvent')
def test_mouse_move_when_crop_mode_topleft(mouse_mock, qapp, item):
pixmap = MagicMock()
pixmap.size.return_value = QtCore.QRectF(0, 0, 100, 80)
item.crop_mode = True
item.pixmap = MagicMock(return_value=pixmap)
item.crop_temp = QtCore.QRectF(10, 20, 30, 40)
item.crop_mode_event_start = QtCore.QPointF(15, 25)
item.crop_mode_move = item.crop_handle_topleft
event = MagicMock()
event.pos.return_value = QtCore.QPointF(10, 20)
item.mouseMoveEvent(event)
assert item.crop_temp == QtCore.QRectF(5, 15, 35, 45)
event.accept.assert_called_once()
mouse_mock.assert_not_called()
@patch('beeref.selection.SelectableMixin.mouseMoveEvent')
def test_mouse_move_when_crop_mode_topright(mouse_mock, qapp, item):
pixmap = MagicMock()
pixmap.size.return_value = QtCore.QRectF(0, 0, 100, 80)
item.crop_mode = True
item.pixmap = MagicMock(return_value=pixmap)
item.crop_temp = QtCore.QRectF(10, 20, 30, 40)
item.crop_mode_event_start = QtCore.QPointF(35, 55)
item.crop_mode_move = item.crop_handle_topright
event = MagicMock()
event.pos.return_value = QtCore.QPointF(30, 50)
item.mouseMoveEvent(event)
assert item.crop_temp == QtCore.QRectF(10, 15, 25, 45)
event.accept.assert_called_once()
mouse_mock.assert_not_called()
@patch('beeref.selection.SelectableMixin.mouseMoveEvent')
def test_mouse_move_when_not_crop_mode(mouse_mock, qapp, item):
event = MagicMock()
event.pos.return_value = QtCore.QPointF(30, 50)
item.mouseMoveEvent(event)
event.accept.assert_not_called()
mouse_mock.assert_called_once_with(event)
@patch('beeref.selection.SelectableMixin.mouseReleaseEvent')
def test_mouse_release_event_when_crop_mode(mouse_mock, qapp, item):
event = MagicMock()
item.crop_mode = True
item.crop_mode_move = item.crop_handle_topright
item.crop_mode_event_start = QtCore.QPointF(44, 55)
item.mouseReleaseEvent(event)
assert item.crop_mode is True
assert item.crop_mode_move is None
assert item.crop_mode_event_start is None
event.accept.assert_called_once()
mouse_mock.assert_not_called()
@patch('beeref.selection.SelectableMixin.mouseReleaseEvent')
def test_mouse_release_event_when_not_crop_mode(mouse_mock, qapp, item):
event = MagicMock()
item.crop_mode = False
item.mouseReleaseEvent(event)
assert item.crop_mode is False
event.accept.assert_not_called()
mouse_mock.assert_called_once_with(event)

View file

@ -1,4 +1,4 @@
from unittest.mock import patch, MagicMock, PropertyMock
from unittest.mock import patch, MagicMock
from PyQt6 import QtCore, QtWidgets
from PyQt6.QtCore import Qt
@ -25,37 +25,31 @@ def test_init(selectable_mock, qapp):
def test_set_pos_center(qapp):
item = BeeTextItem('foo bar')
with patch('beeref.items.BeeTextItem.width',
new_callable=PropertyMock, return_value=200):
with patch('beeref.items.BeeTextItem.height',
new_callable=PropertyMock, return_value=100):
item.set_pos_center(QtCore.QPointF(0, 0))
assert item.pos().x() == -100
assert item.pos().y() == -50
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 200, 100)):
item.set_pos_center(QtCore.QPointF(0, 0))
assert item.pos().x() == -100
assert item.pos().y() == -50
def test_set_pos_center_when_scaled(qapp):
item = BeeTextItem('foo bar')
item.setScale(2)
with patch('beeref.items.BeeTextItem.width',
new_callable=PropertyMock, return_value=200):
with patch('beeref.items.BeeTextItem.height',
new_callable=PropertyMock, return_value=100):
item.set_pos_center(QtCore.QPointF(0, 0))
assert item.pos().x() == -200
assert item.pos().y() == -100
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 200, 100)):
item.set_pos_center(QtCore.QPointF(0, 0))
assert item.pos().x() == -200
assert item.pos().y() == -100
def test_set_pos_center_when_rotated(qapp):
item = BeeTextItem('foo bar')
item.setRotation(90)
with patch('beeref.items.BeeTextItem.width',
new_callable=PropertyMock, return_value=200):
with patch('beeref.items.BeeTextItem.height',
new_callable=PropertyMock, return_value=100):
item.set_pos_center(QtCore.QPointF(0, 0))
assert item.pos().x() == 50
assert item.pos().y() == -100
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 200, 100)):
item.set_pos_center(QtCore.QPointF(0, 0))
assert item.pos().x() == 50
assert item.pos().y() == -100
def test_get_extra_save_data(qapp):
@ -206,21 +200,26 @@ def test_create_copy(qapp):
assert item.scale() == 2.2
def test_enter_edit_mode(qapp):
def test_enter_edit_mode(view):
item = BeeTextItem('foo bar')
view.scene.addItem(item)
item.enter_edit_mode()
assert item.edit_mode is True
assert view.scene.edit_item == item
flags = item.textInteractionFlags()
assert flags == Qt.TextInteractionFlag.TextEditorInteraction
@patch('PyQt6.QtGui.QTextCursor')
@patch('beeref.items.BeeTextItem.setTextCursor')
def test_exit_edit_mode(setcursor_mock, cursor_mock, qapp):
def test_exit_edit_mode(setcursor_mock, cursor_mock, view):
item = BeeTextItem('foo bar')
item.edit_mode = True
view.scene.addItem(item)
view.scene.edit_item = item
item.exit_edit_mode()
assert item.edit_mode is False
assert view.scene.edit_item is None
flags = item.textInteractionFlags()
assert flags == Qt.TextInteractionFlag.NoTextInteraction
cursor_mock.assert_called_once_with(item.document())

View file

@ -1,4 +1,4 @@
from unittest.mock import patch, MagicMock, PropertyMock
from unittest.mock import patch, MagicMock
from PyQt6 import QtCore, QtGui
@ -118,12 +118,73 @@ def test_do_flip_vertical_anchor(qapp, item):
assert item.pos() == QtCore.QPointF(0, 200)
def test_base_item_mixin_center_scene_coords(view, item):
def test_width(view, item):
view.scene.addItem(item)
item.setPos(5, 5)
item.setScale(2)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
assert item.center_scene_coords == QtCore.QPointF(105, 85)
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
assert item.width == 100
def test_width_cropped_item(view, item):
view.scene.addItem(item)
item.setPos(5, 5)
item.setScale(2)
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(5, 5, 100, 80)):
assert item.width == 100
def test_height(view, item):
view.scene.addItem(item)
item.setPos(5, 5)
item.setScale(2)
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
assert item.height == 80
def test_height_cropped_item(view, item):
view.scene.addItem(item)
item.setPos(5, 5)
item.setScale(2)
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(5, 5, 100, 80)):
assert item.height == 80
def test_center(view, item):
view.scene.addItem(item)
item.setPos(5, 5)
item.setScale(2)
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
assert item.center == QtCore.QPointF(50, 40)
def test_center_cropped_item(view, item):
view.scene.addItem(item)
item.setPos(5, 5)
item.setScale(2)
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(10, 10, 40, 30)):
assert item.center == QtCore.QPointF(30, 25)
def test_center_scene_coords(view, item):
view.scene.addItem(item)
item.setPos(5, 5)
item.setScale(2)
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
assert item.center_scene_coords == QtCore.QPointF(105, 85)
def test_center_scene_coords_cropped_item(view, item):
view.scene.addItem(item)
item.setPos(5, 5)
item.setScale(2)
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(10, 10, 40, 30)):
assert item.center_scene_coords == QtCore.QPointF(65, 55)

View file

@ -14,18 +14,6 @@ def test_init(selectable_mock):
selectable_mock.assert_called_once()
def test_width():
item = MultiSelectItem()
item.setRect(0, 0, 50, 100)
assert item.width == 50
def test_height():
item = MultiSelectItem()
item.setRect(0, 0, 50, 100)
assert item.height == 100
def test_paint():
item = MultiSelectItem()
item.paint_selectable = MagicMock()
@ -67,8 +55,8 @@ def test_fit_selection_area():
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.width == 101
assert item.height == 81
assert item.scale() == 1
assert item.rotation() == 0
assert item.flip() == 1

View file

@ -3,18 +3,6 @@ from PyQt6 import QtCore
from beeref.selection import RubberbandItem
def test_width():
item = RubberbandItem()
item.setRect(5, 5, 100, 80)
assert item.width == 100
def test_height():
item = RubberbandItem()
item.setRect(5, 5, 100, 80)
assert item.height == 80
def test_fit_topleft_to_bottomright():
item = RubberbandItem()
item.fit(QtCore.QPointF(-10, -20), QtCore.QPointF(30, 40))

View file

@ -1,6 +1,6 @@
import math
from pytest import approx, mark
from unittest.mock import patch, MagicMock, PropertyMock
from unittest.mock import patch, MagicMock
from PyQt6 import QtCore, QtGui
from PyQt6.QtCore import Qt
@ -8,31 +8,6 @@ from PyQt6.QtCore import Qt
from beeref.assets import BeeAssets
from beeref import commands
from beeref.items import BeePixmapItem
from beeref.scene import BeeGraphicsScene
@mark.skip
class SelectableMixinBaseTestCase():
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)
def test_init_selectable(view):
@ -195,43 +170,37 @@ def test_paint_when_debug_handles(view):
def test_corners(view, item):
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
assert len(item.corners) == 4
assert QtCore.QPointF(0, 0) in item.corners
assert QtCore.QPointF(100, 0) in item.corners
assert QtCore.QPointF(0, 80) in item.corners
assert QtCore.QPointF(100, 80) in item.corners
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
assert len(item.corners) == 4
assert QtCore.QPointF(0, 0) in item.corners
assert QtCore.QPointF(100, 0) in item.corners
assert QtCore.QPointF(0, 80) in item.corners
assert QtCore.QPointF(100, 80) in item.corners
def test_corners_scene_coords_translated(view, item):
item.setPos(5, 5)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
corners = item.corners_scene_coords
assert len(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
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
corners = item.corners_scene_coords
assert len(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(view, item):
item.setScale(2)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
corners = item.corners_scene_coords
assert len(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
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
corners = item.corners_scene_coords
assert len(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(view, item):
@ -279,29 +248,47 @@ def test_rotate_bounds_topleft(view, item):
def test_get_flip_bounds(view, item):
item.SELECT_RESIZE_SIZE = 10
item.SELECT_ROTATE_SIZE = 10
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
edges = item.get_flip_bounds()
assert edges[0]['rect'].topLeft() == QtCore.QPointF(5, -5)
assert edges[0]['rect'].bottomRight() == QtCore.QPointF(95, 5)
assert edges[0]['flip_v'] is True
assert edges[1]['rect'].topLeft() == QtCore.QPointF(5, 75)
assert edges[1]['rect'].bottomRight() == QtCore.QPointF(95, 85)
assert edges[1]['flip_v'] is True
assert edges[2]['rect'].topLeft() == QtCore.QPointF(-5, 5)
assert edges[2]['rect'].bottomRight() == QtCore.QPointF(5, 75)
assert edges[2]['flip_v'] is False
assert edges[3]['rect'].topLeft() == QtCore.QPointF(95, 5)
assert edges[3]['rect'].bottomRight() == QtCore.QPointF(105, 75)
assert edges[3]['flip_v'] is False
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
edges = item.get_flip_bounds()
assert edges[0]['rect'].topLeft() == QtCore.QPointF(5, -5)
assert edges[0]['rect'].bottomRight() == QtCore.QPointF(95, 5)
assert edges[0]['flip_v'] is True
assert edges[1]['rect'].topLeft() == QtCore.QPointF(5, 75)
assert edges[1]['rect'].bottomRight() == QtCore.QPointF(95, 85)
assert edges[1]['flip_v'] is True
assert edges[2]['rect'].topLeft() == QtCore.QPointF(-5, 5)
assert edges[2]['rect'].bottomRight() == QtCore.QPointF(5, 75)
assert edges[2]['flip_v'] is False
assert edges[3]['rect'].topLeft() == QtCore.QPointF(95, 5)
assert edges[3]['rect'].bottomRight() == QtCore.QPointF(105, 75)
assert edges[3]['flip_v'] is False
def test_get_flip_bounds_cropped_item(view, item):
item.SELECT_RESIZE_SIZE = 10
item.SELECT_ROTATE_SIZE = 10
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(5, 5, 100, 80)):
edges = item.get_flip_bounds()
assert edges[0]['rect'].topLeft() == QtCore.QPointF(10, 0)
assert edges[0]['rect'].bottomRight() == QtCore.QPointF(100, 10)
assert edges[0]['flip_v'] is True
assert edges[1]['rect'].topLeft() == QtCore.QPointF(10, 80)
assert edges[1]['rect'].bottomRight() == QtCore.QPointF(100, 90)
assert edges[1]['flip_v'] is True
assert edges[2]['rect'].topLeft() == QtCore.QPointF(0, 10)
assert edges[2]['rect'].bottomRight() == QtCore.QPointF(10, 80)
assert edges[2]['flip_v'] is False
assert edges[3]['rect'].topLeft() == QtCore.QPointF(100, 10)
assert edges[3]['rect'].bottomRight() == QtCore.QPointF(110, 80)
assert edges[3]['flip_v'] is False
def test_bounding_rect_when_not_selected(view, item):
item.setSelected(False)
with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.boundingRect',
return_value=QtCore.QRectF(0, 0, 100, 80)):
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
rect = item.boundingRect()
assert rect.topLeft().x() == 0
assert rect.topLeft().y() == 0
@ -313,22 +300,20 @@ def test_bounding_rect_when_selected(view, item):
item.SELECT_RESIZE_SIZE = 10
item.SELECT_ROTATE_SIZE = 10
item.setSelected(True)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
rect = item.boundingRect()
assert rect.topLeft().x() == -15
assert rect.topLeft().y() == -15
assert rect.bottomRight().x() == 115
assert rect.bottomRight().y() == 95
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
rect = 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(view, item):
view.scene.addItem(item)
item.setSelected(False)
with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.boundingRect',
return_value=QtCore.QRectF(0, 0, 100, 80)):
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
shape = item.shape().boundingRect()
assert shape.topLeft().x() == 0
assert shape.topLeft().y() == 0
@ -344,17 +329,13 @@ def test_shape_when_selected_single(view, item):
path = QtGui.QPainterPath()
path.addRect(QtCore.QRectF(0, 0, 100, 80))
with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.shape',
return_value=path):
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
shape = item.shape().boundingRect()
assert shape.topLeft().x() == -15
assert shape.topLeft().y() == -15
assert shape.bottomRight().x() == 115
assert shape.bottomRight().y() == 95
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
shape = item.shape().boundingRect()
assert shape.topLeft().x() == -15
assert shape.topLeft().y() == -15
assert shape.bottomRight().x() == 115
assert shape.bottomRight().y() == 95
def test_shape_when_selected_multi(view, item):
@ -366,8 +347,8 @@ def test_shape_when_selected_multi(view, item):
item.SELECT_ROTATE_SIZE = 10
item.setSelected(True)
with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.boundingRect',
return_value=QtCore.QRectF(0, 0, 100, 80)):
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
shape = item.shape().boundingRect()
assert shape.topLeft().x() == 0
assert shape.topLeft().y() == 0
@ -381,11 +362,20 @@ def test_get_scale_factor_bottomright(view, item):
item.scale_orig_factor = 1
event = MagicMock()
event.scenePos.return_value = QtCore.QPointF(20, 90)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
assert item.get_scale_factor(event) == approx(1.5, 0.01)
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
assert item.get_scale_factor(event) == approx(1.5, 0.01)
def test_get_scale_factor_bottomright_cropped_item(view, item):
item.event_start = QtCore.QPointF(15, 15)
item.event_direction = QtCore.QPointF(1, 1) / math.sqrt(2)
item.scale_orig_factor = 1
event = MagicMock()
event.scenePos.return_value = QtCore.QPointF(25, 95)
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(5, 5, 100, 80)):
assert item.get_scale_factor(event) == approx(1.5, 0.01)
def test_get_scale_factor_topleft(view, item):
@ -394,115 +384,176 @@ def test_get_scale_factor_topleft(view, item):
item.scale_orig_factor = 0.5
event = MagicMock()
event.scenePos.return_value = QtCore.QPointF(-10, -60)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
assert item.get_scale_factor(event) == approx(2, 0.01)
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
assert item.get_scale_factor(event) == approx(2, 0.01)
def test_get_scale_factor_topleft_cropped_item(view, item):
item.event_start = QtCore.QPointF(15, 15)
item.event_direction = QtCore.QPointF(-1, -1) / math.sqrt(2)
item.scale_orig_factor = 0.5
event = MagicMock()
event.scenePos.return_value = QtCore.QPointF(-5, -55)
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(5, 5, 100, 80)):
assert item.get_scale_factor(event) == approx(2, 0.01)
def test_get_scale_anchor_topleft(view, item):
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
anchor = item.get_scale_anchor(QtCore.QPointF(0, 0))
assert anchor.x() == 100
assert anchor.y() == 80
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
anchor = item.get_scale_anchor(QtCore.QPointF(0, 0))
assert anchor.x() == 100
assert anchor.y() == 80
def test_get_scale_anchor_topleft_cropped_item(view, item):
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(5, 5, 100, 80)):
anchor = item.get_scale_anchor(QtCore.QPointF(5, 5))
assert anchor.x() == 105
assert anchor.y() == 85
def test_get_scale_anchor_bottomright(view, item):
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
anchor = item.get_scale_anchor(QtCore.QPointF(100, 80))
assert anchor.x() == 0
assert anchor.y() == 0
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
anchor = item.get_scale_anchor(QtCore.QPointF(100, 80))
assert anchor.x() == 0
assert anchor.y() == 0
def test_get_scale_anchor_bottomright_cropped_item(view, item):
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(5, 5, 100, 80)):
anchor = item.get_scale_anchor(QtCore.QPointF(105, 85))
assert anchor.x() == 5
assert anchor.y() == 5
def test_get_scale_anchor_topright(view, item):
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
anchor = item.get_scale_anchor(QtCore.QPointF(100, 0))
assert anchor.x() == 0
assert anchor.y() == 80
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
anchor = item.get_scale_anchor(QtCore.QPointF(100, 0))
assert anchor.x() == 0
assert anchor.y() == 80
def test_get_scale_anchor_topright_cropped_item(view, item):
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(5, 5, 100, 80)):
anchor = item.get_scale_anchor(QtCore.QPointF(105, 5))
assert anchor.x() == 5
assert anchor.y() == 85
def test_get_scale_anchor_bottomleft(view, item):
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
anchor = item.get_scale_anchor(QtCore.QPointF(0, 80))
assert anchor.x() == 100
assert anchor.y() == 0
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
anchor = item.get_scale_anchor(QtCore.QPointF(0, 80))
assert anchor.x() == 100
assert anchor.y() == 0
def test_get_scale_anchor_bottomleft_cropped_item(view, item):
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(5, 5, 100, 80)):
anchor = item.get_scale_anchor(QtCore.QPointF(5, 85))
assert anchor.x() == 105
assert anchor.y() == 5
def test_get_corner_direction_topleft(view, item):
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
assert item.get_corner_direction(
QtCore.QPointF(0, 0)) == QtCore.QPointF(-1, -1)
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
assert item.get_corner_direction(
QtCore.QPointF(0, 0)) == QtCore.QPointF(-1, -1)
def test_get_corner_direction_topleft_cropped_item(view, item):
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(5, 5, 100, 80)):
assert item.get_corner_direction(
QtCore.QPointF(5, 5)) == QtCore.QPointF(-1, -1)
def test_get_corner_direction_bottomright(view, item):
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
assert item.get_corner_direction(
QtCore.QPointF(100, 80)) == QtCore.QPointF(1, 1)
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
assert item.get_corner_direction(
QtCore.QPointF(100, 80)) == QtCore.QPointF(1, 1)
def test_get_corner_direction_bottomright_cropped_item(view, item):
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(5, 5, 100, 80)):
assert item.get_corner_direction(
QtCore.QPointF(105, 85)) == QtCore.QPointF(1, 1)
def test_get_corner_direction_topright(view, item):
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
assert item.get_corner_direction(
QtCore.QPointF(100, 0)) == QtCore.QPointF(1, -1)
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
assert item.get_corner_direction(
QtCore.QPointF(100, 0)) == QtCore.QPointF(1, -1)
def test_get_corner_direction_topright_cropped_item(view, item):
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(5, 5, 100, 80)):
assert item.get_corner_direction(
QtCore.QPointF(105, 5)) == QtCore.QPointF(1, -1)
def test_get_corner_direction_bottomleft(view, item):
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
assert item.get_corner_direction(
QtCore.QPointF(0, 80)) == QtCore.QPointF(-1, 1)
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
assert item.get_corner_direction(
QtCore.QPointF(0, 80)) == QtCore.QPointF(-1, 1)
def test_get_direction_from_center_bottomright(view, item):
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
direction = item.get_direction_from_center(QtCore.QPointF(100, 90))
assert direction == approx(QtCore.QPointF(1, 1) / math.sqrt(2))
def test_get_corner_direction_bottomleft_cropped_item(view, item):
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(5, 5, 100, 80)):
assert item.get_corner_direction(
QtCore.QPointF(5, 85)) == QtCore.QPointF(-1, 1)
def test_get_direction_from_center_topleft(view, item):
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
direction = item.get_direction_from_center(QtCore.QPointF(0, -10))
assert direction == approx(QtCore.QPointF(-1, -1) / math.sqrt(2))
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
direction = item.get_direction_from_center(QtCore.QPointF(0, -10))
assert direction == approx(QtCore.QPointF(-1, -1) / math.sqrt(2))
def test_get_direction_from_center_topleft_cropped_item(view, item):
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(5, 5, 100, 80)):
direction = item.get_direction_from_center(QtCore.QPointF(5, -5))
assert direction == approx(QtCore.QPointF(-1, -1) / math.sqrt(2))
def test_get_direction_from_center_bottomright(view, item):
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
direction = item.get_direction_from_center(QtCore.QPointF(100, 90))
assert direction == approx(QtCore.QPointF(1, 1) / math.sqrt(2))
def test_get_direction_from_center_bottomright_cropped_item(view, item):
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(5, 5, 100, 80)):
direction = item.get_direction_from_center(QtCore.QPointF(105, 95))
assert direction == approx(QtCore.QPointF(1, 1) / math.sqrt(2))
def test_get_direction_from_center_bottomright_when_rotated_180(view, item):
item.setRotation(180, QtCore.QPointF(50, 40))
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
direction = item.get_direction_from_center(QtCore.QPointF(100, 90))
assert direction == approx(QtCore.QPointF(1, 1) / math.sqrt(2))
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
direction = item.get_direction_from_center(QtCore.QPointF(100, 90))
assert direction == approx(QtCore.QPointF(1, 1) / math.sqrt(2))
def test_get_rotate_angle(view, item):
@ -548,12 +599,10 @@ def test_hover_move_event_no_selection(view, item):
event = MagicMock()
event.pos.return_value = QtCore.QPointF(0, 0)
item.setCursor = MagicMock()
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
item.hoverMoveEvent(event)
item.setCursor.assert_not_called()
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
item.hoverMoveEvent(event)
item.setCursor.assert_not_called()
@mark.parametrize('pos,flipped,rotation, expected',
@ -575,7 +624,7 @@ def test_hover_move_event_no_selection(view, item):
((0, 0), True, 45, 'SizeVerCursor'),
((0, 0), True, 90, 'SizeFDiagCursor'),
((0, 0), True, 135, 'SizeHorCursor')])
def test_hover_move_event_topleft_scale(
def test_hover_move_event_scale(
pos, flipped, rotation, expected, view, item):
view.scene.addItem(item)
item.setSelected(True)
@ -584,25 +633,21 @@ def test_hover_move_event_topleft_scale(
item.setRotation(rotation)
event = MagicMock()
event.pos.return_value = QtCore.QPointF(*pos)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
item.hoverMoveEvent(event)
assert item.cursor() == getattr(Qt.CursorShape, expected)
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
item.hoverMoveEvent(event)
assert item.cursor() == getattr(Qt.CursorShape, expected)
def test_hover_move_event_bottomright_scale_very_wide_item(view, item):
def test_hover_move_event_scale_bottomright_very_wide_item(view, item):
view.scene.addItem(item)
item.setSelected(True)
event = MagicMock()
event.pos.return_value = QtCore.QPointF(1000, 100)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=1000):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=100):
item.hoverMoveEvent(event)
assert item.cursor() == Qt.CursorShape.SizeFDiagCursor
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 1000, 100)):
item.hoverMoveEvent(event)
assert item.cursor() == Qt.CursorShape.SizeFDiagCursor
def test_hover_move_event_rotate(view, item):
@ -610,12 +655,10 @@ def test_hover_move_event_rotate(view, item):
item.setSelected(True)
event = MagicMock()
event.pos.return_value = QtCore.QPointF(115, 95)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
item.hoverMoveEvent(event)
assert item.cursor() == BeeAssets().cursor_rotate
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
item.hoverMoveEvent(event)
assert item.cursor() == BeeAssets().cursor_rotate
def test_hover_flip_event_top_edge(view, item):
@ -623,12 +666,10 @@ def test_hover_flip_event_top_edge(view, item):
item.setSelected(True)
event = MagicMock()
event.pos.return_value = QtCore.QPointF(50, 0)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
item.hoverMoveEvent(event)
assert item.cursor() == BeeAssets().cursor_flip_v
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
item.hoverMoveEvent(event)
assert item.cursor() == BeeAssets().cursor_flip_v
def test_hover_flip_event_bottom_edge(view, item):
@ -636,12 +677,10 @@ def test_hover_flip_event_bottom_edge(view, item):
item.setSelected(True)
event = MagicMock()
event.pos.return_value = QtCore.QPointF(50, 80)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
item.hoverMoveEvent(event)
assert item.cursor() == BeeAssets().cursor_flip_v
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
item.hoverMoveEvent(event)
assert item.cursor() == BeeAssets().cursor_flip_v
def test_hover_flip_event_left_edge(view, item):
@ -649,12 +688,10 @@ def test_hover_flip_event_left_edge(view, item):
item.setSelected(True)
event = MagicMock()
event.pos.return_value = QtCore.QPointF(0, 50)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
item.hoverMoveEvent(event)
assert item.cursor() == BeeAssets().cursor_flip_h
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
item.hoverMoveEvent(event)
assert item.cursor() == BeeAssets().cursor_flip_h
def test_hover_flip_event_right_edge(view, item):
@ -662,12 +699,10 @@ def test_hover_flip_event_right_edge(view, item):
item.setSelected(True)
event = MagicMock()
event.pos.return_value = QtCore.QPointF(100, 50)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
item.hoverMoveEvent(event)
assert item.cursor() == BeeAssets().cursor_flip_h
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
item.hoverMoveEvent(event)
assert item.cursor() == BeeAssets().cursor_flip_h
def test_hover_flip_event_top_edge_rotated_90(view, item):
@ -676,12 +711,10 @@ def test_hover_flip_event_top_edge_rotated_90(view, item):
item.setRotation(90)
event = MagicMock()
event.pos.return_value = QtCore.QPointF(50, 0)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
item.hoverMoveEvent(event)
assert item.cursor() == BeeAssets().cursor_flip_h
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
item.hoverMoveEvent(event)
assert item.cursor() == BeeAssets().cursor_flip_h
def test_hover_flip_event_left_edge_when_rotated_90(view, item):
@ -690,12 +723,10 @@ def test_hover_flip_event_left_edge_when_rotated_90(view, item):
item.setSelected(True)
item.setRotation(90)
event.pos.return_value = QtCore.QPointF(0, 50)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
item.hoverMoveEvent(event)
assert item.cursor() == BeeAssets().cursor_flip_v
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
item.hoverMoveEvent(event)
assert item.cursor() == BeeAssets().cursor_flip_v
def test_hover_move_event_not_in_handles(view, item):
@ -703,12 +734,10 @@ def test_hover_move_event_not_in_handles(view, item):
item.setSelected(True)
event = MagicMock()
event.pos.return_value = QtCore.QPointF(50, 50)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
item.hoverMoveEvent(event)
assert item.cursor() == Qt.CursorShape.ArrowCursor
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
item.hoverMoveEvent(event)
assert item.cursor() == Qt.CursorShape.ArrowCursor
def test_hover_enter_event_when_selected(view, item):
@ -772,17 +801,15 @@ def test_mouse_press_event_bottomright_scale(view, item):
event.pos.return_value = QtCore.QPointF(99, 79)
event.scenePos.return_value = QtCore.QPointF(101, 81)
event.button.return_value = Qt.MouseButton.LeftButton
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
item.mousePressEvent(event)
assert item.scale_active is True
assert item.event_start == QtCore.QPointF(101, 81)
assert item.event_direction.x() > 0
assert item.event_direction.y() > 0
assert item.scale_orig_factor == 1
event.accept.assert_called_once_with()
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
item.mousePressEvent(event)
assert item.scale_active is True
assert item.event_start == QtCore.QPointF(101, 81)
assert item.event_direction.x() > 0
assert item.event_direction.y() > 0
assert item.scale_orig_factor == 1
event.accept.assert_called_once_with()
def test_mouse_press_event_rotate(view, item):
@ -792,10 +819,9 @@ def test_mouse_press_event_rotate(view, item):
event.pos.return_value = QtCore.QPointF(111, 91)
event.scenePos.return_value = QtCore.QPointF(66, 99)
event.button.return_value = Qt.MouseButton.LeftButton
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mousePressEvent'):
item.mousePressEvent(event)
assert item.rotate_active is True
assert item.event_anchor == QtCore.QPointF(50, 40)
@ -810,12 +836,10 @@ def test_mouse_press_event_flip(view, item):
event.pos.return_value = QtCore.QPointF(0, 40)
event.button.return_value = Qt.MouseButton.LeftButton
view.scene.undo_stack = MagicMock(push=MagicMock())
with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mousePressEvent'):
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
item.mousePressEvent(event)
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mousePressEvent'):
item.mousePressEvent(event)
args = view.scene.undo_stack.push.call_args_list[0][0]
cmd = args[0]
isinstance(cmd, commands.FlipItems)
@ -893,14 +917,12 @@ def test_mouse_move_event_when_scale_action(view, item):
item.scale_orig_factor = 1
with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mouseMoveEvent') as m:
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
item.mouseMoveEvent(event)
m.assert_not_called()
assert item.scale() == approx(1.5, 0.01)
event.accept.assert_called_once_with()
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
item.mouseMoveEvent(event)
m.assert_not_called()
assert item.scale() == approx(1.5, 0.01)
event.accept.assert_called_once_with()
def test_mouse_move_event_when_rotate_action(view, item):
@ -955,21 +977,19 @@ def test_mouse_release_event_when_scale_action(view, item):
item.scale_orig_factor = 1
view.scene.undo_stack = MagicMock(push=MagicMock())
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
item.mouseReleaseEvent(event)
view.scene.undo_stack.push.assert_called_once()
args = view.scene.undo_stack.push.call_args_list[0][0]
cmd = args[0]
isinstance(cmd, commands.ScaleItemsBy)
assert cmd.items == [item]
assert cmd.factor == approx(1.5, 0.01)
assert cmd.anchor == QtCore.QPointF(100, 80)
assert cmd.ignore_first_redo is True
assert item.scale_active is False
event.accept.assert_called_once_with()
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
item.mouseReleaseEvent(event)
view.scene.undo_stack.push.assert_called_once()
args = view.scene.undo_stack.push.call_args_list[0][0]
cmd = args[0]
isinstance(cmd, commands.ScaleItemsBy)
assert cmd.items == [item]
assert cmd.factor == approx(1.5, 0.01)
assert cmd.anchor == QtCore.QPointF(100, 80)
assert cmd.ignore_first_redo is True
assert item.scale_active is False
event.accept.assert_called_once_with()
def test_mouse_release_event_when_scale_action_zero(view, item):
@ -983,14 +1003,12 @@ def test_mouse_release_event_when_scale_action_zero(view, item):
item.scale_orig_factor = 1
view.scene.undo_stack = MagicMock(push=MagicMock())
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
item.mouseReleaseEvent(event)
view.scene.undo_stack.push.assert_not_called()
assert item.scale_active is False
event.accept.assert_called_once_with()
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
item.mouseReleaseEvent(event)
view.scene.undo_stack.push.assert_not_called()
assert item.scale_active is False
event.accept.assert_called_once_with()
def test_mouse_release_event_when_rotate_action(view, item):
@ -1039,11 +1057,9 @@ def test_mouse_release_event_when_flip_action(view, item):
item.flip_active = True
view.scene.undo_stack = MagicMock(push=MagicMock())
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
item.mouseReleaseEvent(event)
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
item.mouseReleaseEvent(event)
view.scene.undo_stack.push.assert_not_called()
assert item.flip_active is False
event.accept.assert_called_once_with()

View file

@ -1,9 +1,9 @@
from unittest.mock import MagicMock, patch, PropertyMock
from unittest.mock import MagicMock, patch
from PyQt6 import QtCore, QtGui
from beeref import commands
from beeref.items import BeePixmapItem
from beeref.items import BeePixmapItem, BeeTextItem
def test_insert_items(view):
@ -36,10 +36,10 @@ def test_insert_items_with_position(view):
item2.setPos(50, 40)
view.scene.addItem(item2)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
with patch.object(item1, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
with patch.object(item2, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
command = commands.InsertItems(
view.scene, [item1, item2], QtCore.QPointF(100, 200))
command.redo()
@ -231,10 +231,10 @@ def test_normalize_items(qapp):
item1.setScale(1)
item2 = BeePixmapItem(QtGui.QImage())
item2.setScale(3)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
with patch.object(item1, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
with patch.object(item2, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
command = commands.NormalizeItems([item1, item2], [2, 0.5])
command.redo()
assert item1.scale() == 2
@ -304,46 +304,42 @@ def test_flip_items_vertical(qapp):
def test_reset_scale(view, item):
item.setScale(2)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
command = commands.ResetScale([item])
command.redo()
assert item.scale() == 1
assert item.pos().x() == 50
assert item.pos().y() == 40
command.undo()
assert item.scale() == 2
assert item.pos().x() == 0
assert item.pos().y() == 0
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
command = commands.ResetScale([item])
command.redo()
assert item.scale() == 1
assert item.pos().x() == 50
assert item.pos().y() == 40
command.undo()
assert item.scale() == 2
assert item.pos().x() == 0
assert item.pos().y() == 0
def test_reset_rotate(view, item):
item.setRotation(180)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
command = commands.ResetRotation([item])
command.redo()
assert item.rotation() == 0
assert item.pos().x() == -100
assert item.pos().y() == -80
command.undo()
assert item.rotation() == 180
assert item.pos().x() == 0
assert item.pos().y() == 0
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
command = commands.ResetRotation([item])
command.redo()
assert item.rotation() == 0
assert item.pos().x() == -100
assert item.pos().y() == -80
command.undo()
assert item.rotation() == 180
assert item.pos().x() == 0
assert item.pos().y() == 0
def test_reset_flip(qapp):
item1 = BeePixmapItem(QtGui.QImage())
item1.do_flip()
item2 = BeePixmapItem(QtGui.QImage())
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
with patch.object(item1, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
with patch.object(item2, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
command = commands.ResetFlip([item1, item2])
command.redo()
assert item1.flip() == 1
@ -361,17 +357,53 @@ def test_reset_flip(qapp):
assert item2.pos().y() == 0
def test_reset_crop(qapp):
item1 = BeePixmapItem(QtGui.QImage())
item1.crop = QtCore.QRectF(10, 20, 30, 50)
item2 = BeePixmapItem(QtGui.QImage())
item2.crop = QtCore.QRectF(5, 6, 55, 66)
with patch.object(BeePixmapItem, 'reset_crop'):
command = commands.ResetCrop([item1, item2])
command.redo()
assert BeePixmapItem.reset_crop.call_count == 2
assert item1.pos() == QtCore.QPointF(0, 0)
assert item2.pos() == QtCore.QPointF(0, 0)
item1.crop = QtCore.QRectF(0, 0, 0, 0)
item2.crop = QtCore.QRectF(0, 0, 0, 0)
command.undo()
assert item1.crop == QtCore.QRectF(10, 20, 30, 50)
assert item1.pos() == QtCore.QPointF(0, 0)
assert item2.crop == QtCore.QRectF(5, 6, 55, 66)
assert item2.pos() == QtCore.QPointF(0, 0)
def test_reset_crop_ignores_uncroppable(qapp):
item = BeeTextItem('foo')
brect = item.boundingRect()
command = commands.ResetCrop([item])
command.redo()
assert item.pos() == QtCore.QPointF(0, 0)
assert item.boundingRect() == brect
command.undo()
assert item.pos() == QtCore.QPointF(0, 0)
assert item.boundingRect() == brect
def test_reset_transforms(qapp):
item1 = BeePixmapItem(QtGui.QImage())
item1.setScale(2)
item1.do_flip()
item2 = BeePixmapItem(QtGui.QImage())
item2 = BeeTextItem('foo')
item2.setRotation(180)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
command = commands.ResetTransforms([item1, item2])
item3 = BeePixmapItem(QtGui.QImage())
item3.crop = QtCore.QRectF(10, 20, 30, 40)
item3.reset_crop = MagicMock()
with patch.object(item1, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
with patch.object(item2, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
command = commands.ResetTransforms([item1, item2, item3])
command.redo()
assert item1.scale() == 1
assert item1.rotation() == 0
@ -383,6 +415,9 @@ def test_reset_transforms(qapp):
assert item2.flip() == 1
assert item2.pos().x() == -100
assert item2.pos().y() == -80
item3.reset_crop.assert_called_once_with()
item3.crop = QtCore.QRectF(0, 0, 0, 0)
command.undo()
assert item1.scale() == 2
assert item1.rotation() == 0
@ -394,6 +429,7 @@ def test_reset_transforms(qapp):
assert item2.flip() == 1
assert item2.pos().x() == 0
assert item2.pos().y() == 0
assert item3.crop == QtCore.QRectF(10, 20, 30, 40)
def test_arrange_items(view):
@ -403,18 +439,38 @@ def test_arrange_items(view):
item2 = BeePixmapItem(QtGui.QImage())
item2.setRotation(90)
view.scene.addItem(item2)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
command = commands.ArrangeItems(
view.scene,
[item1, item2],
[QtCore.QPointF(1, 2), QtCore.QPointF(203, 204)])
item3 = BeePixmapItem(QtGui.QImage())
view.scene.addItem(item3)
with patch.object(item1, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
with patch.object(item2, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
with patch.object(item3, 'bounding_rect_unselected',
return_value=QtCore.QRectF(5, 5, 20, 30)):
command.redo()
assert item1.pos() == QtCore.QPointF(101, 2)
assert item2.pos() == QtCore.QPointF(283, 204)
command.undo()
assert item1.pos() == QtCore.QPointF(0, 0)
assert item2.pos() == QtCore.QPointF(0, 0)
command = commands.ArrangeItems(
view.scene,
[item1, item2, item3],
[QtCore.QPointF(1, 2),
QtCore.QPointF(203, 204),
QtCore.QPointF(307, 308)])
command.redo()
assert item1.pos() == QtCore.QPointF(101, 2)
assert item2.pos() == QtCore.QPointF(283, 204)
assert item3.pos() == QtCore.QPointF(302, 303)
command.undo()
assert item1.pos() == QtCore.QPointF(0, 0)
assert item2.pos() == QtCore.QPointF(0, 0)
assert item3.pos() == QtCore.QPointF(0, 0)
def test_crop_item(item):
item.crop = QtCore.QRectF(0, 0, 100, 80)
command = commands.CropItem(item, QtCore.QRectF(10, 20, 30, 40))
command.redo()
assert item.crop == QtCore.QRectF(10, 20, 30, 40)
assert item.pos() == QtCore.QPointF(0, 0)
command.undo()
assert item.crop == QtCore.QRectF(0, 0, 100, 80)
assert item.pos() == QtCore.QPointF(0, 0)

View file

@ -1,5 +1,5 @@
import math
from unittest.mock import patch, MagicMock, PropertyMock
from unittest.mock import patch, MagicMock
from pytest import approx
@ -17,6 +17,17 @@ def test_add_remove_item(view, item):
assert view.scene.items() == []
def test_cancel_crop_mode_when_crop(view, item):
view.scene.crop_item = item
item.exit_crop_mode = MagicMock()
view.scene.cancel_crop_mode()
item.exit_crop_mode.assert_called_once_with(confirm=False)
def test_cancel_crop_mode_when_no_crop(view, item):
view.scene.cancel_crop_mode()
def test_copy_selection_to_internal_clipboard(view):
item1 = BeePixmapItem(QtGui.QImage())
view.scene.addItem(item1)
@ -60,11 +71,13 @@ def test_raise_to_top(view):
item3 = BeePixmapItem(QtGui.QImage())
view.scene.addItem(item3)
item3.setZValue(0.07)
view.scene.cancel_crop_mode = MagicMock()
view.scene.raise_to_top()
assert item1.zValue() == 0.11 + view.scene.Z_STEP
assert item2.zValue() == 0.07 + view.scene.Z_STEP
assert item3.zValue() == 0.07
view.scene.cancel_crop_mode.assert_called_once_with()
def test_lower_to_bottom(view):
@ -79,11 +92,13 @@ def test_lower_to_bottom(view):
item3 = BeePixmapItem(QtGui.QImage())
view.scene.addItem(item3)
item3.setZValue(-0.07)
view.scene.cancel_crop_mode = MagicMock()
view.scene.lower_to_bottom()
assert item1.zValue() == -0.11 - view.scene.Z_STEP
assert item2.zValue() == -0.07 - view.scene.Z_STEP
assert item3.zValue() == -0.07
view.scene.cancel_crop_mode.assert_called_once_with()
def test_normalize_height(view):
@ -94,17 +109,19 @@ def test_normalize_height(view):
view.scene.addItem(item2)
item2.setSelected(True)
item2.setScale(3)
view.scene.cancel_crop_mode = MagicMock()
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
with patch.object(item1, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
with patch.object(item2, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
view.scene.normalize_height()
assert item1.scale() == 2
assert item1.pos() == QtCore.QPointF(-50, -40)
assert item2.scale() == 2
assert item2.pos() == QtCore.QPointF(50, 40)
view.scene.cancel_crop_mode.assert_called_once_with()
def test_normalize_height_with_rotation(view):
@ -115,19 +132,23 @@ def test_normalize_height_with_rotation(view):
view.scene.addItem(item2)
item2.setSelected(True)
item2.setRotation(90)
view.scene.cancel_crop_mode = MagicMock()
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=200):
with patch.object(item1, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 200)):
with patch.object(item2, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 200)):
view.scene.normalize_height()
assert item1.scale() == 0.75
assert item2.scale() == 1.5
view.scene.cancel_crop_mode.assert_called_once_with()
def test_normalize_height_when_no_items(view):
view.scene.cancel_crop_mode = MagicMock()
view.scene.normalize_height()
view.scene.cancel_crop_mode.assert_called_once_with()
def test_normalize_width(view):
@ -138,17 +159,19 @@ def test_normalize_width(view):
view.scene.addItem(item2)
item2.setSelected(True)
item2.setScale(3)
view.scene.cancel_crop_mode = MagicMock()
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=80):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=100):
with patch.object(item1, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 80, 100)):
with patch.object(item2, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 80, 100)):
view.scene.normalize_width()
assert item1.scale() == 2
assert item1.pos() == QtCore.QPointF(-40, -50)
assert item2.scale() == 2
assert item2.pos() == QtCore.QPointF(40, 50)
view.scene.cancel_crop_mode.assert_called_once_with()
def test_normalize_width_with_rotation(view):
@ -159,19 +182,23 @@ def test_normalize_width_with_rotation(view):
view.scene.addItem(item2)
item2.setSelected(True)
item2.setRotation(90)
view.scene.cancel_crop_mode = MagicMock()
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=200):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=100):
with patch.object(item1, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 200, 100)):
with patch.object(item2, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 200, 100)):
view.scene.normalize_height()
assert item1.scale() == 1.5
assert item2.scale() == 0.75
view.scene.cancel_crop_mode.assert_called_once_with()
def test_normalize_width_when_no_items(view):
view.scene.cancel_crop_mode = MagicMock()
view.scene.normalize_width()
view.scene.cancel_crop_mode.assert_called_once_with()
def test_normalize_size(view):
@ -182,15 +209,17 @@ def test_normalize_size(view):
view.scene.addItem(item2)
item2.setSelected(True)
item2.setScale(2)
view.scene.cancel_crop_mode = MagicMock()
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=100):
with patch.object(item1, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 100)):
with patch.object(item2, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 100)):
view.scene.normalize_size()
assert item1.scale() == approx(math.sqrt(2.5))
assert item2.scale() == approx(math.sqrt(2.5))
view.scene.cancel_crop_mode.assert_called_once_with()
def test_normalize_size_with_rotation(view):
@ -201,19 +230,23 @@ def test_normalize_size_with_rotation(view):
view.scene.addItem(item2)
item2.setSelected(True)
item2.setRotation(90)
view.scene.cancel_crop_mode = MagicMock()
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=200):
with patch.object(item1, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 200)):
with patch.object(item2, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 200)):
view.scene.normalize_size()
assert item1.scale() == 1
assert item2.scale() == 1
view.scene.cancel_crop_mode.assert_called_once_with()
def test_normalize_size_when_no_items(view):
view.scene.cancel_crop_mode = MagicMock()
view.scene.normalize_size()
view.scene.cancel_crop_mode.assert_called_once_with()
def test_arrange_horizontal(view):
@ -225,15 +258,17 @@ def test_arrange_horizontal(view):
view.scene.addItem(item2)
item2.setSelected(True)
item2.setPos(-10, 40)
view.scene.cancel_crop_mode = MagicMock()
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
with patch.object(item1, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
with patch.object(item2, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
view.scene.arrange()
assert item2.pos() == QtCore.QPointF(-50, -30)
assert item1.pos() == QtCore.QPointF(50, -30)
view.scene.cancel_crop_mode.assert_called_once_with()
def test_arrange_vertical(view):
@ -245,15 +280,17 @@ def test_arrange_vertical(view):
view.scene.addItem(item2)
item2.setSelected(True)
item2.setPos(-10, 40)
view.scene.cancel_crop_mode = MagicMock()
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
with patch.object(item1, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
with patch.object(item2, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
view.scene.arrange(vertical=True)
assert item1.pos() == QtCore.QPointF(0, -70)
assert item2.pos() == QtCore.QPointF(0, 10)
view.scene.cancel_crop_mode = MagicMock()
def test_arrange_when_rotated(view):
@ -267,19 +304,23 @@ def test_arrange_when_rotated(view):
item2.setSelected(True)
item2.setPos(-10, 40)
item2.setRotation(90)
view.scene.cancel_crop_mode = MagicMock()
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
with patch.object(item1, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
with patch.object(item2, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 80)):
view.scene.arrange()
assert item2.pos() == QtCore.QPointF(-40, -30)
assert item1.pos() == QtCore.QPointF(40, -30)
view.scene.cancel_crop_mode.assert_called_once_with()
def test_arrange_when_no_items(view):
view.scene.cancel_crop_mode = MagicMock()
view.scene.arrange()
view.scene.cancel_crop_mode.assert_called_once_with()
def test_arrange_optimal(view):
@ -287,17 +328,16 @@ def test_arrange_optimal(view):
item = BeePixmapItem(QtGui.QImage())
view.scene.addItem(item)
item.setSelected(True)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
view.scene.arrange_optimal()
item.crop = QtCore.QRectF(0, 0, 100, 80)
view.scene.cancel_crop_mode = MagicMock()
view.scene.arrange_optimal()
expected_positions = {(-50, -40), (50, -40), (-50, 40), (50, 40)}
actual_positions = {
(i.pos().x(), i.pos().y())
for i in view.scene.selectedItems(user_only=True)}
assert expected_positions == actual_positions
view.scene.cancel_crop_mode.assert_called_once_with()
def test_arrange_optimal_when_rotated(view):
@ -306,27 +346,30 @@ def test_arrange_optimal_when_rotated(view):
view.scene.addItem(item)
item.setRotation(90)
item.setSelected(True)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=80):
view.scene.arrange_optimal()
item.crop = QtCore.QRectF(0, 0, 100, 80)
view.scene.cancel_crop_mode = MagicMock()
view.scene.arrange_optimal()
expected_positions = {(-40, -50), (40, -50), (-40, 50), (40, 50)}
actual_positions = {
(i.pos().x(), i.pos().y())
for i in view.scene.selectedItems(user_only=True)}
assert expected_positions == actual_positions
view.scene.cancel_crop_mode.assert_called_once_with()
def test_arrange_optimal_when_no_items(view):
view.scene.cancel_crop_mode = MagicMock()
view.scene.arrange_optimal()
view.scene.cancel_crop_mode.assert_called_once_with()
def test_flip_items(view, item):
view.scene.addItem(item)
item.setSelected(True)
view.scene.undo_stack = MagicMock(push=MagicMock())
view.scene.cancel_crop_mode = MagicMock()
with patch('beeref.scene.BeeGraphicsScene.itemsBoundingRect',
return_value=QtCore.QRectF(10, 20, 100, 60)):
view.scene.flip_items(vertical=True)
@ -336,32 +379,86 @@ def test_flip_items(view, item):
assert cmd.items == [item]
assert cmd.anchor == QtCore.QPointF(60, 50)
assert cmd.vertical is True
view.scene.cancel_crop_mode.assert_called_once_with()
def test_set_selection_all_items_when_true(view):
def test_crop_items(view, item):
view.scene.addItem(item)
item.setSelected(True)
item.enter_crop_mode = MagicMock()
view.scene.crop_items()
item.enter_crop_mode.assert_called_once_with()
def test_crop_items_when_in_crop_mode(view, item):
view.scene.addItem(item)
item.setSelected(True)
item.enter_crop_mode = MagicMock()
view.scene.crop_item = item
view.scene.crop_items()
item.enter_crop_mode.assert_not_called()
def test_crop_item_multi_select(view, item):
view.scene.addItem(item)
item.setSelected(True)
item.enter_crop_mode = MagicMock()
item2 = BeePixmapItem(QtGui.QImage())
view.scene.addItem(item2)
item2.setSelected(True)
view.scene.crop_items()
item.enter_crop_mode.assert_not_called()
def test_crop_item_no_selection(view, item):
view.scene.addItem(item)
item.setSelected(False)
item.enter_crop_mode = MagicMock()
view.scene.crop_items()
item.enter_crop_mode.assert_not_called()
def test_crop_item_when_not_croppable(view):
item = BeeTextItem('foo')
item.setSelected(True)
item.enter_crop_mode = MagicMock()
view.scene.crop_items()
item.enter_crop_mode.assert_not_called()
def test_set_selected_all_items_when_true(view):
item1 = BeePixmapItem(QtGui.QImage())
view.scene.addItem(item1)
item1.setSelected(True)
item2 = BeePixmapItem(QtGui.QImage())
view.scene.addItem(item2)
item2.setSelected(True)
view.scene.cancel_crop_mode = MagicMock()
view.scene.set_selected_all_items(True)
assert item1.isSelected() is True
assert item2.isSelected() is True
view.scene.cancel_crop_mode.assert_called_once_with()
def test_set_selection_all_items_when_false(view):
def test_set_selected_all_items_when_false(view):
item1 = BeePixmapItem(QtGui.QImage())
view.scene.addItem(item1)
item1.setSelected(True)
item2 = BeePixmapItem(QtGui.QImage())
view.scene.addItem(item2)
item2.setSelected(True)
view.scene.cancel_crop_mode = MagicMock()
view.scene.set_selected_all_items(False)
assert item1.isSelected() is False
assert item2.isSelected() is False
view.scene.cancel_crop_mode.assert_called_once_with()
def test_has_selection_when_no_selection(view, item):
@ -423,6 +520,34 @@ def test_has_multi_selection_when_multi_selection(view):
assert view.scene.has_multi_selection() is True
def test_has_croppable_selection(view, item):
view.scene.addItem(item)
item.setSelected(True)
assert view.scene.has_croppable_selection() is True
def test_has_croppable_selection_when_item_not_croppable(view):
item = BeeTextItem('foo')
view.scene.addItem(item)
item.setSelected(True)
assert view.scene.has_croppable_selection() is False
def test_has_croppable_selection_when_no_selection(view, item):
view.scene.addItem(item)
item.setSelected(False)
assert view.scene.has_croppable_selection() is False
def test_has_croppable_selection_when_multi_selection(view, item):
view.scene.addItem(item)
item.setSelected(True)
item2 = BeePixmapItem(QtGui.QImage())
view.scene.addItem(item2)
item2.setSelected(True)
assert view.scene.has_croppable_selection() is False
@patch('PyQt6.QtWidgets.QGraphicsScene.mousePressEvent')
def test_mouse_press_event_when_right_click(mouse_mock, view):
event = MagicMock(
@ -461,7 +586,6 @@ def test_mouse_press_event_when_left_click_over_item_in_edit_mode(
view.scene.mousePressEvent(event)
event.accept.assert_not_called()
mouse_mock.assert_called_once_with(event)
assert view.scene.edit_item == item
item.exit_edit_mode.assert_not_called()
assert view.scene.move_active is False
assert view.scene.rubberband_active is False
@ -481,7 +605,6 @@ def test_mouse_press_event_when_left_click_over_diff_item_in_edit_mode(
view.scene.mousePressEvent(event)
event.accept.assert_not_called()
mouse_mock.assert_called_once_with(event)
assert view.scene.edit_item is None
txtitem.exit_edit_mode.assert_called_once_with()
assert view.scene.move_active is True
assert view.scene.rubberband_active is False
@ -501,12 +624,66 @@ def test_mouse_press_event_when_left_click_over_no_item_in_edit_mode(
view.scene.mousePressEvent(event)
event.accept.assert_not_called()
mouse_mock.assert_called_once_with(event)
assert view.scene.edit_item is None
item.exit_edit_mode.assert_called_once_with()
assert view.scene.move_active is False
assert view.scene.rubberband_active is True
@patch('PyQt6.QtWidgets.QGraphicsScene.mousePressEvent')
def test_mouse_press_event_when_left_click_over_item_in_crop_mode(
mouse_mock, view, item):
view.scene.addItem(item)
view.scene.cancel_crop_mode = MagicMock()
view.scene.crop_item = item
view.scene.itemAt = MagicMock(return_value=item)
event = MagicMock(
button=MagicMock(return_value=Qt.MouseButton.LeftButton),
)
view.scene.mousePressEvent(event)
event.accept.assert_not_called()
mouse_mock.assert_called_once_with(event)
view.scene.cancel_crop_mode.assert_not_called()
assert view.scene.move_active is False
assert view.scene.rubberband_active is False
@patch('PyQt6.QtWidgets.QGraphicsScene.mousePressEvent')
def test_mouse_press_event_when_left_click_over_diff_item_in_crop_mode(
mouse_mock, view, item):
view.scene.addItem(item)
view.scene.cancel_crop_mode = MagicMock()
view.scene.crop_item = item
other_item = BeePixmapItem(QtGui.QImage())
view.scene.itemAt = MagicMock(return_value=other_item)
event = MagicMock(
button=MagicMock(return_value=Qt.MouseButton.LeftButton),
)
view.scene.mousePressEvent(event)
event.accept.assert_not_called()
mouse_mock.assert_called_once_with(event)
view.scene.cancel_crop_mode.assert_called_once_with()
assert view.scene.move_active is True
assert view.scene.rubberband_active is False
@patch('PyQt6.QtWidgets.QGraphicsScene.mousePressEvent')
def test_mouse_press_event_when_left_click_over_no_item_in_crop_mode(
mouse_mock, view, item):
view.scene.addItem(item)
view.scene.cancel_crop_mode = MagicMock()
view.scene.crop_item = item
view.scene.itemAt = MagicMock(return_value=None)
event = MagicMock(
button=MagicMock(return_value=Qt.MouseButton.LeftButton),
)
view.scene.mousePressEvent(event)
event.accept.assert_not_called()
mouse_mock.assert_called_once_with(event)
view.scene.cancel_crop_mode.assert_called_once_with()
assert view.scene.move_active is False
assert view.scene.rubberband_active is True
@patch('PyQt6.QtWidgets.QGraphicsScene.mousePressEvent')
def test_mouse_press_event_when_left_click_not_over_item(
mouse_mock, view, item):
@ -549,11 +726,9 @@ def test_mouse_doubleclick_event_when_over_item(mouse_mock, view, item):
view.scene.itemAt = MagicMock(return_value=item)
view.fit_rect = MagicMock()
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=100):
view.scene.mouseDoubleClickEvent(event)
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 100)):
view.scene.mouseDoubleClickEvent(event)
assert view.scene.move_active is False
view.fit_rect.assert_called_once_with(
@ -575,15 +750,12 @@ def test_mouse_doubleclick_event_when_over_editable_item(
view.scene.itemAt = MagicMock(return_value=item)
view.fit_rect = MagicMock()
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=100):
view.scene.mouseDoubleClickEvent(event)
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 100)):
view.scene.mouseDoubleClickEvent(event)
assert view.scene.move_active is False
item.enter_edit_mode.assert_called_once_with()
view.scene.edit_item == item
double_mock.assert_not_called()
press_mock.assert_called_once_with(event)
@ -599,11 +771,9 @@ def test_mouse_doubleclick_event_when_item_not_selected(
view.scene.itemAt = MagicMock(return_value=item)
view.fit_rect = MagicMock()
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=100):
view.scene.mouseDoubleClickEvent(event)
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 100)):
view.scene.mouseDoubleClickEvent(event)
assert view.scene.move_active is False
view.fit_rect.assert_called_once_with(
@ -863,10 +1033,10 @@ def test_items_bounding_rect_given_items(view):
item3.setSelected(True)
item3.setPos(1000, 1000)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=100):
with patch.object(item1, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 100)):
with patch.object(item2, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 100)):
rect = view.scene.itemsBoundingRect(items=[item1, item2])
assert rect.topLeft().x() == -33
@ -889,10 +1059,10 @@ def test_items_bounding_rect_two_items_selection_only(view):
item3.setSelected(False)
item3.setPos(1000, 1000)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=100):
with patch.object(item1, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 100)):
with patch.object(item2, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 100)):
rect = view.scene.itemsBoundingRect(selection_only=True)
assert rect.topLeft().x() == -33
@ -905,11 +1075,9 @@ def test_items_bounding_rect_rotated_item(view, item):
view.scene.addItem(item)
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 = view.scene.itemsBoundingRect()
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 100, 100)):
rect = view.scene.itemsBoundingRect()
assert rect.topLeft().x() == 0
assert rect.topLeft().y() == approx(-math.sqrt(2) * 50)
@ -921,11 +1089,9 @@ def test_items_bounding_rect_flipped_item(view):
item = BeePixmapItem(QtGui.QImage())
view.scene.addItem(item)
item.do_flip()
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=50):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=100):
rect = view.scene.itemsBoundingRect()
with patch.object(item, 'bounding_rect_unselected',
return_value=QtCore.QRectF(0, 0, 50, 100)):
rect = view.scene.itemsBoundingRect()
assert rect.topLeft().x() == -50
assert rect.topLeft().y() == 0

View file

@ -182,6 +182,8 @@ def test_on_action_open(dialog_mock, view, qtbot):
filename = os.path.join(root, 'assets', 'test1item.bee')
dialog_mock.return_value = (filename, None)
view.on_loading_finished = MagicMock()
view.scene.cancel_crop_mode = MagicMock()
view.on_action_open()
qtbot.waitUntil(lambda: view.on_loading_finished.called is True)
assert len(view.scene.items()) == 1
@ -189,26 +191,31 @@ def test_on_action_open(dialog_mock, view, qtbot):
assert item.isSelected() is False
assert item.pixmap()
view.on_loading_finished.assert_called_once_with(filename, [])
view.scene.cancel_crop_mode.assert_called_once_with()
@patch('PyQt6.QtWidgets.QFileDialog.getOpenFileName')
@patch('beeref.view.BeeGraphicsView.on_action_open')
def test_on_action_open_when_no_filename(dialog_mock, open_mock, view):
@patch('beeref.view.BeeGraphicsView.open_from_file')
def test_on_action_open_when_no_filename(open_mock, dialog_mock, view):
dialog_mock.return_value = (None, None)
view.scene.cancel_crop_mode = MagicMock()
view.on_action_open()
open_mock.assert_not_called()
view.scene.cancel_crop_mode.assert_called_once_with()
@patch('PyQt6.QtWidgets.QFileDialog.getSaveFileName')
def test_on_action_save_as(dialog_mock, view, imgfilename3x3, tmpdir):
item = BeePixmapItem(QtGui.QImage(imgfilename3x3))
view.scene.addItem(item)
view.scene.cancel_crop_mode = MagicMock()
filename = os.path.join(tmpdir, 'test.bee')
assert os.path.exists(filename) is False
dialog_mock.return_value = (filename, None)
view.on_action_save_as()
view.worker.wait()
assert os.path.exists(filename) is True
view.scene.cancel_crop_mode.assert_called_once_with()
@patch('PyQt6.QtWidgets.QFileDialog.getSaveFileName')
@ -217,9 +224,11 @@ def test_on_action_save_as_when_no_filename(
save_mock, dialog_mock, view, imgfilename3x3):
item = BeePixmapItem(QtGui.QImage(imgfilename3x3))
view.scene.addItem(item)
view.scene.cancel_crop_mode = MagicMock()
dialog_mock.return_value = (None, None)
view.on_action_save_as()
save_mock.assert_not_called()
view.scene.cancel_crop_mode.assert_called_once_with()
@patch('PyQt6.QtWidgets.QFileDialog.getSaveFileName')
@ -227,6 +236,7 @@ def test_on_action_save_as_filename_doesnt_end_with_bee(
dialog_mock, view, qtbot, imgfilename3x3, tmpdir):
item = BeePixmapItem(QtGui.QImage(imgfilename3x3))
view.scene.addItem(item)
view.scene.cancel_crop_mode = MagicMock()
view.on_saving_finished = MagicMock()
filename = os.path.join(tmpdir, 'test')
assert os.path.exists(filename) is False
@ -235,6 +245,7 @@ def test_on_action_save_as_filename_doesnt_end_with_bee(
qtbot.waitUntil(lambda: view.on_saving_finished.called is True)
assert os.path.exists(f'{filename}.bee') is True
view.on_saving_finished.assert_called_once_with(f'{filename}.bee', [])
view.scene.cancel_crop_mode.assert_called_once_with()
@patch('PyQt6.QtWidgets.QFileDialog.getSaveFileName')
@ -244,17 +255,20 @@ def test_on_action_save_as_when_error(
item = BeePixmapItem(QtGui.QImage(imgfilename3x3))
view.scene.addItem(item)
view.on_saving_finished = MagicMock()
view.scene.cancel_crop_mode = MagicMock()
filename = os.path.join(tmpdir, 'test.bee')
dialog_mock.return_value = (filename, None)
save_mock.side_effect = sqlite3.Error('foo')
view.on_action_save_as()
qtbot.waitUntil(lambda: view.on_saving_finished.called is True)
view.on_saving_finished.assert_called_once_with(filename, ['foo'])
view.scene.cancel_crop_mode.assert_called_once_with()
def test_on_action_save(view, qtbot, imgfilename3x3, tmpdir):
item = BeePixmapItem(QtGui.QImage(imgfilename3x3))
view.scene.addItem(item)
view.scene.cancel_crop_mode = MagicMock()
view.filename = os.path.join(tmpdir, 'test.bee')
root = os.path.dirname(__file__)
shutil.copyfile(os.path.join(root, 'assets', 'test1item.bee'),
@ -264,15 +278,18 @@ def test_on_action_save(view, qtbot, imgfilename3x3, tmpdir):
qtbot.waitUntil(lambda: view.on_saving_finished.called is True)
assert os.path.exists(view.filename) is True
view.on_saving_finished.assert_called_once_with(view.filename, [])
view.scene.cancel_crop_mode.assert_called_once_with()
@patch('beeref.view.BeeGraphicsView.on_action_save_as')
def test_on_action_save_when_no_filename(save_as_mock, view, imgfilename3x3):
item = BeePixmapItem(QtGui.QImage(imgfilename3x3))
view.scene.addItem(item)
view.scene.cancel_crop_mode = MagicMock()
view.filename = None
view.on_action_save()
save_as_mock.assert_called_once_with()
view.scene.cancel_crop_mode.assert_called_once_with()
@patch('beeref.widgets.HelpDialog.show')
@ -295,6 +312,7 @@ def test_on_action_insert_images_new_scene(
dialog_mock, clear_mock, view, imgfilename3x3, qtbot):
dialog_mock.return_value = ([imgfilename3x3], None)
view.on_insert_images_finished = MagicMock()
view.scene.cancel_crop_mode = MagicMock()
view.on_action_insert_images()
qtbot.waitUntil(lambda: view.on_insert_images_finished.called is True)
assert len(view.scene.items()) == 1
@ -303,6 +321,7 @@ def test_on_action_insert_images_new_scene(
assert item.pixmap()
clear_mock.assert_called_once_with()
view.on_insert_images_finished.assert_called_once_with(True, '', [])
view.scene.cancel_crop_mode.assert_called_once_with()
@patch('beeref.scene.BeeGraphicsScene.clearSelection')
@ -312,6 +331,7 @@ def test_on_action_insert_images_existing_scene(
view.scene.addItem(item)
dialog_mock.return_value = ([imgfilename3x3], None)
view.on_insert_images_finished = MagicMock()
view.scene.cancel_crop_mode = MagicMock()
view.on_action_insert_images()
qtbot.waitUntil(lambda: view.on_insert_images_finished.called is True)
assert len(view.scene.items()) == 2
@ -320,6 +340,7 @@ def test_on_action_insert_images_existing_scene(
assert item.pixmap()
clear_mock.assert_called_once_with()
view.on_insert_images_finished.assert_called_once_with(False, '', [])
view.scene.cancel_crop_mode.assert_called_once_with()
@patch('beeref.scene.BeeGraphicsScene.clearSelection')
@ -328,6 +349,7 @@ def test_on_action_insert_images_when_error(
dialog_mock, clear_mock, view, imgfilename3x3, qtbot):
dialog_mock.return_value = ([imgfilename3x3, 'iaeiae', 'trntrn'], None)
view.on_insert_images_finished = MagicMock()
view.scene.cancel_crop_mode = MagicMock()
view.on_action_insert_images()
qtbot.waitUntil(lambda: view.on_insert_images_finished.called is True)
assert len(view.scene.items()) == 1
@ -337,22 +359,26 @@ def test_on_action_insert_images_when_error(
clear_mock.assert_called_once_with()
view.on_insert_images_finished.assert_called_once_with(
True, '', ['iaeiae', 'trntrn'])
view.scene.cancel_crop_mode.assert_called_once_with()
@patch('beeref.scene.BeeGraphicsScene.clearSelection')
def test_on_action_insert_text(clear_mock, view):
view.scene.cancel_crop_mode = MagicMock()
view.on_action_insert_text()
clear_mock.assert_called_once_with()
assert len(view.scene.items()) == 1
item = view.scene.items()[0]
assert item.toPlainText() == 'Text'
assert item.isSelected() is True
view.scene.cancel_crop_mode.assert_called_once_with()
@patch('PyQt6.QtWidgets.QApplication.clipboard')
def test_on_action_copy_image(clipboard_mock, view, imgfilename3x3):
item = BeePixmapItem(QtGui.QImage(imgfilename3x3))
view.scene.addItem(item)
view.scene.cancel_crop_mode = MagicMock()
item.setSelected(True)
mimedata = QtCore.QMimeData()
clipboard_mock.return_value.mimeData.return_value = mimedata
@ -361,12 +387,14 @@ def test_on_action_copy_image(clipboard_mock, view, imgfilename3x3):
clipboard_mock.return_value.setPixmap.assert_called_once()
view.scene.internal_clipboard == [item]
assert mimedata.data('beeref/items') == b'1'
view.scene.cancel_crop_mode.assert_called_once_with()
@patch('PyQt6.QtWidgets.QApplication.clipboard')
def test_on_action_copy_text(clipboard_mock, view, imgfilename3x3):
item = BeeTextItem('foo bar')
view.scene.addItem(item)
view.scene.cancel_crop_mode = MagicMock()
item.setSelected(True)
mimedata = QtCore.QMimeData()
clipboard_mock.return_value.mimeData.return_value = mimedata
@ -375,6 +403,7 @@ def test_on_action_copy_text(clipboard_mock, view, imgfilename3x3):
clipboard_mock.return_value.setText.assert_called_once_with('foo bar')
view.scene.internal_clipboard == [item]
assert mimedata.data('beeref/items') == b'1'
view.scene.cancel_crop_mode.assert_called_once_with()
@patch('beeref.view.BeeGraphicsView.on_action_fit_scene')
@ -383,10 +412,12 @@ def test_on_action_copy_text(clipboard_mock, view, imgfilename3x3):
def test_on_action_paste_external_new_scene(
clipboard_mock, clear_mock, fit_mock, view, imgfilename3x3):
clipboard_mock.return_value = QtGui.QImage(imgfilename3x3)
view.scene.cancel_crop_mode = MagicMock()
view.on_action_paste()
assert len(view.scene.items()) == 1
assert view.scene.items()[0].isSelected() is True
fit_mock.assert_called_once_with()
view.scene.cancel_crop_mode.assert_called_once_with()
@patch('beeref.view.BeeGraphicsView.on_action_fit_scene')
@ -395,12 +426,14 @@ def test_on_action_paste_external_new_scene(
def test_on_action_paste_external_existing_scene(
clipboard_mock, clear_mock, fit_mock, view, item, imgfilename3x3):
view.scene.addItem(item)
view.scene.cancel_crop_mode = MagicMock()
clipboard_mock.return_value = QtGui.QImage(imgfilename3x3)
view.on_action_paste()
assert len(view.scene.items()) == 2
assert view.scene.items()[0].isSelected() is True
assert view.scene.items()[1].isSelected() is False
fit_mock.assert_not_called()
view.scene.cancel_crop_mode.assert_called_once_with()
@patch('beeref.scene.BeeGraphicsScene.clearSelection')
@ -411,10 +444,12 @@ def test_on_action_paste_internal(mimedata_mock, clear_mock, view):
mimedata_mock.return_value = mimedata
item = BeePixmapItem(QtGui.QImage())
view.scene.internal_clipboard = [item]
view.scene.cancel_crop_mode = MagicMock()
view.on_action_paste()
assert len(view.scene.items()) == 1
assert view.scene.items()[0].isSelected() is True
clear_mock.assert_called_once_with()
view.scene.cancel_crop_mode.assert_called()
@patch('beeref.scene.BeeGraphicsScene.clearSelection')
@ -423,22 +458,26 @@ def test_on_action_paste_internal(mimedata_mock, clear_mock, view):
def test_on_action_paste_when_text(img_mock, text_mock, clear_mock, view):
img_mock.return_value = QtGui.QImage()
text_mock.return_value = 'foo bar'
view.scene.cancel_crop_mode = MagicMock()
view.on_action_paste()
assert len(view.scene.items()) == 1
assert view.scene.items()[0].isSelected() is True
assert view.scene.items()[0].toPlainText() == 'foo bar'
clear_mock.assert_called_once_with()
view.scene.cancel_crop_mode.assert_called_once_with()
@patch('beeref.scene.BeeGraphicsScene.clearSelection')
@patch('PyQt6.QtGui.QClipboard.text')
@patch('PyQt6.QtGui.QClipboard.image')
def test_on_action_paste_when_empty(img_mock, text_mock, clear_mock, view):
view.scene.cancel_crop_mode = MagicMock()
img_mock.return_value = QtGui.QImage()
text_mock.return_value = ''
view.on_action_paste()
assert len(view.scene.items()) == 0
clear_mock.assert_not_called()
view.scene.cancel_crop_mode.assert_called_once_with()
@patch('beeref.view.BeeGraphicsView.on_action_copy')
@ -508,11 +547,13 @@ def test_on_action_show_titlebar_unchecked(
def test_on_action_delete_items(view, item):
view.scene.cancel_crop_mode = MagicMock()
view.scene.addItem(item)
item.setSelected(True)
view.on_action_delete_items()
assert view.scene.items() == []
assert view.undo_stack.isClean() is False
view.scene.cancel_crop_mode.assert_called_once()
@patch('PyQt6.QtGui.QUndoStack.isClean', return_value=True)