diff --git a/beeref/__main__.py b/beeref/__main__.py index eae1a50..a2be6e1 100755 --- a/beeref/__main__.py +++ b/beeref/__main__.py @@ -32,7 +32,6 @@ class BeeRefMainWindow(QtWidgets.QWidget): def __init__(self, app): super().__init__() - self.setWindowTitle('BeeRef') self.setWindowIcon(BeeAssets().logo) layout = QtWidgets.QVBoxLayout() layout.setContentsMargins(QtCore.QMargins(0, 0, 0, 0)) diff --git a/beeref/scene.py b/beeref/scene.py index c2ca9a9..44dec3c 100644 --- a/beeref/scene.py +++ b/beeref/scene.py @@ -168,7 +168,7 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene): if rpack.overlapping(sizes, positions): # Bug in rpack: - # https://github.com/Penlect/rectangle-packer/issues/4#issuecomment-822411097 + # https://github.com/Penlect/rectangle-packer/issues/13 positions = [(p[1], p[0]) for p in positions] # We want the items to center around the selection's center, diff --git a/beeref/view.py b/beeref/view.py index 3a3d7d7..91de77a 100644 --- a/beeref/view.py +++ b/beeref/view.py @@ -14,6 +14,7 @@ # along with BeeRef. If not, see . import logging +import os from PyQt6 import QtCore, QtGui, QtWidgets from PyQt6.QtCore import Qt @@ -44,6 +45,7 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin): self.undo_stack.setUndoLimit(100) self.undo_stack.canRedoChanged.connect(self.on_can_redo_changed) self.undo_stack.canUndoChanged.connect(self.on_can_undo_changed) + self.undo_stack.cleanChanged.connect(self.on_undo_clean_changed) self.scene = BeeGraphicsScene(self.undo_stack) self.filename = None @@ -73,6 +75,26 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin): # Load file given via command line if commandline_args.filename: self.open_from_file(commandline_args.filename) + self.update_window_title() + + @property + def filename(self): + return self._filename + + @filename.setter + def filename(self, value): + self._filename = value + self.update_window_title() + + def update_window_title(self): + clean = self.undo_stack.isClean() + if clean and not self.filename: + title = 'BeeRef' + else: + name = os.path.basename(self.filename or '[Untitled]') + clean = '' if clean else '*' + title = f'{name}{clean} - BeeRef' + self.parent().setWindowTitle(title) def on_scene_changed(self, region): if not self.scene.items(): @@ -89,6 +111,9 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin): def on_can_undo_changed(self, can_undo): self.actiongroup_set_enabled('active_when_can_undo', can_undo) + def on_undo_clean_changed(self, clean): + self.update_window_title() + def on_context_menu(self, point): self.context_menu.exec(self.mapToGlobal(point)) @@ -267,6 +292,7 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin): def on_saving_finished(self, filename, errors): if filename: self.filename = filename + self.undo_stack.setClean() else: QtWidgets.QMessageBox.warning( self, diff --git a/tests/test_view.py b/tests/test_view.py index 21d1a2b..d3b62e5 100644 --- a/tests/test_view.py +++ b/tests/test_view.py @@ -13,20 +13,36 @@ from beeref.view import BeeGraphicsView from .base import BeeTestCase -class BeeGraphicsViewTestCase(BeeTestCase): +class ViewBaseTestCase(BeeTestCase): def setUp(self): config_patcher = patch('beeref.view.commandline_args') self.config_mock = config_patcher.start() self.config_mock.filename = None self.addCleanup(config_patcher.stop) - self.view = BeeGraphicsView(self.app) + self.parent = QtWidgets.QWidget() + self.view = BeeGraphicsView(self.app, self.parent) + + def tearDown(self): + del self.view + + +class BeeGraphicsViewTestCase(ViewBaseTestCase): + + def setUp(self): + config_patcher = patch('beeref.view.commandline_args') + self.config_mock = config_patcher.start() + self.config_mock.filename = None + self.addCleanup(config_patcher.stop) + self.parent = QtWidgets.QWidget() + self.view = BeeGraphicsView(self.app, self.parent) def tearDown(self): del self.view def test_inits_menu(self): - view = BeeGraphicsView(self.app) + parent = QtWidgets.QWidget() + view = BeeGraphicsView(self.app, parent) assert isinstance(view.context_menu, QtWidgets.QMenu) assert len(view.actions()) > 0 assert view.bee_actions @@ -35,14 +51,17 @@ class BeeGraphicsViewTestCase(BeeTestCase): @patch('beeref.view.BeeGraphicsView.open_from_file') def test_init_without_filename(self, open_file_mock): self.config_mock.filename = None - view = BeeGraphicsView(self.app) + parent = QtWidgets.QWidget() + view = BeeGraphicsView(self.app, parent) open_file_mock.assert_not_called() + assert parent.windowTitle() == 'BeeRef' del view @patch('beeref.view.BeeGraphicsView.open_from_file') def test_init_with_filename(self, open_file_mock): self.config_mock.filename = 'test.bee' - view = BeeGraphicsView(self.app) + parent = QtWidgets.QWidget() + view = BeeGraphicsView(self.app, parent) open_file_mock.assert_called_once_with('test.bee') del view @@ -84,6 +103,7 @@ class BeeGraphicsViewTestCase(BeeTestCase): assert self.view.transform().isIdentity() assert self.view.filename is None self.view.undo_stack.clear.assert_called_once_with() + assert self.parent.windowTitle() == 'BeeRef' def test_reset_previous_transform_when_other_item(self): item1 = MagicMock() @@ -159,6 +179,9 @@ class BeeGraphicsViewTestCase(BeeTestCase): assert items[0][0].pixmap() assert items[0][1] is False clear_mock.assert_called_once_with() + # FIXME: #1 + # Can't check signal handling currently + # assert self.parent.windowTitle() == 'test1item.bee - BeeRef' @patch('PyQt6.QtWidgets.QMessageBox.warning') def test_open_from_file_when_error(self, warn_mock): @@ -183,6 +206,9 @@ class BeeGraphicsViewTestCase(BeeTestCase): assert len(items) == 1 assert items[0][0].pixmap() assert items[0][1] is False + # FIXME: #1 + # Can't check signal handling currently + # assert self.parent.windowTitle() == 'test1item.bee - BeeRef' @patch('PyQt6.QtWidgets.QFileDialog.getOpenFileName') @patch('beeref.view.BeeGraphicsView.on_action_open') @@ -309,3 +335,30 @@ class BeeGraphicsViewTestCase(BeeTestCase): self.view.on_action_paste() assert len(self.view.scene.items()) == 0 clear_mock.assert_not_called() + + +class UpdateWindowTitleTestCase(ViewBaseTestCase): + + @patch('PyQt6.QtGui.QUndoStack.isClean', return_value=True) + def test_update_window_title_no_changes_no_filename(self, clear_mock): + self.view.filename = None + self.view.update_window_title() + assert self.parent.windowTitle() == 'BeeRef' + + @patch('PyQt6.QtGui.QUndoStack.isClean', return_value=False) + def test_update_window_title_changes_no_filename(self, clear_mock): + self.view.filename = None + self.view.update_window_title() + assert self.parent.windowTitle() == '[Untitled]* - BeeRef' + + @patch('PyQt6.QtGui.QUndoStack.isClean', return_value=True) + def test_update_window_title_no_changes_filename(self, clear_mock): + self.view.filename = 'test.bee' + self.view.update_window_title() + assert self.parent.windowTitle() == 'test.bee - BeeRef' + + @patch('PyQt6.QtGui.QUndoStack.isClean', return_value=False) + def test_update_window_title_changes_filename(self, clear_mock): + self.view.filename = 'test.bee' + self.view.update_window_title() + assert self.parent.windowTitle() == 'test.bee* - BeeRef'