diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 94595a8..1b959f8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,8 @@ Added Keyboard Shortcuts). The KeyboardSettings.ini file will now only store values which are changed from the default, since it's no longer needed as a reference. +* Settings dialog now displays icons to indicate changes from default + values Fixed diff --git a/beeref/actions/actions.py b/beeref/actions/actions.py index e2884b8..08da46a 100644 --- a/beeref/actions/actions.py +++ b/beeref/actions/actions.py @@ -87,6 +87,7 @@ class Action(dict): return QtGui.QKeySequence() def shortcuts_changed(self): + """Whether shortcuts have changed from their defaults.""" return self.get_shortcuts() != self.get('shortcuts', []) def get_default_shortcut(self, index): diff --git a/beeref/config.py b/beeref/config.py index d2e6d4c..9368c33 100644 --- a/beeref/config.py +++ b/beeref/config.py @@ -159,6 +159,11 @@ class BeeSettings(QtCore.QSettings): val = conf['default'] return val + def value_changed(self, key): + """Whether the value for given key has changed from its default.""" + + return self.valueOrDefault(key) != self.FIELDS[key]['default'] + def restore_defaults(self): """Restore all the values specified in FILEDS to their default values by removing them from the settings file. diff --git a/beeref/widgets/settings.py b/beeref/widgets/settings.py index e4f9890..1c3d44f 100644 --- a/beeref/widgets/settings.py +++ b/beeref/widgets/settings.py @@ -26,57 +26,73 @@ from beeref.config import BeeSettings, KeyboardSettings, settings_events logger = logging.getLogger(__name__) -class RadioGroup(QtWidgets.QGroupBox): +CHANGED_SYMBOL = '✎' + + +class GroupBase(QtWidgets.QGroupBox): + + def __init__(self): + super().__init__() + self.settings = BeeSettings() + self.update_title() + self.layout = QtWidgets.QVBoxLayout() + self.setLayout(self.layout) + settings_events.restore_defaults.connect(self.on_restore_defaults) + + if self.HELPTEXT: + helptxt = QtWidgets.QLabel(self.HELPTEXT) + helptxt.setWordWrap(True) + self.layout.addWidget(helptxt) + + def update_title(self): + title = [self.TITLE] + if self.settings.value_changed(self.KEY): + title.append(CHANGED_SYMBOL) + self.setTitle(' '.join(title)) + + def on_value_changed(self, value): + if self.ignore_value_changed: + return + + if value != self.settings.valueOrDefault(self.KEY): + logger.debug(f'Setting {self.KEY} changed to: {value}') + self.settings.setValue(self.KEY, value) + self.update_title() + + +class RadioGroup(GroupBase): TITLE = None HELPTEXT = None KEY = None OPTIONS = None def __init__(self): - super().__init__(self.TITLE) - self.settings = BeeSettings() - layout = QtWidgets.QVBoxLayout() - self.setLayout(layout) - settings_events.restore_keyboard_defaults.connect( - self.on_restore_defaults) + super().__init__() - if self.HELPTEXT: - helptxt = QtWidgets.QLabel(self.HELPTEXT) - helptxt.setWordWrap(True) - layout.addWidget(helptxt) - - self.ignore_values_changed = True + self.ignore_value_changed = True self.buttons = {} for (value, label, helptext) in self.OPTIONS: btn = QtWidgets.QRadioButton(label) self.buttons[value] = btn btn.setToolTip(helptext) - btn.toggled.connect( - partial(self.on_values_changed, value=value, button=btn)) + btn.toggled.connect(partial(self.on_value_changed, value=value)) if value == self.settings.valueOrDefault(self.KEY): btn.setChecked(True) - layout.addWidget(btn) + self.layout.addWidget(btn) - self.ignore_values_changed = False - layout.addStretch(100) - - def on_values_changed(self, value, button): - if self.ignore_values_changed: - return - - if value != self.settings.valueOrDefault(self.KEY): - logger.debug(f'Setting {self.KEY} changed to: {value}') - self.settings.setValue(self.KEY, value) + self.ignore_value_changed = False + self.layout.addStretch(100) def on_restore_defaults(self): new_value = self.settings.valueOrDefault(self.KEY) - self.ignore_values_changed = True + self.ignore_value_changed = True for value, btn in self.buttons.items(): btn.setChecked(value == new_value) - self.ignore_values_changed = False + self.ignore_value_changed = False + self.update_title() -class IntegerGroup(QtWidgets.QGroupBox): +class IntegerGroup(GroupBase): TITLE = None HELPTEXT = None KEY = None @@ -84,38 +100,21 @@ class IntegerGroup(QtWidgets.QGroupBox): MAX = None def __init__(self): - super().__init__(self.TITLE) - self.settings = BeeSettings() - layout = QtWidgets.QVBoxLayout() - self.setLayout(layout) - settings_events.restore_defaults.connect(self.on_restore_defaults) - - if self.HELPTEXT: - helptxt = QtWidgets.QLabel(self.HELPTEXT) - helptxt.setWordWrap(True) - layout.addWidget(helptxt) - + super().__init__() self.input = QtWidgets.QSpinBox() self.input.setValue(self.settings.valueOrDefault(self.KEY)) self.input.setRange(self.MIN, self.MAX) self.input.valueChanged.connect(self.on_value_changed) - layout.addWidget(self.input) - layout.addStretch(100) - self.ignore_values_changed = False - - def on_value_changed(self, value): - if self.ignore_values_changed: - return - - if value != self.settings.valueOrDefault(self.KEY): - logger.debug(f'Setting {self.KEY} changed to: {value}') - self.settings.setValue(self.KEY, value) + self.layout.addWidget(self.input) + self.layout.addStretch(100) + self.ignore_value_changed = False def on_restore_defaults(self): new_value = self.settings.valueOrDefault(self.KEY) - self.ignore_values_changed = True + self.ignore_value_changed = True self.input.setValue(new_value) - self.ignore_values_changed = False + self.ignore_value_changed = False + self.update_title() class ImageStorageFormatWidget(RadioGroup): @@ -262,7 +261,7 @@ class KeyboardShortcutsModel(QtCore.QAbstractTableModel): if index.column() == 0: return txt.replace('&', '').removesuffix('...') if index.column() == 1 and action.shortcuts_changed(): - return '✎' + return CHANGED_SYMBOL if index.column() > 1: return action.get_qkeysequence(index.column() - 2) @@ -353,7 +352,8 @@ class KeyboardShortcutsView(QtWidgets.QTableView): 1, QtWidgets.QHeaderView.ResizeMode.ResizeToContents) self.setSelectionMode( QtWidgets.QHeaderView.SelectionMode.SingleSelection) - settings_events.restore_defaults.connect(self.on_restore_defaults) + settings_events.restore_keyboard_defaults.connect( + self.on_restore_defaults) def on_restore_defaults(self): self.viewport().update() diff --git a/tests/test_config.py b/tests/test_config.py index f4d27d7..d40da3f 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -59,6 +59,15 @@ def test_settings_value_or_default_gets_default_when_cast_error(settings): assert settings.valueOrDefault('Items/arrange_gap') == 0 +def test_settings_value_changed_when_default(settings): + assert settings.value_changed('Items/image_storage_format') is False + + +def test_settings_value_changed_when_chagned(settings): + settings.setValue('Items/image_storage_format', 'jpg') + assert settings.value_changed('Items/image_storage_format') is True + + def test_settings_restore_defaults_restores(settings): settings.setValue('Items/image_storage_format', 'png') settings.restore_defaults() diff --git a/tests/widgets/test_settings.py b/tests/widgets/test_settings.py index b593581..b2f05dc 100644 --- a/tests/widgets/test_settings.py +++ b/tests/widgets/test_settings.py @@ -15,6 +15,17 @@ from beeref.widgets.settings import ( ) +def test_image_storage_format_sets_title_when_not_edited(settings, view): + widget = ImageStorageFormatWidget() + assert widget.title() == 'Image Storage Format:' + + +def test_image_storage_format_sets_title_when_edited(settings, view): + settings.setValue('Items/image_storage_format', 'jpg') + widget = ImageStorageFormatWidget() + assert widget.title() == 'Image Storage Format: ✎' + + def test_image_storage_format_selects_radiobox(settings, view): settings.setValue('Items/image_storage_format', 'jpg') widget = ImageStorageFormatWidget() @@ -31,6 +42,7 @@ def test_image_storage_format_saves_change(settings, view): assert widget.buttons['png'].isChecked() is False assert widget.buttons['jpg'].isChecked() is True assert settings.valueOrDefault('Items/image_storage_format') == 'jpg' + assert widget.title() == 'Image Storage Format: ✎' def test_image_storage_format_on_restore_defaults(settings, view): @@ -41,6 +53,7 @@ def test_image_storage_format_on_restore_defaults(settings, view): assert widget.buttons['best'].isChecked() is True assert widget.buttons['png'].isChecked() is False assert widget.buttons['jpg'].isChecked() is False + assert widget.title() == 'Image Storage Format:' def test_arrange_gap_initialises_input_from_settings(settings, view): @@ -49,11 +62,23 @@ def test_arrange_gap_initialises_input_from_settings(settings, view): assert widget.input.value() == 6 +def test_arrange_gap_sets_title_when_not_edited(settings, view): + widget = ArrangeGapWidget() + assert widget.title() == 'Arrange Gap:' + + +def test_arrange_gap_sets_title_when_edited(settings, view): + settings.setValue('Items/arrange_gap', 6) + widget = ArrangeGapWidget() + assert widget.title() == 'Arrange Gap: ✎' + + def test_arrange_gap_saves_change(settings, view): settings.setValue('Items/arrange_gap', 6) widget = ArrangeGapWidget() widget.input.setValue(8) assert settings.valueOrDefault('Items/arrange_gap') == 8 + assert widget.title() == 'Arrange Gap: ✎' def test_arrange_gap_on_restore_defaults(settings, view): @@ -62,6 +87,7 @@ def test_arrange_gap_on_restore_defaults(settings, view): settings.setValue('Items/arrange_gap', 0) widget.on_restore_defaults() assert widget.input.value() == 0 + assert widget.title() == 'Arrange Gap:' @patch('PyQt6.QtWidgets.QMessageBox.question',