diff --git a/beeref/view.py b/beeref/view.py
index a4b8d32..2501a12 100644
--- a/beeref/view.py
+++ b/beeref/view.py
@@ -46,7 +46,7 @@ class BeeGraphicsView(MainControlsMixin,
self.app = app
self.parent = parent
self.settings = BeeSettings()
- self.welcome_overlay = widgets.WelcomeOverlay(self)
+ self.welcome_overlay = widgets.welcome_overlay.WelcomeOverlay(self)
self.setBackgroundBrush(
QtGui.QBrush(QtGui.QColor(*constants.COLORS['Scene:Canvas'])))
diff --git a/beeref/widgets/__init__.py b/beeref/widgets/__init__.py
index f0918ae..0aba20b 100644
--- a/beeref/widgets/__init__.py
+++ b/beeref/widgets/__init__.py
@@ -16,148 +16,17 @@
import logging
import os.path
-from PyQt6 import QtCore, QtGui, QtWidgets
+from PyQt6 import QtCore, QtWidgets
from PyQt6.QtCore import Qt
from beeref import constants
-from beeref.config import logfile_name, BeeSettings
-from beeref.main_controls import MainControlsMixin
-from beeref.widgets import settings # noqa: F401
+from beeref.config import logfile_name
+from beeref.widgets import settings, welcome_overlay # noqa: F401
logger = logging.getLogger(__name__)
-class RecentFilesModel(QtCore.QAbstractListModel):
- """An entry in the 'Recent Files' list."""
-
- def __init__(self, files):
- super().__init__()
- self.files = files
-
- def rowCount(self, parent):
- return len(self.files)
-
- def data(self, index, role):
- if role == QtCore.Qt.ItemDataRole.DisplayRole:
- return os.path.basename(self.files[index.row()])
- if role == QtCore.Qt.ItemDataRole.FontRole:
- font = QtGui.QFont()
- font.setUnderline(True)
- return font
-
-
-class RecentFilesView(QtWidgets.QListView):
-
- def __init__(self, parent, files=None):
- super().__init__(parent)
- self.files = files or []
- self.clicked.connect(self.on_clicked)
- self.setModel(RecentFilesModel(self.files))
- self.setMouseTracking(True)
-
- def on_clicked(self, index):
- self.parent().parent().open_from_file(self.files[index.row()])
-
- def update_files(self, files):
- self.files = files
- self.model().files = files
- self.reset()
-
- def sizeHint(self):
- size = QtCore.QSize()
- height = sum(
- (self.sizeHintForRow(i) + 2) for i in range(len(self.files)))
- width = max(self.sizeHintForColumn(i) for i in range(len(self.files)))
- size.setHeight(height)
- size.setWidth(width + 2)
- return size
-
- def mouseMoveEvent(self, event):
- index = self.indexAt(
- QtCore.QPoint(int(event.position().x()),
- int(event.position().y())))
- if index.isValid():
- self.setCursor(Qt.CursorShape.PointingHandCursor)
- else:
- self.setCursor(Qt.CursorShape.ArrowCursor)
-
- super().mouseMoveEvent(event)
-
-
-class WelcomeOverlay(MainControlsMixin, QtWidgets.QWidget):
- """Some basic info to be displayed when the scene is empty."""
-
- txt = """
Paste or drop images here.
- Right-click for more options.
"""
-
- def __init__(self, parent):
- super().__init__(parent)
- self.control_target = parent
- self.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground)
- self.init_main_controls(main_window=parent.parent)
-
- # Recent files
- self.files_layout = QtWidgets.QVBoxLayout()
- self.files_layout.addStretch(50)
- self.files_layout.addWidget(
- QtWidgets.QLabel('Recent Files
', self))
- self.files_view = RecentFilesView(self)
- self.files_layout.addWidget(self.files_view)
- self.files_layout.addStretch(50)
-
- # Help text
- 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(self.label)
- self.layout.addStretch(50)
- self.setLayout(self.layout)
-
- def show(self):
- files = BeeSettings().get_recent_files(existing_only=True)
- self.files_view.update_files(files)
- if files and self.layout.indexOf(self.files_layout) < 0:
- 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
- super().mousePressEvent(event)
-
- def mouseMoveEvent(self, event):
- if self.mouseMoveEventMainControls(event):
- return
- super().mouseMoveEvent(event)
-
- def mouseReleaseEvent(self, event):
- if self.mouseReleaseEventMainControls(event):
- return
- super().mouseReleaseEvent(event)
-
- def keyPressEvent(self, event):
- if self.keyPressEventMainControls(event):
- return
- super().keyPressEvent(event)
-
-
class BeeProgressDialog(QtWidgets.QProgressDialog):
def __init__(self, label, worker, maximum=0, parent=None):
diff --git a/beeref/widgets/welcome_overlay.py b/beeref/widgets/welcome_overlay.py
new file mode 100644
index 0000000..510ee0c
--- /dev/null
+++ b/beeref/widgets/welcome_overlay.py
@@ -0,0 +1,156 @@
+# This file is part of BeeRef.
+#
+# BeeRef is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# BeeRef is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with BeeRef. If not, see .
+
+import logging
+import os.path
+
+from PyQt6 import QtCore, QtGui, QtWidgets
+from PyQt6.QtCore import Qt
+
+from beeref.config import BeeSettings
+from beeref.main_controls import MainControlsMixin
+
+
+logger = logging.getLogger(__name__)
+
+
+class RecentFilesModel(QtCore.QAbstractListModel):
+ """An entry in the 'Recent Files' list."""
+
+ def __init__(self, files):
+ super().__init__()
+ self.files = files
+
+ def rowCount(self, parent):
+ return len(self.files)
+
+ def data(self, index, role):
+ if role == QtCore.Qt.ItemDataRole.DisplayRole:
+ return os.path.basename(self.files[index.row()])
+ if role == QtCore.Qt.ItemDataRole.FontRole:
+ font = QtGui.QFont()
+ font.setUnderline(True)
+ return font
+
+
+class RecentFilesView(QtWidgets.QListView):
+
+ def __init__(self, parent, files=None):
+ super().__init__(parent)
+ self.files = files or []
+ self.clicked.connect(self.on_clicked)
+ self.setModel(RecentFilesModel(self.files))
+ self.setMouseTracking(True)
+
+ def on_clicked(self, index):
+ self.parent().parent().open_from_file(self.files[index.row()])
+
+ def update_files(self, files):
+ self.files = files
+ self.model().files = files
+ self.reset()
+
+ def sizeHint(self):
+ size = QtCore.QSize()
+ height = sum(
+ (self.sizeHintForRow(i) + 2) for i in range(len(self.files)))
+ width = max(self.sizeHintForColumn(i) for i in range(len(self.files)))
+ size.setHeight(height)
+ size.setWidth(width + 2)
+ return size
+
+ def mouseMoveEvent(self, event):
+ index = self.indexAt(
+ QtCore.QPoint(int(event.position().x()),
+ int(event.position().y())))
+ if index.isValid():
+ self.setCursor(Qt.CursorShape.PointingHandCursor)
+ else:
+ self.setCursor(Qt.CursorShape.ArrowCursor)
+
+ super().mouseMoveEvent(event)
+
+
+class WelcomeOverlay(MainControlsMixin, QtWidgets.QWidget):
+ """Some basic info to be displayed when the scene is empty."""
+
+ txt = """Paste or drop images here.
+ Right-click for more options.
"""
+
+ def __init__(self, parent):
+ super().__init__(parent)
+ self.control_target = parent
+ self.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground)
+ self.init_main_controls(main_window=parent.parent)
+
+ # Recent files
+ self.files_layout = QtWidgets.QVBoxLayout()
+ self.files_layout.addStretch(50)
+ self.files_layout.addWidget(
+ QtWidgets.QLabel('Recent Files
', self))
+ self.files_view = RecentFilesView(self)
+ self.files_layout.addWidget(self.files_view)
+ self.files_layout.addStretch(50)
+
+ # Help text
+ 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(self.label)
+ self.layout.addStretch(50)
+ self.setLayout(self.layout)
+
+ def show(self):
+ files = BeeSettings().get_recent_files(existing_only=True)
+ self.files_view.update_files(files)
+ if files and self.layout.indexOf(self.files_layout) < 0:
+ 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
+ super().mousePressEvent(event)
+
+ def mouseMoveEvent(self, event):
+ if self.mouseMoveEventMainControls(event):
+ return
+ super().mouseMoveEvent(event)
+
+ def mouseReleaseEvent(self, event):
+ if self.mouseReleaseEventMainControls(event):
+ return
+ super().mouseReleaseEvent(event)
+
+ def keyPressEvent(self, event):
+ if self.keyPressEventMainControls(event):
+ return
+ super().keyPressEvent(event)
diff --git a/tests/items/test_textitem.py b/tests/items/test_textitem.py
index 607a216..ba125ab 100644
--- a/tests/items/test_textitem.py
+++ b/tests/items/test_textitem.py
@@ -336,6 +336,20 @@ def test_key_press_event_enter(exit_mock, key_press_mock, view):
exit_mock.assert_called_once_with()
+@patch('PyQt6.QtWidgets.QGraphicsTextItem.keyPressEvent')
+@patch('beeref.items.BeeTextItem.exit_edit_mode')
+def test_key_press_event_escape(exit_mock, key_press_mock, view):
+ item = BeeTextItem('foo bar')
+ view.scene.addItem(item)
+ view.scene.edit_item = item
+ event = MagicMock()
+ event.key.return_value = Qt.Key.Key_Escape
+ event.modifiers.return_value = Qt.KeyboardModifier.NoModifier
+ item.keyPressEvent(event)
+ key_press_mock.assert_not_called()
+ exit_mock.assert_called_once_with(commit=False)
+
+
def test_item_to_clipboard(qapp):
clipboard = QtWidgets.QApplication.clipboard()
item = BeeTextItem('foo bar')
diff --git a/tests/test_view.py b/tests/test_view.py
index 2dda4b1..2b6226f 100644
--- a/tests/test_view.py
+++ b/tests/test_view.py
@@ -39,7 +39,7 @@ def test_init_with_filename(open_file_mock, view, qapp, commandline_args):
del view
-@patch('beeref.widgets.WelcomeOverlay.hide')
+@patch('beeref.widgets.welcome_overlay.WelcomeOverlay.hide')
def test_on_scene_changed_when_items(hide_mock, view):
item = BeePixmapItem(QtGui.QImage())
view.scene.addItem(item)
@@ -51,7 +51,7 @@ def test_on_scene_changed_when_items(hide_mock, view):
assert view.get_scale() == 2
-@patch('beeref.widgets.WelcomeOverlay.show')
+@patch('beeref.widgets.welcome_overlay.WelcomeOverlay.show')
def test_on_scene_changed_when_no_items(show_mock, view):
view.scale(2, 2)
with patch('beeref.view.BeeGraphicsView.recalc_scene_rect') as r:
@@ -600,7 +600,7 @@ def test_on_action_show_titlebar_unchecked(
create_mock.assert_called_once()
-@patch('beeref.widgets.WelcomeOverlay.cursor')
+@patch('beeref.widgets.welcome_overlay.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)))
diff --git a/tests/widgets/test_settings.py b/tests/widgets/test_settings.py
new file mode 100644
index 0000000..5999ac8
--- /dev/null
+++ b/tests/widgets/test_settings.py
@@ -0,0 +1,45 @@
+from unittest.mock import patch
+
+from PyQt6 import QtWidgets
+from beeref.widgets.settings import (
+ ImageStorageFormatWidget,
+ SettingsDialog,
+)
+
+
+def test_image_storage_format_selects_radiobox(settings, view):
+ settings.setValue('Items/image_storage_format', 'jpg')
+ widget = ImageStorageFormatWidget()
+ assert widget.buttons['best'].isChecked() is False
+ assert widget.buttons['png'].isChecked() is False
+ assert widget.buttons['jpg'].isChecked() is True
+
+
+def test_image_storage_format_saves_change(settings, view):
+ settings.setValue('Items/image_storage_format', 'best')
+ widget = ImageStorageFormatWidget()
+ widget.buttons['jpg'].setChecked(True)
+ assert widget.buttons['best'].isChecked() is False
+ assert widget.buttons['png'].isChecked() is False
+ assert widget.buttons['jpg'].isChecked() is True
+ assert settings.valueOrDefault('Items/image_storage_format', 'jpg')
+
+
+def test_image_storage_format_on_restore_defaults(settings, view):
+ widget = ImageStorageFormatWidget()
+ widget.buttons['jpg'].setChecked(True)
+ settings.setValue('Items/image_storage_format', 'best')
+ widget.on_restore_defaults()
+ assert widget.buttons['best'].isChecked() is True
+ assert widget.buttons['png'].isChecked() is False
+ assert widget.buttons['jpg'].isChecked() is False
+
+
+@patch('PyQt6.QtWidgets.QMessageBox.question',
+ return_value=QtWidgets.QMessageBox.StandardButton.Yes)
+def test_settings_dialog_on_restore_defaults(msg_mock, settings, view):
+ dialog = SettingsDialog(view)
+ settings.setValue('Items/image_storage_format', 'jpg')
+ dialog.on_restore_defaults()
+ msg_mock.assert_called_once()
+ assert settings.valueOrDefault('Items/image_storage_format') == 'best'
diff --git a/tests/widgets/test_welcome_overlay.py b/tests/widgets/test_welcome_overlay.py
new file mode 100644
index 0000000..28e2109
--- /dev/null
+++ b/tests/widgets/test_welcome_overlay.py
@@ -0,0 +1,25 @@
+from unittest.mock import MagicMock
+
+from PyQt6 import QtCore
+
+from beeref.widgets.welcome_overlay import RecentFilesModel
+
+
+def test_recent_files_model_rowcount(view):
+ model = RecentFilesModel(['foo.png', 'bar.png'])
+ assert model.rowCount(None) == 2
+
+
+def test_recent_files_model_data_diplayrole(view):
+ model = RecentFilesModel(['foo.png', 'bar.png'])
+ index = MagicMock()
+ index.row.return_value = 1
+ assert model.data(index, QtCore.Qt.ItemDataRole.DisplayRole) == 'bar.png'
+
+
+def test_recent_files_model_data_fontrole(view):
+ model = RecentFilesModel(['foo.png', 'bar.png'])
+ index = MagicMock()
+ index.row.return_value = 1
+ font = model.data(index, QtCore.Qt.ItemDataRole.FontRole)
+ assert font.underline() is True
diff --git a/tests/test_widgets.py b/tests/widgets/test_widgets.py
similarity index 71%
rename from tests/test_widgets.py
rename to tests/widgets/test_widgets.py
index 33f1dae..26382a1 100644
--- a/tests/test_widgets.py
+++ b/tests/widgets/test_widgets.py
@@ -1,12 +1,9 @@
-from unittest.mock import MagicMock
-
from PyQt6 import QtCore, QtWidgets
from PyQt6.QtCore import Qt
from beeref.config import logfile_name
from beeref.widgets import (
DebugLogDialog,
- RecentFilesModel,
SceneToPixmapExporterDialog)
@@ -23,26 +20,6 @@ def test_debug_log_dialog(qtbot, settings, view):
assert clipboard.text() == 'my log output'
-def test_recent_files_model_rowcount(view):
- model = RecentFilesModel(['foo.png', 'bar.png'])
- assert model.rowCount(None) == 2
-
-
-def test_recent_files_model_data_diplayrole(view):
- model = RecentFilesModel(['foo.png', 'bar.png'])
- index = MagicMock()
- index.row.return_value = 1
- assert model.data(index, QtCore.Qt.ItemDataRole.DisplayRole) == 'bar.png'
-
-
-def test_recent_files_model_data_fontrole(view):
- model = RecentFilesModel(['foo.png', 'bar.png'])
- index = MagicMock()
- index.row.return_value = 1
- font = model.data(index, QtCore.Qt.ItemDataRole.FontRole)
- assert font.underline() is True
-
-
def test_scene_to_pixmap_exporter_dialog_sets_defaults(view):
dlg = SceneToPixmapExporterDialog(view, QtCore.QSize(1200, 1600))
assert dlg.width_input.value() == 1200