diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 64fcf39..68646b8 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,16 @@
0.2.1 - (unreleased)
====================
+Added
+-----
+
+* Show list of recent files on welcome screen
+
+Fixed
+-----
+
+* Various typos (by luzpaz)
+
0.2.0 - 2021-09-06
==================
diff --git a/beeref/gui.py b/beeref/gui.py
index 002616f..7b50639 100644
--- a/beeref/gui.py
+++ b/beeref/gui.py
@@ -16,33 +16,111 @@
import logging
import os.path
-from PyQt6 import QtCore, QtWidgets
+from PyQt6 import QtCore, QtGui, QtWidgets
from PyQt6.QtCore import Qt
from beeref import constants
-from beeref.config import logfile_name
+from beeref.config import logfile_name, BeeSettings
+from beeref.main_controls import MainControlsMixin
logger = logging.getLogger(__name__)
-class WelcomeOverlay(QtWidgets.QWidget):
+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=None):
+ def __init__(self, parent):
super().__init__(parent)
+ self.control_target = parent
self.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground)
- self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents)
- label = QtWidgets.QLabel(self)
- label.setText(self.txt)
+ self.init_main_controls()
+
+ # 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
+ label = QtWidgets.QLabel(self.txt, self)
label.setAlignment(Qt.AlignmentFlag.AlignVCenter
| Qt.AlignmentFlag.AlignCenter)
- layout = QtWidgets.QVBoxLayout()
- layout.addWidget(label)
- self.setLayout(layout)
+ self.layout = QtWidgets.QHBoxLayout()
+ self.layout.addStretch(50)
+ self.layout.addWidget(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()
class BeeProgressDialog(QtWidgets.QProgressDialog):
diff --git a/beeref/main_controls.py b/beeref/main_controls.py
new file mode 100644
index 0000000..4626e34
--- /dev/null
+++ b/beeref/main_controls.py
@@ -0,0 +1,78 @@
+# 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
+
+from PyQt6 import QtCore, QtGui
+from PyQt6.QtCore import Qt
+
+from beeref import commands
+from beeref.items import BeePixmapItem
+from beeref import fileio
+
+
+logger = logging.getLogger(__name__)
+
+
+class MainControlsMixin:
+ """Basic controls shared by the main view and the welcome overlay:
+
+ * Right-click menu
+ * Dropping files
+ """
+
+ def init_main_controls(self):
+ self.setContextMenuPolicy(
+ Qt.ContextMenuPolicy.CustomContextMenu)
+ self.customContextMenuRequested.connect(
+ self.control_target.on_context_menu)
+ self.setAcceptDrops(True)
+
+ def dragEnterEvent(self, event):
+ mimedata = event.mimeData()
+ logger.debug(f'Drag enter event: {mimedata.formats()}')
+ if mimedata.hasUrls():
+ event.acceptProposedAction()
+ elif mimedata.hasImage():
+ event.acceptProposedAction()
+ else:
+ logger.info('Attempted drop not an image')
+
+ def dragMoveEvent(self, event):
+ event.acceptProposedAction()
+
+ def dropEvent(self, event):
+ mimedata = event.mimeData()
+ logger.debug(f'Handling file drop: {mimedata.formats()}')
+ pos = QtCore.QPoint(round(event.position().x()),
+ round(event.position().y()))
+ if mimedata.hasUrls():
+ logger.debug(f'Found dropped urls: {mimedata.urls()}')
+ if not self.control_target.scene.items():
+ # Check if we have a bee file we can open directly
+ path = mimedata.urls()[0]
+ if (path.isLocalFile()
+ and fileio.is_bee_file(path.toLocalFile())):
+ self.control_target.open_from_file(path.toLocalFile())
+ return
+ self.control_target.do_insert_images(mimedata.urls(), pos)
+ elif mimedata.hasImage():
+ img = QtGui.QImage(mimedata.imageData())
+ item = BeePixmapItem(img)
+ pos = self.control_target.mapToScene(pos)
+ self.control_target.undo_stack.push(
+ commands.InsertItems(self.control_target.scene, [item], pos))
+ else:
+ logger.info('Drop not an image')
diff --git a/beeref/view.py b/beeref/view.py
index a750070..46f46c3 100644
--- a/beeref/view.py
+++ b/beeref/view.py
@@ -28,6 +28,7 @@ from beeref import constants
from beeref import fileio
from beeref import gui
from beeref.items import BeePixmapItem, BeeTextItem
+from beeref.main_controls import MainControlsMixin
from beeref.scene import BeeGraphicsScene
@@ -35,7 +36,9 @@ commandline_args = CommandlineArgs()
logger = logging.getLogger(__name__)
-class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin):
+class BeeGraphicsView(MainControlsMixin,
+ QtWidgets.QGraphicsView,
+ ActionsMixin):
def __init__(self, app, parent=None):
super().__init__(parent)
@@ -47,7 +50,6 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin):
self.setBackgroundBrush(
QtGui.QBrush(QtGui.QColor(*constants.COLORS['Scene:Canvas'])))
self.setFrameShape(QtWidgets.QFrame.Shape.NoFrame)
- self.setAcceptDrops(True)
self.undo_stack = QtGui.QUndoStack(self)
self.undo_stack.setUndoLimit(100)
@@ -68,9 +70,8 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin):
# Context menu and actions
self.build_menu_and_actions()
- self.setContextMenuPolicy(
- Qt.ContextMenuPolicy.CustomContextMenu)
- self.customContextMenuRequested.connect(self.on_context_menu)
+ self.control_target = self
+ self.init_main_controls()
# Load file given via command line
if commandline_args.filename:
@@ -671,39 +672,3 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin):
super().resizeEvent(event)
self.recalc_scene_rect()
self.welcome_overlay.resize(self.size())
-
- def dragEnterEvent(self, event):
- mimedata = event.mimeData()
- logger.debug(f'Drag enter event: {mimedata.formats()}')
- if mimedata.hasUrls():
- event.acceptProposedAction()
- elif mimedata.hasImage():
- event.acceptProposedAction()
- else:
- logger.info('Attempted drop not an image')
-
- def dragMoveEvent(self, event):
- event.acceptProposedAction()
-
- def dropEvent(self, event):
- mimedata = event.mimeData()
- logger.debug(f'Handling file drop: {mimedata.formats()}')
- pos = QtCore.QPoint(round(event.position().x()),
- round(event.position().y()))
- if mimedata.hasUrls():
- logger.debug(f'Found dropped urls: {mimedata.urls()}')
- if not self.scene.items():
- # Check if we have a bee file we can open directly
- path = mimedata.urls()[0]
- if (path.isLocalFile()
- and fileio.is_bee_file(path.toLocalFile())):
- self.open_from_file(path.toLocalFile())
- return
- self.do_insert_images(mimedata.urls(), pos)
- elif mimedata.hasImage():
- img = QtGui.QImage(mimedata.imageData())
- item = BeePixmapItem(img)
- pos = self.mapToScene(pos)
- self.undo_stack.push(commands.InsertItems(self.scene, [item], pos))
- else:
- logger.info('Drop not an image')
diff --git a/tests/test_gui.py b/tests/test_gui.py
index 855683d..3cd72c7 100644
--- a/tests/test_gui.py
+++ b/tests/test_gui.py
@@ -1,8 +1,10 @@
-from PyQt6 import QtWidgets
+from unittest.mock import MagicMock
+
+from PyQt6 import QtCore, QtWidgets
from PyQt6.QtCore import Qt
from beeref.config import logfile_name
-from beeref.gui import DebugLogDialog
+from beeref.gui import DebugLogDialog, RecentFilesModel
def test_debug_log_dialog(qtbot, settings, view):
@@ -16,3 +18,23 @@ def test_debug_log_dialog(qtbot, settings, view):
qtbot.mouseClick(dialog.copy_button, Qt.MouseButton.LeftButton)
clipboard = QtWidgets.QApplication.clipboard()
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