Add Fit Scene, Fit Selection and Fit Image (via doubleclick)

This commit is contained in:
Rebecca Breu 2021-04-14 13:26:35 +02:00
parent 96ab9d2ef2
commit 90067f96ab
7 changed files with 162 additions and 2 deletions

View file

@ -120,4 +120,18 @@ actions = [
'shortcuts': ['Ctrl+N'],
'callback': 'clear_scene',
},
{
'id': 'fit_scene',
'text': '&Fit Scene',
'shortcuts': ['1'],
'callback': 'on_action_fit_scene',
},
{
'id': 'fit_selection',
'text': 'Fit &Selection',
'shortcuts': ['2'],
'callback': 'on_action_fit_selection',
'group': 'active_when_selection',
'enabled': False,
},
]

View file

@ -28,7 +28,6 @@ menu_structure = [
'quit',
],
},
'insert_images',
{
'menu': '&Edit',
'items': [
@ -38,6 +37,14 @@ menu_structure = [
'delete',
],
},
{
'menu': '&View',
'items': [
'fit_scene',
'fit_selection',
],
},
'insert_images',
{
'menu': '&Transform',
'items': [

View file

@ -125,6 +125,15 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene):
super().mousePressEvent(event)
def mouseDoubleClickEvent(self, event):
item = self.itemAt(event.scenePos(), self.views()[0].transform())
if item:
self.move_active = False
self.views()[0].fit_rect(self.get_selection_rect(),
toggle_item=item)
return
super().mouseDoubleClickEvent(event)
def mouseMoveEvent(self, event):
if self.rubberband_active:
if not self.rubberband_item.scene():
@ -133,6 +142,7 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene):
self.rubberband_item.bring_to_front()
self.rubberband_item.fit(self.event_start, event.scenePos())
self.setSelectionArea(self.rubberband_item.shape())
self.views()[0].reset_previous_transform()
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):

View file

@ -341,6 +341,7 @@ class SelectableMixin(BaseItemMixin):
self.setCursor(Qt.CursorShape.ArrowCursor)
def mousePressEvent(self, event):
self.scene().views()[0].reset_previous_transform(toggle_item=self)
if not self.isSelected():
self.just_selected = True
if (event.button() == Qt.MouseButtons.LeftButton
@ -430,6 +431,8 @@ class SelectableMixin(BaseItemMixin):
return edge['flip_v']
def mouseMoveEvent(self, event):
self.scene().views()[0].reset_previous_transform()
if self.scale_active:
factor = self.get_scale_factor(event)
for item in self.selection_action_items():

View file

@ -58,6 +58,7 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin):
# self.setTransformationAnchor(
# QtWidgets.QGraphicsView.ViewportAnchor.AnchorUnderMouse)
self.previous_transform = None
self.pan_active = False
self.scene.changed.connect(self.on_scene_changed)
self.scene.selectionChanged.connect(self.on_selection_changed)
@ -110,6 +111,37 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin):
self.filename = None
self.setTransform(QtGui.QTransform())
def reset_previous_transform(self, toggle_item=None):
if (self.previous_transform
and self.previous_transform['toggle_item'] != toggle_item):
self.previous_transform = None
def fit_rect(self, rect, toggle_item=None):
if toggle_item and self.previous_transform:
logger.debug('Fit view: Reset to previous')
self.setTransform(self.previous_transform['transform'])
self.horizontalScrollBar().setValue(
self.previous_transform['hscroll'])
self.verticalScrollBar().setValue(
self.previous_transform['vscroll'])
self.previous_transform = None
return
if toggle_item:
self.previous_transform = {
'toggle_item': toggle_item,
'transform': QtGui.QTransform(self.transform()),
'hscroll': self.horizontalScrollBar().value(),
'vscroll': self.verticalScrollBar().value(),
}
logger.debug(f'Fit view: {rect}')
self.fitInView(rect, Qt.AspectRatioMode.KeepAspectRatio)
def on_action_fit_scene(self):
self.fit_rect(self.scene.itemsBoundingRect())
def on_action_fit_selection(self):
self.fit_rect(self.scene.get_selection_rect())
def on_action_undo(self):
logger.debug('Undo: %s' % self.undo_stack.undoText())
self.undo_stack.undo()
@ -334,6 +366,7 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin):
def mouseMoveEvent(self, event):
if self.pan_active:
self.reset_previous_transform()
point = event.position()
hscroll = self.horizontalScrollBar()
hscroll.setValue(hscroll.value() + self.pan_start.x() - point.x())

View file

@ -174,6 +174,35 @@ class BeeGraphicsSceneTestCase(BeeTestCase):
assert self.scene.rubberband_active is True
assert self.scene.event_start == QtCore.QPointF(10, 20)
@patch('PyQt6.QtWidgets.QGraphicsScene.mouseDoubleClickEvent')
def test_mouse_doubleclick_event_when_over_item(self, mouse_mock):
event = MagicMock()
self.scene.move_active = True
item = BeePixmapItem(QtGui.QImage())
self.scene.addItem(item)
item.setPos(30, 40)
item.setSelected(True)
self.scene.itemAt = MagicMock(return_value=item)
with patch('beeref.items.BeePixmapItem.width',
new_callable=PropertyMock, return_value=100):
with patch('beeref.items.BeePixmapItem.height',
new_callable=PropertyMock, return_value=100):
self.scene.mouseDoubleClickEvent(event)
assert self.scene.move_active is False
self.view.fit_rect.assert_called_once_with(
QtCore.QRectF(30, 40, 100, 100), toggle_item=item)
mouse_mock.assert_not_called()
@patch('PyQt6.QtWidgets.QGraphicsScene.mouseDoubleClickEvent')
def test_mouse_doubleclick_event_when_not_over_item(self, mouse_mock):
event = MagicMock()
self.scene.itemAt = MagicMock(return_value=None)
self.scene.mouseDoubleClickEvent(event)
self.view.fit_rect.assert_not_called()
mouse_mock.assert_called_once_with(event)
@patch('PyQt6.QtWidgets.QGraphicsScene.mouseMoveEvent')
def test_mouse_move_event_when_rubberband_new(self, mouse_mock):
item = BeePixmapItem(QtGui.QImage(self.imgfilename3x3))

View file

@ -4,7 +4,8 @@ from unittest.mock import MagicMock, patch
from pytest import mark
from PyQt6 import QtGui, QtWidgets
from PyQt6 import QtCore, QtGui, QtWidgets
from PyQt6.QtCore import Qt
from beeref.items import BeePixmapItem
from beeref import fileio
@ -84,6 +85,69 @@ class BeeGraphicsViewTestCase(BeeTestCase):
assert self.view.filename is None
self.view.undo_stack.clear.assert_called_once_with()
def test_reset_previous_transform_when_other_item(self):
item1 = MagicMock()
item2 = MagicMock()
self.view.previous_transform = {
'transform': 'foo',
'toggle_item': item1,
}
self.view.reset_previous_transform(toggle_item=item2)
assert self.view.previous_transform is None
def test_reset_previous_transform_when_same_item(self):
item = MagicMock()
self.view.previous_transform = {
'transform': 'foo',
'toggle_item': item,
}
self.view.reset_previous_transform(toggle_item=item)
assert self.view.previous_transform == {
'transform': 'foo',
'toggle_item': item,
}
@patch('beeref.view.BeeGraphicsView.fitInView')
def test_fit_rect_no_toggle(self, fit_mock):
rect = QtCore.QRectF(30, 40, 100, 80)
self.view.fit_rect(rect)
fit_mock.assert_called_once_with(
rect, Qt.AspectRatioMode.KeepAspectRatio)
@patch('beeref.view.BeeGraphicsView.fitInView')
def test_fit_rect_toggle_when_no_previous(self, fit_mock):
item = MagicMock()
self.view.previous_transform = None
self.view.setSceneRect(QtCore.QRectF(-2000, -2000, 4000, 4000))
rect = QtCore.QRectF(30, 40, 100, 80)
self.view.scale(2, 2)
self.view.horizontalScrollBar().setValue(-40)
self.view.verticalScrollBar().setValue(-50)
self.view.fit_rect(rect, toggle_item=item)
fit_mock.assert_called_once_with(
rect, Qt.AspectRatioMode.KeepAspectRatio)
assert self.view.previous_transform['toggle_item'] == item
assert self.view.previous_transform['transform'].m11() == 2
assert self.view.previous_transform['hscroll'] == -40
assert self.view.previous_transform['vscroll'] == -50
@patch('beeref.view.BeeGraphicsView.fitInView')
def test_fit_rect_toggle_when_previous(self, fit_mock):
item = MagicMock()
self.view.previous_transform = {
'toggle_item': item,
'transform': QtGui.QTransform.fromScale(2, 2),
'hscroll': -40,
'vscroll': -50,
}
self.view.setSceneRect(QtCore.QRectF(-2000, -2000, 4000, 4000))
rect = QtCore.QRectF(30, 40, 100, 80)
self.view.fit_rect(rect, toggle_item=item)
fit_mock.assert_not_called()
assert self.view.get_scale() == 2
self.view.horizontalScrollBar().value == -40
self.view.verticalScrollBar().value == -50
@patch('beeref.view.BeeGraphicsView.clear_scene')
def test_open_from_file(self, clear_mock):
root = os.path.dirname(__file__)