mirror of
https://github.com/rbreu/beeref.git
synced 2026-03-11 08:54:28 +00:00
Add dialog for changing keyboard settings
This commit is contained in:
parent
1eab4712d3
commit
fb3bc234c7
17 changed files with 1275 additions and 373 deletions
2
.github/workflows/pytest.yml
vendored
2
.github/workflows/pytest.yml
vendored
|
|
@ -9,7 +9,7 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
python-version: ['3.9', '3.11']
|
||||
pyqt-version: ['6.4.0', '6.6.1']
|
||||
pyqt-version: ['6.5.0', '6.6.1']
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@ Added
|
|||
* The opacity of images can be changed (Images -> Change Opacity).
|
||||
* Images can be set to display as grayscale (Images -> Grayscale).
|
||||
* The scene can now also be exported as SVG
|
||||
* Keyboard shortcuts can now be edited from within BeeRef (Settings ->
|
||||
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.
|
||||
|
||||
|
||||
Fixed
|
||||
|
|
|
|||
|
|
@ -13,308 +13,409 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with BeeRef. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
actions = [
|
||||
{
|
||||
from collections import OrderedDict
|
||||
from functools import cached_property
|
||||
import logging
|
||||
|
||||
from PyQt6 import QtGui
|
||||
|
||||
from beeref.actions.menu_structure import menu_structure
|
||||
from beeref.config import KeyboardSettings, settings_events
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Action(dict):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.qaction = None
|
||||
self.kb_settings = KeyboardSettings()
|
||||
super().__init__(*args, **kwargs)
|
||||
settings_events.restore_keyboard_defaults.connect(
|
||||
self.on_restore_defaults)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self['id'] == other['id']
|
||||
|
||||
def on_restore_defaults(self):
|
||||
if self.qaction:
|
||||
self.qaction.setShortcuts(self.get_shortcuts())
|
||||
|
||||
@cached_property
|
||||
def menu_path(self):
|
||||
path = []
|
||||
|
||||
def _get_path(menu_item):
|
||||
if isinstance(menu_item['items'], list):
|
||||
# This is a normal menu
|
||||
for item in menu_item['items']:
|
||||
if item == self['id']:
|
||||
path.append(menu_item['menu'])
|
||||
return True
|
||||
if isinstance(item, dict):
|
||||
# This is a submenu
|
||||
if _get_path(item):
|
||||
path.append(menu_item['menu'])
|
||||
return True
|
||||
elif menu_item['items'] == self.get('menu_id'):
|
||||
# This is a dynamic submenu (e.g. Recent Files)
|
||||
path.append(menu_item['menu'])
|
||||
return True
|
||||
|
||||
for menu_item in menu_structure:
|
||||
_get_path(menu_item)
|
||||
|
||||
return path[::-1]
|
||||
|
||||
def get_shortcuts(self):
|
||||
return self.kb_settings.get_shortcuts(
|
||||
'Actions', self['id'], self.get('shortcuts'))
|
||||
|
||||
def set_shortcuts(self, value):
|
||||
logger.debug(f'Setting shortcut "{self["id"]}" to: {value}')
|
||||
self.kb_settings.set_shortcuts(
|
||||
'Actions', self['id'], value, self.get('shortcuts'))
|
||||
if self.qaction:
|
||||
self.qaction.setShortcuts(value)
|
||||
|
||||
def get_qkeysequence(self, index):
|
||||
"""Current shortcuts as QKeySequence"""
|
||||
try:
|
||||
return QtGui.QKeySequence(self.get_shortcuts()[index])
|
||||
except IndexError:
|
||||
return QtGui.QKeySequence()
|
||||
|
||||
def shortcuts_changed(self):
|
||||
return self.get_shortcuts() != self.get('shortcuts', [])
|
||||
|
||||
def get_default_shortcut(self, index):
|
||||
try:
|
||||
return self.get('shortcuts', [])[index]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
|
||||
class ActionList(OrderedDict):
|
||||
|
||||
def __init__(self, actions):
|
||||
super().__init__()
|
||||
for action in actions:
|
||||
self[action['id']] = action
|
||||
|
||||
def __getitem__(self, key):
|
||||
if isinstance(key, int):
|
||||
key = list(self.keys())[key]
|
||||
return super().__getitem__(key)
|
||||
|
||||
|
||||
actions = ActionList([
|
||||
Action({
|
||||
'id': 'open',
|
||||
'text': '&Open',
|
||||
'shortcuts': ['Ctrl+O'],
|
||||
'callback': 'on_action_open',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'save',
|
||||
'text': '&Save',
|
||||
'shortcuts': ['Ctrl+S'],
|
||||
'callback': 'on_action_save',
|
||||
'group': 'active_when_items_in_scene',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'save_as',
|
||||
'text': 'Save &As...',
|
||||
'shortcuts': ['Ctrl+Shift+S'],
|
||||
'callback': 'on_action_save_as',
|
||||
'group': 'active_when_items_in_scene',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'export_scene',
|
||||
'text': 'E&xport Scene...',
|
||||
'shortcuts': ['Ctrl+Shift+E'],
|
||||
'callback': 'on_action_export_scene',
|
||||
'group': 'active_when_items_in_scene',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'quit',
|
||||
'text': '&Quit',
|
||||
'shortcuts': ['Ctrl+Q'],
|
||||
'callback': 'on_action_quit',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'insert_images',
|
||||
'text': '&Images...',
|
||||
'shortcuts': ['Ctrl+I'],
|
||||
'callback': 'on_action_insert_images',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'insert_text',
|
||||
'text': '&Text',
|
||||
'shortcuts': ['Ctrl+T'],
|
||||
'callback': 'on_action_insert_text',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'undo',
|
||||
'text': '&Undo',
|
||||
'shortcuts': ['Ctrl+Z'],
|
||||
'callback': 'on_action_undo',
|
||||
'group': 'active_when_can_undo',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'redo',
|
||||
'text': '&Redo',
|
||||
'shortcuts': ['Ctrl+Shift+Z'],
|
||||
'callback': 'on_action_redo',
|
||||
'group': 'active_when_can_redo',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'copy',
|
||||
'text': '&Copy',
|
||||
'shortcuts': ['Ctrl+C'],
|
||||
'callback': 'on_action_copy',
|
||||
'group': 'active_when_selection',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'cut',
|
||||
'text': 'Cu&t',
|
||||
'shortcuts': ['Ctrl+X'],
|
||||
'callback': 'on_action_cut',
|
||||
'group': 'active_when_selection',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'paste',
|
||||
'text': '&Paste',
|
||||
'shortcuts': ['Ctrl+V'],
|
||||
'callback': 'on_action_paste',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'delete',
|
||||
'text': '&Delete',
|
||||
'shortcuts': ['Del'],
|
||||
'callback': 'on_action_delete_items',
|
||||
'group': 'active_when_selection',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'raise_to_top',
|
||||
'text': '&Raise to Top',
|
||||
'shortcuts': ['PgUp'],
|
||||
'callback': 'on_action_raise_to_top',
|
||||
'group': 'active_when_selection',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'lower_to_bottom',
|
||||
'text': 'Lower to Bottom',
|
||||
'shortcuts': ['PgDown'],
|
||||
'callback': 'on_action_lower_to_bottom',
|
||||
'group': 'active_when_selection',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'normalize_height',
|
||||
'text': '&Height',
|
||||
'shortcuts': ['Shift+H'],
|
||||
'callback': 'on_action_normalize_height',
|
||||
'group': 'active_when_selection',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'normalize_width',
|
||||
'text': '&Width',
|
||||
'shortcuts': ['Shift+W'],
|
||||
'callback': 'on_action_normalize_width',
|
||||
'group': 'active_when_selection',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'normalize_size',
|
||||
'text': '&Size',
|
||||
'shortcuts': ['Shift+S'],
|
||||
'callback': 'on_action_normalize_size',
|
||||
'group': 'active_when_selection',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'arrange_optimal',
|
||||
'text': '&Optimal',
|
||||
'shortcuts': ['Shift+O'],
|
||||
'callback': 'on_action_arrange_optimal',
|
||||
'group': 'active_when_selection',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'arrange_horizontal',
|
||||
'text': '&Horizontal',
|
||||
'callback': 'on_action_arrange_horizontal',
|
||||
'group': 'active_when_selection',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'arrange_vertical',
|
||||
'text': '&Vertical',
|
||||
'callback': 'on_action_arrange_vertical',
|
||||
'group': 'active_when_selection',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'change_opacity',
|
||||
'text': 'Change &Opacity...',
|
||||
'callback': 'on_action_change_opacity',
|
||||
'group': 'active_when_selection',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'grayscale',
|
||||
'text': '&Grayscale',
|
||||
'shortcuts': ['G'],
|
||||
'checkable': True,
|
||||
'callback': 'on_action_grayscale',
|
||||
'group': 'active_when_selection',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'crop',
|
||||
'text': '&Crop',
|
||||
'shortcuts': ['Shift+C'],
|
||||
'callback': 'on_action_crop',
|
||||
'group': 'active_when_single_image',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'flip_horizontally',
|
||||
'text': 'Flip &Horizontally',
|
||||
'shortcuts': ['H'],
|
||||
'callback': 'on_action_flip_horizontally',
|
||||
'group': 'active_when_selection',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'flip_vertically',
|
||||
'text': 'Flip &Vertically',
|
||||
'shortcuts': ['V'],
|
||||
'callback': 'on_action_flip_vertically',
|
||||
'group': 'active_when_selection',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'new_scene',
|
||||
'text': '&New Scene',
|
||||
'shortcuts': ['Ctrl+N'],
|
||||
'callback': 'clear_scene',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'fit_scene',
|
||||
'text': '&Fit Scene',
|
||||
'shortcuts': ['1'],
|
||||
'callback': 'on_action_fit_scene',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'fit_selection',
|
||||
'text': 'Fit &Selection',
|
||||
'shortcuts': ['2'],
|
||||
'callback': 'on_action_fit_selection',
|
||||
'group': 'active_when_selection',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'reset_scale',
|
||||
'text': 'Reset &Scale',
|
||||
'callback': 'on_action_reset_scale',
|
||||
'group': 'active_when_selection',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'reset_rotation',
|
||||
'text': 'Reset &Rotation',
|
||||
'callback': 'on_action_reset_rotation',
|
||||
'group': 'active_when_selection',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'reset_flip',
|
||||
'text': 'Reset &Flip',
|
||||
'callback': 'on_action_reset_flip',
|
||||
'group': 'active_when_selection',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'reset_crop',
|
||||
'text': 'Reset Cro&p',
|
||||
'callback': 'on_action_reset_crop',
|
||||
'group': 'active_when_selection',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'reset_transforms',
|
||||
'text': 'Reset &All',
|
||||
'shortcuts': ['R'],
|
||||
'callback': 'on_action_reset_transforms',
|
||||
'group': 'active_when_selection',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'select_all',
|
||||
'text': '&Select All',
|
||||
'shortcuts': ['Ctrl+A'],
|
||||
'callback': 'on_action_select_all',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'deselect_all',
|
||||
'text': 'Deselect &All',
|
||||
'shortcuts': ['Ctrl+Shift+A'],
|
||||
'callback': 'on_action_deselect_all',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'help',
|
||||
'text': '&Help',
|
||||
'shortcuts': ['F1', 'Ctrl+H'],
|
||||
'callback': 'on_action_help',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'about',
|
||||
'text': '&About',
|
||||
'callback': 'on_action_about',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'debuglog',
|
||||
'text': 'Show &Debug Log',
|
||||
'callback': 'on_action_debuglog',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'show_scrollbars',
|
||||
'text': 'Show &Scrollbars',
|
||||
'checkable': True,
|
||||
'settings': 'View/show_scrollbars',
|
||||
'callback': 'on_action_show_scrollbars',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'show_menubar',
|
||||
'text': 'Show &Menu Bar',
|
||||
'checkable': True,
|
||||
'settings': 'View/show_menubar',
|
||||
'callback': 'on_action_show_menubar',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'show_titlebar',
|
||||
'text': 'Show &Title Bar',
|
||||
'checkable': True,
|
||||
'checked': True,
|
||||
'callback': 'on_action_show_titlebar',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'move_window',
|
||||
'text': 'Move &Window',
|
||||
'shortcuts': ['Ctrl+M'],
|
||||
'callback': 'on_action_move_window',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'fullscreen',
|
||||
'text': '&Fullscreen',
|
||||
'shortcuts': ['F11'],
|
||||
'checkable': True,
|
||||
'callback': 'on_action_fullscreen',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'always_on_top',
|
||||
'text': '&Always On Top',
|
||||
'checkable': True,
|
||||
'callback': 'on_action_always_on_top',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'settings',
|
||||
'text': '&Settings',
|
||||
'callback': 'on_action_settings',
|
||||
},
|
||||
{
|
||||
}),
|
||||
Action({
|
||||
'id': 'keyboard_settings',
|
||||
'text': '&Keyboard Shortcuts',
|
||||
'callback': 'on_action_keyboard_settings',
|
||||
}),
|
||||
Action({
|
||||
'id': 'open_settings_dir',
|
||||
'text': 'Open Settings Folder',
|
||||
'text': '&Open Settings Folder',
|
||||
'callback': 'on_action_open_settings_dir',
|
||||
},
|
||||
]
|
||||
}),
|
||||
])
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ menu_structure = [
|
|||
'menu': '&Settings',
|
||||
'items': [
|
||||
'settings',
|
||||
'keyboard_settings',
|
||||
'open_settings_dir',
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -19,11 +19,9 @@ import os.path
|
|||
|
||||
from PyQt6 import QtGui, QtWidgets
|
||||
|
||||
from .actions import actions
|
||||
from .actions import Action, actions
|
||||
from .menu_structure import menu_structure, MENU_SEPARATOR
|
||||
|
||||
from beeref.config import KeyboardSettings
|
||||
|
||||
|
||||
class ActionsMixin:
|
||||
|
||||
|
|
@ -35,11 +33,10 @@ class ActionsMixin:
|
|||
"""Creates a new menu or rebuilds the given menu."""
|
||||
self.context_menu = QtWidgets.QMenu(self)
|
||||
self.toplevel_menus = []
|
||||
self.bee_actions = {}
|
||||
self.bee_actiongroups = defaultdict(list)
|
||||
self._post_create_functions = []
|
||||
self._create_actions()
|
||||
self._create_menu(self.bee_actions, self.context_menu, menu_structure)
|
||||
self._create_menu(self.context_menu, menu_structure)
|
||||
for func, arg in self._post_create_functions:
|
||||
func(arg)
|
||||
del self._post_create_functions
|
||||
|
|
@ -71,10 +68,9 @@ class ActionsMixin:
|
|||
partial(self._store_checkable_setting, settings_key))
|
||||
|
||||
def _create_actions(self):
|
||||
for action in actions:
|
||||
for action in actions.values():
|
||||
qaction = QtGui.QAction(action['text'], self)
|
||||
shortcuts = KeyboardSettings().get_shortcuts(
|
||||
'Actions', action['id'], action.get('shortcuts'))
|
||||
shortcuts = action.get_shortcuts()
|
||||
if shortcuts:
|
||||
qaction.setShortcuts(shortcuts)
|
||||
if action.get('checkable', False):
|
||||
|
|
@ -83,25 +79,25 @@ class ActionsMixin:
|
|||
qaction.triggered.connect(getattr(self, action['callback']))
|
||||
self.addAction(qaction)
|
||||
qaction.setEnabled(action.get('enabled', True))
|
||||
self.bee_actions[action['id']] = qaction
|
||||
if 'group' in action:
|
||||
self.bee_actiongroups[action['group']].append(qaction)
|
||||
qaction.setEnabled(False)
|
||||
action.qaction = qaction
|
||||
|
||||
def _create_menu(self, actions, menu, items):
|
||||
def _create_menu(self, menu, items):
|
||||
if isinstance(items, str):
|
||||
getattr(self, items)(menu)
|
||||
return menu
|
||||
for item in items:
|
||||
if isinstance(item, str):
|
||||
menu.addAction(actions[item])
|
||||
menu.addAction(actions[item].qaction)
|
||||
if item == MENU_SEPARATOR:
|
||||
menu.addSeparator()
|
||||
if isinstance(item, dict):
|
||||
submenu = menu.addMenu(item['menu'])
|
||||
if menu == self.context_menu:
|
||||
self.toplevel_menus.append(submenu)
|
||||
self._create_menu(actions, submenu, item['items'])
|
||||
self._create_menu(submenu, item['items'])
|
||||
|
||||
return menu
|
||||
|
||||
|
|
@ -112,31 +108,31 @@ 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)
|
||||
|
||||
for i in range(10):
|
||||
action_id = f'recent_files_{i}'
|
||||
key = 0 if i == 9 else i + 1
|
||||
if key < 10:
|
||||
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[action_id] = qaction
|
||||
items.append(action_id)
|
||||
action = Action({'id': action_id,
|
||||
'menu_id': '_build_recent_files',
|
||||
'text': f'File {i + 1}',
|
||||
'shortcuts': [f'Ctrl+{key}']})
|
||||
actions[action_id] = action
|
||||
|
||||
# Set shortcuts 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}'])
|
||||
if i < len(files):
|
||||
filename = files[i]
|
||||
qaction = QtGui.QAction(os.path.basename(filename), self)
|
||||
qaction.setShortcuts(action.get_shortcuts())
|
||||
qaction.triggered.connect(
|
||||
partial(self.open_from_file, filename))
|
||||
self.addAction(qaction)
|
||||
action.qaction = qaction
|
||||
self._recent_files_submenu.addAction(qaction)
|
||||
items.append(action_id)
|
||||
|
||||
def _clear_recent_files(self):
|
||||
for action in self._recent_files_submenu.actions():
|
||||
self.removeAction(action)
|
||||
self._recent_files_submenu.clear()
|
||||
for key in list(self.bee_actions.keys()):
|
||||
for key in list(actions.keys()):
|
||||
if key.startswith('recent_files_'):
|
||||
self.bee_actions.pop(key)
|
||||
actions[key].qaction = None
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ class CommandlineArgs:
|
|||
|
||||
class BeeSettingsEvents(QtCore.QObject):
|
||||
restore_defaults = QtCore.pyqtSignal()
|
||||
restore_keyboard_defaults = QtCore.pyqtSignal()
|
||||
|
||||
|
||||
# We want to send and receive settings events globally, not per
|
||||
|
|
@ -203,8 +204,6 @@ 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(
|
||||
|
|
@ -212,21 +211,29 @@ class KeyboardSettings(QtCore.QSettings):
|
|||
'KeyboardSettings.ini')
|
||||
super().__init__(filename, settings_format)
|
||||
|
||||
def set_shortcuts(self, group, key, values):
|
||||
self.setValue(f'{group}/{key}', ', '.join(values))
|
||||
def set_shortcuts(self, group, key, values, default=None):
|
||||
if values == default:
|
||||
self.remove(f'{group}/{key}')
|
||||
else:
|
||||
self.setValue(f'{group}/{key}', ', '.join(values))
|
||||
|
||||
def get_shortcuts(self, group, key, default=None):
|
||||
values = self.value(f'{group}/{key}')
|
||||
if values is not None:
|
||||
values = list(filter(lambda x: x, values.split(', ')))
|
||||
logger.debug(f'Found custom shortcuts for {group}/{key}: {values}')
|
||||
return values
|
||||
|
||||
values = default or []
|
||||
if self.save_unknown_shortcuts:
|
||||
self.set_shortcuts(group, key, values)
|
||||
return list(default or []) # Always return new instance of default
|
||||
|
||||
return values
|
||||
def restore_defaults(self):
|
||||
"""Restore all the values specified in FILEDS to their default values
|
||||
by removing them from the settings file.
|
||||
"""
|
||||
|
||||
logger.debug('Restoring keyboard shortcuts to defaults')
|
||||
for key in self.allKeys():
|
||||
self.remove(key)
|
||||
settings_events.restore_keyboard_defaults.emit()
|
||||
|
||||
|
||||
def logfile_name():
|
||||
|
|
|
|||
|
|
@ -36,5 +36,7 @@ COLORS = {
|
|||
# BeeRef specific:
|
||||
'Scene:Selection': (116, 234, 231),
|
||||
'Scene:Canvas': (60, 60, 60),
|
||||
'Scene:Text': (200, 200, 200)
|
||||
'Scene:Text': (200, 200, 200),
|
||||
'Table:AlternativeRow': (70, 70, 70),
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
class ExporterRegistry(dict):
|
||||
|
||||
DEFAULT_TYPE = 'default exporter'
|
||||
DEFAULT_TYPE = 0
|
||||
|
||||
def __getitem__(self, key):
|
||||
key = key.removeprefix('.')
|
||||
|
|
|
|||
|
|
@ -458,6 +458,9 @@ class BeeGraphicsView(MainControlsMixin,
|
|||
def on_action_settings(self):
|
||||
widgets.settings.SettingsDialog(self)
|
||||
|
||||
def on_action_keyboard_settings(self):
|
||||
widgets.settings.KeyboardSettingsDialog(self)
|
||||
|
||||
def on_action_help(self):
|
||||
widgets.HelpDialog(self)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,10 +16,11 @@
|
|||
from functools import partial
|
||||
import logging
|
||||
|
||||
from PyQt6 import QtWidgets
|
||||
from PyQt6 import QtWidgets, QtCore, QtGui
|
||||
|
||||
from beeref import constants
|
||||
from beeref.config import BeeSettings, settings_events
|
||||
from beeref.actions.actions import actions
|
||||
from beeref.config import BeeSettings, KeyboardSettings, settings_events
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -36,7 +37,8 @@ class RadioGroup(QtWidgets.QGroupBox):
|
|||
self.settings = BeeSettings()
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
settings_events.restore_defaults.connect(self.on_restore_defaults)
|
||||
settings_events.restore_keyboard_defaults.connect(
|
||||
self.on_restore_defaults)
|
||||
|
||||
if self.HELPTEXT:
|
||||
helptxt = QtWidgets.QLabel(self.HELPTEXT)
|
||||
|
|
@ -177,3 +179,227 @@ class SettingsDialog(QtWidgets.QDialog):
|
|||
|
||||
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
|
||||
BeeSettings().restore_defaults()
|
||||
|
||||
|
||||
class KeyboardShortcutsEditor(QtWidgets.QKeySequenceEdit):
|
||||
|
||||
def __init__(self, parent, index):
|
||||
super().__init__(parent)
|
||||
self.action = actions[index.row()]
|
||||
try:
|
||||
self.old_value = self.action.get_shortcuts()[index.column() - 2]
|
||||
except IndexError:
|
||||
self.old_value = ''
|
||||
self.setClearButtonEnabled(True)
|
||||
self.setMaximumSequenceLength(1)
|
||||
self.editingFinished.connect(self.on_editing_finished)
|
||||
self.finished_last_called_with = None
|
||||
self.remove_from_other = None
|
||||
|
||||
def on_editing_finished(self):
|
||||
shortcut = self.keySequence().toString()
|
||||
|
||||
if self.finished_last_called_with == shortcut:
|
||||
# Workaround for bug
|
||||
# https://bugreports.qt.io/browse/QTBUG-40
|
||||
# editingFinished signal is emitted twice because of
|
||||
# the QMessageBox below
|
||||
return
|
||||
|
||||
self.remove_from_other = None
|
||||
self.finished_last_called_with = shortcut
|
||||
for action in actions.values():
|
||||
if action == self.action:
|
||||
continue
|
||||
if shortcut in action.get_shortcuts():
|
||||
txt = ': '.join(action.menu_path + [action['text']])
|
||||
txt = txt.replace('&', '').removesuffix('...')
|
||||
msg = ('<p>This shortcut is already used for:</p>'
|
||||
f'<p>{txt}</p>'
|
||||
'<p>Do you want to remove the other shortcut'
|
||||
' to save this one?</p>')
|
||||
reply = QtWidgets.QMessageBox.question(
|
||||
self, 'Save Shortcut?', msg)
|
||||
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
|
||||
self.remove_from_other = action
|
||||
else:
|
||||
self.setKeySequence(self.old_value)
|
||||
|
||||
|
||||
class KeyboardShortcutsDelegate(QtWidgets.QStyledItemDelegate):
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
return KeyboardShortcutsEditor(parent, index)
|
||||
|
||||
def setModelData(self, editor, model, index):
|
||||
model.setData(
|
||||
index,
|
||||
editor.keySequence(),
|
||||
QtCore.Qt.ItemDataRole.EditRole,
|
||||
remove_from_other=editor.remove_from_other)
|
||||
|
||||
|
||||
class KeyboardShortcutsModel(QtCore.QAbstractTableModel):
|
||||
"""An entry in the keyboard shortcuts table."""
|
||||
|
||||
HEADER = ('Action', '✎', 'Shortcut', 'Alternative')
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.settings = KeyboardSettings()
|
||||
|
||||
def rowCount(self, parent):
|
||||
return len(actions)
|
||||
|
||||
def columnCount(self, parent):
|
||||
return len(self.HEADER)
|
||||
|
||||
def data(self, index, role):
|
||||
if role in (QtCore.Qt.ItemDataRole.DisplayRole,
|
||||
QtCore.Qt.ItemDataRole.EditRole):
|
||||
action = actions[index.row()]
|
||||
txt = ': '.join(action.menu_path + [action['text']])
|
||||
if index.column() == 0:
|
||||
return txt.replace('&', '').removesuffix('...')
|
||||
if index.column() == 1 and action.shortcuts_changed():
|
||||
return '✎'
|
||||
if index.column() > 1:
|
||||
return action.get_qkeysequence(index.column() - 2)
|
||||
|
||||
if role == QtCore.Qt.ItemDataRole.ToolTipRole:
|
||||
action = actions[index.row()]
|
||||
changed = action.shortcuts_changed()
|
||||
if changed and index.column() == 1:
|
||||
return 'Changed from default'
|
||||
if changed and index.column() > 1:
|
||||
default = action.get_default_shortcut(index.column() - 2)
|
||||
default = default or '-'
|
||||
return f'Default: {default}'
|
||||
|
||||
def setData(self, index, value, role, remove_from_other=None):
|
||||
action = actions[index.row()]
|
||||
shortcuts = action.get_shortcuts() + [None, None]
|
||||
shortcuts[index.column() - 2] = value.toString()
|
||||
shortcuts = list(filter(bool, shortcuts))
|
||||
if len(shortcuts) != len(set(shortcuts)):
|
||||
# We got the same shortcut twice
|
||||
shortcuts = set(shortcuts)
|
||||
action.set_shortcuts(shortcuts)
|
||||
# Whole row might be affected, so excpliclity emit dataChanged
|
||||
self.dataChanged.emit(self.index(index.row(), 1),
|
||||
self.index(index.row(), 3))
|
||||
|
||||
if remove_from_other:
|
||||
# This shortcut has conflicts with another action and the
|
||||
# user chose to remove the other shortcut
|
||||
shortcuts = remove_from_other.get_shortcuts()
|
||||
shortcuts.remove(value.toString())
|
||||
remove_from_other.set_shortcuts(shortcuts)
|
||||
row = list(actions.keys()).index(remove_from_other['id'])
|
||||
self.dataChanged.emit(self.index(row, 1),
|
||||
self.index(row, 3))
|
||||
|
||||
return True
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
if (role == QtCore.Qt.ItemDataRole.DisplayRole
|
||||
and orientation == QtCore.Qt.Orientation.Horizontal):
|
||||
return self.HEADER[section]
|
||||
|
||||
def flags(self, index):
|
||||
base = (QtCore.Qt.ItemFlag.ItemIsEnabled
|
||||
| QtCore.Qt.ItemFlag.ItemNeverHasChildren)
|
||||
if index.column() <= 1:
|
||||
return base
|
||||
else:
|
||||
return (base | QtCore.Qt.ItemFlag.ItemIsEditable)
|
||||
|
||||
|
||||
class KeyboardShortcutsProxy(QtCore.QSortFilterProxyModel):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setSourceModel(KeyboardShortcutsModel())
|
||||
self.setFilterCaseSensitivity(
|
||||
QtCore.Qt.CaseSensitivity.CaseInsensitive)
|
||||
|
||||
def data(self, index, role):
|
||||
if (role == QtCore.Qt.ItemDataRole.BackgroundRole
|
||||
and index.row() % 2):
|
||||
return QtGui.QColor(*constants.COLORS['Table:AlternativeRow'])
|
||||
else:
|
||||
return super().data(index, role)
|
||||
|
||||
def setData(self, index, value, role, remove_from_other=None):
|
||||
result = self.sourceModel().setData(
|
||||
self.mapToSource(index),
|
||||
value,
|
||||
role,
|
||||
remove_from_other=remove_from_other)
|
||||
return result
|
||||
|
||||
|
||||
class KeyboardShortcutsView(QtWidgets.QTableView):
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
self.setMinimumSize(QtCore.QSize(400, 200))
|
||||
self.setItemDelegate(KeyboardShortcutsDelegate())
|
||||
self.setShowGrid(False)
|
||||
self.setModel(KeyboardShortcutsProxy())
|
||||
self.horizontalHeader().setSectionResizeMode(
|
||||
0, QtWidgets.QHeaderView.ResizeMode.Stretch)
|
||||
self.horizontalHeader().setSectionResizeMode(
|
||||
1, QtWidgets.QHeaderView.ResizeMode.ResizeToContents)
|
||||
self.setSelectionMode(
|
||||
QtWidgets.QHeaderView.SelectionMode.SingleSelection)
|
||||
settings_events.restore_defaults.connect(self.on_restore_defaults)
|
||||
|
||||
def on_restore_defaults(self):
|
||||
self.viewport().update()
|
||||
|
||||
|
||||
class KeyboardSettingsDialog(QtWidgets.QDialog):
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle('Keyboard Shortcuts')
|
||||
tabs = QtWidgets.QTabWidget()
|
||||
|
||||
# Keyboard shortcuts
|
||||
keyboard = QtWidgets.QWidget(parent)
|
||||
kb_layout = QtWidgets.QVBoxLayout()
|
||||
keyboard.setLayout(kb_layout)
|
||||
table = KeyboardShortcutsView(keyboard)
|
||||
search_input = QtWidgets.QLineEdit()
|
||||
search_input.setPlaceholderText('Search...')
|
||||
search_input.textChanged.connect(
|
||||
lambda value: table.model().setFilterFixedString(value))
|
||||
kb_layout.addWidget(search_input)
|
||||
kb_layout.addWidget(table)
|
||||
tabs.addTab(keyboard, '&Keyboard Shortcuts')
|
||||
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
layout.addWidget(tabs)
|
||||
|
||||
# Bottom row of buttons
|
||||
buttons = QtWidgets.QDialogButtonBox(
|
||||
QtWidgets.QDialogButtonBox.StandardButton.Close)
|
||||
buttons.rejected.connect(self.reject)
|
||||
reset_btn = QtWidgets.QPushButton('&Restore Defaults')
|
||||
reset_btn.setAutoDefault(False)
|
||||
reset_btn.clicked.connect(self.on_restore_defaults)
|
||||
buttons.addButton(reset_btn,
|
||||
QtWidgets.QDialogButtonBox.ButtonRole.ActionRole)
|
||||
|
||||
layout.addWidget(buttons)
|
||||
self.show()
|
||||
|
||||
def on_restore_defaults(self, *args, **kwargs):
|
||||
reply = QtWidgets.QMessageBox.question(
|
||||
self,
|
||||
'Restore defaults?',
|
||||
'Do you want to restore all settings to their default values?')
|
||||
|
||||
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
|
||||
KeyboardSettings().restore_defaults()
|
||||
|
|
|
|||
4
setup.py
4
setup.py
|
|
@ -9,8 +9,8 @@ setup(
|
|||
license='LICENSE',
|
||||
description='A simple reference image viewer',
|
||||
install_requires=[
|
||||
'pyQt6>=6.4.0,<=6.6.1',
|
||||
'pyQt6-Qt6>=6.4.0,<=6.6.1',
|
||||
'pyQt6>=6.5.0,<=6.6.1',
|
||||
'pyQt6-Qt6>=6.5.0,<=6.6.1',
|
||||
'rectangle-packer>=2.0.1,<=2.0.2',
|
||||
'exif>=1.3.5,<=1.6.0',
|
||||
],
|
||||
|
|
|
|||
159
tests/actions/test_actions.py
Normal file
159
tests/actions/test_actions.py
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
from unittest.mock import patch
|
||||
|
||||
from PyQt6 import QtGui
|
||||
|
||||
from beeref.actions.actions import Action, ActionList
|
||||
|
||||
|
||||
def test_action_equals_true():
|
||||
action1 = Action({'id': 'foo', 'text': 'Foo', 'shortcuts': ['Ctrl+R']})
|
||||
action2 = Action({'id': 'foo', 'text': 'Bar', 'shortcuts': ['Ctrl+F']})
|
||||
assert action1 == action2
|
||||
|
||||
|
||||
def test_action_equals_false():
|
||||
action1 = Action({'id': 'foo', 'text': 'Foo', 'shortcuts': ['Ctrl+R']})
|
||||
action2 = Action({'id': 'Bar', 'text': 'Foo', 'shortcuts': ['Ctrl+R']})
|
||||
assert not action1 == action2
|
||||
|
||||
|
||||
def test_action_on_restore_defaults(kbsettings, view):
|
||||
action = Action({'id': 'foo', 'shortcuts': ['Ctrl+R']})
|
||||
action.qaction = QtGui.QAction('foo', view)
|
||||
action.on_restore_defaults()
|
||||
assert action.qaction.shortcuts() == ['Ctrl+R']
|
||||
|
||||
|
||||
def test_action_on_restore_defaults_when_no_defaults(kbsettings, view):
|
||||
action = Action({'id': 'foo'})
|
||||
action.qaction = QtGui.QAction('foo', view)
|
||||
action.qaction.setShortcuts(['Ctrl+R'])
|
||||
action.on_restore_defaults()
|
||||
assert action.qaction.shortcuts() == []
|
||||
|
||||
|
||||
def test_action_get_overwritten_shortcuts(kbsettings):
|
||||
kbsettings.set_shortcuts('Actions', 'foo', ['Alt+O'])
|
||||
action = Action({'id': 'foo', 'shortcuts': ['Ctrl+F']})
|
||||
assert action.get_shortcuts() == ['Alt+O']
|
||||
|
||||
|
||||
def test_action_get_shortcuts_gets_default(kbsettings):
|
||||
action = Action({'id': 'foo', 'shortcuts': ['Ctrl+F']})
|
||||
assert action.get_shortcuts() == ['Ctrl+F']
|
||||
|
||||
|
||||
def test_action_set_shortcuts_when_no_qaction(kbsettings):
|
||||
action = Action({'id': 'foo'})
|
||||
action.qaction = None
|
||||
action.set_shortcuts(['Ctrl+F'])
|
||||
assert kbsettings.get_shortcuts('Actions', 'foo') == ['Ctrl+F']
|
||||
|
||||
|
||||
def test_action_set_shortcuts_when_qaction(kbsettings, view):
|
||||
action = Action({'id': 'foo'})
|
||||
action.qaction = QtGui.QAction('foo', view)
|
||||
action.set_shortcuts(['Ctrl+F'])
|
||||
assert kbsettings.get_shortcuts('Actions', 'foo') == ['Ctrl+F']
|
||||
assert action.qaction.shortcuts() == ['Ctrl+F']
|
||||
|
||||
|
||||
def test_action_get_qkeysequence_first(kbsettings):
|
||||
action = Action({'id': 'foo', 'shortcuts': ['Ctrl+F']})
|
||||
assert action.get_qkeysequence(0) == QtGui.QKeySequence('Ctrl+F')
|
||||
|
||||
|
||||
def test_action_get_qkeysequence_second(kbsettings):
|
||||
action = Action({'id': 'foo', 'shortcuts': ['Ctrl+F', 'Ctrl+B']})
|
||||
assert action.get_qkeysequence(1) == QtGui.QKeySequence('Ctrl+B')
|
||||
|
||||
|
||||
def test_action_get_qkeysequence_first_when_not_set(kbsettings):
|
||||
action = Action({'id': 'foo'})
|
||||
assert action.get_qkeysequence(0) == QtGui.QKeySequence()
|
||||
|
||||
|
||||
def test_action_get_qkeysequence_second_when_not_set(kbsettings):
|
||||
action = Action({'id': 'foo', 'shortcuts': ['Ctrl+F']})
|
||||
assert action.get_qkeysequence(1) == QtGui.QKeySequence()
|
||||
|
||||
|
||||
def test_action_shortcuts_changed_when_not_changed(kbsettings):
|
||||
action = Action({'id': 'foo', 'shortcuts': ['Ctrl+F']})
|
||||
assert action.shortcuts_changed() is False
|
||||
|
||||
|
||||
def test_action_shortcuts_changed_when_changed(kbsettings):
|
||||
action = Action({'id': 'foo', 'shortcuts': ['Ctrl+F']})
|
||||
kbsettings.set_shortcuts('Actions', 'foo', ['Ctrl+B'])
|
||||
assert action.shortcuts_changed() is True
|
||||
|
||||
|
||||
def test_action_shortcuts_changed_when_empty(kbsettings):
|
||||
action = Action({'id': 'foo'})
|
||||
assert action.shortcuts_changed() is False
|
||||
|
||||
|
||||
def test_action_get_default_shortcut_first():
|
||||
action = Action({'id': 'foo', 'shortcuts': ['Ctrl+F']})
|
||||
assert action.get_default_shortcut(0) == 'Ctrl+F'
|
||||
|
||||
|
||||
def test_action_get_default_shortcut_second():
|
||||
action = Action({'id': 'foo', 'shortcuts': ['Ctrl+F', 'Ctrl+B']})
|
||||
assert action.get_default_shortcut(1) == 'Ctrl+B'
|
||||
|
||||
|
||||
def test_action_get_default_shortcut_first_when_none_set():
|
||||
action = Action({'id': 'foo'})
|
||||
assert action.get_default_shortcut(0) is None
|
||||
|
||||
|
||||
def test_action_get_default_shortcut_second_when_none_set():
|
||||
action = Action({'id': 'foo', 'shortcuts': ['Ctrl+F']})
|
||||
assert action.get_default_shortcut(1) is None
|
||||
|
||||
|
||||
@patch('beeref.actions.actions.menu_structure',
|
||||
[{'menu': 'Foo', 'items': ['bar', 'baz']}])
|
||||
def test_action_menu_path():
|
||||
action = Action({'id': 'baz'})
|
||||
assert action.menu_path == ['Foo']
|
||||
|
||||
|
||||
@patch('beeref.actions.actions.menu_structure',
|
||||
[{'menu': 'Foo', 'items': [{'menu': 'Bar', 'items': ['baz']}]}])
|
||||
def test_action_menu_path_with_submenus():
|
||||
action = Action({'id': 'baz'})
|
||||
assert action.menu_path == ['Foo', 'Bar']
|
||||
|
||||
|
||||
@patch('beeref.actions.actions.menu_structure',
|
||||
[{'menu': 'Foo', 'items': '_build_recent_files'}])
|
||||
def test_action_menu_path_recent_files():
|
||||
action = Action({'id': 'baz', 'menu_id': '_build_recent_files'})
|
||||
assert action.menu_path == ['Foo']
|
||||
|
||||
|
||||
@patch('beeref.actions.actions.menu_structure',
|
||||
[{'menu': 'Foo', 'items': [
|
||||
{'menu': 'Bar', 'items': '_build_recent_files'}]}])
|
||||
def test_action_menu_path_recent_files_in_submenu():
|
||||
action = Action({'id': 'baz', 'menu_id': '_build_recent_files'})
|
||||
assert action.menu_path == ['Foo', 'Bar']
|
||||
|
||||
|
||||
def test_actionlist_inits_dict():
|
||||
action1 = Action({'id': 'foo'})
|
||||
action2 = Action({'id': 'bar'})
|
||||
actionlist = ActionList([action1, action2])
|
||||
actionlist['foo'] == action1
|
||||
actionlist['bar'] == action2
|
||||
|
||||
|
||||
def test_actionlist_acts_as_list():
|
||||
action1 = Action({'id': 'foo'})
|
||||
action2 = Action({'id': 'bar'})
|
||||
actionlist = ActionList([action1, action2])
|
||||
actionlist[0] == action1
|
||||
actionlist[1] == action2
|
||||
|
|
@ -4,6 +4,7 @@ from unittest.mock import patch, MagicMock, call
|
|||
from PyQt6 import QtWidgets
|
||||
|
||||
from beeref.actions import ActionsMixin
|
||||
from beeref.actions.actions import Action, ActionList
|
||||
from beeref.actions.menu_structure import MENU_SEPARATOR
|
||||
|
||||
|
||||
|
|
@ -24,21 +25,19 @@ class FooWidget(QtWidgets.QWidget, ActionsMixin):
|
|||
|
||||
@patch('PyQt6.QtGui.QAction.triggered')
|
||||
@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(
|
||||
kb_mock, actions_mock, menu_mock, toggle_mock, trigger_mock, qapp):
|
||||
@patch('beeref.actions.mixin.menu_structure',
|
||||
[{'menu': 'Foo', 'items': ['foo']}])
|
||||
@patch('beeref.actions.mixin.actions',
|
||||
ActionList([Action({
|
||||
'id': 'foo',
|
||||
'text': '&Foo',
|
||||
'shortcuts': ['Ctrl+F'],
|
||||
'callback': 'on_foo',
|
||||
})]))
|
||||
@patch('beeref.config.KeyboardSettings.get_shortcuts')
|
||||
def test_create_actions(kb_mock, toggle_mock, trigger_mock, qapp):
|
||||
kb_mock.side_effect = lambda group, key, default: default
|
||||
widget = FooWidget()
|
||||
actions_mock.__iter__.return_value = [{
|
||||
'id': 'foo',
|
||||
'text': '&Foo',
|
||||
'shortcuts': ['Ctrl+F'],
|
||||
'callback': 'on_foo',
|
||||
}]
|
||||
|
||||
menu_mock.__iter__.return_value = ['foo']
|
||||
widget.build_menu_and_actions()
|
||||
trigger_mock.connect.assert_called_once_with(widget.on_foo)
|
||||
toggle_mock.connect.assert_not_called()
|
||||
|
|
@ -48,44 +47,43 @@ def test_create_actions(
|
|||
assert qaction.text() == '&Foo'
|
||||
assert qaction.shortcut() == 'Ctrl+F'
|
||||
assert qaction.isEnabled() is True
|
||||
assert widget.bee_actions['foo'] == qaction
|
||||
from beeref.actions.mixin import actions
|
||||
assert actions['foo'].qaction == qaction
|
||||
kb_mock.assert_called_once_with('Actions', 'foo', ['Ctrl+F'])
|
||||
|
||||
|
||||
@patch('beeref.actions.mixin.menu_structure')
|
||||
@patch('beeref.actions.mixin.actions')
|
||||
def test_create_actions_with_shortcut_from_settings(
|
||||
actions_mock, menu_mock, qapp, kbsettings):
|
||||
kbsettings.set_shortcuts('Actions', 'foo', ['Alt+O'])
|
||||
widget = FooWidget()
|
||||
actions_mock.__iter__.return_value = [{
|
||||
'id': 'foo',
|
||||
'text': '&Foo',
|
||||
'shortcuts': ['Ctrl+F'],
|
||||
'callback': 'on_foo',
|
||||
}]
|
||||
|
||||
menu_mock.__iter__.return_value = ['foo']
|
||||
widget.build_menu_and_actions()
|
||||
qaction = widget.actions()[0]
|
||||
assert qaction.shortcut() == 'Alt+O'
|
||||
@patch('beeref.actions.mixin.menu_structure',
|
||||
[{'menu': 'Foo', 'items': ['foo']}])
|
||||
def test_create_actions_with_shortcut_from_settings(qapp, kbsettings):
|
||||
with patch('beeref.actions.mixin.actions',
|
||||
ActionList([Action({
|
||||
'id': 'foo',
|
||||
'text': '&Foo',
|
||||
'shortcuts': ['Ctrl+F'],
|
||||
'callback': 'on_foo'})])):
|
||||
# Create Action inside the test function so that its
|
||||
# kbsettings get created after the kbsettings fixture changes
|
||||
# the file path
|
||||
kbsettings.set_shortcuts('Actions', 'foo', ['Alt+O'])
|
||||
widget = FooWidget()
|
||||
widget.build_menu_and_actions()
|
||||
qaction = widget.actions()[0]
|
||||
assert qaction.shortcuts() == ['Alt+O']
|
||||
|
||||
|
||||
@patch('PyQt6.QtGui.QAction.triggered')
|
||||
@patch('PyQt6.QtGui.QAction.toggled')
|
||||
@patch('beeref.actions.mixin.menu_structure')
|
||||
@patch('beeref.actions.mixin.actions')
|
||||
def test_create_actions_checkable(
|
||||
actions_mock, menu_mock, toggle_mock, trigger_mock, qapp):
|
||||
@patch('beeref.actions.mixin.menu_structure',
|
||||
[{'menu': 'Foo', 'items': ['foo']}])
|
||||
@patch('beeref.actions.mixin.actions',
|
||||
ActionList([Action({
|
||||
'id': 'foo',
|
||||
'text': '&Foo',
|
||||
'checkable': True,
|
||||
'callback': 'on_foo',
|
||||
})]))
|
||||
def test_create_actions_checkable(toggle_mock, trigger_mock, qapp):
|
||||
widget = FooWidget()
|
||||
actions_mock.__iter__.return_value = [{
|
||||
'id': 'foo',
|
||||
'text': '&Foo',
|
||||
'checkable': True,
|
||||
'callback': 'on_foo',
|
||||
}]
|
||||
|
||||
menu_mock.__iter__.return_value = ['foo']
|
||||
widget.build_menu_and_actions()
|
||||
trigger_mock.connect.assert_not_called()
|
||||
toggle_mock.connect.assert_called_once_with(widget.on_foo)
|
||||
|
|
@ -95,25 +93,23 @@ def test_create_actions_checkable(
|
|||
assert qaction.text() == '&Foo'
|
||||
assert qaction.isEnabled() is True
|
||||
assert qaction.isChecked() is False
|
||||
assert widget.bee_actions['foo'] == qaction
|
||||
|
||||
|
||||
@patch('PyQt6.QtGui.QAction.triggered')
|
||||
@patch('PyQt6.QtGui.QAction.toggled')
|
||||
@patch('beeref.actions.mixin.menu_structure')
|
||||
@patch('beeref.actions.mixin.actions')
|
||||
@patch('beeref.actions.mixin.menu_structure',
|
||||
[{'menu': 'Foo', 'items': ['foo']}])
|
||||
@patch('beeref.actions.mixin.actions',
|
||||
ActionList([Action({
|
||||
'id': 'foo',
|
||||
'text': '&Foo',
|
||||
'checkable': True,
|
||||
'checked': True,
|
||||
'callback': 'on_foo',
|
||||
})]))
|
||||
def test_create_actions_checkable_checked_true(
|
||||
actions_mock, menu_mock, toggle_mock, trigger_mock, qapp):
|
||||
toggle_mock, trigger_mock, qapp):
|
||||
widget = FooWidget()
|
||||
actions_mock.__iter__.return_value = [{
|
||||
'id': 'foo',
|
||||
'text': '&Foo',
|
||||
'checkable': True,
|
||||
'checked': True,
|
||||
'callback': 'on_foo',
|
||||
}]
|
||||
|
||||
menu_mock.__iter__.return_value = ['foo']
|
||||
widget.build_menu_and_actions()
|
||||
trigger_mock.connect.assert_not_called()
|
||||
toggle_mock.connect.assert_called_once_with(widget.on_foo)
|
||||
|
|
@ -123,27 +119,24 @@ def test_create_actions_checkable_checked_true(
|
|||
assert qaction.text() == '&Foo'
|
||||
assert qaction.isEnabled() is True
|
||||
assert qaction.isChecked() is True
|
||||
assert widget.bee_actions['foo'] == qaction
|
||||
|
||||
|
||||
@patch.object(FooWidget, 'on_foo')
|
||||
@patch.object(FooWidget, 'settings')
|
||||
@patch('PyQt6.QtGui.QAction.toggled')
|
||||
@patch('beeref.actions.mixin.menu_structure')
|
||||
@patch('beeref.actions.mixin.actions')
|
||||
@patch('beeref.actions.mixin.menu_structure',
|
||||
[{'menu': 'Foo', 'items': ['foo']}])
|
||||
@patch('beeref.actions.mixin.actions',
|
||||
ActionList([Action({
|
||||
'id': 'foo',
|
||||
'text': '&Foo',
|
||||
'checkable': True,
|
||||
'settings': 'foo/bar',
|
||||
'callback': 'on_foo',
|
||||
})]))
|
||||
def test_create_actions_checkable_with_settings(
|
||||
actions_mock, menu_mock, toggle_mock, settings_mock, callback_mock,
|
||||
qapp):
|
||||
toggle_mock, settings_mock, callback_mock, qapp):
|
||||
widget = FooWidget()
|
||||
actions_mock.__iter__.return_value = [{
|
||||
'id': 'foo',
|
||||
'text': '&Foo',
|
||||
'checkable': True,
|
||||
'callback': 'on_foo',
|
||||
'settings': 'foo/bar',
|
||||
}]
|
||||
|
||||
menu_mock.__iter__.return_value = ['foo']
|
||||
settings_mock.value.return_value = True
|
||||
widget.build_menu_and_actions()
|
||||
settings_mock.value.assert_called_once_with(
|
||||
|
|
@ -154,152 +147,141 @@ def test_create_actions_checkable_with_settings(
|
|||
callback_mock.assert_called_once_with(True)
|
||||
|
||||
|
||||
@patch('beeref.actions.mixin.menu_structure')
|
||||
@patch('beeref.actions.mixin.actions')
|
||||
def test_create_actions_with_group(actions_mock, menu_mock, qapp):
|
||||
@patch('beeref.actions.mixin.menu_structure',
|
||||
[{'menu': 'Foo', 'items': ['foo']}])
|
||||
@patch('beeref.actions.mixin.actions',
|
||||
ActionList([Action({
|
||||
'id': 'foo',
|
||||
'text': '&Foo',
|
||||
'callback': 'on_foo',
|
||||
'group': 'bar',
|
||||
})]))
|
||||
def test_create_actions_with_group(qapp):
|
||||
widget = FooWidget()
|
||||
actions_mock.__iter__.return_value = [{
|
||||
'id': 'foo',
|
||||
'text': '&Foo',
|
||||
'callback': 'on_foo',
|
||||
'group': 'bar',
|
||||
}]
|
||||
menu_mock.__iter__.return_value = ['foo']
|
||||
widget.build_menu_and_actions()
|
||||
assert len(widget.actions()) == 1
|
||||
qaction = widget.actions()[0]
|
||||
assert widget.bee_actiongroups['bar'] == [qaction]
|
||||
|
||||
|
||||
@patch('beeref.actions.mixin.menu_structure')
|
||||
@patch('beeref.actions.mixin.actions')
|
||||
def test_build_menu_and_actions_with_actions(actions_mock, menu_mock, qapp):
|
||||
@patch('beeref.actions.mixin.menu_structure',
|
||||
[{'menu': 'Foo', 'items': ['foo']}])
|
||||
@patch('beeref.actions.mixin.actions',
|
||||
ActionList([Action({
|
||||
'id': 'foo',
|
||||
'text': '&Foo',
|
||||
'callback': 'on_foo',
|
||||
})]))
|
||||
def test_build_menu_and_actions_with_actions(qapp):
|
||||
widget = FooWidget()
|
||||
actions_mock.__iter__.return_value = [{
|
||||
'id': 'foo',
|
||||
'text': '&Foo',
|
||||
'callback': 'on_foo',
|
||||
'group': 'bar',
|
||||
}]
|
||||
menu_mock.__iter__.return_value = ['foo']
|
||||
with patch('PyQt6.QtWidgets.QMenu.addAction') as add_mock:
|
||||
widget.build_menu_and_actions()
|
||||
assert isinstance(widget.context_menu, QtWidgets.QMenu)
|
||||
add_mock.assert_called_once_with(widget.bee_actions['foo'])
|
||||
from beeref.actions.mixin import actions
|
||||
add_mock.assert_called_once_with(actions['foo'].qaction)
|
||||
|
||||
|
||||
@patch('beeref.actions.mixin.menu_structure')
|
||||
@patch('beeref.actions.mixin.actions')
|
||||
def test_build_menu_and_actions_with_separator(actions_mock, menu_mock, qapp):
|
||||
@patch('beeref.actions.mixin.menu_structure',
|
||||
[{'menu': 'Foo', 'items': [MENU_SEPARATOR]}])
|
||||
@patch('beeref.actions.mixin.actions', ActionList([]))
|
||||
def test_build_menu_and_actions_with_separator(qapp):
|
||||
widget = FooWidget()
|
||||
menu_mock.__iter__.return_value = [MENU_SEPARATOR]
|
||||
with patch('PyQt6.QtWidgets.QMenu.addSeparator') as sep_mock:
|
||||
widget.build_menu_and_actions()
|
||||
assert isinstance(widget.context_menu, QtWidgets.QMenu)
|
||||
sep_mock.assert_called_once_with()
|
||||
|
||||
|
||||
@patch('beeref.actions.mixin.menu_structure')
|
||||
@patch('beeref.actions.mixin.actions')
|
||||
def test_build_menu_and_actions_with_submenu(actions_mock, menu_mock, qapp):
|
||||
@patch('beeref.actions.mixin.menu_structure',
|
||||
[{'menu': 'Foo', 'items': [{'menu': 'Bar', 'items': ['foo']}]}])
|
||||
@patch('beeref.actions.mixin.actions',
|
||||
ActionList([Action({
|
||||
'id': 'foo',
|
||||
'text': '&Foo',
|
||||
'callback': 'on_foo',
|
||||
})]))
|
||||
def test_build_menu_and_actions_with_submenu(qapp):
|
||||
widget = FooWidget()
|
||||
actions_mock.__iter__.return_value = [{
|
||||
'id': 'foo',
|
||||
'text': '&Foo',
|
||||
'callback': 'on_foo',
|
||||
'group': 'bar',
|
||||
}]
|
||||
menu_mock.__iter__.return_value = [
|
||||
{'menu': '&Bar', 'items': ['foo']}]
|
||||
with patch('PyQt6.QtWidgets.QMenu.addAction') as add_mock:
|
||||
with patch('PyQt6.QtWidgets.QMenu.addMenu') as addmenu_mock:
|
||||
addmenu_mock.return_value = QtWidgets.QMenu()
|
||||
widget.build_menu_and_actions()
|
||||
assert isinstance(widget.context_menu, QtWidgets.QMenu)
|
||||
addmenu_mock.assert_called_once_with('&Bar')
|
||||
add_mock.assert_called_once_with(widget.bee_actions['foo'])
|
||||
addmenu_mock.assert_called_with('Bar')
|
||||
from beeref.actions.mixin import actions
|
||||
add_mock.assert_called_once_with(actions['foo'].qaction)
|
||||
|
||||
|
||||
@patch('beeref.actions.mixin.menu_structure')
|
||||
@patch('beeref.actions.mixin.actions')
|
||||
def test_actiongroup_set_enabled(actions_mock, menu_mock, qapp):
|
||||
@patch('beeref.actions.mixin.menu_structure',
|
||||
[{'menu': 'Foo', 'items': ['foo']}])
|
||||
@patch('beeref.actions.mixin.actions',
|
||||
ActionList([
|
||||
Action({
|
||||
'id': 'foo',
|
||||
'text': '&Foo',
|
||||
'callback': 'on_foo',
|
||||
'group': 'g1',
|
||||
}),
|
||||
Action({
|
||||
'id': 'bar',
|
||||
'text': '&Bar',
|
||||
'callback': 'on_foo',
|
||||
'group': 'g2',
|
||||
}),
|
||||
]))
|
||||
def test_actiongroup_set_enabled(qapp):
|
||||
widget = FooWidget()
|
||||
actions_mock.__iter__.return_value = [
|
||||
{
|
||||
'id': 'foo',
|
||||
'text': '&Foo',
|
||||
'callback': 'on_foo',
|
||||
'group': 'g1',
|
||||
},
|
||||
{
|
||||
'id': 'bar',
|
||||
'text': '&Bar',
|
||||
'callback': 'on_foo',
|
||||
'group': 'g2',
|
||||
},
|
||||
]
|
||||
|
||||
menu_mock.__iter__.return_value = ['foo']
|
||||
widget.build_menu_and_actions()
|
||||
widget.actiongroup_set_enabled('g1', True)
|
||||
assert widget.bee_actions['foo'].isEnabled() is True
|
||||
assert widget.bee_actions['bar'].isEnabled() is False
|
||||
from beeref.actions.mixin import actions
|
||||
assert actions['foo'].qaction.isEnabled() is True
|
||||
assert actions['bar'].qaction.isEnabled() is False
|
||||
|
||||
|
||||
@patch('beeref.actions.mixin.menu_structure')
|
||||
@patch('beeref.actions.mixin.actions')
|
||||
def test_build_menu_and_actions_disables_actiongroups(
|
||||
actions_mock, menu_mock, qapp):
|
||||
@patch('beeref.actions.mixin.menu_structure',
|
||||
[{'menu': 'Foo', 'items': ['foo']}])
|
||||
@patch('beeref.actions.mixin.actions',
|
||||
ActionList([Action({
|
||||
'id': 'foo',
|
||||
'text': '&Foo',
|
||||
'callback': 'on_foo',
|
||||
'group': 'active_when_selection',
|
||||
})]))
|
||||
def test_build_menu_and_actions_disables_actiongroups(qapp):
|
||||
widget = FooWidget()
|
||||
widget.scene.has_selection.return_value = False
|
||||
actions_mock.__iter__.return_value = [
|
||||
{
|
||||
'id': 'foo',
|
||||
'text': '&Foo',
|
||||
'callback': 'on_foo',
|
||||
'group': 'active_when_selection',
|
||||
},
|
||||
]
|
||||
|
||||
menu_mock.__iter__.return_value = ['foo']
|
||||
widget.build_menu_and_actions()
|
||||
qaction = widget.actions()[0]
|
||||
assert qaction.isEnabled() is False
|
||||
|
||||
|
||||
@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_more_files_than_shortcuts(
|
||||
kb_mock, actions_mock, menu_mock, triggered_mock, qapp):
|
||||
@patch('beeref.config.KeyboardSettings.get_shortcuts')
|
||||
@patch('beeref.actions.mixin.menu_structure',
|
||||
[{'menu': 'Foo', 'items': '_build_recent_files'}])
|
||||
@patch('beeref.actions.mixin.actions', ActionList([]))
|
||||
def test_create_recent_files_more_than_10_files(
|
||||
kb_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)]
|
||||
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()) == 15
|
||||
assert len(widget.actions()) == 10
|
||||
|
||||
from beeref.actions.mixin import actions
|
||||
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
|
||||
assert actions['recent_files_0'].qaction == qaction1
|
||||
qaction10 = widget.actions()[9]
|
||||
assert qaction10.text() == '9.bee'
|
||||
assert qaction10.shortcut() == 'Ctrl+0'
|
||||
assert qaction10.isEnabled() is True
|
||||
assert widget.bee_actions['recent_files_9'] == qaction10
|
||||
qaction15 = widget.actions()[-1]
|
||||
assert qaction15.text() == '14.bee'
|
||||
assert qaction15.shortcut() == ''
|
||||
assert qaction15.isEnabled() is True
|
||||
assert widget.bee_actions['recent_files_14'] == qaction15
|
||||
assert actions['recent_files_9'].qaction == qaction10
|
||||
|
||||
assert kb_mock.call_count == 10
|
||||
kb_mock.assert_has_calls(
|
||||
|
|
@ -309,73 +291,62 @@ def test_create_recent_files_more_files_than_shortcuts(
|
|||
|
||||
|
||||
@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):
|
||||
@patch('beeref.actions.mixin.menu_structure',
|
||||
[{'menu': 'Foo', 'items': '_build_recent_files'}])
|
||||
@patch('beeref.actions.mixin.actions', ActionList([]))
|
||||
@patch('beeref.config.KeyboardSettings.get_shortcuts')
|
||||
def test_create_recent_files_fewer_files_than_10_files(
|
||||
kb_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
|
||||
|
||||
from beeref.actions.mixin import actions
|
||||
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
|
||||
assert actions['recent_files_0'].qaction == 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 actions['recent_files_4'].qaction == qaction5
|
||||
assert actions['recent_files_5'].qaction is None
|
||||
|
||||
assert kb_mock.call_count == 10
|
||||
assert kb_mock.call_count == 5
|
||||
kb_mock.assert_has_calls(
|
||||
[call('Actions', 'recent_files_0', ['Ctrl+1']),
|
||||
call('Actions', 'recent_files_9', ['Ctrl+0'])],
|
||||
call('Actions', 'recent_files_4', ['Ctrl+5'])],
|
||||
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):
|
||||
@patch('beeref.actions.mixin.menu_structure',
|
||||
[{'menu': 'Foo', 'items': '_build_recent_files'}])
|
||||
@patch('beeref.actions.mixin.actions', ActionList([]))
|
||||
@patch('beeref.config.KeyboardSettings.get_shortcuts')
|
||||
def test_create_recent_files_when_no_files(kb_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)
|
||||
kb_mock.assert_not_called()
|
||||
|
||||
|
||||
@patch('PyQt6.QtGui.QAction.triggered')
|
||||
@patch('beeref.actions.mixin.menu_structure')
|
||||
@patch('beeref.actions.mixin.actions')
|
||||
def test_update_recent_files(actions_mock, menu_mock, triggered_mock, qapp):
|
||||
@patch('beeref.actions.mixin.menu_structure',
|
||||
[{'menu': 'Foo', 'items': '_build_recent_files'}])
|
||||
@patch('beeref.actions.mixin.actions', ActionList([]))
|
||||
def test_update_recent_files(triggered_mock, qapp):
|
||||
widget = FooWidget()
|
||||
widget.settings.get_recent_files.return_value = [
|
||||
os.path.abspath('foo.bee')]
|
||||
menu_mock.__iter__.return_value = [{
|
||||
'menu': 'Open &Recent',
|
||||
'items': '_build_recent_files',
|
||||
}]
|
||||
|
||||
widget.build_menu_and_actions()
|
||||
triggered_mock.connect.reset_mock()
|
||||
|
|
|
|||
|
|
@ -17,9 +17,13 @@ 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 reset_beeref_actions():
|
||||
from beeref.actions.actions import actions
|
||||
for key in list(actions.keys()):
|
||||
if key.startswith('recent_files_'):
|
||||
actions.pop(key)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
|
|
|
|||
|
|
@ -59,13 +59,13 @@ def test_settings_value_or_default_gets_default_when_cast_error(settings):
|
|||
assert settings.valueOrDefault('Items/arrange_gap') == 0
|
||||
|
||||
|
||||
def test_restore_defaults_restores(settings):
|
||||
def test_settings_restore_defaults_restores(settings):
|
||||
settings.setValue('Items/image_storage_format', 'png')
|
||||
settings.restore_defaults()
|
||||
assert settings.contains('Items/image_storage_format') is False
|
||||
|
||||
|
||||
def test_restore_defaults_leaves_other_settings(settings):
|
||||
def test_settings_restore_defaults_leaves_other_settings(settings):
|
||||
settings.setValue('foo/bar', 'baz')
|
||||
settings.restore_defaults()
|
||||
assert settings.contains('foo/bar') is True
|
||||
|
|
@ -122,34 +122,34 @@ def test_keyboardsettings_set_shortcuts_multiple(kbsettings):
|
|||
|
||||
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()
|
||||
shortcuts = kbsettings.get_shortcuts('Actions', 'bar', ['Ctrl+B'])
|
||||
assert shortcuts == ['Ctrl+R']
|
||||
|
||||
|
||||
def test_keyboardsettings_get_shortcuts_default(kbsettings):
|
||||
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'])
|
||||
shortcuts = kbsettings.get_shortcuts('Actions', 'bar', ['Ctrl+B'])
|
||||
assert shortcuts == ['Ctrl+B']
|
||||
|
||||
|
||||
def test_keyboardsettings_get_shortcuts_default_doesnt_override_empty(
|
||||
kbsettings):
|
||||
kbsettings.set_shortcuts('Actions', 'bar', [])
|
||||
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()
|
||||
@patch('beeref.config.KeyboardSettings.setValue')
|
||||
@patch('beeref.config.KeyboardSettings.remove')
|
||||
def test_keyboardsettings_set_shortcuts_other_than_default_saves(
|
||||
remove_mock, set_mock, kbsettings):
|
||||
kbsettings.set_shortcuts('Actions', 'bar', ['Ctrl+R'], ['Ctrl+Z'])
|
||||
set_mock.assert_called_once_with('Actions/bar', 'Ctrl+R')
|
||||
remove_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_keyboardsettings_get_shortcuts_not_set_no_defaults(kbsettings):
|
||||
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', [])
|
||||
@patch('beeref.config.KeyboardSettings.setValue')
|
||||
@patch('beeref.config.KeyboardSettings.remove')
|
||||
def test_keyboardsettings_set_shortcuts_with_than_default_doesnt_save(
|
||||
remove_mock, set_mock, kbsettings):
|
||||
kbsettings.set_shortcuts('Actions', 'bar', ['Ctrl+R'], ['Ctrl+R'])
|
||||
set_mock.assert_not_called()
|
||||
remove_mock.assert_called_once_with('Actions/bar')
|
||||
|
||||
|
||||
def test_keyboardsettings_restore_defaults_restores(kbsettings):
|
||||
kbsettings.setValue('Actions/bar', 'Ctrl+R')
|
||||
kbsettings.restore_defaults()
|
||||
assert kbsettings.contains('Actions/bar') is False
|
||||
|
|
|
|||
|
|
@ -11,12 +11,12 @@ from beeref.items import BeePixmapItem, BeeTextItem
|
|||
from beeref.view import BeeGraphicsView
|
||||
|
||||
|
||||
def test_inits_menu(view, qapp):
|
||||
def test_inits_menu(qapp):
|
||||
parent = QtWidgets.QMainWindow()
|
||||
view = BeeGraphicsView(qapp, parent)
|
||||
assert isinstance(view.context_menu, QtWidgets.QMenu)
|
||||
assert len(view.actions()) > 0
|
||||
assert view.bee_actions
|
||||
assert view.actions()
|
||||
assert view.bee_actiongroups
|
||||
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ def test_init_without_filename(open_file_mock, qapp, commandline_args):
|
|||
|
||||
|
||||
@patch('beeref.view.BeeGraphicsView.open_from_file')
|
||||
def test_init_with_filename(open_file_mock, view, qapp, commandline_args):
|
||||
def test_init_with_filename(open_file_mock, qapp, commandline_args):
|
||||
commandline_args.filename = 'test.bee'
|
||||
parent = QtWidgets.QMainWindow()
|
||||
view = BeeGraphicsView(qapp, parent)
|
||||
|
|
@ -374,6 +374,12 @@ def test_on_action_settings(show_mock, view):
|
|||
show_mock.assert_called_once()
|
||||
|
||||
|
||||
@patch('beeref.widgets.settings.KeyboardSettingsDialog.show')
|
||||
def test_on_action_keyboard_settings(show_mock, view):
|
||||
view.on_action_keyboard_settings()
|
||||
show_mock.assert_called_once()
|
||||
|
||||
|
||||
@patch('beeref.widgets.HelpDialog.show')
|
||||
def test_on_action_help(show_mock, view):
|
||||
view.on_action_help()
|
||||
|
|
|
|||
|
|
@ -1,9 +1,16 @@
|
|||
from unittest.mock import patch
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from PyQt6 import QtWidgets
|
||||
from PyQt6 import QtWidgets, QtCore, QtGui
|
||||
|
||||
from beeref.actions.actions import Action, ActionList
|
||||
from beeref.widgets.settings import (
|
||||
ArrangeGapWidget,
|
||||
ImageStorageFormatWidget,
|
||||
KeyboardSettingsDialog,
|
||||
KeyboardShortcutsDelegate,
|
||||
KeyboardShortcutsEditor,
|
||||
KeyboardShortcutsModel,
|
||||
KeyboardShortcutsProxy,
|
||||
SettingsDialog,
|
||||
)
|
||||
|
||||
|
|
@ -67,3 +74,418 @@ def test_settings_dialog_on_restore_defaults(msg_mock, settings, view):
|
|||
msg_mock.assert_called_once()
|
||||
assert settings.valueOrDefault('Items/image_storage_format') == 'best'
|
||||
assert settings.valueOrDefault('Items/arrange_gap') == 0
|
||||
|
||||
|
||||
def test_keyboard_shortcuts_editor_no_conflicts(view):
|
||||
a1 = Action({'id': 'foo', 'shortcuts': ['Ctrl+F']})
|
||||
a2 = Action({'id': 'bar', 'shortcuts': ['Ctrl+B']})
|
||||
with patch('beeref.widgets.settings.actions', ActionList([a1, a2])):
|
||||
editor = KeyboardShortcutsEditor(
|
||||
view,
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=2),
|
||||
row=MagicMock(return_value=0)))
|
||||
editor.setKeySequence('Ctrl+A')
|
||||
editor.on_editing_finished()
|
||||
assert editor.keySequence().toString() == 'Ctrl+A'
|
||||
assert editor.remove_from_other is None
|
||||
|
||||
|
||||
def test_keyboard_shortcuts_editor_reenter_existing_shortcut(view):
|
||||
a1 = Action({'id': 'foo', 'shortcuts': ['Ctrl+F']})
|
||||
a2 = Action({'id': 'bar', 'shortcuts': ['Ctrl+B']})
|
||||
with patch('beeref.widgets.settings.actions', ActionList([a1, a2])):
|
||||
editor = KeyboardShortcutsEditor(
|
||||
view,
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=2),
|
||||
row=MagicMock(return_value=0)))
|
||||
editor.setKeySequence('Ctrl+F')
|
||||
editor.on_editing_finished()
|
||||
assert editor.keySequence().toString() == 'Ctrl+F'
|
||||
assert editor.remove_from_other is None
|
||||
|
||||
|
||||
@patch('PyQt6.QtWidgets.QMessageBox.question',
|
||||
return_value=QtWidgets.QMessageBox.StandardButton.No)
|
||||
def test_keyboard_shortcuts_editor_conflicts_choose_to_cancel(
|
||||
question_mock, view):
|
||||
a1 = Action({'id': 'foo', 'text': 'Foo', 'shortcuts': ['Ctrl+F']})
|
||||
a2 = Action({'id': 'bar', 'text': 'Bar', 'shortcuts': ['Ctrl+B']})
|
||||
with patch('beeref.widgets.settings.actions', ActionList([a1, a2])):
|
||||
editor = KeyboardShortcutsEditor(
|
||||
view,
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=2),
|
||||
row=MagicMock(return_value=0)))
|
||||
editor.setKeySequence('Ctrl+B')
|
||||
editor.on_editing_finished()
|
||||
assert editor.keySequence().toString() == 'Ctrl+F'
|
||||
assert editor.remove_from_other is None
|
||||
|
||||
|
||||
@patch('PyQt6.QtWidgets.QMessageBox.question',
|
||||
return_value=QtWidgets.QMessageBox.StandardButton.Yes)
|
||||
def test_keyboard_shortcuts_editor_conflicts_choose_to_confirm(
|
||||
question_mock, view):
|
||||
a1 = Action({'id': 'foo', 'text': 'Foo', 'shortcuts': ['Ctrl+F']})
|
||||
a2 = Action({'id': 'bar', 'text': 'Bar', 'shortcuts': ['Ctrl+B']})
|
||||
with patch('beeref.widgets.settings.actions', ActionList([a1, a2])):
|
||||
editor = KeyboardShortcutsEditor(
|
||||
view,
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=2),
|
||||
row=MagicMock(return_value=0)))
|
||||
editor.setKeySequence('Ctrl+B')
|
||||
editor.on_editing_finished()
|
||||
assert editor.keySequence().toString() == 'Ctrl+B'
|
||||
assert editor.remove_from_other == a2
|
||||
|
||||
|
||||
@patch('PyQt6.QtWidgets.QMessageBox.question',
|
||||
return_value=QtWidgets.QMessageBox.StandardButton.No)
|
||||
def test_keyboard_shortcuts_editor_conflicts_choose_to_cancel_when_no_shortcut(
|
||||
question_mock, view):
|
||||
a1 = Action({'id': 'foo', 'text': 'Foo'})
|
||||
a2 = Action({'id': 'bar', 'text': 'Bar', 'shortcuts': ['Ctrl+B']})
|
||||
with patch('beeref.widgets.settings.actions', ActionList([a1, a2])):
|
||||
editor = KeyboardShortcutsEditor(
|
||||
view,
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=2),
|
||||
row=MagicMock(return_value=0)))
|
||||
editor.setKeySequence('Ctrl+B')
|
||||
editor.on_editing_finished()
|
||||
assert editor.keySequence().toString() == ''
|
||||
assert a2.get_shortcuts() == ['Ctrl+B']
|
||||
|
||||
|
||||
def test_keyboard_shortcuts_delegate_setmodeldata(view):
|
||||
a1 = Action({'id': 'foo', 'text': 'Foo'})
|
||||
a2 = Action({'id': 'bar', 'text': 'Bar', 'shortcuts': ['Ctrl+B']})
|
||||
with patch('beeref.widgets.settings.actions', ActionList([a1, a2])):
|
||||
model = KeyboardShortcutsModel()
|
||||
delegate = KeyboardShortcutsDelegate()
|
||||
editor = delegate.createEditor(view, None, index=model.index(0, 2))
|
||||
editor.setKeySequence('Ctrl+F')
|
||||
delegate.setModelData(
|
||||
editor, model, index=model.index(0, 2))
|
||||
assert a1.get_shortcuts() == ['Ctrl+F']
|
||||
|
||||
|
||||
def test_keyboard_shortcuts_model_columncount():
|
||||
model = KeyboardShortcutsModel()
|
||||
model.columnCount(None) == 4
|
||||
|
||||
|
||||
@patch('beeref.widgets.settings.actions',
|
||||
ActionList([
|
||||
Action({'id': 'foo', 'text': 'Foo', 'shortcuts': ['Ctrl+F']}),
|
||||
Action({'id': 'bar', 'text': 'Bar', 'shortcuts': ['Ctrl+B']})
|
||||
]))
|
||||
def test_keyboard_shortcuts_model_rowcount():
|
||||
model = KeyboardShortcutsModel()
|
||||
model.rowCount(None) == 2
|
||||
|
||||
|
||||
def test_keyboard_shortcuts_model_data_gets_text():
|
||||
action = Action({'id': 'foo', 'text': '&Foo', 'shortcuts': ['Ctrl+F']})
|
||||
action.menu_path = ['&Bar', 'Ba&z']
|
||||
with patch('beeref.widgets.settings.actions', ActionList([action])):
|
||||
model = KeyboardShortcutsModel()
|
||||
value = model.data(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=0),
|
||||
row=MagicMock(return_value=0)),
|
||||
role=QtCore.Qt.ItemDataRole.DisplayRole)
|
||||
assert value == 'Bar: Baz: Foo'
|
||||
|
||||
|
||||
def test_keyboard_shortcuts_model_data_gets_changed_when_not_changed():
|
||||
action = Action({'id': 'foo', 'text': 'Foo', 'shortcuts': ['Ctrl+F']})
|
||||
with patch('beeref.widgets.settings.actions', ActionList([action])):
|
||||
model = KeyboardShortcutsModel()
|
||||
value = model.data(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=1),
|
||||
row=MagicMock(return_value=0)),
|
||||
role=QtCore.Qt.ItemDataRole.DisplayRole)
|
||||
assert value is None
|
||||
|
||||
|
||||
def test_keyboard_shortcuts_model_data_gets_changed_when_changed(kbsettings):
|
||||
action = Action({'id': 'foo', 'text': 'Foo', 'shortcuts': ['Ctrl+F']})
|
||||
with patch('beeref.widgets.settings.actions', ActionList([action])):
|
||||
action.set_shortcuts(['Ctrl+B'])
|
||||
model = KeyboardShortcutsModel()
|
||||
value = model.data(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=1),
|
||||
row=MagicMock(return_value=0)),
|
||||
role=QtCore.Qt.ItemDataRole.DisplayRole)
|
||||
assert value == '✎'
|
||||
|
||||
|
||||
def test_keyboard_shortcuts_model_data_gets_shortcut():
|
||||
action = Action({'id': 'foo', 'text': 'Foo', 'shortcuts': ['Ctrl+F']})
|
||||
with patch('beeref.widgets.settings.actions', ActionList([action])):
|
||||
model = KeyboardShortcutsModel()
|
||||
value = model.data(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=2),
|
||||
row=MagicMock(return_value=0)),
|
||||
role=QtCore.Qt.ItemDataRole.DisplayRole)
|
||||
assert value == 'Ctrl+F'
|
||||
|
||||
|
||||
def test_keyboard_shortcuts_model_data_tooltip_changed_when_not_changed():
|
||||
action = Action({'id': 'foo', 'text': 'Foo', 'shortcuts': ['Ctrl+F']})
|
||||
with patch('beeref.widgets.settings.actions', ActionList([action])):
|
||||
model = KeyboardShortcutsModel()
|
||||
value = model.data(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=1),
|
||||
row=MagicMock(return_value=0)),
|
||||
role=QtCore.Qt.ItemDataRole.ToolTipRole)
|
||||
assert value is None
|
||||
|
||||
|
||||
def test_keyboard_shortcuts_model_data_tooltip_changed_when_changed():
|
||||
action = Action({'id': 'foo', 'text': 'Foo', 'shortcuts': ['Ctrl+F']})
|
||||
action.set_shortcuts(['Ctrl+B'])
|
||||
with patch('beeref.widgets.settings.actions', ActionList([action])):
|
||||
model = KeyboardShortcutsModel()
|
||||
value = model.data(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=1),
|
||||
row=MagicMock(return_value=0)),
|
||||
role=QtCore.Qt.ItemDataRole.ToolTipRole)
|
||||
assert value == 'Changed from default'
|
||||
|
||||
|
||||
def test_keyboard_shortcuts_model_data_tooltip_shortcut_when_not_changed():
|
||||
action = Action({'id': 'foo', 'text': 'Foo', 'shortcuts': ['Ctrl+F']})
|
||||
with patch('beeref.widgets.settings.actions', ActionList([action])):
|
||||
model = KeyboardShortcutsModel()
|
||||
value = model.data(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=2),
|
||||
row=MagicMock(return_value=0)),
|
||||
role=QtCore.Qt.ItemDataRole.ToolTipRole)
|
||||
assert value is None
|
||||
|
||||
|
||||
def test_keyboard_shortcuts_model_data_tooltip_shortcut_not_changed_not_set():
|
||||
action = Action({'id': 'foo', 'text': 'Foo'})
|
||||
with patch('beeref.widgets.settings.actions', ActionList([action])):
|
||||
model = KeyboardShortcutsModel()
|
||||
value = model.data(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=3),
|
||||
row=MagicMock(return_value=0)),
|
||||
role=QtCore.Qt.ItemDataRole.ToolTipRole)
|
||||
assert value is None
|
||||
|
||||
|
||||
def test_keyboard_shortcuts_model_data_tooltip_shortcut_when_changed():
|
||||
action = Action({'id': 'foo', 'text': 'Foo', 'shortcuts': ['Ctrl+F']})
|
||||
with patch('beeref.widgets.settings.actions', ActionList([action])):
|
||||
action.set_shortcuts(['Ctrl+B'])
|
||||
model = KeyboardShortcutsModel()
|
||||
value = model.data(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=2),
|
||||
row=MagicMock(return_value=0)),
|
||||
role=QtCore.Qt.ItemDataRole.ToolTipRole)
|
||||
assert value == 'Default: Ctrl+F'
|
||||
|
||||
|
||||
def test_keyboard_shortcuts_model_data_tooltip_shortcut_changed_from_none():
|
||||
action = Action({'id': 'foo', 'text': 'Foo'})
|
||||
with patch('beeref.widgets.settings.actions', ActionList([action])):
|
||||
action.set_shortcuts(['Ctrl+B'])
|
||||
model = KeyboardShortcutsModel()
|
||||
value = model.data(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=2),
|
||||
row=MagicMock(return_value=0)),
|
||||
role=QtCore.Qt.ItemDataRole.ToolTipRole)
|
||||
assert value == 'Default: -'
|
||||
|
||||
|
||||
def test_keyboard_shortcuts_model_setdata_saves():
|
||||
action = Action({'id': 'foo', 'shortcuts': ['Ctrl+F']})
|
||||
with patch('beeref.widgets.settings.actions', ActionList([action])):
|
||||
model = KeyboardShortcutsModel()
|
||||
model.setData(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=2),
|
||||
row=MagicMock(return_value=0)),
|
||||
value=QtGui.QKeySequence('Ctrl+B'),
|
||||
role=None)
|
||||
assert action.get_shortcuts() == ['Ctrl+B']
|
||||
|
||||
|
||||
def test_keyboard_shortcuts_model_setdata_saves_second_shortcut():
|
||||
action = Action({'id': 'foo', 'shortcuts': ['Ctrl+F']})
|
||||
with patch('beeref.widgets.settings.actions', ActionList([action])):
|
||||
model = KeyboardShortcutsModel()
|
||||
model.setData(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=3),
|
||||
row=MagicMock(return_value=0)),
|
||||
value=QtGui.QKeySequence('Ctrl+B'),
|
||||
role=None)
|
||||
assert action.get_shortcuts() == ['Ctrl+F', 'Ctrl+B']
|
||||
|
||||
|
||||
def test_keyboard_shortcuts_model_setdata_saves_second_shortcut_no_first():
|
||||
action = Action({'id': 'foo'})
|
||||
with patch('beeref.widgets.settings.actions', ActionList([action])):
|
||||
model = KeyboardShortcutsModel()
|
||||
model.setData(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=3),
|
||||
row=MagicMock(return_value=0)),
|
||||
value=QtGui.QKeySequence('Ctrl+B'),
|
||||
role=None)
|
||||
assert action.get_shortcuts() == ['Ctrl+B']
|
||||
|
||||
|
||||
def test_keyboard_shortcuts_model_setdata_removes_duplicate():
|
||||
action = Action({'id': 'foo', 'shortcuts': ['Ctrl+B']})
|
||||
with patch('beeref.widgets.settings.actions', ActionList([action])):
|
||||
model = KeyboardShortcutsModel()
|
||||
model.setData(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=3),
|
||||
row=MagicMock(return_value=0)),
|
||||
value=QtGui.QKeySequence('Ctrl+B'),
|
||||
role=None)
|
||||
assert action.get_shortcuts() == ['Ctrl+B']
|
||||
|
||||
|
||||
def test_keyboard_shortcuts_model_setdata_remove_from_other():
|
||||
a1 = Action({'id': 'foo', 'shortcuts': ['Ctrl+F']})
|
||||
a2 = Action({'id': 'bar', 'shortcuts': ['Ctrl+B']})
|
||||
with patch('beeref.widgets.settings.actions', ActionList([a1, a2])):
|
||||
model = KeyboardShortcutsModel()
|
||||
model.setData(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=2),
|
||||
row=MagicMock(return_value=0)),
|
||||
value=QtGui.QKeySequence('Ctrl+B'),
|
||||
role=None,
|
||||
remove_from_other=a2)
|
||||
assert a1.get_shortcuts() == ['Ctrl+B']
|
||||
assert a2.get_shortcuts() == []
|
||||
|
||||
|
||||
def test_keyboard_shortcuts_model_headerdata():
|
||||
model = KeyboardShortcutsModel()
|
||||
header = model.headerData(
|
||||
0,
|
||||
QtCore.Qt.Orientation.Horizontal,
|
||||
QtCore.Qt.ItemDataRole.DisplayRole)
|
||||
assert header == 'Action'
|
||||
|
||||
|
||||
def test_flags_first_column():
|
||||
model = KeyboardShortcutsModel()
|
||||
flags = model.flags(model.index(0, 0))
|
||||
assert flags == (QtCore.Qt.ItemFlag.ItemIsEnabled
|
||||
| QtCore.Qt.ItemFlag.ItemNeverHasChildren)
|
||||
|
||||
|
||||
def test_flags_shortcut_column():
|
||||
model = KeyboardShortcutsModel()
|
||||
flags = model.flags(model.index(0, 2))
|
||||
assert flags == (QtCore.Qt.ItemFlag.ItemIsEnabled
|
||||
| QtCore.Qt.ItemFlag.ItemNeverHasChildren
|
||||
| QtCore.Qt.ItemFlag.ItemIsEditable)
|
||||
|
||||
|
||||
@patch('beeref.widgets.settings.actions',
|
||||
ActionList([Action({'id': 'bar', 'text': 'Bar'}),
|
||||
Action({'id': 'foo', 'text': 'Foo'}),
|
||||
Action({'id': 'baz', 'text': 'Baz'})]))
|
||||
def test_keyboard_shortcuts_proxy_data_unfiltered():
|
||||
proxy = KeyboardShortcutsProxy()
|
||||
color1 = proxy.data(
|
||||
proxy.index(0, 0), QtCore.Qt.ItemDataRole.BackgroundRole)
|
||||
color2 = proxy.data(
|
||||
proxy.index(1, 0), QtCore.Qt.ItemDataRole.BackgroundRole)
|
||||
color3 = proxy.data(
|
||||
proxy.index(2, 0), QtCore.Qt.ItemDataRole.BackgroundRole)
|
||||
|
||||
assert color1 == color3
|
||||
assert color1 != color2
|
||||
|
||||
assert proxy.data(
|
||||
proxy.index(0, 0), QtCore.Qt.ItemDataRole.DisplayRole) == 'Bar'
|
||||
assert proxy.data(
|
||||
proxy.index(1, 0), QtCore.Qt.ItemDataRole.DisplayRole) == 'Foo'
|
||||
assert proxy.data(
|
||||
proxy.index(2, 0), QtCore.Qt.ItemDataRole.DisplayRole) == 'Baz'
|
||||
|
||||
|
||||
@patch('beeref.widgets.settings.actions',
|
||||
ActionList([Action({'id': 'bar', 'text': 'Bar'}),
|
||||
Action({'id': 'foo', 'text': 'Foo'}),
|
||||
Action({'id': 'baz', 'text': 'Baz'})]))
|
||||
def test_keyboard_shortcuts_proxy_data_filtered():
|
||||
proxy = KeyboardShortcutsProxy()
|
||||
proxy.setFilterFixedString('b')
|
||||
color1 = proxy.data(
|
||||
proxy.index(0, 0), QtCore.Qt.ItemDataRole.BackgroundRole)
|
||||
color2 = proxy.data(
|
||||
proxy.index(1, 0), QtCore.Qt.ItemDataRole.BackgroundRole)
|
||||
|
||||
assert color1 != color2
|
||||
|
||||
assert proxy.data(
|
||||
proxy.index(0, 0), QtCore.Qt.ItemDataRole.DisplayRole) == 'Bar'
|
||||
assert proxy.data(
|
||||
proxy.index(1, 0), QtCore.Qt.ItemDataRole.DisplayRole) == 'Baz'
|
||||
|
||||
|
||||
def test_keyboard_shortcuts_proxy_setdata_saves_correct_filtered_index():
|
||||
a1 = Action({'id': 'bar', 'text': 'Bar'})
|
||||
a2 = Action({'id': 'foo', 'text': 'Foo'})
|
||||
a3 = Action({'id': 'baz', 'text': 'Baz'})
|
||||
|
||||
with patch('beeref.widgets.settings.actions', ActionList([a1, a2, a3])):
|
||||
proxy = KeyboardShortcutsProxy()
|
||||
proxy.setFilterFixedString('b')
|
||||
proxy.setData(
|
||||
index=proxy.index(1, 2),
|
||||
value=QtGui.QKeySequence('Ctrl+B'),
|
||||
role=None)
|
||||
assert a1.get_shortcuts() == []
|
||||
assert a2.get_shortcuts() == []
|
||||
assert a3.get_shortcuts() == ['Ctrl+B']
|
||||
|
||||
|
||||
def test_keyboard_shortcuts_proxy_setdata_remove_from_other():
|
||||
a1 = Action({'id': 'foo', 'shortcuts': ['Ctrl+F']})
|
||||
a2 = Action({'id': 'bar', 'shortcuts': ['Ctrl+B']})
|
||||
with patch('beeref.widgets.settings.actions', ActionList([a1, a2])):
|
||||
proxy = KeyboardShortcutsProxy()
|
||||
proxy.setData(
|
||||
index=proxy.index(0, 2),
|
||||
value=QtGui.QKeySequence('Ctrl+B'),
|
||||
role=None,
|
||||
remove_from_other=a2)
|
||||
assert a1.get_shortcuts() == ['Ctrl+B']
|
||||
assert a2.get_shortcuts() == []
|
||||
|
||||
|
||||
@patch('PyQt6.QtWidgets.QMessageBox.question',
|
||||
return_value=QtWidgets.QMessageBox.StandardButton.Yes)
|
||||
@patch('beeref.config.KeyboardSettings.restore_defaults')
|
||||
def test_keyboard_settings_dialog_on_restore_defaults(
|
||||
restore_mock, msg_mock, kbsettings, view):
|
||||
dialog = KeyboardSettingsDialog(view)
|
||||
dialog.on_restore_defaults()
|
||||
msg_mock.assert_called_once()
|
||||
restore_mock.assert_called()
|
||||
|
|
|
|||
Loading…
Reference in a new issue