From 723bb5819ddb839128bfc8ddf970fd0391779dfd Mon Sep 17 00:00:00 2001 From: Rebecca Breu Date: Sun, 26 Nov 2023 16:43:02 +0100 Subject: [PATCH] Add action "Move window" --- CHANGELOG.rst | 10 ++++---- beeref/actions/actions.py | 6 +++++ beeref/actions/menu_structure.py | 2 ++ beeref/main_controls.py | 31 +++++++++++++++++++++--- beeref/view.py | 20 ++++++++++++++-- beeref/widgets/__init__.py | 27 +++++++++++++++++---- tests/test_view.py | 41 ++++++++++++++++++++++++++++++-- 7 files changed, 122 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 97c30af..cc55b8c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,18 +10,20 @@ Added behaviour can be changed to always use PNG (the former behaviour) or always JPG. To apply this behaviour to already saved images in existing bee files, you can save them as new files. -* Enable antialiasing/smoothing. Images that are being displayed at a +* Enabled antialiasing/smoothing. Images that are being displayed at a large zoom factor are exempt to make sure that icons, pixel sprites etc can be viewed correctly. * A scene can now be exported to a single image (File -> Export Scene...) -* Editing of text items will now be undoable after leaving edit mode -* Empty text items will be deleted after leaving edit mode -* Text edit mode can now be aborted with Escape +* Alternative way to move the BeeRef window without the title bar: + View -> Move Window (or press "M") Changed ------- +* Editing of text items will now be undoable after leaving edit mode +* Empty text items will be deleted after leaving edit mode +* Text edit mode can now be aborted with Escape * "Save as" will now open pre-select the folder of the currently opened file * "Save" and "Save as" are now inactive when the scene is empty diff --git a/beeref/actions/actions.py b/beeref/actions/actions.py index a74ac53..e49b059 100644 --- a/beeref/actions/actions.py +++ b/beeref/actions/actions.py @@ -274,6 +274,12 @@ 'checked': True, 'callback': 'on_action_show_titlebar', }, + { + 'id': 'move_window', + 'text': 'Move &Window', + 'shortcuts': ['Ctrl+M'], + 'callback': 'on_action_move_window', + }, { 'id': 'fullscreen', 'text': '&Fullscreen', diff --git a/beeref/actions/menu_structure.py b/beeref/actions/menu_structure.py index 44f4a75..99c2ab6 100644 --- a/beeref/actions/menu_structure.py +++ b/beeref/actions/menu_structure.py @@ -62,6 +62,8 @@ 'show_scrollbars', 'show_menubar', 'show_titlebar', + MENU_SEPARATOR, + 'move_window', ], }, { diff --git a/beeref/main_controls.py b/beeref/main_controls.py index 122936e..e4fc8a5 100644 --- a/beeref/main_controls.py +++ b/beeref/main_controls.py @@ -31,6 +31,7 @@ class MainControlsMixin: * Right-click menu * Dropping files + * Moving the window without title bar """ def init_main_controls(self, main_window): @@ -42,6 +43,21 @@ def init_main_controls(self, main_window): self.setAcceptDrops(True) self.movewin_active = False + def enter_movewin_mode(self): + logger.debug('Entering movewin mode') + self.setMouseTracking(True) + self.movewin_active = True + self.event_start = QtCore.QPointF(self.cursor().pos()) + if hasattr(self, 'disable_mouse_events'): + self.disable_mouse_events() + + def exit_movewin_mode(self): + logger.debug('Exiting movewin mode') + self.setMouseTracking(False) + self.movewin_active = False + if hasattr(self, 'enable_mouse_events'): + self.enable_mouse_events() + def dragEnterEvent(self, event): mimedata = event.mimeData() logger.debug(f'Drag enter event: {mimedata.formats()}') @@ -80,11 +96,14 @@ def dropEvent(self, event): logger.info('Drop not an image') def mousePressEventMainControls(self, event): + if self.movewin_active: + self.exit_movewin_mode() + event.accept() + return True if (event.button() == Qt.MouseButton.LeftButton and event.modifiers() == (Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.AltModifier)): - self.movewin_active = True - self.event_start = self.mapToGlobal(event.position()) + self.enter_movewin_mode() event.accept() return True @@ -100,6 +119,12 @@ def mouseMoveEventMainControls(self, event): def mouseReleaseEventMainControls(self, event): if self.movewin_active: - self.movewin_active = False + self.exit_movewin_mode() + event.accept() + return True + + def keyPressEventMainControls(self, event): + if self.movewin_active: + self.exit_movewin_mode() event.accept() return True diff --git a/beeref/view.py b/beeref/view.py index 9ce1e7a..5474c06 100644 --- a/beeref/view.py +++ b/beeref/view.py @@ -105,9 +105,13 @@ def on_scene_changed(self, region): if not self.scene.items(): logger.debug('No items in scene') self.setTransform(QtGui.QTransform()) + self.welcome_overlay.setFocus() + self.clearFocus() self.welcome_overlay.show() self.actiongroup_set_enabled('active_when_items_in_scene', False) else: + self.setFocus() + self.welcome_overlay.clearFocus() self.welcome_overlay.hide() self.actiongroup_set_enabled('active_when_items_in_scene', True) self.recalc_scene_rect() @@ -216,6 +220,12 @@ def on_action_show_titlebar(self, checked): self.parent.create() self.parent.show() + def on_action_move_window(self): + if self.welcome_overlay.isHidden(): + self.enter_movewin_mode() + else: + self.welcome_overlay.enter_movewin_mode() + def on_action_undo(self): logger.debug('Undo: %s' % self.undo_stack.undoText()) self.scene.cancel_crop_mode() @@ -662,6 +672,9 @@ def wheelEvent(self, event): event.accept() def mousePressEvent(self, event): + if self.mousePressEventMainControls(event): + return + if (event.button() == Qt.MouseButton.MiddleButton and event.modifiers() == Qt.KeyboardModifier.ControlModifier): self.zoom_active = True @@ -679,8 +692,6 @@ def mousePressEvent(self, event): event.accept() return - if self.mousePressEventMainControls(event): - return super().mousePressEvent(event) def mouseMoveEvent(self, event): @@ -723,3 +734,8 @@ def resizeEvent(self, event): super().resizeEvent(event) self.recalc_scene_rect() self.welcome_overlay.resize(self.size()) + + def keyPressEvent(self, event): + if self.keyPressEventMainControls(event): + return + super().keyPressEvent(event) diff --git a/beeref/widgets/__init__.py b/beeref/widgets/__init__.py index 41183e8..f0918ae 100644 --- a/beeref/widgets/__init__.py +++ b/beeref/widgets/__init__.py @@ -107,12 +107,12 @@ def __init__(self, parent): self.files_layout.addStretch(50) # Help text - label = QtWidgets.QLabel(self.txt, self) - label.setAlignment(Qt.AlignmentFlag.AlignVCenter - | Qt.AlignmentFlag.AlignCenter) + self.label = QtWidgets.QLabel(self.txt, self) + self.label.setAlignment(Qt.AlignmentFlag.AlignVCenter + | Qt.AlignmentFlag.AlignCenter) self.layout = QtWidgets.QHBoxLayout() self.layout.addStretch(50) - self.layout.addWidget(label) + self.layout.addWidget(self.label) self.layout.addStretch(50) self.setLayout(self.layout) @@ -123,6 +123,20 @@ def show(self): self.layout.insertLayout(0, self.files_layout) super().show() + def disable_mouse_events(self): + self.files_view.setAttribute( + Qt.WidgetAttribute.WA_TransparentForMouseEvents) + self.label.setAttribute( + Qt.WidgetAttribute.WA_TransparentForMouseEvents) + + def enable_mouse_events(self): + self.files_view.setAttribute( + Qt.WidgetAttribute.WA_TransparentForMouseEvents, + on=False) + self.label.setAttribute( + Qt.WidgetAttribute.WA_TransparentForMouseEvents, + on=False) + def mousePressEvent(self, event): if self.mousePressEventMainControls(event): return @@ -138,6 +152,11 @@ def mouseReleaseEvent(self, event): return super().mouseReleaseEvent(event) + def keyPressEvent(self, event): + if self.keyPressEventMainControls(event): + return + super().keyPressEvent(event) + class BeeProgressDialog(QtWidgets.QProgressDialog): diff --git a/tests/test_view.py b/tests/test_view.py index 3e23c14..abca065 100644 --- a/tests/test_view.py +++ b/tests/test_view.py @@ -598,6 +598,25 @@ def test_on_action_show_titlebar_unchecked( create_mock.assert_called_once() +@patch('beeref.widgets.WelcomeOverlay.cursor') +def test_on_action_move_window_when_welcome_overlay(cursor_mock, view): + cursor_mock.return_value = MagicMock( + pos=MagicMock(return_value=QtCore.QPointF(10.0, 20.0))) + view.on_action_move_window() + assert view.welcome_overlay.movewin_active is True + assert view.welcome_overlay.event_start == QtCore.QPointF(10.0, 20.0) + + +@patch('beeref.view.BeeGraphicsView.cursor') +def test_on_action_move_window_when_scene(cursor_mock, view): + cursor_mock.return_value = MagicMock( + pos=MagicMock(return_value=QtCore.QPointF(10.0, 20.0))) + view.welcome_overlay.hide() + view.on_action_move_window() + assert view.movewin_active is True + assert view.event_start == QtCore.QPointF(10.0, 20.0) + + def test_on_action_delete_items(view, item): view.scene.cancel_crop_mode = MagicMock() view.scene.addItem(item) @@ -781,9 +800,11 @@ def test_mouse_press_pan_alt_left_drag(mouse_event_mock, view): @patch('PyQt6.QtWidgets.QGraphicsView.mousePressEvent') -def test_mouse_press_move_window(mouse_event_mock, view): +@patch('beeref.view.BeeGraphicsView.cursor') +def test_mouse_press_move_window(cursor_mock, mouse_event_mock, view): event = MagicMock() - event.position.return_value = QtCore.QPointF(10.0, 20.0) + cursor_mock.return_value = MagicMock( + pos=MagicMock(return_value=QtCore.QPointF(10.0, 20.0))) event.button.return_value = Qt.MouseButton.LeftButton event.modifiers.return_value = ( Qt.KeyboardModifier.AltModifier | Qt.KeyboardModifier.ControlModifier) @@ -796,6 +817,22 @@ def test_mouse_press_move_window(mouse_event_mock, view): event.accept.assert_called_once_with() +@patch('PyQt6.QtWidgets.QGraphicsView.mousePressEvent') +def test_mouse_press_when_move_window_active(mouse_event_mock, view): + view.movewin_active = True + view.mousePressEvent(MagicMock()) + assert view.movewin_active is False + mouse_event_mock.assert_not_called() + + +@patch('PyQt6.QtWidgets.QGraphicsView.keyPressEvent') +def test_key_press_when_move_window_active(key_event_mock, view): + view.movewin_active = True + view.keyPressEvent(MagicMock()) + assert view.movewin_active is False + key_event_mock.assert_not_called() + + @patch('PyQt6.QtWidgets.QGraphicsView.mousePressEvent') def test_mouse_press_unhandled(mouse_event_mock, view): event = MagicMock()