mirror of
https://github.com/rbreu/beeref.git
synced 2026-03-11 08:54:28 +00:00
Add Fit Scene, Fit Selection and Fit Image (via doubleclick)
This commit is contained in:
parent
96ab9d2ef2
commit
90067f96ab
7 changed files with 162 additions and 2 deletions
|
|
@ -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,
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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': [
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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__)
|
||||
|
|
|
|||
Loading…
Reference in a new issue