mirror of
https://github.com/rbreu/beeref.git
synced 2026-03-11 08:54:28 +00:00
Add confirmation dialog when discarding an unsaved file
This commit is contained in:
parent
a89027f0ba
commit
aac2d0edfc
13 changed files with 270 additions and 36 deletions
2
.github/workflows/pytest.yml
vendored
2
.github/workflows/pytest.yml
vendored
|
|
@ -8,7 +8,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
||||||
pyqt-version: ["6.5.3", "6.7.0"]
|
pyqt-version: ["6.7.0"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ Added
|
||||||
QT_IMAGEIO_MAXALLOC
|
QT_IMAGEIO_MAXALLOC
|
||||||
* Display error messages when images can't be loaded from bee files
|
* Display error messages when images can't be loaded from bee files
|
||||||
* Added option to export all images from scene (File -> Export Images)
|
* Added option to export all images from scene (File -> Export Images)
|
||||||
|
* Added a confirmation dialog when attempting to close unsaved files.
|
||||||
|
The confirmation dialog can be disalbed in:
|
||||||
|
Settings -> Miscellaneous -> Confirm when closing an unsaved file
|
||||||
|
|
||||||
|
|
||||||
Fixed
|
Fixed
|
||||||
|
|
|
||||||
|
|
@ -312,7 +312,7 @@ actions = ActionList([
|
||||||
id='new_scene',
|
id='new_scene',
|
||||||
text='&New Scene',
|
text='&New Scene',
|
||||||
shortcuts=['Ctrl+N'],
|
shortcuts=['Ctrl+N'],
|
||||||
callback='clear_scene',
|
callback='on_action_new_scene',
|
||||||
),
|
),
|
||||||
Action(
|
Action(
|
||||||
id='fit_scene',
|
id='fit_scene',
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ class ActionsMixin:
|
||||||
qaction = QtGui.QAction(os.path.basename(filename), self)
|
qaction = QtGui.QAction(os.path.basename(filename), self)
|
||||||
qaction.setShortcuts(action.get_shortcuts())
|
qaction.setShortcuts(action.get_shortcuts())
|
||||||
qaction.triggered.connect(
|
qaction.triggered.connect(
|
||||||
partial(self.open_from_file, filename))
|
partial(self.on_action_open_recent_file, filename))
|
||||||
self.addAction(qaction)
|
self.addAction(qaction)
|
||||||
action.qaction = qaction
|
action.qaction = qaction
|
||||||
self._recent_files_submenu.addAction(qaction)
|
self._recent_files_submenu.addAction(qaction)
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,10 @@ settings_events = BeeSettingsEvents()
|
||||||
class BeeSettings(QtCore.QSettings):
|
class BeeSettings(QtCore.QSettings):
|
||||||
|
|
||||||
FIELDS = {
|
FIELDS = {
|
||||||
|
'Save/confirm_close_unsaved': {
|
||||||
|
'default': True,
|
||||||
|
'cast': bool,
|
||||||
|
},
|
||||||
'Items/image_storage_format': {
|
'Items/image_storage_format': {
|
||||||
'default': 'best',
|
'default': 'best',
|
||||||
'validate': lambda x: x in ('png', 'jpg', 'best'),
|
'validate': lambda x: x in ('png', 'jpg', 'best'),
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
IMG_LOADING_ERROR_MSG = (
|
IMG_LOADING_ERROR_MSG = (
|
||||||
'Unknown format or too big?\n'
|
'Unknown format or too big?\n'
|
||||||
'Check Settings -> Miscellaneous -> Maximum Image Size')
|
'Check Settings -> Images & Items -> Maximum Image Size')
|
||||||
|
|
||||||
|
|
||||||
class BeeFileIOError(Exception):
|
class BeeFileIOError(Exception):
|
||||||
|
|
|
||||||
|
|
@ -200,6 +200,26 @@ class BeeGraphicsView(MainControlsMixin,
|
||||||
self.fitInView(rect, Qt.AspectRatioMode.KeepAspectRatio)
|
self.fitInView(rect, Qt.AspectRatioMode.KeepAspectRatio)
|
||||||
logger.trace('Fit view done')
|
logger.trace('Fit view done')
|
||||||
|
|
||||||
|
def get_confirmation_unsaved_changes(self, msg):
|
||||||
|
confirm = self.settings.valueOrDefault('Save/confirm_close_unsaved')
|
||||||
|
if confirm and not self.undo_stack.isClean():
|
||||||
|
answer = QtWidgets.QMessageBox.question(
|
||||||
|
self,
|
||||||
|
'Discard unsaved changes?',
|
||||||
|
msg,
|
||||||
|
QtWidgets.QMessageBox.StandardButton.Yes |
|
||||||
|
QtWidgets.QMessageBox.StandardButton.Cancel)
|
||||||
|
return answer == QtWidgets.QMessageBox.StandardButton.Yes
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def on_action_new_scene(self):
|
||||||
|
confirm = self.get_confirmation_unsaved_changes(
|
||||||
|
'There are unsaved changes. '
|
||||||
|
'Are you sure you want to open a new scene?')
|
||||||
|
if confirm:
|
||||||
|
self.clear_scene()
|
||||||
|
|
||||||
def on_action_fit_scene(self):
|
def on_action_fit_scene(self):
|
||||||
self.fit_rect(self.scene.itemsBoundingRect())
|
self.fit_rect(self.scene.itemsBoundingRect())
|
||||||
|
|
||||||
|
|
@ -388,6 +408,13 @@ class BeeGraphicsView(MainControlsMixin,
|
||||||
self.scene.add_queued_items()
|
self.scene.add_queued_items()
|
||||||
self.on_action_fit_scene()
|
self.on_action_fit_scene()
|
||||||
|
|
||||||
|
def on_action_open_recent_file(self, filename):
|
||||||
|
confirm = self.get_confirmation_unsaved_changes(
|
||||||
|
'There are unsaved changes. '
|
||||||
|
'Are you sure you want to open a new scene?')
|
||||||
|
if confirm:
|
||||||
|
self.open_from_file(filename)
|
||||||
|
|
||||||
def open_from_file(self, filename):
|
def open_from_file(self, filename):
|
||||||
logger.info(f'Opening file {filename}')
|
logger.info(f'Opening file {filename}')
|
||||||
self.clear_scene()
|
self.clear_scene()
|
||||||
|
|
@ -402,6 +429,12 @@ class BeeGraphicsView(MainControlsMixin,
|
||||||
self.worker.start()
|
self.worker.start()
|
||||||
|
|
||||||
def on_action_open(self):
|
def on_action_open(self):
|
||||||
|
confirm = self.get_confirmation_unsaved_changes(
|
||||||
|
'There are unsaved changes. '
|
||||||
|
'Are you sure you want to open a new scene?')
|
||||||
|
if not confirm:
|
||||||
|
return
|
||||||
|
|
||||||
self.cancel_active_modes()
|
self.cancel_active_modes()
|
||||||
filename, f = QtWidgets.QFileDialog.getOpenFileName(
|
filename, f = QtWidgets.QFileDialog.getOpenFileName(
|
||||||
parent=self,
|
parent=self,
|
||||||
|
|
@ -528,8 +561,11 @@ class BeeGraphicsView(MainControlsMixin,
|
||||||
self.worker.start()
|
self.worker.start()
|
||||||
|
|
||||||
def on_action_quit(self):
|
def on_action_quit(self):
|
||||||
logger.info('User quit. Exiting...')
|
confirm = self.get_confirmation_unsaved_changes(
|
||||||
self.app.quit()
|
'There are unsaved changes. Are you sure you want to quit?')
|
||||||
|
if confirm:
|
||||||
|
logger.info('User quit. Exiting...')
|
||||||
|
self.app.quit()
|
||||||
|
|
||||||
def on_action_settings(self):
|
def on_action_settings(self):
|
||||||
widgets.settings.SettingsDialog(self)
|
widgets.settings.SettingsDialog(self)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ from functools import partial
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from PyQt6 import QtWidgets
|
from PyQt6 import QtWidgets
|
||||||
|
from PyQt6.QtCore import Qt
|
||||||
|
|
||||||
from beeref import constants
|
from beeref import constants
|
||||||
from beeref.config import BeeSettings, settings_events
|
from beeref.config import BeeSettings, settings_events
|
||||||
|
|
@ -26,6 +27,9 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class GroupBase(QtWidgets.QGroupBox):
|
class GroupBase(QtWidgets.QGroupBox):
|
||||||
|
TITLE = None
|
||||||
|
HELPTEXT = None
|
||||||
|
KEY = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
@ -50,16 +54,24 @@ class GroupBase(QtWidgets.QGroupBox):
|
||||||
if self.ignore_value_changed:
|
if self.ignore_value_changed:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
value = self.convert_value_from_qt(value)
|
||||||
if value != self.settings.valueOrDefault(self.KEY):
|
if value != self.settings.valueOrDefault(self.KEY):
|
||||||
logger.debug(f'Setting {self.KEY} changed to: {value}')
|
logger.debug(f'Setting {self.KEY} changed to: {value}')
|
||||||
self.settings.setValue(self.KEY, value)
|
self.settings.setValue(self.KEY, value)
|
||||||
self.update_title()
|
self.update_title()
|
||||||
|
|
||||||
|
def convert_value_from_qt(self, value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
def on_restore_defaults(self):
|
||||||
|
new_value = self.settings.valueOrDefault(self.KEY)
|
||||||
|
self.ignore_value_changed = True
|
||||||
|
self.set_value(new_value)
|
||||||
|
self.ignore_value_changed = False
|
||||||
|
self.update_title()
|
||||||
|
|
||||||
|
|
||||||
class RadioGroup(GroupBase):
|
class RadioGroup(GroupBase):
|
||||||
TITLE = None
|
|
||||||
HELPTEXT = None
|
|
||||||
KEY = None
|
|
||||||
OPTIONS = None
|
OPTIONS = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
@ -79,19 +91,12 @@ class RadioGroup(GroupBase):
|
||||||
self.ignore_value_changed = False
|
self.ignore_value_changed = False
|
||||||
self.layout.addStretch(100)
|
self.layout.addStretch(100)
|
||||||
|
|
||||||
def on_restore_defaults(self):
|
def set_value(self, value):
|
||||||
new_value = self.settings.valueOrDefault(self.KEY)
|
for old_value, btn in self.buttons.items():
|
||||||
self.ignore_value_changed = True
|
btn.setChecked(old_value == value)
|
||||||
for value, btn in self.buttons.items():
|
|
||||||
btn.setChecked(value == new_value)
|
|
||||||
self.ignore_value_changed = False
|
|
||||||
self.update_title()
|
|
||||||
|
|
||||||
|
|
||||||
class IntegerGroup(GroupBase):
|
class IntegerGroup(GroupBase):
|
||||||
TITLE = None
|
|
||||||
HELPTEXT = None
|
|
||||||
KEY = None
|
|
||||||
MIN = None
|
MIN = None
|
||||||
MAX = None
|
MAX = None
|
||||||
|
|
||||||
|
|
@ -99,18 +104,33 @@ class IntegerGroup(GroupBase):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.input = QtWidgets.QSpinBox()
|
self.input = QtWidgets.QSpinBox()
|
||||||
self.input.setRange(self.MIN, self.MAX)
|
self.input.setRange(self.MIN, self.MAX)
|
||||||
self.input.setValue(self.settings.valueOrDefault(self.KEY))
|
self.set_value(self.settings.valueOrDefault(self.KEY))
|
||||||
self.input.valueChanged.connect(self.on_value_changed)
|
self.input.valueChanged.connect(self.on_value_changed)
|
||||||
self.layout.addWidget(self.input)
|
self.layout.addWidget(self.input)
|
||||||
self.layout.addStretch(100)
|
self.layout.addStretch(100)
|
||||||
self.ignore_value_changed = False
|
self.ignore_value_changed = False
|
||||||
|
|
||||||
def on_restore_defaults(self):
|
def set_value(self, value):
|
||||||
new_value = self.settings.valueOrDefault(self.KEY)
|
self.input.setValue(value)
|
||||||
self.ignore_value_changed = True
|
|
||||||
self.input.setValue(new_value)
|
|
||||||
|
class SingleCheckboxGroup(GroupBase):
|
||||||
|
LABEL = None
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.input = QtWidgets.QCheckBox(self.LABEL)
|
||||||
|
self.set_value(self.settings.valueOrDefault(self.KEY))
|
||||||
|
self.input.checkStateChanged.connect(self.on_value_changed)
|
||||||
|
self.layout.addWidget(self.input)
|
||||||
|
self.layout.addStretch(100)
|
||||||
self.ignore_value_changed = False
|
self.ignore_value_changed = False
|
||||||
self.update_title()
|
|
||||||
|
def set_value(self, value):
|
||||||
|
self.input.setChecked(value)
|
||||||
|
|
||||||
|
def convert_value_from_qt(self, value):
|
||||||
|
return value == Qt.CheckState.Checked
|
||||||
|
|
||||||
|
|
||||||
class ImageStorageFormatWidget(RadioGroup):
|
class ImageStorageFormatWidget(RadioGroup):
|
||||||
|
|
@ -144,6 +164,15 @@ class AllocationLimitWidget(IntegerGroup):
|
||||||
MAX = 10000
|
MAX = 10000
|
||||||
|
|
||||||
|
|
||||||
|
class ConfirmCloseUnsavedWidget(SingleCheckboxGroup):
|
||||||
|
TITLE = 'Confirm when closing an unsaved file:'
|
||||||
|
HELPTEXT = (
|
||||||
|
'When about to close an unsaved file, should BeeRef ask for '
|
||||||
|
'confirmation?')
|
||||||
|
LABEL = 'Confirm when closing'
|
||||||
|
KEY = 'Save/confirm_close_unsaved'
|
||||||
|
|
||||||
|
|
||||||
class SettingsDialog(QtWidgets.QDialog):
|
class SettingsDialog(QtWidgets.QDialog):
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
@ -154,11 +183,18 @@ class SettingsDialog(QtWidgets.QDialog):
|
||||||
misc = QtWidgets.QWidget()
|
misc = QtWidgets.QWidget()
|
||||||
misc_layout = QtWidgets.QGridLayout()
|
misc_layout = QtWidgets.QGridLayout()
|
||||||
misc.setLayout(misc_layout)
|
misc.setLayout(misc_layout)
|
||||||
misc_layout.addWidget(ImageStorageFormatWidget(), 0, 0)
|
misc_layout.addWidget(ConfirmCloseUnsavedWidget(), 0, 0)
|
||||||
misc_layout.addWidget(ArrangeGapWidget(), 0, 1)
|
|
||||||
misc_layout.addWidget(AllocationLimitWidget(), 1, 0)
|
|
||||||
tabs.addTab(misc, '&Miscellaneous')
|
tabs.addTab(misc, '&Miscellaneous')
|
||||||
|
|
||||||
|
# Images & Items
|
||||||
|
items = QtWidgets.QWidget()
|
||||||
|
items_layout = QtWidgets.QGridLayout()
|
||||||
|
items.setLayout(items_layout)
|
||||||
|
items_layout.addWidget(ImageStorageFormatWidget(), 0, 0)
|
||||||
|
items_layout.addWidget(ArrangeGapWidget(), 0, 1)
|
||||||
|
items_layout.addWidget(AllocationLimitWidget(), 1, 0)
|
||||||
|
tabs.addTab(items, '&Images && Items')
|
||||||
|
|
||||||
layout = QtWidgets.QVBoxLayout()
|
layout = QtWidgets.QVBoxLayout()
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
layout.addWidget(tabs)
|
layout.addWidget(tabs)
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ requires-python = ">=3.9,<3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"exif>=1.3.5,<=1.6.0",
|
"exif>=1.3.5,<=1.6.0",
|
||||||
"lxml==5.1.0",
|
"lxml==5.1.0",
|
||||||
"pyQt6-Qt6>=6.5.3,<=6.7.0",
|
"pyQt6-Qt6>=6.7.0,<=6.7.0",
|
||||||
"pyQt6>=6.5.0,<=6.7.0",
|
"pyQt6>=6.7.0,<=6.7.0",
|
||||||
"rectangle-packer>=2.0.1,<=2.0.2",
|
"rectangle-packer>=2.0.1,<=2.0.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
3
setup.py
Normal file
3
setup.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
setup()
|
||||||
|
|
@ -20,7 +20,7 @@ class FooWidget(QtWidgets.QWidget, ActionsMixin):
|
||||||
def on_bar(self):
|
def on_bar(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def open_from_file(self):
|
def on_action_open_recent_file(self, filename):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from unittest.mock import MagicMock, patch, mock_open
|
||||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||||
from PyQt6.QtCore import Qt
|
from PyQt6.QtCore import Qt
|
||||||
|
|
||||||
from beeref import widgets
|
from beeref import commands, widgets
|
||||||
from beeref.actions import actions
|
from beeref.actions import actions
|
||||||
from beeref.config import logfile_name
|
from beeref.config import logfile_name
|
||||||
from beeref.items import BeePixmapItem, BeeTextItem
|
from beeref.items import BeePixmapItem, BeeTextItem
|
||||||
|
|
@ -153,6 +153,88 @@ def test_fit_rect_toggle_when_previous(center_mock, fit_mock, view):
|
||||||
assert view.get_scale() == 2
|
assert view.get_scale() == 2
|
||||||
|
|
||||||
|
|
||||||
|
@patch('PyQt6.QtWidgets.QMessageBox.question')
|
||||||
|
def test_get_confirmation_unsaved_changes_when_no_changes(
|
||||||
|
dlg_mock, settings, view, item):
|
||||||
|
view.scene.addItem(item)
|
||||||
|
assert view.undo_stack.isClean()
|
||||||
|
assert view.get_confirmation_unsaved_changes('foo') is True
|
||||||
|
dlg_mock.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
@patch('PyQt6.QtWidgets.QMessageBox.question')
|
||||||
|
def test_get_confirmation_unsaved_changes_when_changes_confirmation_disabled(
|
||||||
|
dlg_mock, settings, view, item):
|
||||||
|
settings.setValue('Save/confirm_close_unsaved', False)
|
||||||
|
view.undo_stack.push(
|
||||||
|
commands.InsertItems(view.scene, [item], QtCore.QPointF(0, 0)))
|
||||||
|
assert view.undo_stack.isClean() is False
|
||||||
|
assert view.get_confirmation_unsaved_changes('foo') is True
|
||||||
|
dlg_mock.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
@patch('PyQt6.QtWidgets.QMessageBox.question',
|
||||||
|
return_value=QtWidgets.QMessageBox.StandardButton.Yes)
|
||||||
|
def test_get_confirmation_unsaved_changes_when_changes_confirmed(
|
||||||
|
dlg_mock, settings, view, item):
|
||||||
|
view.undo_stack.push(
|
||||||
|
commands.InsertItems(view.scene, [item], QtCore.QPointF(0, 0)))
|
||||||
|
assert view.undo_stack.isClean() is False
|
||||||
|
assert view.get_confirmation_unsaved_changes('foo') is True
|
||||||
|
dlg_mock.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
@patch('PyQt6.QtWidgets.QMessageBox.question',
|
||||||
|
return_value=QtWidgets.QMessageBox.StandardButton.Cancel)
|
||||||
|
def test_get_confirmation_unsaved_changes_when_changes_not_confirmed(
|
||||||
|
dlg_mock, settings, view, item):
|
||||||
|
view.undo_stack.push(
|
||||||
|
commands.InsertItems(view.scene, [item], QtCore.QPointF(0, 0)))
|
||||||
|
assert view.undo_stack.isClean() is False
|
||||||
|
assert view.get_confirmation_unsaved_changes('foo') is False
|
||||||
|
dlg_mock.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
@patch('beeref.view.BeeGraphicsView.get_confirmation_unsaved_changes',
|
||||||
|
return_value=False)
|
||||||
|
def test_on_action_new_scene_when_unsaved_changes_not_confirmed(
|
||||||
|
confirm_mock, view):
|
||||||
|
view.clear_scene = MagicMock()
|
||||||
|
view.on_action_new_scene()
|
||||||
|
confirm_mock.assert_called_once()
|
||||||
|
view.clear_scene.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
@patch('beeref.view.BeeGraphicsView.get_confirmation_unsaved_changes',
|
||||||
|
return_value=True)
|
||||||
|
def test_on_action_new_scene_when_unsaved_changes_confirmed(
|
||||||
|
confirm_mock, view):
|
||||||
|
view.clear_scene = MagicMock()
|
||||||
|
view.on_action_new_scene()
|
||||||
|
confirm_mock.assert_called_once()
|
||||||
|
view.clear_scene.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
@patch('beeref.view.BeeGraphicsView.get_confirmation_unsaved_changes',
|
||||||
|
return_value=False)
|
||||||
|
def test_on_action_open_recent_file_when_unsaved_changes_not_confirmed(
|
||||||
|
confirm_mock, view):
|
||||||
|
view.open_from_file = MagicMock()
|
||||||
|
view.on_action_open_recent_file('foo.bee')
|
||||||
|
confirm_mock.assert_called_once()
|
||||||
|
view.open_from_file.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
@patch('beeref.view.BeeGraphicsView.get_confirmation_unsaved_changes',
|
||||||
|
return_value=True)
|
||||||
|
def test_on_action_open_recent_file_when_unsaved_changes_confirmed(
|
||||||
|
confirm_mock, view):
|
||||||
|
view.open_from_file = MagicMock()
|
||||||
|
view.on_action_open_recent_file('foo.bee')
|
||||||
|
confirm_mock.assert_called_once()
|
||||||
|
view.open_from_file.assert_called_once_with('foo.bee')
|
||||||
|
|
||||||
|
|
||||||
@patch('beeref.view.BeeGraphicsView.clear_scene')
|
@patch('beeref.view.BeeGraphicsView.clear_scene')
|
||||||
def test_open_from_file(clear_mock, view, qtbot):
|
def test_open_from_file(clear_mock, view, qtbot):
|
||||||
root = os.path.dirname(__file__)
|
root = os.path.dirname(__file__)
|
||||||
|
|
@ -199,6 +281,19 @@ def test_on_action_open(dialog_mock, view, qtbot):
|
||||||
view.cancel_active_modes.assert_called_with()
|
view.cancel_active_modes.assert_called_with()
|
||||||
|
|
||||||
|
|
||||||
|
@patch('beeref.view.BeeGraphicsView.get_confirmation_unsaved_changes',
|
||||||
|
return_value=False)
|
||||||
|
@patch('PyQt6.QtWidgets.QFileDialog.getOpenFileName')
|
||||||
|
def test_on_action_open_when_unsaved_changes_not_confirmed(
|
||||||
|
dialog_mock, confirm_mock, view):
|
||||||
|
view.cancel_active_modes = MagicMock()
|
||||||
|
view.open_from_file = MagicMock()
|
||||||
|
view.on_action_open()
|
||||||
|
view.cancel_active_modes.assert_not_called()
|
||||||
|
dialog_mock.assert_not_called()
|
||||||
|
view.open_from_file.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
@patch('PyQt6.QtWidgets.QFileDialog.getOpenFileName')
|
@patch('PyQt6.QtWidgets.QFileDialog.getOpenFileName')
|
||||||
@patch('beeref.view.BeeGraphicsView.open_from_file')
|
@patch('beeref.view.BeeGraphicsView.open_from_file')
|
||||||
def test_on_action_open_when_no_filename(open_mock, dialog_mock, view):
|
def test_on_action_open_when_no_filename(open_mock, dialog_mock, view):
|
||||||
|
|
@ -464,6 +559,26 @@ def test_on_action_export_images_file_exists_canceled(
|
||||||
imgfilename.read_text() == 'foo'
|
imgfilename.read_text() == 'foo'
|
||||||
|
|
||||||
|
|
||||||
|
@patch('beeref.view.BeeGraphicsView.get_confirmation_unsaved_changes',
|
||||||
|
return_value=False)
|
||||||
|
@patch('beeref.__main__.BeeRefApplication.quit')
|
||||||
|
def test_on_action_quit_when_unsaved_changes_not_confirmed(
|
||||||
|
quit_mock, confirm_mock, view):
|
||||||
|
view.on_action_quit()
|
||||||
|
confirm_mock.assert_called_once()
|
||||||
|
quit_mock.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
@patch('beeref.view.BeeGraphicsView.get_confirmation_unsaved_changes',
|
||||||
|
return_value=True)
|
||||||
|
@patch('beeref.__main__.BeeRefApplication.quit')
|
||||||
|
def test_on_action_quit_when_unsaved_changes_confirmed(
|
||||||
|
quit_mock, confirm_mock, view):
|
||||||
|
view.on_action_quit()
|
||||||
|
confirm_mock.assert_called_once()
|
||||||
|
quit_mock.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
@patch('beeref.widgets.settings.SettingsDialog.show')
|
@patch('beeref.widgets.settings.SettingsDialog.show')
|
||||||
def test_on_action_settings(show_mock, view):
|
def test_on_action_settings(show_mock, view):
|
||||||
view.on_action_settings()
|
view.on_action_settings()
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ from PyQt6 import QtWidgets
|
||||||
|
|
||||||
from beeref.widgets.settings import (
|
from beeref.widgets.settings import (
|
||||||
ArrangeGapWidget,
|
ArrangeGapWidget,
|
||||||
|
ConfirmCloseUnsavedWidget,
|
||||||
ImageStorageFormatWidget,
|
ImageStorageFormatWidget,
|
||||||
SettingsDialog,
|
SettingsDialog,
|
||||||
)
|
)
|
||||||
|
|
@ -31,7 +32,7 @@ def test_image_storage_format_selects_radiobox(settings, view):
|
||||||
def test_image_storage_format_saves_change(settings, view):
|
def test_image_storage_format_saves_change(settings, view):
|
||||||
settings.setValue('Items/image_storage_format', 'best')
|
settings.setValue('Items/image_storage_format', 'best')
|
||||||
widget = ImageStorageFormatWidget()
|
widget = ImageStorageFormatWidget()
|
||||||
widget.buttons['jpg'].setChecked(True)
|
widget.set_value('jpg')
|
||||||
assert widget.buttons['best'].isChecked() is False
|
assert widget.buttons['best'].isChecked() is False
|
||||||
assert widget.buttons['png'].isChecked() is False
|
assert widget.buttons['png'].isChecked() is False
|
||||||
assert widget.buttons['jpg'].isChecked() is True
|
assert widget.buttons['jpg'].isChecked() is True
|
||||||
|
|
@ -41,7 +42,7 @@ def test_image_storage_format_saves_change(settings, view):
|
||||||
|
|
||||||
def test_image_storage_format_on_restore_defaults(settings, view):
|
def test_image_storage_format_on_restore_defaults(settings, view):
|
||||||
widget = ImageStorageFormatWidget()
|
widget = ImageStorageFormatWidget()
|
||||||
widget.buttons['jpg'].setChecked(True)
|
widget.set_value('jpg')
|
||||||
settings.setValue('Items/image_storage_format', 'best')
|
settings.setValue('Items/image_storage_format', 'best')
|
||||||
widget.on_restore_defaults()
|
widget.on_restore_defaults()
|
||||||
assert widget.buttons['best'].isChecked() is True
|
assert widget.buttons['best'].isChecked() is True
|
||||||
|
|
@ -70,27 +71,63 @@ def test_arrange_gap_sets_title_when_edited(settings, view):
|
||||||
def test_arrange_gap_saves_change(settings, view):
|
def test_arrange_gap_saves_change(settings, view):
|
||||||
settings.setValue('Items/arrange_gap', 6)
|
settings.setValue('Items/arrange_gap', 6)
|
||||||
widget = ArrangeGapWidget()
|
widget = ArrangeGapWidget()
|
||||||
widget.input.setValue(8)
|
widget.set_value(8)
|
||||||
assert settings.valueOrDefault('Items/arrange_gap') == 8
|
assert settings.valueOrDefault('Items/arrange_gap') == 8
|
||||||
assert widget.title() == 'Arrange Gap: ✎'
|
assert widget.title() == 'Arrange Gap: ✎'
|
||||||
|
|
||||||
|
|
||||||
def test_arrange_gap_on_restore_defaults(settings, view):
|
def test_arrange_gap_on_restore_defaults(settings, view):
|
||||||
widget = ArrangeGapWidget()
|
widget = ArrangeGapWidget()
|
||||||
widget.input.setValue(7)
|
widget.set_value(7)
|
||||||
settings.setValue('Items/arrange_gap', 0)
|
settings.setValue('Items/arrange_gap', 0)
|
||||||
widget.on_restore_defaults()
|
widget.on_restore_defaults()
|
||||||
assert widget.input.value() == 0
|
assert widget.input.value() == 0
|
||||||
assert widget.title() == 'Arrange Gap:'
|
assert widget.title() == 'Arrange Gap:'
|
||||||
|
|
||||||
|
|
||||||
|
def test_confirm_closed_initialises_input_from_settings(settings, view):
|
||||||
|
settings.setValue('Save/confirm_close_unsaved', False)
|
||||||
|
widget = ConfirmCloseUnsavedWidget()
|
||||||
|
assert widget.input.isChecked() is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_confirm_closed_sets_title_when_not_edited(settings, view):
|
||||||
|
widget = ConfirmCloseUnsavedWidget()
|
||||||
|
assert widget.title() == 'Confirm when closing an unsaved file:'
|
||||||
|
|
||||||
|
|
||||||
|
def test_confirm_closed_sets_title_when_edited(settings, view):
|
||||||
|
settings.setValue('Save/confirm_close_unsaved', False)
|
||||||
|
widget = ConfirmCloseUnsavedWidget()
|
||||||
|
assert widget.title() == 'Confirm when closing an unsaved file: ✎'
|
||||||
|
|
||||||
|
|
||||||
|
def test_confirm_closed_saves_change(settings, view):
|
||||||
|
settings.setValue('Save/confirm_close_unsaved', True)
|
||||||
|
widget = ConfirmCloseUnsavedWidget()
|
||||||
|
widget.set_value(False)
|
||||||
|
assert settings.valueOrDefault('Save/confirm_close_unsaved') is False
|
||||||
|
assert widget.title() == 'Confirm when closing an unsaved file: ✎'
|
||||||
|
|
||||||
|
|
||||||
|
def test_confirm_closed_on_restore_defaults(settings, view):
|
||||||
|
widget = ConfirmCloseUnsavedWidget()
|
||||||
|
widget.set_value(False)
|
||||||
|
settings.setValue('Save/confirm_close_unsaved', True)
|
||||||
|
widget.on_restore_defaults()
|
||||||
|
assert widget.input.isChecked() is True
|
||||||
|
assert widget.title() == 'Confirm when closing an unsaved file:'
|
||||||
|
|
||||||
|
|
||||||
@patch('PyQt6.QtWidgets.QMessageBox.question',
|
@patch('PyQt6.QtWidgets.QMessageBox.question',
|
||||||
return_value=QtWidgets.QMessageBox.StandardButton.Yes)
|
return_value=QtWidgets.QMessageBox.StandardButton.Yes)
|
||||||
def test_settings_dialog_on_restore_defaults(msg_mock, settings, view):
|
def test_settings_dialog_on_restore_defaults(msg_mock, settings, view):
|
||||||
dialog = SettingsDialog(view)
|
dialog = SettingsDialog(view)
|
||||||
settings.setValue('Items/image_storage_format', 'jpg')
|
settings.setValue('Items/image_storage_format', 'jpg')
|
||||||
settings.setValue('Items/arrange_gap', 10)
|
settings.setValue('Items/arrange_gap', 10)
|
||||||
|
settings.setValue('Save/confirm_close_unsaved', False)
|
||||||
dialog.on_restore_defaults()
|
dialog.on_restore_defaults()
|
||||||
msg_mock.assert_called_once()
|
msg_mock.assert_called_once()
|
||||||
assert settings.valueOrDefault('Items/image_storage_format') == 'best'
|
assert settings.valueOrDefault('Items/image_storage_format') == 'best'
|
||||||
assert settings.valueOrDefault('Items/arrange_gap') == 0
|
assert settings.valueOrDefault('Items/arrange_gap') == 0
|
||||||
|
assert settings.valueOrDefault('Save/confirm_close_unsaved') is True
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue