From 5475f5d8439ef4608501692d567d166f8bb4e8a7 Mon Sep 17 00:00:00 2001 From: Rebecca Breu Date: Tue, 5 Oct 2021 10:44:03 +0200 Subject: [PATCH] Create keyboard settings file; keyboard settings for recent files --- CHANGELOG.rst | 2 + beeref/actions/actions.py | 5 +++ beeref/actions/menu_structure.py | 6 +++ beeref/actions/mixin.py | 16 +++++-- beeref/config.py | 9 +++- beeref/view.py | 5 +++ tests/actions/test_mixin.py | 75 ++++++++++++++++++++++++++++++-- tests/conftest.py | 4 ++ tests/test_config.py | 29 ++++++++++-- 9 files changed, 140 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 68646b8..726f5c4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,8 @@ Added ----- * Show list of recent files on welcome screen +* Keyboard shortcuts can now be configured via a settings file. + Go to "Settings -> Open Settings Folder" and edit KeyboardSettings.ini Fixed ----- diff --git a/beeref/actions/actions.py b/beeref/actions/actions.py index 5327fac..1e1c395 100644 --- a/beeref/actions/actions.py +++ b/beeref/actions/actions.py @@ -265,4 +265,9 @@ actions = [ 'checkable': True, 'callback': 'on_action_always_on_top', }, + { + 'id': 'open_settings_dir', + 'text': 'Open Settings Folder', + 'callback': 'on_action_open_settings_dir', + }, ] diff --git a/beeref/actions/menu_structure.py b/beeref/actions/menu_structure.py index 870c69d..cf2aab9 100644 --- a/beeref/actions/menu_structure.py +++ b/beeref/actions/menu_structure.py @@ -98,6 +98,12 @@ menu_structure = [ 'arrange_vertical', ], }, + { + 'menu': '&Settings', + 'items': [ + 'open_settings_dir', + ], + }, { 'menu': '&Help', 'items': [ diff --git a/beeref/actions/mixin.py b/beeref/actions/mixin.py index 2a2536a..04deb17 100644 --- a/beeref/actions/mixin.py +++ b/beeref/actions/mixin.py @@ -112,16 +112,26 @@ class ActionsMixin: files = self.settings.get_recent_files(existing_only=True) items = [] + i = -1 for i, filename in enumerate(files): qaction = QtGui.QAction(os.path.basename(filename), self) + action_id = f'recent_files_{i}' key = 0 if i == 9 else i + 1 if key < 10: - qaction.setShortcuts([f'Ctrl+{key}']) + shortcuts = KeyboardSettings().get_shortcuts( + 'Actions', action_id, [f'Ctrl+{key}']) + qaction.setShortcuts(shortcuts) qaction.triggered.connect(partial(self.open_from_file, filename)) self.addAction(qaction) self._recent_files_submenu.addAction(qaction) - self.bee_actions[f'recent_files_{i}'] = qaction - items.append(f'recent_files_{i}') + self.bee_actions[action_id] = qaction + items.append(action_id) + + # Set shorcuts in settings file for remaining slots: + for j in range(i + 1, 10): + key = 0 if j == 9 else j + 1 + KeyboardSettings().get_shortcuts( + 'Actions', f'recent_files_{j}', [f'Ctrl+{key}']) def _clear_recent_files(self): for action in self._recent_files_submenu.actions(): diff --git a/beeref/config.py b/beeref/config.py index c62bce2..4828dc5 100644 --- a/beeref/config.py +++ b/beeref/config.py @@ -143,6 +143,8 @@ class BeeSettings(QtCore.QSettings): class KeyboardSettings(QtCore.QSettings): + save_unknown_shortcuts = True + def __init__(self): settings_format = QtCore.QSettings.Format.IniFormat filename = os.path.join( @@ -159,7 +161,12 @@ class KeyboardSettings(QtCore.QSettings): values = list(filter(lambda x: x, values.split(', '))) logger.debug(f'Found custom shortcuts for {group}/{key}: {values}') return values - return default or [] + + values = default or [] + if self.save_unknown_shortcuts: + self.set_shortcuts(group, key, values) + + return values def logfile_name(): diff --git a/beeref/view.py b/beeref/view.py index 46f46c3..df77912 100644 --- a/beeref/view.py +++ b/beeref/view.py @@ -489,6 +489,11 @@ class BeeGraphicsView(MainControlsMixin, return logger.info('No image data or text in clipboard') + def on_action_open_settings_dir(self): + dirname = os.path.dirname(self.settings.fileName()) + QtGui.QDesktopServices.openUrl( + QtCore.QUrl.fromLocalFile(dirname)) + def on_selection_changed(self): logger.debug('Currently selected items: %s', len(self.scene.selectedItems(user_only=True))) diff --git a/tests/actions/test_mixin.py b/tests/actions/test_mixin.py index e8c521e..ec56a60 100644 --- a/tests/actions/test_mixin.py +++ b/tests/actions/test_mixin.py @@ -1,5 +1,5 @@ import os.path -from unittest.mock import patch, MagicMock +from unittest.mock import patch, MagicMock, call from PyQt6 import QtWidgets @@ -26,8 +26,10 @@ class FooWidget(QtWidgets.QWidget, ActionsMixin): @patch('PyQt6.QtGui.QAction.toggled') @patch('beeref.actions.mixin.menu_structure') @patch('beeref.actions.mixin.actions') +@patch('beeref.actions.mixin.KeyboardSettings.get_shortcuts') def test_create_actions( - actions_mock, menu_mock, toggle_mock, trigger_mock, qapp): + kb_mock, actions_mock, menu_mock, toggle_mock, trigger_mock, qapp): + kb_mock.side_effect = lambda group, key, default: default widget = FooWidget() actions_mock.__iter__.return_value = [{ 'id': 'foo', @@ -47,6 +49,7 @@ def test_create_actions( assert qaction.shortcut() == 'Ctrl+F' assert qaction.isEnabled() is True assert widget.bee_actions['foo'] == qaction + kb_mock.assert_called_once_with('Actions', 'foo', ['Ctrl+F']) @patch('beeref.actions.mixin.menu_structure') @@ -267,7 +270,10 @@ def test_build_menu_and_actions_disables_actiongroups( @patch('PyQt6.QtGui.QAction.triggered') @patch('beeref.actions.mixin.menu_structure') @patch('beeref.actions.mixin.actions') -def test_create_recent_files(actions_mock, menu_mock, triggered_mock, qapp): +@patch('beeref.actions.mixin.KeyboardSettings.get_shortcuts') +def test_create_recent_files_more_files_than_shortcuts( + kb_mock, actions_mock, menu_mock, triggered_mock, qapp): + kb_mock.side_effect = lambda group, key, default: default widget = FooWidget() widget.settings.get_recent_files.return_value = [ os.path.abspath(f'{i}.bee') for i in range(15)] @@ -295,6 +301,69 @@ def test_create_recent_files(actions_mock, menu_mock, triggered_mock, qapp): assert qaction15.isEnabled() is True assert widget.bee_actions['recent_files_14'] == qaction15 + assert kb_mock.call_count == 10 + kb_mock.assert_has_calls( + [call('Actions', 'recent_files_0', ['Ctrl+1']), + call('Actions', 'recent_files_9', ['Ctrl+0'])], + any_order=True) + + +@patch('PyQt6.QtGui.QAction.triggered') +@patch('beeref.actions.mixin.menu_structure') +@patch('beeref.actions.mixin.actions') +@patch('beeref.actions.mixin.KeyboardSettings.get_shortcuts') +def test_create_recent_files_fewer_files_than_shortcuts( + kb_mock, actions_mock, menu_mock, triggered_mock, qapp): + kb_mock.side_effect = lambda group, key, default: default + widget = FooWidget() + widget.settings.get_recent_files.return_value = [ + os.path.abspath(f'{i}.bee') for i in range(5)] + menu_mock.__iter__.return_value = [{ + 'menu': 'Open &Recent', + 'items': '_build_recent_files', + }] + + widget.build_menu_and_actions() + triggered_mock.connect.assert_called() + assert len(widget.actions()) == 5 + qaction1 = widget.actions()[0] + assert qaction1.text() == '0.bee' + assert qaction1.shortcut() == 'Ctrl+1' + assert qaction1.isEnabled() is True + assert widget.bee_actions['recent_files_0'] == qaction1 + qaction5 = widget.actions()[4] + assert qaction5.text() == '4.bee' + assert qaction5.shortcut() == 'Ctrl+5' + assert qaction5.isEnabled() is True + assert widget.bee_actions['recent_files_4'] == qaction5 + + assert kb_mock.call_count == 10 + kb_mock.assert_has_calls( + [call('Actions', 'recent_files_0', ['Ctrl+1']), + call('Actions', 'recent_files_9', ['Ctrl+0'])], + any_order=True) + + +@patch('beeref.actions.mixin.menu_structure') +@patch('beeref.actions.mixin.actions') +@patch('beeref.actions.mixin.KeyboardSettings.get_shortcuts') +def test_create_recent_files_when_no_files( + kb_mock, actions_mock, menu_mock, qapp): + kb_mock.side_effect = lambda group, key, default: default + widget = FooWidget() + widget.settings.get_recent_files.return_value = [] + menu_mock.__iter__.return_value = [{ + 'menu': 'Open &Recent', + 'items': '_build_recent_files', + }] + widget.build_menu_and_actions() + assert len(widget.actions()) == 0 + assert kb_mock.call_count == 10 + kb_mock.assert_has_calls( + [call('Actions', 'recent_files_0', ['Ctrl+1']), + call('Actions', 'recent_files_9', ['Ctrl+0'])], + any_order=True) + @patch('PyQt6.QtGui.QAction.triggered') @patch('beeref.actions.mixin.menu_structure') diff --git a/tests/conftest.py b/tests/conftest.py index 5cfb114..893c13e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -17,6 +17,10 @@ def pytest_configure(config): import logging.config logging.config.dictConfig = MagicMock + # Disable creation of KeyboardSettings.ini to speed tests up + from beeref.config import KeyboardSettings + KeyboardSettings.save_unknown_shortcuts = False + @pytest.fixture(autouse=True) def commandline_args(): diff --git a/tests/test_config.py b/tests/test_config.py index 8dadb6a..1eb9b72 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -83,15 +83,36 @@ def test_keyboardsettings_set_shortcuts_multiple(kbsettings): assert kbsettings.get_shortcuts('Actions', 'foo') == ['Ctrl+F', 'Alt+O'] +def test_keyboardsettings_get_shortcuts_existing(kbsettings): + kbsettings.set_shortcuts('Actions', 'bar', ['Ctrl+R']) + with patch.object(kbsettings, 'set_shortcuts') as set_mock: + with patch.object(kbsettings, 'save_unknown_shortcuts', True): + shortcuts = kbsettings.get_shortcuts('Actions', 'bar', ['Ctrl+B']) + assert shortcuts == ['Ctrl+R'] + set_mock.assert_not_called() + + def test_keyboardsettings_get_shortcuts_default(kbsettings): - assert kbsettings.get_shortcuts('Actions', 'bar', ['Ctrl+B']) == ['Ctrl+B'] + with patch.object(kbsettings, 'set_shortcuts') as set_mock: + with patch.object(kbsettings, 'save_unknown_shortcuts', True): + shortcuts = kbsettings.get_shortcuts('Actions', 'bar', ['Ctrl+B']) + assert shortcuts == ['Ctrl+B'] + set_mock.assert_called_once_with('Actions', 'bar', ['Ctrl+B']) -def test_keyboardsettings_get_shortcuts_defaults_dont_overwrite_empty( +def test_keyboardsettings_get_shortcuts_default_doesnt_override_empty( kbsettings): kbsettings.set_shortcuts('Actions', 'bar', []) - assert kbsettings.get_shortcuts('Actions', 'bar', ['Ctrl+B']) == [] + with patch.object(kbsettings, 'set_shortcuts') as set_mock: + with patch.object(kbsettings, 'save_unknown_shortcuts', True): + shortcuts = kbsettings.get_shortcuts('Actions', 'bar', ['Ctrl+B']) + assert shortcuts == [] + set_mock.assert_not_called() def test_keyboardsettings_get_shortcuts_not_set_no_defaults(kbsettings): - assert kbsettings.get_shortcuts('Actions', 'baz') == [] + with patch('beeref.config.KeyboardSettings.set_shortcuts') as set_mock: + with patch.object(kbsettings, 'save_unknown_shortcuts', True): + shortcuts = kbsettings.get_shortcuts('Actions', 'baz') + assert shortcuts == [] + set_mock.assert_called_once_with('Actions', 'baz', [])