mirror of
https://github.com/rbreu/beeref.git
synced 2026-03-11 08:54:28 +00:00
Configurable mouse and mouswheel controls
This commit is contained in:
parent
a532d1d667
commit
c64385b2c0
31 changed files with 5083 additions and 1278 deletions
|
|
@ -13,6 +13,8 @@ Added
|
|||
* Added panning via scrollwheel:
|
||||
* Scroll wheel + Shift + Ctrl: pan vertically
|
||||
* Scroll wheel + Shift: pan horizontally
|
||||
* Make mouse and mouse wheel controls configurable
|
||||
(Settings -> Keyboard & Mouse)
|
||||
|
||||
|
||||
Fixed
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with BeeRef. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from collections import OrderedDict
|
||||
from functools import cached_property
|
||||
import logging
|
||||
|
||||
|
|
@ -21,22 +20,39 @@ from PyQt6 import QtGui
|
|||
|
||||
from beeref.actions.menu_structure import menu_structure
|
||||
from beeref.config import KeyboardSettings, settings_events
|
||||
from beeref.utils import ActionList
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Action(dict):
|
||||
class Action:
|
||||
SETTINGS_GROUP = 'Actions'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
def __init__(self, id, text, callback=None, shortcuts=None,
|
||||
checkable=False, checked=False, group=None, settings=None,
|
||||
enabled=True, menu_item=None, menu_id=None):
|
||||
self.id = id
|
||||
self.text = text
|
||||
self.callback = callback
|
||||
self.shortcuts = shortcuts or []
|
||||
self.checkable = checkable
|
||||
self.checked = checked
|
||||
self.group = group
|
||||
self.settings = settings
|
||||
self.enabled = enabled
|
||||
self.menu_item = menu_item
|
||||
self.menu_id = menu_id
|
||||
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']
|
||||
return self.id == other.id
|
||||
|
||||
def __str__(self):
|
||||
return self.id
|
||||
|
||||
def on_restore_defaults(self):
|
||||
if self.qaction:
|
||||
|
|
@ -50,7 +66,7 @@ class Action(dict):
|
|||
if isinstance(menu_item['items'], list):
|
||||
# This is a normal menu
|
||||
for item in menu_item['items']:
|
||||
if item == self['id']:
|
||||
if item == self.id:
|
||||
path.append(menu_item['menu'])
|
||||
return True
|
||||
if isinstance(item, dict):
|
||||
|
|
@ -58,7 +74,7 @@ class Action(dict):
|
|||
if _get_path(item):
|
||||
path.append(menu_item['menu'])
|
||||
return True
|
||||
elif menu_item['items'] == self.get('menu_id'):
|
||||
elif menu_item['items'] == self.menu_id:
|
||||
# This is a dynamic submenu (e.g. Recent Files)
|
||||
path.append(menu_item['menu'])
|
||||
return True
|
||||
|
|
@ -69,13 +85,13 @@ class Action(dict):
|
|||
return path[::-1]
|
||||
|
||||
def get_shortcuts(self):
|
||||
return self.kb_settings.get_shortcuts(
|
||||
'Actions', self['id'], self.get('shortcuts'))
|
||||
return self.kb_settings.get_list(
|
||||
self.SETTINGS_GROUP, self.id, self.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'))
|
||||
logger.debug(f'Setting shortcut "{self.id}" to: {value}')
|
||||
self.kb_settings.set_list(
|
||||
self.SETTINGS_GROUP, self.id, value, self.shortcuts)
|
||||
if self.qaction:
|
||||
self.qaction.setShortcuts(value)
|
||||
|
||||
|
|
@ -88,348 +104,335 @@ class Action(dict):
|
|||
|
||||
def shortcuts_changed(self):
|
||||
"""Whether shortcuts have changed from their defaults."""
|
||||
return self.get_shortcuts() != self.get('shortcuts', [])
|
||||
return self.get_shortcuts() != self.shortcuts
|
||||
|
||||
def get_default_shortcut(self, index):
|
||||
try:
|
||||
return self.get('shortcuts', [])[index]
|
||||
return self.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': 'show_color_gamut',
|
||||
'text': 'Show &Color Gamut',
|
||||
'callback': 'on_action_show_color_gamut',
|
||||
'group': 'active_when_single_image',
|
||||
}),
|
||||
Action({
|
||||
'id': 'sample_color',
|
||||
'text': 'Sample Color',
|
||||
'shortcuts': ['S'],
|
||||
'callback': 'on_action_sample_color',
|
||||
'group': 'active_when_items_in_scene',
|
||||
}),
|
||||
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',
|
||||
'callback': 'on_action_open_settings_dir',
|
||||
}),
|
||||
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='show_color_gamut',
|
||||
text='Show &Color Gamut',
|
||||
callback='on_action_show_color_gamut',
|
||||
group='active_when_single_image',
|
||||
),
|
||||
Action(
|
||||
id='sample_color',
|
||||
text='Sample Color',
|
||||
shortcuts=['S'],
|
||||
callback='on_action_sample_color',
|
||||
group='active_when_items_in_scene',
|
||||
),
|
||||
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 && Mouse',
|
||||
callback='on_action_keyboard_settings',
|
||||
),
|
||||
Action(
|
||||
id='open_settings_dir',
|
||||
text='&Open Settings Folder',
|
||||
callback='on_action_open_settings_dir',
|
||||
),
|
||||
])
|
||||
|
|
|
|||
|
|
@ -55,10 +55,10 @@ class ActionsMixin:
|
|||
|
||||
def _init_action_checkable(self, actiondef, qaction):
|
||||
qaction.setCheckable(True)
|
||||
callback = getattr(self, actiondef['callback'])
|
||||
callback = getattr(self, actiondef.callback)
|
||||
qaction.toggled.connect(callback)
|
||||
settings_key = actiondef.get('settings')
|
||||
checked = actiondef.get('checked', False)
|
||||
settings_key = actiondef.settings
|
||||
checked = actiondef.checked
|
||||
qaction.setChecked(checked)
|
||||
if settings_key:
|
||||
val = self.settings.value(settings_key, checked, type=bool)
|
||||
|
|
@ -69,18 +69,18 @@ class ActionsMixin:
|
|||
|
||||
def _create_actions(self):
|
||||
for action in actions.values():
|
||||
qaction = QtGui.QAction(action['text'], self)
|
||||
qaction = QtGui.QAction(action.text, self)
|
||||
shortcuts = action.get_shortcuts()
|
||||
if shortcuts:
|
||||
qaction.setShortcuts(shortcuts)
|
||||
if action.get('checkable', False):
|
||||
if action.checkable:
|
||||
self._init_action_checkable(action, qaction)
|
||||
else:
|
||||
qaction.triggered.connect(getattr(self, action['callback']))
|
||||
qaction.triggered.connect(getattr(self, action.callback))
|
||||
self.addAction(qaction)
|
||||
qaction.setEnabled(action.get('enabled', True))
|
||||
if 'group' in action:
|
||||
self.bee_actiongroups[action['group']].append(qaction)
|
||||
qaction.setEnabled(action.enabled)
|
||||
if action.group:
|
||||
self.bee_actiongroups[action.group].append(qaction)
|
||||
qaction.setEnabled(False)
|
||||
action.qaction = qaction
|
||||
|
||||
|
|
@ -112,10 +112,10 @@ class ActionsMixin:
|
|||
for i in range(10):
|
||||
action_id = f'recent_files_{i}'
|
||||
key = 0 if i == 9 else i + 1
|
||||
action = Action({'id': action_id,
|
||||
'menu_id': '_build_recent_files',
|
||||
'text': f'File {i + 1}',
|
||||
'shortcuts': [f'Ctrl+{key}']})
|
||||
action = Action(id=action_id,
|
||||
menu_id='_build_recent_files',
|
||||
text=f'File {i + 1}',
|
||||
shortcuts=[f'Ctrl+{key}'])
|
||||
actions[action_id] = action
|
||||
|
||||
if i < len(files):
|
||||
|
|
|
|||
91
beeref/config/__init__.py
Normal file
91
beeref/config/__init__.py
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
# This file is part of BeeRef.
|
||||
#
|
||||
# BeeRef is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BeeRef is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BeeRef. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
"""Handling of command line args and Qt settings."""
|
||||
|
||||
import logging
|
||||
import logging.config
|
||||
import os.path
|
||||
|
||||
from PyQt6 import QtCore
|
||||
|
||||
from beeref import constants
|
||||
from beeref.config.controls import KeyboardSettings # noqa F401
|
||||
from beeref.config.settings import ( # noqa F401
|
||||
BeeSettings,
|
||||
CommandlineArgs,
|
||||
settings_events,
|
||||
)
|
||||
from beeref.logging import qt_message_handler
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def logfile_name():
|
||||
return os.path.join(
|
||||
os.path.dirname(BeeSettings().fileName()), f'{constants.APPNAME}.log')
|
||||
|
||||
|
||||
logging_conf = {
|
||||
'version': 1,
|
||||
'formatters': {
|
||||
'verbose': {
|
||||
'format': ('{asctime} {name} {process:d} {thread:d} {message}'),
|
||||
'style': '{',
|
||||
},
|
||||
'simple': {
|
||||
'format': '{levelname} {name}: {message}',
|
||||
'style': '{',
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
'console': {
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'simple',
|
||||
'level': CommandlineArgs().loglevel,
|
||||
},
|
||||
'file': {
|
||||
'class': 'beeref.logging.BeeRotatingFileHandler',
|
||||
'formatter': 'verbose',
|
||||
'filename': logfile_name(),
|
||||
'maxBytes': 1024 * 1000, # 1MB
|
||||
'backupCount': 1,
|
||||
'level': 'DEBUG',
|
||||
'delay': True,
|
||||
}
|
||||
},
|
||||
'loggers': {
|
||||
'beeref': {
|
||||
'handlers': ['console', 'file'],
|
||||
'level': 'TRACE',
|
||||
'propagate': False,
|
||||
},
|
||||
'Qt': {
|
||||
'handlers': ['console', 'file'],
|
||||
'level': 'DEBUG',
|
||||
'propagate': False,
|
||||
},
|
||||
},
|
||||
'root': {
|
||||
'handlers': ['console', 'file'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
}
|
||||
|
||||
logging.config.dictConfig(logging_conf)
|
||||
|
||||
# Redirect Qt logging to Python logger:
|
||||
QtCore.qInstallMessageHandler(qt_message_handler)
|
||||
336
beeref/config/controls.py
Normal file
336
beeref/config/controls.py
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
# This file is part of BeeRef.
|
||||
#
|
||||
# BeeRef is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BeeRef is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BeeRef. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
"""Handling of keyboard shortcuts and mouse controls."""
|
||||
|
||||
from collections import OrderedDict
|
||||
from functools import cached_property
|
||||
import logging
|
||||
import logging.config
|
||||
import os.path
|
||||
|
||||
from beeref.config.settings import BeeSettings, settings_events
|
||||
from beeref.utils import ActionList
|
||||
|
||||
from PyQt6 import QtCore
|
||||
from PyQt6.QtCore import Qt
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MouseConfigBase:
|
||||
MODIFIER_MAP = OrderedDict((
|
||||
('No Modifier', Qt.KeyboardModifier.NoModifier),
|
||||
('Shift', Qt.KeyboardModifier.ShiftModifier),
|
||||
('Ctrl', Qt.KeyboardModifier.ControlModifier),
|
||||
('Alt', Qt.KeyboardModifier.AltModifier),
|
||||
('Meta', Qt.KeyboardModifier.MetaModifier),
|
||||
('Keypad', Qt.KeyboardModifier.KeypadModifier),
|
||||
))
|
||||
|
||||
BUTTON_MAP = OrderedDict((
|
||||
('Not Configured', Qt.MouseButton.NoButton),
|
||||
('Left', Qt.MouseButton.LeftButton),
|
||||
('Middle', Qt.MouseButton.MiddleButton),
|
||||
))
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.id == other.id
|
||||
|
||||
def __str__(self):
|
||||
return self.id
|
||||
|
||||
@cached_property
|
||||
def kb_settings(self):
|
||||
return KeyboardSettings()
|
||||
|
||||
def get_modifiers(self):
|
||||
return self.kb_settings.get_list(
|
||||
self.SETTINGS_GROUP, f'{self.id}_modifiers', self.modifiers)
|
||||
|
||||
def set_modifiers(self, value):
|
||||
logger.debug(
|
||||
f'Setting {self.SETTINGS_GROUP} modifiers '
|
||||
f'for "{self.id}" to: {value}')
|
||||
self.kb_settings.set_list(
|
||||
self.SETTINGS_GROUP, f'{self.id}_modifiers', value, self.modifiers)
|
||||
|
||||
def get_inverted(self):
|
||||
return self.kb_settings.get_value(
|
||||
self.SETTINGS_GROUP, f'{self.id}_inverted', self.inverted)
|
||||
|
||||
def set_inverted(self, value):
|
||||
logger.debug(
|
||||
f'Setting {self.SETTINGS_GROUP} inverted '
|
||||
f'for "{self.id}" to: {value}')
|
||||
self.kb_settings.set_value(
|
||||
self.SETTINGS_GROUP, f'{self.id}_inverted', value, self.inverted)
|
||||
|
||||
@classmethod
|
||||
def modifiers_to_qt(cls, modifiers):
|
||||
combined = cls.MODIFIER_MAP[modifiers[0]]
|
||||
for mod in modifiers[1:]:
|
||||
combined = combined | cls.MODIFIER_MAP[mod]
|
||||
return combined
|
||||
|
||||
|
||||
class MouseWheelConfig(MouseConfigBase):
|
||||
|
||||
SETTINGS_GROUP = 'MouseWheel'
|
||||
|
||||
def __init__(self, id, group, text, modifiers, invertible):
|
||||
self.id = id
|
||||
self.group = group
|
||||
self.text = text
|
||||
self.modifiers = modifiers
|
||||
self.invertible = invertible
|
||||
self.inverted = False
|
||||
|
||||
def controls_changed(self):
|
||||
"""Whether controls have changed from their defaults."""
|
||||
return (set(self.get_modifiers()) != set(self.modifiers)
|
||||
or self.get_inverted() != self.inverted)
|
||||
|
||||
def is_configured(self):
|
||||
"""Whether controls have been configured for this action."""
|
||||
return bool(self.get_modifiers())
|
||||
|
||||
def remove_controls(self):
|
||||
self.set_modifiers([])
|
||||
self.set_inverted(False)
|
||||
|
||||
def conflicts_with(self, other):
|
||||
"""Whether controls conflict with `other`.
|
||||
|
||||
For unconfigured controls, always return False."""
|
||||
return (self.is_configured()
|
||||
and other.is_configured()
|
||||
and set(self.get_modifiers()) == set(other.get_modifiers()))
|
||||
|
||||
def matches_event(self, event):
|
||||
if not self.is_configured():
|
||||
return False
|
||||
modifiers = self.get_modifiers()
|
||||
return self.modifiers_to_qt(modifiers) == event.modifiers()
|
||||
|
||||
|
||||
class MouseConfig(MouseConfigBase):
|
||||
|
||||
SETTINGS_GROUP = 'Mouse'
|
||||
|
||||
def __init__(self, id, group, text, button, modifiers, invertible):
|
||||
self.id = id
|
||||
self.group = group
|
||||
self.text = text
|
||||
self.button = button
|
||||
self.modifiers = modifiers
|
||||
self.invertible = invertible
|
||||
self.inverted = False
|
||||
|
||||
def get_button(self):
|
||||
return self.kb_settings.get_value(
|
||||
self.SETTINGS_GROUP, f'{self.id}_button', self.button)
|
||||
|
||||
def set_button(self, value):
|
||||
logger.debug(
|
||||
f'Setting {self.SETTINGS_GROUP} button '
|
||||
f'for "{self.id}" to: {value}')
|
||||
self.kb_settings.set_value(
|
||||
self.SETTINGS_GROUP, f'{self.id}_button', value, self.button)
|
||||
|
||||
def conflicts_with(self, other):
|
||||
"""Whether controls conflict with `other`.
|
||||
|
||||
For unconfigured controls, always return False.
|
||||
"""
|
||||
return (self.is_configured()
|
||||
and other.is_configured()
|
||||
and self.get_button() == other.get_button()
|
||||
and set(self.get_modifiers()) == set(other.get_modifiers()))
|
||||
|
||||
def controls_changed(self):
|
||||
"""Whether controls have changed from their defaults."""
|
||||
return (self.get_button() != self.button
|
||||
or set(self.get_modifiers()) != set(self.modifiers)
|
||||
or self.get_inverted() != self.inverted)
|
||||
|
||||
def is_configured(self):
|
||||
"""Whether controls have been configured for this action."""
|
||||
return self.get_button() != 'Not Configured'
|
||||
|
||||
def remove_controls(self):
|
||||
self.set_button('Not Configured')
|
||||
self.set_modifiers([])
|
||||
self.set_inverted(False)
|
||||
|
||||
def matches_event(self, event):
|
||||
if not self.is_configured():
|
||||
return False
|
||||
modifiers = self.get_modifiers()
|
||||
return (self.modifiers_to_qt(modifiers) == event.modifiers()
|
||||
and self.BUTTON_MAP[self.get_button()] == event.button())
|
||||
|
||||
|
||||
class KeyboardSettings(QtCore.QSettings):
|
||||
|
||||
MOUSEWHEEL_ACTIONS = ActionList([
|
||||
MouseWheelConfig(
|
||||
id='zoom1',
|
||||
group='zoom',
|
||||
text='Zoom',
|
||||
modifiers=('No Modifier',),
|
||||
invertible=True,
|
||||
),
|
||||
MouseWheelConfig(
|
||||
id='zoom2',
|
||||
group='zoom',
|
||||
text='Zoom (alternative)',
|
||||
modifiers=(),
|
||||
invertible=True,
|
||||
),
|
||||
MouseWheelConfig(
|
||||
id='pan_horizontal1',
|
||||
group='pan_horizontal',
|
||||
text='Pan horizontally',
|
||||
modifiers=('Shift',),
|
||||
invertible=True,
|
||||
),
|
||||
MouseWheelConfig(
|
||||
id='pan_horizontal2',
|
||||
group='pan_horizontal',
|
||||
text='Pan horizontally (alternative)',
|
||||
modifiers=(),
|
||||
invertible=True,
|
||||
),
|
||||
MouseWheelConfig(
|
||||
id='pan_vertical1',
|
||||
group='pan_vertical',
|
||||
text='Pan vertically',
|
||||
modifiers=('Shift', 'Ctrl'),
|
||||
invertible=True,
|
||||
),
|
||||
MouseWheelConfig(
|
||||
id='pan_vertical2',
|
||||
group='pan_vertical',
|
||||
text='Pan vertically (alternative)',
|
||||
modifiers=(),
|
||||
invertible=True,
|
||||
),
|
||||
])
|
||||
|
||||
MOUSE_ACTIONS = ActionList([
|
||||
MouseConfig(
|
||||
id='zoom1',
|
||||
group='zoom',
|
||||
text='Zoom',
|
||||
button='Middle',
|
||||
modifiers=('Ctrl',),
|
||||
invertible=True,
|
||||
),
|
||||
MouseConfig(
|
||||
id='zoom2',
|
||||
group='zoom',
|
||||
text='Zoom (alternative)',
|
||||
button='Not Configured',
|
||||
modifiers=(),
|
||||
invertible=True,
|
||||
),
|
||||
MouseConfig(
|
||||
id='pan1',
|
||||
group='pan',
|
||||
text='Pan',
|
||||
button='Middle',
|
||||
modifiers=('No Modifier',),
|
||||
invertible=False,
|
||||
),
|
||||
MouseConfig(
|
||||
id='pan2',
|
||||
group='pan',
|
||||
text='Pan (alternative)',
|
||||
button='Left',
|
||||
modifiers=('Alt',),
|
||||
invertible=False,
|
||||
),
|
||||
MouseConfig(
|
||||
id='movewindow1',
|
||||
group='movewindow',
|
||||
text='Move Window',
|
||||
button='Left',
|
||||
modifiers=('Ctrl', 'Alt'),
|
||||
invertible=False,
|
||||
),
|
||||
MouseConfig(
|
||||
id='movewindow2',
|
||||
group='movewindow (alternative)',
|
||||
text='Move Window',
|
||||
button='Not Configured',
|
||||
modifiers=(),
|
||||
invertible=False,
|
||||
),
|
||||
])
|
||||
|
||||
def __init__(self):
|
||||
settings_format = QtCore.QSettings.Format.IniFormat
|
||||
filename = os.path.join(
|
||||
os.path.dirname(BeeSettings().fileName()),
|
||||
'KeyboardSettings.ini')
|
||||
super().__init__(filename, settings_format)
|
||||
|
||||
def set_list(self, group, key, values, default=None):
|
||||
if values == default:
|
||||
self.remove(f'{group}/{key}')
|
||||
else:
|
||||
self.setValue(f'{group}/{key}', ', '.join(values))
|
||||
|
||||
def get_list(self, group, key, default=None):
|
||||
values = self.value(f'{group}/{key}')
|
||||
if values is not None:
|
||||
values = list(filter(lambda x: x, values.split(', ')))
|
||||
return values
|
||||
|
||||
return list(default or []) # Always return new instance of default
|
||||
|
||||
def get_value(self, group, key, default=None):
|
||||
value = self.value(f'{group}/{key}')
|
||||
return default if value is None else value
|
||||
|
||||
def set_value(self, group, key, value, default=None):
|
||||
if value == default:
|
||||
self.remove(f'{group}/{key}')
|
||||
else:
|
||||
self.setValue(f'{group}/{key}', value)
|
||||
|
||||
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 and mouse controls to defaults')
|
||||
for key in self.allKeys():
|
||||
self.remove(key)
|
||||
settings_events.restore_keyboard_defaults.emit()
|
||||
|
||||
def mousewheel_action_for_event(self, event):
|
||||
for action in self.MOUSEWHEEL_ACTIONS.values():
|
||||
if action.matches_event(event):
|
||||
return action.group, action.get_inverted()
|
||||
return None, None
|
||||
|
||||
def mouse_action_for_event(self, event):
|
||||
for action in self.MOUSE_ACTIONS.values():
|
||||
if action.matches_event(event):
|
||||
return action.group, action.get_inverted()
|
||||
return None, None
|
||||
|
|
@ -13,17 +13,13 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with BeeRef. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
"""Handling of command line args and Qt settings."""
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import logging.config
|
||||
import os.path
|
||||
|
||||
from PyQt6 import QtCore
|
||||
|
||||
from beeref import constants
|
||||
from beeref.logging import qt_message_handler
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -142,7 +138,6 @@ class BeeSettings(QtCore.QSettings):
|
|||
'validate' are specified in the FIELDS entry for the given
|
||||
key. The default value will be returned if validation or type
|
||||
casting fails.
|
||||
|
||||
"""
|
||||
|
||||
val = self.value(key)
|
||||
|
|
@ -205,94 +200,3 @@ class BeeSettings(QtCore.QSettings):
|
|||
if existing_only:
|
||||
values = [f for f in values if os.path.exists(f)]
|
||||
return values
|
||||
|
||||
|
||||
class KeyboardSettings(QtCore.QSettings):
|
||||
|
||||
def __init__(self):
|
||||
settings_format = QtCore.QSettings.Format.IniFormat
|
||||
filename = os.path.join(
|
||||
os.path.dirname(BeeSettings().fileName()),
|
||||
'KeyboardSettings.ini')
|
||||
super().__init__(filename, settings_format)
|
||||
|
||||
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(', ')))
|
||||
return values
|
||||
|
||||
return list(default or []) # Always return new instance of default
|
||||
|
||||
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():
|
||||
return os.path.join(
|
||||
os.path.dirname(BeeSettings().fileName()), f'{constants.APPNAME}.log')
|
||||
|
||||
|
||||
logging_conf = {
|
||||
'version': 1,
|
||||
'formatters': {
|
||||
'verbose': {
|
||||
'format': ('{asctime} {name} {process:d} {thread:d} {message}'),
|
||||
'style': '{',
|
||||
},
|
||||
'simple': {
|
||||
'format': '{levelname} {name}: {message}',
|
||||
'style': '{',
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
'console': {
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'simple',
|
||||
'level': CommandlineArgs().loglevel,
|
||||
},
|
||||
'file': {
|
||||
'class': 'beeref.logging.BeeRotatingFileHandler',
|
||||
'formatter': 'verbose',
|
||||
'filename': logfile_name(),
|
||||
'maxBytes': 1024 * 1000, # 1MB
|
||||
'backupCount': 1,
|
||||
'level': 'DEBUG',
|
||||
'delay': True,
|
||||
}
|
||||
},
|
||||
'loggers': {
|
||||
'beeref': {
|
||||
'handlers': ['console', 'file'],
|
||||
'level': 'TRACE',
|
||||
'propagate': False,
|
||||
},
|
||||
'Qt': {
|
||||
'handlers': ['console', 'file'],
|
||||
'level': 'DEBUG',
|
||||
'propagate': False,
|
||||
},
|
||||
},
|
||||
'root': {
|
||||
'handlers': ['console', 'file'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
}
|
||||
|
||||
logging.config.dictConfig(logging_conf)
|
||||
|
||||
# Redirect Qt logging to Python logger:
|
||||
QtCore.qInstallMessageHandler(qt_message_handler)
|
||||
|
|
@ -19,6 +19,8 @@ VERSION = '0.3.3-dev'
|
|||
WEBSITE = 'https://github.com/rbreu/beeref'
|
||||
COPYRIGHT = 'Copyright © 2021-2023 Rebecca Breu'
|
||||
|
||||
CHANGED_SYMBOL = '✎'
|
||||
|
||||
COLORS = {
|
||||
# Qt:
|
||||
'Active:Base': (60, 60, 60),
|
||||
|
|
@ -31,6 +33,10 @@ COLORS = {
|
|||
'Active:ButtonText': (200, 200, 200),
|
||||
'Active:Highlight': (83, 167, 165),
|
||||
'Active:Link': (90, 181, 179),
|
||||
|
||||
'Disabled:Base': (40, 40, 40),
|
||||
'Disabled:Window': (40, 40, 40, 50),
|
||||
'Disabled:WindowText': (120, 120, 120),
|
||||
'Disabled:Light': (0, 0, 0, 0),
|
||||
'Disabled:Text': (140, 140, 140),
|
||||
|
||||
|
|
|
|||
|
|
@ -114,9 +114,10 @@ class MainControlsMixin:
|
|||
self.exit_movewin_mode()
|
||||
event.accept()
|
||||
return True
|
||||
if (event.button() == Qt.MouseButton.LeftButton
|
||||
and event.modifiers() == (Qt.KeyboardModifier.ControlModifier
|
||||
| Qt.KeyboardModifier.AltModifier)):
|
||||
|
||||
action, inverted =\
|
||||
self.control_target.keyboard_settings.mouse_action_for_event(event)
|
||||
if action == 'movewindow':
|
||||
self.enter_movewin_mode()
|
||||
event.accept()
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with BeeRef. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from collections import OrderedDict
|
||||
import re
|
||||
|
||||
from PyQt6 import QtCore, QtGui
|
||||
|
|
@ -93,3 +94,16 @@ def qcolor_to_hex(color):
|
|||
rgb = color.name()
|
||||
alpha = hex(color.alpha()).removeprefix('0x')
|
||||
return f'{rgb}{alpha}'
|
||||
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ from PyQt6.QtCore import Qt
|
|||
|
||||
from beeref.actions import ActionsMixin, actions
|
||||
from beeref import commands
|
||||
from beeref.config import CommandlineArgs, BeeSettings
|
||||
from beeref.config import CommandlineArgs, BeeSettings, KeyboardSettings
|
||||
from beeref import constants
|
||||
from beeref import fileio
|
||||
from beeref.fileio.export import exporter_registry
|
||||
|
|
@ -51,6 +51,7 @@ class BeeGraphicsView(MainControlsMixin,
|
|||
self.app = app
|
||||
self.parent = parent
|
||||
self.settings = BeeSettings()
|
||||
self.keyboard_settings = KeyboardSettings()
|
||||
self.welcome_overlay = widgets.welcome_overlay.WelcomeOverlay(self)
|
||||
|
||||
self.setBackgroundBrush(
|
||||
|
|
@ -493,7 +494,7 @@ class BeeGraphicsView(MainControlsMixin,
|
|||
widgets.settings.SettingsDialog(self)
|
||||
|
||||
def on_action_keyboard_settings(self):
|
||||
widgets.settings.KeyboardSettingsDialog(self)
|
||||
widgets.controls.ControlsDialog(self)
|
||||
|
||||
def on_action_help(self):
|
||||
widgets.HelpDialog(self)
|
||||
|
|
@ -749,17 +750,23 @@ class BeeGraphicsView(MainControlsMixin,
|
|||
self.reset_previous_transform()
|
||||
|
||||
def wheelEvent(self, event):
|
||||
if event.modifiers() == Qt.KeyboardModifier.NoModifier:
|
||||
self.zoom(event.angleDelta().y(), event.position())
|
||||
action, inverted\
|
||||
= self.keyboard_settings.mousewheel_action_for_event(event)
|
||||
|
||||
delta = event.angleDelta().y()
|
||||
if inverted:
|
||||
delta = delta * -1
|
||||
|
||||
if action == 'zoom':
|
||||
self.zoom(delta, event.position())
|
||||
event.accept()
|
||||
return
|
||||
if event.modifiers() == (Qt.KeyboardModifier.ShiftModifier
|
||||
| Qt.KeyboardModifier.ControlModifier):
|
||||
self.pan(QtCore.QPointF(0, 0.5 * event.angleDelta().y()))
|
||||
if action == 'pan_horizontal':
|
||||
self.pan(QtCore.QPointF(0, 0.5 * delta))
|
||||
event.accept()
|
||||
return
|
||||
if event.modifiers() == Qt.KeyboardModifier.ShiftModifier:
|
||||
self.pan(QtCore.QPointF(0.5 * event.angleDelta().y(), 0))
|
||||
if action == 'pan_vertical':
|
||||
self.pan(QtCore.QPointF(0.5 * delta, 0))
|
||||
event.accept()
|
||||
return
|
||||
|
||||
|
|
@ -785,17 +792,17 @@ class BeeGraphicsView(MainControlsMixin,
|
|||
event.accept()
|
||||
return
|
||||
|
||||
if (event.button() == Qt.MouseButton.MiddleButton
|
||||
and event.modifiers() == Qt.KeyboardModifier.ControlModifier):
|
||||
action, inverted = self.keyboard_settings.mouse_action_for_event(event)
|
||||
|
||||
if action == 'zoom':
|
||||
self.active_mode = self.ZOOM_MODE
|
||||
self.event_start = event.position()
|
||||
self.event_anchor = event.position()
|
||||
self.event_inverted = inverted
|
||||
event.accept()
|
||||
return
|
||||
|
||||
if (event.button() == Qt.MouseButton.MiddleButton
|
||||
or (event.button() == Qt.MouseButton.LeftButton
|
||||
and event.modifiers() == Qt.KeyboardModifier.AltModifier)):
|
||||
if action == 'pan':
|
||||
logger.trace('Begin pan')
|
||||
self.active_mode = self.PAN_MODE
|
||||
self.event_start = event.position()
|
||||
|
|
@ -821,6 +828,8 @@ class BeeGraphicsView(MainControlsMixin,
|
|||
self.reset_previous_transform()
|
||||
pos = event.position()
|
||||
delta = (self.event_start - pos).y()
|
||||
if self.event_inverted:
|
||||
delta *= -1
|
||||
self.event_start = pos
|
||||
self.zoom(delta * 20, self.event_anchor)
|
||||
event.accept()
|
||||
|
|
|
|||
|
|
@ -21,7 +21,12 @@ from PyQt6.QtCore import Qt
|
|||
|
||||
from beeref import constants, commands
|
||||
from beeref.config import logfile_name
|
||||
from beeref.widgets import settings, welcome_overlay, color_gamut # noqa: F401
|
||||
from beeref.widgets import ( # noqa: F401
|
||||
controls,
|
||||
settings,
|
||||
welcome_overlay,
|
||||
color_gamut,
|
||||
)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -69,12 +74,12 @@ class HelpDialog(QtWidgets.QDialog):
|
|||
# Controls
|
||||
with open(os.path.join(docdir, 'controls.html')) as f:
|
||||
controls_txt = f.read()
|
||||
controls = QtWidgets.QLabel(controls_txt)
|
||||
controls.setTextInteractionFlags(
|
||||
controls_label = QtWidgets.QLabel(controls_txt)
|
||||
controls_label.setTextInteractionFlags(
|
||||
Qt.TextInteractionFlag.TextSelectableByMouse)
|
||||
scroll = QtWidgets.QScrollArea(self)
|
||||
scroll.setWidgetResizable(True)
|
||||
scroll.setWidget(controls)
|
||||
scroll.setWidget(controls_label)
|
||||
tabs.addTab(scroll, '&Controls')
|
||||
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
|
|
|
|||
96
beeref/widgets/controls/__init__.py
Normal file
96
beeref/widgets/controls/__init__.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# This file is part of BeeRef.
|
||||
#
|
||||
# BeeRef is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BeeRef is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BeeRef. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
|
||||
from PyQt6 import QtWidgets
|
||||
|
||||
from beeref.config import KeyboardSettings
|
||||
from beeref.widgets.controls.keyboard import KeyboardShortcutsView
|
||||
from beeref.widgets.controls.mouse import MouseView
|
||||
from beeref.widgets.controls.mousewheel import MouseWheelView
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ControlsDialog(QtWidgets.QDialog):
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle('Keyboard & Mouse Controls')
|
||||
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(table.model().setFilterFixedString)
|
||||
kb_layout.addWidget(search_input)
|
||||
kb_layout.addWidget(table)
|
||||
tabs.addTab(keyboard, '&Keyboard Shortcuts')
|
||||
|
||||
# Mouse controls
|
||||
mouse = QtWidgets.QWidget(parent)
|
||||
mouse_layout = QtWidgets.QVBoxLayout()
|
||||
mouse.setLayout(mouse_layout)
|
||||
table = MouseView(mouse)
|
||||
search_input = QtWidgets.QLineEdit()
|
||||
search_input.setPlaceholderText('Search...')
|
||||
search_input.textChanged.connect(table.model().setFilterFixedString)
|
||||
mouse_layout.addWidget(search_input)
|
||||
mouse_layout.addWidget(table)
|
||||
tabs.addTab(mouse, '&Mouse')
|
||||
|
||||
# Mouse wheel controls
|
||||
mousewheel = QtWidgets.QWidget(parent)
|
||||
wheel_layout = QtWidgets.QVBoxLayout()
|
||||
mousewheel.setLayout(wheel_layout)
|
||||
table = MouseWheelView(mousewheel)
|
||||
search_input = QtWidgets.QLineEdit()
|
||||
search_input.setPlaceholderText('Search...')
|
||||
search_input.textChanged.connect(table.model().setFilterFixedString)
|
||||
wheel_layout.addWidget(search_input)
|
||||
wheel_layout.addWidget(table)
|
||||
tabs.addTab(mousewheel, 'Mouse &Wheel')
|
||||
|
||||
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 keyboard and mouse settings '
|
||||
'to their default values?')
|
||||
|
||||
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
|
||||
KeyboardSettings().restore_defaults()
|
||||
262
beeref/widgets/controls/common.py
Normal file
262
beeref/widgets/controls/common.py
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
# This file is part of BeeRef.
|
||||
#
|
||||
# BeeRef is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BeeRef is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BeeRef. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from functools import partial
|
||||
|
||||
from PyQt6 import QtWidgets, QtCore
|
||||
from PyQt6.QtCore import Qt
|
||||
|
||||
from beeref.config import KeyboardSettings
|
||||
from beeref import constants
|
||||
|
||||
|
||||
class MouseControlsEditorBase(QtWidgets.QDialog):
|
||||
"""Common code for MouseWheel and Mouse control editors."""
|
||||
|
||||
saved = QtCore.pyqtSignal()
|
||||
|
||||
def init_dialog(self, parent, index, actions, title):
|
||||
super().__init__(parent)
|
||||
self.actions = actions
|
||||
self.action = self.actions[index.row()]
|
||||
self.setWindowTitle(f'title {self.action.text}')
|
||||
self.old_modifiers = self.action.get_modifiers()
|
||||
self.remove_from_other = None
|
||||
self.ignore_on_changed = False
|
||||
self.setAutoFillBackground(True)
|
||||
self.layout = QtWidgets.QVBoxLayout()
|
||||
self.setLayout(self.layout)
|
||||
self.setModal(True)
|
||||
|
||||
def init_modifiers_input(self):
|
||||
group = QtWidgets.QGroupBox('Modifiers')
|
||||
group_layout = QtWidgets.QVBoxLayout()
|
||||
group.setLayout(group_layout)
|
||||
self.layout.addWidget(group)
|
||||
self.checkboxes = {}
|
||||
for mod in self.action.MODIFIER_MAP.keys():
|
||||
checkbox = QtWidgets.QCheckBox(mod)
|
||||
checkbox.setChecked(mod in self.old_modifiers)
|
||||
checkbox.stateChanged.connect(
|
||||
partial(self.on_modifiers_changed, mod))
|
||||
self.checkboxes[mod] = checkbox
|
||||
group_layout.addWidget(checkbox)
|
||||
|
||||
def init_button_row(self):
|
||||
buttons = QtWidgets.QDialogButtonBox(
|
||||
QtWidgets.QDialogButtonBox.StandardButton.Cancel
|
||||
| QtWidgets.QDialogButtonBox.StandardButton.Ok)
|
||||
buttons.accepted.connect(self.on_save)
|
||||
buttons.rejected.connect(self.reject)
|
||||
self.layout.addWidget(buttons)
|
||||
|
||||
def set_modifiers_no_modifier(self):
|
||||
"""Check 'No Modifiers', uncheck everything else."""
|
||||
for key, checkbox in self.checkboxes.items():
|
||||
checkbox.setChecked(key == 'No Modifier')
|
||||
|
||||
def on_modifiers_changed(self, modifier, value):
|
||||
"""Ensure that when 'No Modifiers' is checked, nothing else is
|
||||
checked at the same time.
|
||||
|
||||
If everything is unchecked, set 'No Modifiers' automatically.
|
||||
"""
|
||||
if self.ignore_on_changed:
|
||||
return
|
||||
|
||||
checked = value == Qt.CheckState.Checked.value
|
||||
self.ignore_on_changed = True
|
||||
|
||||
if checked and modifier == 'No Modifier':
|
||||
self.set_modifiers_no_modifier()
|
||||
|
||||
if checked and modifier != 'No Modifier':
|
||||
self.checkboxes['No Modifier'].setChecked(False)
|
||||
|
||||
if not checked and not self.get_modifiers(cleaned=False):
|
||||
self.set_modifiers_no_modifier()
|
||||
|
||||
self.ignore_on_changed = False
|
||||
|
||||
def get_modifiers(self, cleaned=True):
|
||||
modifiers = [key for key, checkbox in self.checkboxes.items()
|
||||
if checkbox.isChecked()]
|
||||
if cleaned and 'No Modifier' in modifiers:
|
||||
# In this case the list already should only have the one
|
||||
# entry, but just to make sure...
|
||||
return ['No Modifier']
|
||||
return modifiers
|
||||
|
||||
def set_modifiers(self, modifiers):
|
||||
for key, checkbox in self.checkboxes.items():
|
||||
checkbox.setChecked(key in modifiers)
|
||||
|
||||
def get_temp_action(self):
|
||||
raise NotImplementedError # pragma: no cover
|
||||
|
||||
def reset_inputs(self):
|
||||
raise NotImplementedError # pragma: no cover
|
||||
|
||||
def on_save(self):
|
||||
"""Don't let users save the same controls on different actions."""
|
||||
|
||||
temp = self.get_temp_action()
|
||||
self.remove_from_other = None
|
||||
for action in self.actions.values():
|
||||
if action == self.action:
|
||||
continue
|
||||
if action.conflicts_with(temp):
|
||||
msg = ('<p>These controls are already used for:</p>'
|
||||
f'<p>{action.text}</p>'
|
||||
'<p>Do you want to remove the other controls'
|
||||
' to save these ones?</p>')
|
||||
reply = QtWidgets.QMessageBox.question(
|
||||
self, 'Save Controls?', msg)
|
||||
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
|
||||
self.remove_from_other = action
|
||||
self.accept()
|
||||
self.saved.emit()
|
||||
else:
|
||||
self.reset_inputs()
|
||||
return
|
||||
self.accept()
|
||||
self.saved.emit()
|
||||
|
||||
|
||||
class MouseControlsModelBase(QtCore.QAbstractTableModel):
|
||||
COLUMNS = None
|
||||
COL_ACTION = 1
|
||||
COL_CHANGED = 2
|
||||
COL_BUTTON = 3
|
||||
COL_MODIFIERS = 4
|
||||
COL_INVERTED = 5
|
||||
|
||||
HEADERS = {
|
||||
COL_ACTION: 'Action',
|
||||
COL_CHANGED: constants.CHANGED_SYMBOL,
|
||||
COL_BUTTON: 'Button',
|
||||
COL_MODIFIERS: 'Modifiers',
|
||||
COL_INVERTED: 'Inverted',
|
||||
}
|
||||
|
||||
def __init__(self, actions):
|
||||
super().__init__()
|
||||
self.settings = KeyboardSettings()
|
||||
self.actions = actions
|
||||
|
||||
def rowCount(self, parent):
|
||||
return len(self.actions)
|
||||
|
||||
def columnCount(self, parent):
|
||||
return len(self.COLUMNS)
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
if (role == QtCore.Qt.ItemDataRole.DisplayRole
|
||||
and orientation == QtCore.Qt.Orientation.Horizontal):
|
||||
key = self.COLUMNS[section]
|
||||
return self.HEADERS[key]
|
||||
|
||||
def flags(self, index):
|
||||
key = self.COLUMNS[index.column()]
|
||||
base = (QtCore.Qt.ItemFlag.ItemIsEnabled
|
||||
| QtCore.Qt.ItemFlag.ItemNeverHasChildren)
|
||||
|
||||
if key in (self.COL_ACTION, self.COL_CHANGED):
|
||||
return base
|
||||
elif key in (self.COL_BUTTON, self.COL_MODIFIERS):
|
||||
return (base | QtCore.Qt.ItemFlag.ItemIsEditable)
|
||||
elif key == self.COL_INVERTED:
|
||||
action = self.actions[index.row()]
|
||||
if action.invertible and action.is_configured():
|
||||
return (base
|
||||
| QtCore.Qt.ItemFlag.ItemIsEditable
|
||||
| QtCore.Qt.ItemFlag.ItemIsUserCheckable)
|
||||
else:
|
||||
return base
|
||||
|
||||
def data(self, index, role):
|
||||
key = self.COLUMNS[index.column()]
|
||||
action = self.actions[index.row()]
|
||||
|
||||
if role in (QtCore.Qt.ItemDataRole.DisplayRole,
|
||||
QtCore.Qt.ItemDataRole.EditRole):
|
||||
if key == self.COL_ACTION:
|
||||
return action.text
|
||||
if key == self.COL_CHANGED and action.controls_changed():
|
||||
return constants.CHANGED_SYMBOL
|
||||
if key == self.COL_BUTTON:
|
||||
return action.get_button()
|
||||
if key == self.COL_MODIFIERS:
|
||||
return ' + '.join(action.get_modifiers())
|
||||
if key == self.COL_INVERTED:
|
||||
if not action.is_configured() or not action.invertible:
|
||||
return None
|
||||
return 'Yes' if action.get_inverted() else 'No'
|
||||
|
||||
if role == QtCore.Qt.ItemDataRole.ToolTipRole:
|
||||
changed = action.controls_changed()
|
||||
if not changed:
|
||||
return
|
||||
if key == self.COL_CHANGED:
|
||||
return 'Changed from default'
|
||||
if key == self.COL_BUTTON:
|
||||
if action.button == 'Not Configured':
|
||||
default = 'Not configured'
|
||||
else:
|
||||
default = action.button
|
||||
return f'Default: {default}'
|
||||
if key == self.COL_MODIFIERS:
|
||||
if not action.modifiers:
|
||||
default = 'Not configured'
|
||||
else:
|
||||
default = ' + '.join(action.modifiers)
|
||||
return f'Default: {default}'
|
||||
if key == self.COL_INVERTED and action.invertible:
|
||||
default = 'Yes' if action.inverted else 'No'
|
||||
return f'Default: {default}'
|
||||
|
||||
if role == QtCore.Qt.ItemDataRole.CheckStateRole:
|
||||
if (key == self.COL_INVERTED
|
||||
and action.is_configured()
|
||||
and action.invertible):
|
||||
return (Qt.CheckState.Checked if action.get_inverted()
|
||||
else Qt.CheckState.Unchecked)
|
||||
|
||||
def set_data_on_action(self, action, value):
|
||||
raise NotImplementedError # pragma: no cover
|
||||
|
||||
def setData(self, index, value, role, remove_from_other=None):
|
||||
key = self.COLUMNS[index.column()]
|
||||
action = self.actions[index.row()]
|
||||
if key == self.COL_INVERTED:
|
||||
action.set_inverted(
|
||||
True if value == Qt.CheckState.Checked.value else False)
|
||||
else:
|
||||
self.set_data_on_action(action, value)
|
||||
if remove_from_other:
|
||||
# These controls has conflicts with another action and the
|
||||
# user chose to remove the other controls
|
||||
remove_from_other.remove_controls()
|
||||
row = list(self.actions.keys()).index(remove_from_other.id)
|
||||
self.dataChanged.emit(
|
||||
self.index(row, 0),
|
||||
self.index(row, self.columnCount(None) - 1))
|
||||
|
||||
# Whole row might be affected, so excpliclity emit dataChanged
|
||||
self.dataChanged.emit(
|
||||
self.index(index.row(), 0),
|
||||
self.index(index.row(), self.columnCount(None) - 1))
|
||||
|
||||
return True
|
||||
201
beeref/widgets/controls/keyboard.py
Normal file
201
beeref/widgets/controls/keyboard.py
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
# This file is part of BeeRef.
|
||||
#
|
||||
# BeeRef is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BeeRef is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BeeRef. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
from PyQt6 import QtWidgets, QtCore
|
||||
|
||||
from beeref import constants
|
||||
from beeref.actions.actions import actions
|
||||
from beeref.config import KeyboardSettings, settings_events
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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):
|
||||
"""Don't let users save the same shortcuts on different actions."""
|
||||
|
||||
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', constants.CHANGED_SYMBOL, '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 constants.CHANGED_SYMBOL
|
||||
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 'Not set'
|
||||
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 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)
|
||||
self.setAlternatingRowColors(True)
|
||||
settings_events.restore_defaults.connect(
|
||||
self.on_restore_defaults)
|
||||
|
||||
def on_restore_defaults(self):
|
||||
self.viewport().update()
|
||||
170
beeref/widgets/controls/mouse.py
Normal file
170
beeref/widgets/controls/mouse.py
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
# This file is part of BeeRef.
|
||||
#
|
||||
# BeeRef is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BeeRef is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BeeRef. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from functools import partial
|
||||
import logging
|
||||
|
||||
from PyQt6 import QtWidgets, QtCore
|
||||
|
||||
from beeref.config import KeyboardSettings, settings_events
|
||||
from beeref.config.controls import MouseConfig
|
||||
from beeref.widgets.controls.common import (
|
||||
MouseControlsEditorBase,
|
||||
MouseControlsModelBase,
|
||||
)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MouseControlsEditor(MouseControlsEditorBase):
|
||||
|
||||
def __init__(self, parent, index):
|
||||
self.init_dialog(parent, index, KeyboardSettings.MOUSE_ACTIONS,
|
||||
'Mouse Controls for:')
|
||||
self.old_button = self.action.get_button()
|
||||
|
||||
self.layout.addWidget(QtWidgets.QLabel('Mouse Button:'))
|
||||
self.button_input = QtWidgets.QComboBox(parent=parent)
|
||||
self.button_input.insertItems(0, self.action.BUTTON_MAP.keys())
|
||||
values = list(self.action.BUTTON_MAP.keys())
|
||||
self.button_input.setCurrentIndex(values.index(self.old_button))
|
||||
self.layout.addWidget(self.button_input)
|
||||
|
||||
self.init_modifiers_input()
|
||||
self.init_button_row()
|
||||
self.on_button_changed()
|
||||
self.button_input.currentIndexChanged.connect(self.on_button_changed)
|
||||
self.show()
|
||||
|
||||
def on_button_changed(self):
|
||||
"""Disable modifier inputs when no button configured; enable
|
||||
otherwise.
|
||||
"""
|
||||
self.ignore_on_changed = True
|
||||
if self.get_button() == 'Not Configured':
|
||||
for key, checkbox in self.checkboxes.items():
|
||||
checkbox.setChecked(False)
|
||||
self.set_modifiers_enabled(False)
|
||||
else:
|
||||
if not self.get_modifiers(cleaned=False):
|
||||
self.set_modifiers_no_modifier()
|
||||
self.set_modifiers_enabled(True)
|
||||
|
||||
self.ignore_on_changed = False
|
||||
|
||||
def set_modifiers_enabled(self, enabled):
|
||||
for key, checkbox in self.checkboxes.items():
|
||||
checkbox.setEnabled(enabled)
|
||||
|
||||
def get_button(self):
|
||||
values = list(self.action.BUTTON_MAP.keys())
|
||||
return values[self.button_input.currentIndex()]
|
||||
|
||||
def set_button(self, value):
|
||||
values = list(self.action.BUTTON_MAP.keys())
|
||||
self.button_input.setCurrentIndex(values.index(value))
|
||||
|
||||
def get_modifiers(self, cleaned=True):
|
||||
if cleaned and self.get_button() == 'Not Configured':
|
||||
# In this case the list should already be empty but just
|
||||
# to make sure...
|
||||
return []
|
||||
return super().get_modifiers(cleaned=True)
|
||||
|
||||
def get_temp_action(self):
|
||||
return MouseConfig(button=self.get_button(),
|
||||
modifiers=self.get_modifiers(),
|
||||
group=None, text=None, invertible=None, id=None)
|
||||
|
||||
def reset_inputs(self):
|
||||
self.set_button(self.old_button)
|
||||
self.set_modifiers(self.old_modifiers)
|
||||
|
||||
|
||||
class MouseDelegate(QtWidgets.QStyledItemDelegate):
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
widget = QtWidgets.QWidget(parent)
|
||||
widget.editor = MouseControlsEditor(widget, index)
|
||||
widget.editor.saved.connect(
|
||||
partial(self.setModelData, widget, index.model(), index))
|
||||
return widget
|
||||
|
||||
def setModelData(self, editor, model, index):
|
||||
editor = editor.editor
|
||||
if editor.result() == QtWidgets.QDialog.DialogCode.Accepted:
|
||||
model.setData(
|
||||
index,
|
||||
{'button': editor.get_button(),
|
||||
'modifiers': editor.get_modifiers()},
|
||||
QtCore.Qt.ItemDataRole.EditRole,
|
||||
remove_from_other=editor.remove_from_other)
|
||||
|
||||
|
||||
class MouseModel(MouseControlsModelBase):
|
||||
"""An entry in the keyboard shortcuts table."""
|
||||
|
||||
COLUMNS = (MouseControlsModelBase.COL_ACTION,
|
||||
MouseControlsModelBase.COL_CHANGED,
|
||||
MouseControlsModelBase.COL_BUTTON,
|
||||
MouseControlsModelBase.COL_MODIFIERS,
|
||||
MouseControlsModelBase.COL_INVERTED)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(KeyboardSettings.MOUSE_ACTIONS)
|
||||
|
||||
def set_data_on_action(self, action, value):
|
||||
action.set_button(value['button'])
|
||||
action.set_modifiers(value['modifiers'])
|
||||
|
||||
|
||||
class MouseProxy(QtCore.QSortFilterProxyModel):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setSourceModel(MouseModel())
|
||||
self.setFilterCaseSensitivity(
|
||||
QtCore.Qt.CaseSensitivity.CaseInsensitive)
|
||||
|
||||
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 MouseView(QtWidgets.QTableView):
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
self.setMinimumSize(QtCore.QSize(400, 200))
|
||||
self.setItemDelegate(MouseDelegate())
|
||||
self.setShowGrid(False)
|
||||
self.setModel(MouseProxy())
|
||||
self.horizontalHeader().setSectionResizeMode(
|
||||
0, QtWidgets.QHeaderView.ResizeMode.Stretch)
|
||||
self.horizontalHeader().setSectionResizeMode(
|
||||
1, QtWidgets.QHeaderView.ResizeMode.ResizeToContents)
|
||||
self.setSelectionMode(
|
||||
QtWidgets.QHeaderView.SelectionMode.SingleSelection)
|
||||
self.setAlternatingRowColors(True)
|
||||
settings_events.restore_defaults.connect(
|
||||
self.on_restore_defaults)
|
||||
|
||||
def on_restore_defaults(self):
|
||||
self.viewport().update()
|
||||
120
beeref/widgets/controls/mousewheel.py
Normal file
120
beeref/widgets/controls/mousewheel.py
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
# This file is part of BeeRef.
|
||||
#
|
||||
# BeeRef is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BeeRef is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BeeRef. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from functools import partial
|
||||
import logging
|
||||
|
||||
from PyQt6 import QtWidgets, QtCore
|
||||
|
||||
from beeref.config import KeyboardSettings, settings_events
|
||||
from beeref.config.controls import MouseWheelConfig
|
||||
from beeref.widgets.controls.common import (
|
||||
MouseControlsEditorBase,
|
||||
MouseControlsModelBase,
|
||||
)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MouseWheelModifiersEditor(MouseControlsEditorBase):
|
||||
|
||||
def __init__(self, parent, index):
|
||||
self.init_dialog(parent, index, KeyboardSettings.MOUSEWHEEL_ACTIONS,
|
||||
'MouseWheel Controls for:')
|
||||
self.init_modifiers_input()
|
||||
self.init_button_row()
|
||||
self.show()
|
||||
|
||||
def get_temp_action(self):
|
||||
return MouseWheelConfig(
|
||||
modifiers=self.get_modifiers(),
|
||||
group=None, text=None, invertible=None, id=None)
|
||||
|
||||
def reset_inputs(self):
|
||||
self.set_modifiers(self.old_modifiers)
|
||||
|
||||
|
||||
class MouseWheelDelegate(QtWidgets.QStyledItemDelegate):
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
widget = QtWidgets.QWidget(parent)
|
||||
widget.editor = MouseWheelModifiersEditor(widget, index)
|
||||
widget.editor.saved.connect(
|
||||
partial(self.setModelData, widget, index.model(), index))
|
||||
return widget
|
||||
|
||||
def setModelData(self, editor, model, index):
|
||||
editor = editor.editor
|
||||
if editor.result() == QtWidgets.QDialog.DialogCode.Accepted:
|
||||
model.setData(
|
||||
index,
|
||||
editor.get_modifiers(),
|
||||
QtCore.Qt.ItemDataRole.EditRole,
|
||||
remove_from_other=editor.remove_from_other)
|
||||
|
||||
|
||||
class MouseWheelModel(MouseControlsModelBase):
|
||||
"""An entry in the keyboard shortcuts table."""
|
||||
|
||||
COLUMNS = (MouseControlsModelBase.COL_ACTION,
|
||||
MouseControlsModelBase.COL_CHANGED,
|
||||
MouseControlsModelBase.COL_MODIFIERS,
|
||||
MouseControlsModelBase.COL_INVERTED)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(KeyboardSettings.MOUSEWHEEL_ACTIONS)
|
||||
|
||||
def set_data_on_action(self, action, value):
|
||||
action.set_modifiers(value)
|
||||
|
||||
|
||||
class MouseWheelProxy(QtCore.QSortFilterProxyModel):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setSourceModel(MouseWheelModel())
|
||||
self.setFilterCaseSensitivity(
|
||||
QtCore.Qt.CaseSensitivity.CaseInsensitive)
|
||||
|
||||
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 MouseWheelView(QtWidgets.QTableView):
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
self.setMinimumSize(QtCore.QSize(400, 200))
|
||||
self.setItemDelegate(MouseWheelDelegate())
|
||||
self.setShowGrid(False)
|
||||
self.setModel(MouseWheelProxy())
|
||||
self.horizontalHeader().setSectionResizeMode(
|
||||
0, QtWidgets.QHeaderView.ResizeMode.Stretch)
|
||||
self.horizontalHeader().setSectionResizeMode(
|
||||
1, QtWidgets.QHeaderView.ResizeMode.ResizeToContents)
|
||||
self.setSelectionMode(
|
||||
QtWidgets.QHeaderView.SelectionMode.SingleSelection)
|
||||
self.setAlternatingRowColors(True)
|
||||
settings_events.restore_defaults.connect(
|
||||
self.on_restore_defaults)
|
||||
|
||||
def on_restore_defaults(self):
|
||||
self.viewport().update()
|
||||
|
|
@ -16,19 +16,15 @@
|
|||
from functools import partial
|
||||
import logging
|
||||
|
||||
from PyQt6 import QtWidgets, QtCore
|
||||
from PyQt6 import QtWidgets
|
||||
|
||||
from beeref import constants
|
||||
from beeref.actions.actions import actions
|
||||
from beeref.config import BeeSettings, KeyboardSettings, settings_events
|
||||
from beeref.config import BeeSettings, settings_events
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
CHANGED_SYMBOL = '✎'
|
||||
|
||||
|
||||
class GroupBase(QtWidgets.QGroupBox):
|
||||
|
||||
def __init__(self):
|
||||
|
|
@ -47,7 +43,7 @@ class GroupBase(QtWidgets.QGroupBox):
|
|||
def update_title(self):
|
||||
title = [self.TITLE]
|
||||
if self.settings.value_changed(self.KEY):
|
||||
title.append(CHANGED_SYMBOL)
|
||||
title.append(constants.CHANGED_SYMBOL)
|
||||
self.setTitle(' '.join(title))
|
||||
|
||||
def on_value_changed(self, value):
|
||||
|
|
@ -178,222 +174,3 @@ 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 CHANGED_SYMBOL
|
||||
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 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)
|
||||
self.setAlternatingRowColors(True)
|
||||
settings_events.restore_keyboard_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()
|
||||
|
|
|
|||
2
setup.py
2
setup.py
|
|
@ -18,9 +18,11 @@ setup(
|
|||
'beeref',
|
||||
'beeref.actions',
|
||||
'beeref.assets',
|
||||
'beeref.config',
|
||||
'beeref.documentation',
|
||||
'beeref.fileio',
|
||||
'beeref.widgets',
|
||||
'beeref.widgets.controls',
|
||||
],
|
||||
entry_points={
|
||||
'gui_scripts': [
|
||||
|
|
|
|||
|
|
@ -2,30 +2,35 @@ from unittest.mock import patch
|
|||
|
||||
from PyQt6 import QtGui
|
||||
|
||||
from beeref.actions.actions import Action, ActionList
|
||||
from beeref.actions.actions import Action
|
||||
|
||||
|
||||
def test_action_str():
|
||||
action = Action(id='foo', text='Foo', shortcuts=['Ctrl+R'])
|
||||
assert str(action) == 'foo'
|
||||
|
||||
|
||||
def test_action_equals_true():
|
||||
action1 = Action({'id': 'foo', 'text': 'Foo', 'shortcuts': ['Ctrl+R']})
|
||||
action2 = Action({'id': 'foo', 'text': 'Bar', 'shortcuts': ['Ctrl+F']})
|
||||
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']})
|
||||
action1 = Action(id='foo', text='Foo', shortcuts=['Ctrl+R'])
|
||||
action2 = Action(id='bar', text='Bar', shortcuts=['Ctrl+R'])
|
||||
assert not action1 == action2
|
||||
|
||||
|
||||
def test_action_on_restore_defaults(kbsettings, view):
|
||||
action = Action({'id': 'foo', 'shortcuts': ['Ctrl+R']})
|
||||
action = Action(id='foo', text='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 = Action(id='foo', text='Foo')
|
||||
action.qaction = QtGui.QAction('foo', view)
|
||||
action.qaction.setShortcuts(['Ctrl+R'])
|
||||
action.on_restore_defaults()
|
||||
|
|
@ -33,105 +38,105 @@ def test_action_on_restore_defaults_when_no_defaults(kbsettings, view):
|
|||
|
||||
|
||||
def test_action_get_overwritten_shortcuts(kbsettings):
|
||||
kbsettings.set_shortcuts('Actions', 'foo', ['Alt+O'])
|
||||
action = Action({'id': 'foo', 'shortcuts': ['Ctrl+F']})
|
||||
kbsettings.set_list('Actions', 'foo', ['Alt+O'])
|
||||
action = Action(id='foo', text='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']})
|
||||
action = Action(id='foo', text='Foo', shortcuts=['Ctrl+F'])
|
||||
assert action.get_shortcuts() == ['Ctrl+F']
|
||||
|
||||
|
||||
def test_action_set_shortcuts_when_no_qaction(kbsettings):
|
||||
action = Action({'id': 'foo'})
|
||||
action = Action(id='foo', text='Foo')
|
||||
action.qaction = None
|
||||
action.set_shortcuts(['Ctrl+F'])
|
||||
assert kbsettings.get_shortcuts('Actions', 'foo') == ['Ctrl+F']
|
||||
assert kbsettings.get_list('Actions', 'foo') == ['Ctrl+F']
|
||||
|
||||
|
||||
def test_action_set_shortcuts_when_qaction(kbsettings, view):
|
||||
action = Action({'id': 'foo'})
|
||||
action = Action(id='foo', text='Foo')
|
||||
action.qaction = QtGui.QAction('foo', view)
|
||||
action.set_shortcuts(['Ctrl+F'])
|
||||
assert kbsettings.get_shortcuts('Actions', 'foo') == ['Ctrl+F']
|
||||
assert kbsettings.get_list('Actions', 'foo') == ['Ctrl+F']
|
||||
assert action.qaction.shortcuts() == ['Ctrl+F']
|
||||
|
||||
|
||||
def test_action_get_qkeysequence_first(kbsettings):
|
||||
action = Action({'id': 'foo', 'shortcuts': ['Ctrl+F']})
|
||||
action = Action(id='foo', text='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']})
|
||||
action = Action(id='foo', text='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'})
|
||||
action = Action(id='foo', text='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']})
|
||||
action = Action(id='foo', text='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']})
|
||||
action = Action(id='foo', text='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'])
|
||||
action = Action(id='foo', text='Foo', shortcuts=['Ctrl+F'])
|
||||
kbsettings.set_list('Actions', 'foo', ['Ctrl+B'])
|
||||
assert action.shortcuts_changed() is True
|
||||
|
||||
|
||||
def test_action_shortcuts_changed_when_empty(kbsettings):
|
||||
action = Action({'id': 'foo'})
|
||||
action = Action(id='foo', text='Foo')
|
||||
assert action.shortcuts_changed() is False
|
||||
|
||||
|
||||
def test_action_get_default_shortcut_first():
|
||||
action = Action({'id': 'foo', 'shortcuts': ['Ctrl+F']})
|
||||
action = Action(id='foo', text='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']})
|
||||
action = Action(id='foo', text='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'})
|
||||
action = Action(id='foo', text='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']})
|
||||
action = Action(id='foo', text='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'})
|
||||
action = Action(id='baz', text='Foo')
|
||||
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'})
|
||||
action = Action(id='baz', text='Foo')
|
||||
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'})
|
||||
action = Action(id='baz', text='Foo', menu_id='_build_recent_files')
|
||||
assert action.menu_path == ['Foo']
|
||||
|
||||
|
||||
|
|
@ -139,21 +144,5 @@ def test_action_menu_path_recent_files():
|
|||
[{'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'})
|
||||
action = Action(id='baz', text='Foo', 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,8 +4,9 @@ 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.actions import Action
|
||||
from beeref.actions.menu_structure import MENU_SEPARATOR
|
||||
from beeref.utils import ActionList
|
||||
|
||||
|
||||
class FooWidget(QtWidgets.QWidget, ActionsMixin):
|
||||
|
|
@ -28,13 +29,13 @@ class FooWidget(QtWidgets.QWidget, ActionsMixin):
|
|||
@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')
|
||||
ActionList([Action(
|
||||
id='foo',
|
||||
text='&Foo',
|
||||
shortcuts=['Ctrl+F'],
|
||||
callback='on_foo',
|
||||
)]))
|
||||
@patch('beeref.config.KeyboardSettings.get_list')
|
||||
def test_create_actions(kb_mock, toggle_mock, trigger_mock, qapp):
|
||||
kb_mock.side_effect = lambda group, key, default: default
|
||||
widget = FooWidget()
|
||||
|
|
@ -56,15 +57,15 @@ def test_create_actions(kb_mock, toggle_mock, trigger_mock, qapp):
|
|||
[{'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'})])):
|
||||
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'])
|
||||
kbsettings.set_list('Actions', 'foo', ['Alt+O'])
|
||||
widget = FooWidget()
|
||||
widget.build_menu_and_actions()
|
||||
qaction = widget.actions()[0]
|
||||
|
|
@ -76,12 +77,12 @@ def test_create_actions_with_shortcut_from_settings(qapp, kbsettings):
|
|||
@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',
|
||||
})]))
|
||||
ActionList([Action(
|
||||
id='foo',
|
||||
text='&Foo',
|
||||
checkable=True,
|
||||
callback='on_foo',
|
||||
)]))
|
||||
def test_create_actions_checkable(toggle_mock, trigger_mock, qapp):
|
||||
widget = FooWidget()
|
||||
widget.build_menu_and_actions()
|
||||
|
|
@ -100,13 +101,13 @@ def test_create_actions_checkable(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,
|
||||
'checked': True,
|
||||
'callback': 'on_foo',
|
||||
})]))
|
||||
ActionList([Action(
|
||||
id='foo',
|
||||
text='&Foo',
|
||||
checkable=True,
|
||||
checked=True,
|
||||
callback='on_foo',
|
||||
)]))
|
||||
def test_create_actions_checkable_checked_true(
|
||||
toggle_mock, trigger_mock, qapp):
|
||||
widget = FooWidget()
|
||||
|
|
@ -127,13 +128,13 @@ def test_create_actions_checkable_checked_true(
|
|||
@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',
|
||||
})]))
|
||||
ActionList([Action(
|
||||
id='foo',
|
||||
text='&Foo',
|
||||
checkable=True,
|
||||
settings='foo/bar',
|
||||
callback='on_foo',
|
||||
)]))
|
||||
def test_create_actions_checkable_with_settings(
|
||||
toggle_mock, settings_mock, callback_mock, qapp):
|
||||
widget = FooWidget()
|
||||
|
|
@ -150,12 +151,12 @@ def test_create_actions_checkable_with_settings(
|
|||
@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',
|
||||
})]))
|
||||
ActionList([Action(
|
||||
id='foo',
|
||||
text='&Foo',
|
||||
callback='on_foo',
|
||||
group='bar',
|
||||
)]))
|
||||
def test_create_actions_with_group(qapp):
|
||||
widget = FooWidget()
|
||||
widget.build_menu_and_actions()
|
||||
|
|
@ -167,11 +168,11 @@ def test_create_actions_with_group(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',
|
||||
})]))
|
||||
ActionList([Action(
|
||||
id='foo',
|
||||
text='&Foo',
|
||||
callback='on_foo',
|
||||
)]))
|
||||
def test_build_menu_and_actions_with_actions(qapp):
|
||||
widget = FooWidget()
|
||||
with patch('PyQt6.QtWidgets.QMenu.addAction') as add_mock:
|
||||
|
|
@ -195,11 +196,11 @@ def test_build_menu_and_actions_with_separator(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',
|
||||
})]))
|
||||
ActionList([Action(
|
||||
id='foo',
|
||||
text='&Foo',
|
||||
callback='on_foo',
|
||||
)]))
|
||||
def test_build_menu_and_actions_with_submenu(qapp):
|
||||
widget = FooWidget()
|
||||
with patch('PyQt6.QtWidgets.QMenu.addAction') as add_mock:
|
||||
|
|
@ -216,18 +217,18 @@ def test_build_menu_and_actions_with_submenu(qapp):
|
|||
[{'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',
|
||||
}),
|
||||
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()
|
||||
|
|
@ -241,12 +242,12 @@ def test_actiongroup_set_enabled(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',
|
||||
})]))
|
||||
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
|
||||
|
|
@ -256,7 +257,7 @@ def test_build_menu_and_actions_disables_actiongroups(qapp):
|
|||
|
||||
|
||||
@patch('PyQt6.QtGui.QAction.triggered')
|
||||
@patch('beeref.config.KeyboardSettings.get_shortcuts')
|
||||
@patch('beeref.config.KeyboardSettings.get_list')
|
||||
@patch('beeref.actions.mixin.menu_structure',
|
||||
[{'menu': 'Foo', 'items': '_build_recent_files'}])
|
||||
@patch('beeref.actions.mixin.actions', ActionList([]))
|
||||
|
|
@ -294,7 +295,7 @@ def test_create_recent_files_more_than_10_files(
|
|||
@patch('beeref.actions.mixin.menu_structure',
|
||||
[{'menu': 'Foo', 'items': '_build_recent_files'}])
|
||||
@patch('beeref.actions.mixin.actions', ActionList([]))
|
||||
@patch('beeref.config.KeyboardSettings.get_shortcuts')
|
||||
@patch('beeref.config.KeyboardSettings.get_list')
|
||||
def test_create_recent_files_fewer_files_than_10_files(
|
||||
kb_mock, triggered_mock, qapp):
|
||||
kb_mock.side_effect = lambda group, key, default: default
|
||||
|
|
@ -329,7 +330,7 @@ def test_create_recent_files_fewer_files_than_10_files(
|
|||
@patch('beeref.actions.mixin.menu_structure',
|
||||
[{'menu': 'Foo', 'items': '_build_recent_files'}])
|
||||
@patch('beeref.actions.mixin.actions', ActionList([]))
|
||||
@patch('beeref.config.KeyboardSettings.get_shortcuts')
|
||||
@patch('beeref.config.KeyboardSettings.get_list')
|
||||
def test_create_recent_files_when_no_files(kb_mock, qapp):
|
||||
kb_mock.side_effect = lambda group, key, default: default
|
||||
widget = FooWidget()
|
||||
|
|
|
|||
0
tests/config/__init__.py
Normal file
0
tests/config/__init__.py
Normal file
510
tests/config/test_controls.py
Normal file
510
tests/config/test_controls.py
Normal file
|
|
@ -0,0 +1,510 @@
|
|||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from PyQt6.QtCore import Qt
|
||||
|
||||
from beeref.config.controls import (
|
||||
KeyboardSettings,
|
||||
MouseConfig,
|
||||
MouseWheelConfig,
|
||||
)
|
||||
|
||||
|
||||
def test_mousewheelconfig_eq():
|
||||
action1 = MouseWheelConfig(
|
||||
id='foo', group='foobar', text='Foo', modifiers=[], invertible=False)
|
||||
action2 = MouseWheelConfig(
|
||||
id='foo', group='foobar', text='Baz', modifiers=[], invertible=False)
|
||||
assert action1 == action2
|
||||
|
||||
|
||||
def test_mousewheelconfig_str():
|
||||
action = MouseWheelConfig(
|
||||
id='foo', group='foobar', text='Foo', modifiers=[], invertible=False)
|
||||
assert str(action) == 'foo'
|
||||
|
||||
|
||||
def test_mousewheelconfig_kb_settings():
|
||||
action = MouseWheelConfig(
|
||||
id='foo', group='foobar', text='Foo', modifiers=[], invertible=False)
|
||||
assert isinstance(action.kb_settings, KeyboardSettings)
|
||||
|
||||
|
||||
def test_mousewheelconfig_get_modifiers(kbsettings):
|
||||
kbsettings.set_list('MouseWheel', 'foo_modifiers', ['Ctrl', 'Shift'])
|
||||
action = MouseWheelConfig(
|
||||
id='foo', group='foobar', text='Foo', modifiers=[], invertible=False)
|
||||
assert action.get_modifiers() == ['Ctrl', 'Shift']
|
||||
|
||||
|
||||
def test_mousewheelconfig_get_modifiers_default(kbsettings):
|
||||
action = MouseWheelConfig(
|
||||
id='foo', group='foobar', text='Foo', modifiers=['Shift'],
|
||||
invertible=False)
|
||||
assert action.get_modifiers() == ['Shift']
|
||||
|
||||
|
||||
def test_mousewheelconfig_set_modifiers(kbsettings):
|
||||
action = MouseWheelConfig(
|
||||
id='foo', group='foobar', text='Foo', modifiers=[], invertible=False)
|
||||
action.set_modifiers(['Shift'])
|
||||
assert kbsettings.get_list('MouseWheel', 'foo_modifiers') == ['Shift']
|
||||
|
||||
|
||||
def test_mousewheelconfig_set_modifiers_default(kbsettings):
|
||||
kbsettings.set_list('MouseWheel', 'foo_modifiers', ['Alt', 'Ctrl'])
|
||||
action = MouseWheelConfig(
|
||||
id='foo', group='foobar', text='Foo',
|
||||
modifiers=['Shift'], invertible=False)
|
||||
action.set_modifiers(['Shift'])
|
||||
assert kbsettings.value('MouseWheel/foo_modifiers') is None
|
||||
|
||||
|
||||
def test_mousewheelconfig_get_inverted(kbsettings):
|
||||
kbsettings.set_value('MouseWheel', 'foo_inverted', True)
|
||||
action = MouseWheelConfig(
|
||||
id='foo', group='foobar', text='Foo', modifiers=[], invertible=True)
|
||||
assert action.get_inverted() is True
|
||||
|
||||
|
||||
def test_mousewheelconfig_get_inverted_default():
|
||||
action = MouseWheelConfig(
|
||||
id='foo', group='foobar', text='Foo', modifiers=[], invertible=True)
|
||||
assert action.get_inverted() is False
|
||||
|
||||
|
||||
def test_mousewheelconfig_set_inverted(kbsettings):
|
||||
action = MouseWheelConfig(
|
||||
id='foo', group='foobar', text='Foo', modifiers=[], invertible=True)
|
||||
action.set_inverted(True)
|
||||
assert kbsettings.get_value('MouseWheel', 'foo_inverted') is True
|
||||
|
||||
|
||||
def test_mousewheelconfig_set_inverted_default(kbsettings):
|
||||
kbsettings.set_value('MouseWheel', 'foo_inverted', True)
|
||||
action = MouseWheelConfig(
|
||||
id='foo', group='foobar', text='Foo', modifiers=[], invertible=True)
|
||||
action.set_inverted(False)
|
||||
assert kbsettings.value('MouseWheel/foo_inverted') is None
|
||||
|
||||
|
||||
def test_mousewheelconfig_modifiers_to_qt_multiple():
|
||||
assert MouseWheelConfig.modifiers_to_qt(['Shift', 'Ctrl']) == (
|
||||
Qt.KeyboardModifier.ShiftModifier
|
||||
| Qt.KeyboardModifier.ControlModifier)
|
||||
|
||||
|
||||
def test_mousewheelconfig_modifiers_to_single():
|
||||
assert MouseWheelConfig.modifiers_to_qt(['Alt']) ==\
|
||||
Qt.KeyboardModifier.AltModifier
|
||||
|
||||
|
||||
def test_mousewheelconfig_controls_changed_not_changed():
|
||||
action = MouseWheelConfig(
|
||||
id='foo', group='foobar', text='Foo', modifiers=['Shift'],
|
||||
invertible=True)
|
||||
assert action.controls_changed() is False
|
||||
|
||||
|
||||
def test_mousewheelconfig_controls_changed_modifiers_changed():
|
||||
action = MouseWheelConfig(
|
||||
id='foo', group='foobar', text='Foo', modifiers=['Shift'],
|
||||
invertible=True)
|
||||
action.set_modifiers(['Alt'])
|
||||
assert action.controls_changed() is True
|
||||
|
||||
|
||||
def test_mousewheelconfig_controls_changed_inverted_changed():
|
||||
action = MouseWheelConfig(
|
||||
id='foo', group='foobar', text='Foo', modifiers=['Shift'],
|
||||
invertible=True)
|
||||
action.set_inverted(True)
|
||||
assert action.controls_changed() is True
|
||||
|
||||
|
||||
def test_mousewheelconfig_controls_is_configured_true():
|
||||
action = MouseWheelConfig(
|
||||
id='foo', group='foobar', text='Foo', modifiers=['No Modifiers'],
|
||||
invertible=True)
|
||||
assert action.is_configured() is True
|
||||
|
||||
|
||||
def test_mousewheelconfig_controls_is_configured_false():
|
||||
action = MouseWheelConfig(
|
||||
id='foo', group='foobar', text='Foo', modifiers=[], invertible=True)
|
||||
assert action.is_configured() is False
|
||||
|
||||
|
||||
def test_mousewheelconfig_controls_remove_controls():
|
||||
action = MouseWheelConfig(
|
||||
id='foo', group='foobar', text='Foo', modifiers=['Shift'],
|
||||
invertible=True)
|
||||
action.set_inverted(True)
|
||||
action.remove_controls()
|
||||
assert action.get_modifiers() == []
|
||||
assert action.get_inverted() is False
|
||||
|
||||
|
||||
def test_mousewheelconfig_conflicts_with_true():
|
||||
action1 = MouseWheelConfig(
|
||||
id='foo', group='foobar', text='Foo', modifiers=['Shift'],
|
||||
invertible=True)
|
||||
action2 = MouseWheelConfig(
|
||||
id='bar', group='foobar', text='Bar', modifiers=['Shift'],
|
||||
invertible=True)
|
||||
assert action1.conflicts_with(action2) is True
|
||||
|
||||
|
||||
def test_mousewheelconfig_conflicts_with_false():
|
||||
action1 = MouseWheelConfig(
|
||||
id='foo', group='foobar', text='Foo', modifiers=['Shift'],
|
||||
invertible=True)
|
||||
action2 = MouseWheelConfig(
|
||||
id='bar', group='foobar', text='Bar', modifiers=['Shift', 'Ctrl'],
|
||||
invertible=True)
|
||||
assert action1.conflicts_with(action2) is False
|
||||
|
||||
|
||||
def test_mousewheelconfig_conflicts_false_when_both_not_configured():
|
||||
action1 = MouseWheelConfig(
|
||||
id='foo', group='foobar', text='Foo', modifiers=[],
|
||||
invertible=True)
|
||||
action2 = MouseWheelConfig(
|
||||
id='bar', group='foobar', text='Bar', modifiers=[],
|
||||
invertible=True)
|
||||
assert action1.conflicts_with(action2) is False
|
||||
|
||||
|
||||
def test_mousewheelconfig_matches_event_true():
|
||||
event = MagicMock(
|
||||
modifiers=MagicMock(return_value=Qt.KeyboardModifier.AltModifier))
|
||||
action = MouseWheelConfig(
|
||||
id='foo', group='foobar', text='Foo', modifiers=['Alt'],
|
||||
invertible=True)
|
||||
assert action.matches_event(event) is True
|
||||
|
||||
|
||||
def test_mousewheelconfig_matches_event_false():
|
||||
event = MagicMock(
|
||||
modifiers=MagicMock(return_value=Qt.KeyboardModifier.AltModifier))
|
||||
action = MouseWheelConfig(
|
||||
id='foo', group='foobar', text='Foo', modifiers=['Ctrl'],
|
||||
invertible=True)
|
||||
assert action.matches_event(event) is False
|
||||
|
||||
|
||||
def test_mousewheelconfig_matches_event_false_when_not_configured():
|
||||
event = MagicMock(
|
||||
modifiers=MagicMock(return_value=Qt.KeyboardModifier.AltModifier))
|
||||
action = MouseWheelConfig(
|
||||
id='foo', group='foobar', text='Foo', modifiers=[],
|
||||
invertible=True)
|
||||
assert action.matches_event(event) is False
|
||||
|
||||
|
||||
def test_mouseconfig_get_button(kbsettings):
|
||||
kbsettings.set_value('Mouse', 'foo_button', 'Left')
|
||||
action = MouseConfig(
|
||||
id='foo', group='foobar', text='Foo', button='Middle', modifiers=[],
|
||||
invertible=False)
|
||||
assert action.get_button() == 'Left'
|
||||
|
||||
|
||||
def test_mouseconfig_get_button_default():
|
||||
action = MouseConfig(
|
||||
id='foo', group='foobar', text='Foo', button='Left', modifiers=[],
|
||||
invertible=False)
|
||||
assert action.get_button() == 'Left'
|
||||
|
||||
|
||||
def test_mouseconfig_set_button(kbsettings):
|
||||
action = MouseConfig(
|
||||
id='foo', group='foobar', text='Foo', button='Left', modifiers=[],
|
||||
invertible=False)
|
||||
action.set_button('Middle')
|
||||
assert kbsettings.get_value('Mouse', 'foo_button') == 'Middle'
|
||||
|
||||
|
||||
def test_mouseconfig_set_button_default(kbsettings):
|
||||
kbsettings.set_value('Mouse', 'foo_button', 'Middle')
|
||||
action = MouseConfig(
|
||||
id='foo', group='foobar', text='Foo', button='Middle', modifiers=[],
|
||||
invertible=False)
|
||||
action.set_button('Middle')
|
||||
assert kbsettings.value('Mouse/foo_button') is None
|
||||
|
||||
|
||||
def test_mouseconfig_conflicts_with_true():
|
||||
action1 = MouseConfig(
|
||||
id='foo', group='foobar', text='Foo', button='Left',
|
||||
modifiers=['Shift'], invertible=True)
|
||||
action2 = MouseConfig(
|
||||
id='bar', group='foobar', text='Bar', button='Left',
|
||||
modifiers=['Shift'], invertible=True)
|
||||
assert action1.conflicts_with(action2) is True
|
||||
|
||||
|
||||
def test_mouseconfig_conflicts_with_false_when_diff_buttons():
|
||||
action1 = MouseConfig(
|
||||
id='foo', group='foobar', text='Foo', button='Left',
|
||||
modifiers=['Shift'], invertible=True)
|
||||
action2 = MouseConfig(
|
||||
id='bar', group='foobar', text='Bar', button='Middle',
|
||||
modifiers=['Shift'], invertible=True)
|
||||
assert action1.conflicts_with(action2) is False
|
||||
|
||||
|
||||
def test_mouseconfig_conflicts_with_false_when_diff_modifiers():
|
||||
action1 = MouseConfig(
|
||||
id='foo', group='foobar', text='Foo', button='Middle',
|
||||
modifiers=['Shift'], invertible=True)
|
||||
action2 = MouseConfig(
|
||||
id='bar', group='foobar', text='Bar', button='Middle',
|
||||
modifiers=['Shift', 'Ctrl'], invertible=True)
|
||||
assert action1.conflicts_with(action2) is False
|
||||
|
||||
|
||||
def test_mouseconfig_conflicts_false_when_both_not_configured():
|
||||
action1 = MouseConfig(
|
||||
id='foo', group='foobar', text='Foo', button='Not Configured',
|
||||
modifiers=[], invertible=True)
|
||||
action2 = MouseConfig(
|
||||
id='bar', group='foobar', text='Bar', button='Not Configured',
|
||||
modifiers=[], invertible=True)
|
||||
assert action1.conflicts_with(action2) is False
|
||||
|
||||
|
||||
def test_mouseconfig_is_configured_false():
|
||||
action = MouseConfig(
|
||||
id='foo', group='foobar', text='Foo', button='Middle',
|
||||
modifiers=[], invertible=True)
|
||||
action.set_button('Not Configured')
|
||||
assert action.conflicts_with(action) is False
|
||||
|
||||
|
||||
def test_mouseconfig_is_configured_true():
|
||||
action = MouseConfig(
|
||||
id='foo', group='foobar', text='Foo', button='Not Configured',
|
||||
modifiers=[], invertible=True)
|
||||
action.set_button('Left')
|
||||
assert action.conflicts_with(action) is True
|
||||
|
||||
|
||||
def test_mouseconfig_controls_changed_false():
|
||||
action = MouseConfig(
|
||||
id='foo', group='foobar', text='Foo', button='Middle',
|
||||
modifiers=['Shift'], invertible=True)
|
||||
action.controls_changed() is False
|
||||
|
||||
|
||||
def test_mouseconfig_controls_changed_true_when_button_changed():
|
||||
action = MouseConfig(
|
||||
id='foo', group='foobar', text='Foo', button='Middle',
|
||||
modifiers=['Shift'], invertible=True)
|
||||
action.set_button('Left')
|
||||
action.controls_changed() is True
|
||||
|
||||
|
||||
def test_mouseconfig_controls_changed_true_when_modifiers_changed():
|
||||
action = MouseConfig(
|
||||
id='foo', group='foobar', text='Foo', button='Middle',
|
||||
modifiers=['Shift'], invertible=True)
|
||||
action.set_modifiers(['Shift', 'Ctrl'])
|
||||
action.controls_changed() is True
|
||||
|
||||
|
||||
def test_mouseconfig_controls_changed_true_when_inverted_changed():
|
||||
action = MouseConfig(
|
||||
id='foo', group='foobar', text='Foo', button='Middle',
|
||||
modifiers=['Shift'], invertible=True)
|
||||
action.set_inverted(True)
|
||||
action.controls_changed() is True
|
||||
|
||||
|
||||
def test_mouseconfig_remove_controls():
|
||||
action = MouseConfig(
|
||||
id='foo', group='foobar', text='Foo', button='Middle',
|
||||
modifiers=['Shift'], invertible=True)
|
||||
action.set_inverted(True)
|
||||
|
||||
action.remove_controls()
|
||||
assert action.get_button() == 'Not Configured'
|
||||
assert action.get_modifiers() == []
|
||||
assert action.get_inverted() is False
|
||||
|
||||
|
||||
def test_mouseconfig_matches_event_true():
|
||||
event = MagicMock(
|
||||
button=MagicMock(return_value=Qt.MouseButton.LeftButton),
|
||||
modifiers=MagicMock(return_value=Qt.KeyboardModifier.AltModifier))
|
||||
action = MouseConfig(
|
||||
id='foo', group='foobar', text='Foo', button='Left',
|
||||
modifiers=['Alt'], invertible=True)
|
||||
assert action.matches_event(event) is True
|
||||
|
||||
|
||||
def test_mouseconfig_matches_event_false_when_diff_button():
|
||||
event = MagicMock(
|
||||
button=MagicMock(return_value=Qt.MouseButton.LeftButton),
|
||||
modifiers=MagicMock(return_value=Qt.KeyboardModifier.AltModifier))
|
||||
action = MouseConfig(
|
||||
id='foo', group='foobar', text='Foo', button='Middle',
|
||||
modifiers=['Alt'], invertible=True)
|
||||
assert action.matches_event(event) is False
|
||||
|
||||
|
||||
def test_mouseconfig_matches_event_false_when_diff_modifiers():
|
||||
event = MagicMock(
|
||||
button=MagicMock(return_value=Qt.MouseButton.LeftButton),
|
||||
modifiers=MagicMock(return_value=Qt.KeyboardModifier.AltModifier))
|
||||
action = MouseConfig(
|
||||
id='foo', group='foobar', text='Foo', button='Left',
|
||||
modifiers=['Ctrl'], invertible=True)
|
||||
assert action.matches_event(event) is False
|
||||
|
||||
|
||||
def test_mouseconfig_matches_event_false_when_not_configured():
|
||||
event = MagicMock(
|
||||
button=MagicMock(return_value=Qt.MouseButton.LeftButton),
|
||||
modifiers=MagicMock(return_value=Qt.KeyboardModifier.AltModifier))
|
||||
action = MouseConfig(
|
||||
id='foo', group='foobar', text='Foo', button='Not Configured',
|
||||
modifiers=[], invertible=True)
|
||||
assert action.matches_event(event) is False
|
||||
|
||||
|
||||
def test_keyboardsettings_set_value(kbsettings):
|
||||
kbsettings.set_value('mygroup', 'foo', 'bar')
|
||||
assert kbsettings.value('mygroup/foo', 'bar')
|
||||
|
||||
|
||||
def test_keyboardsettings_set_value_default_value(kbsettings):
|
||||
kbsettings.setValue('mygroup/foo', 'bar')
|
||||
kbsettings.set_value('mygroup', 'foo', 'baz', 'baz')
|
||||
assert kbsettings.value('mygroup/foo') is None
|
||||
|
||||
|
||||
def test_keyboardsettings_get_value_existing(kbsettings):
|
||||
kbsettings.set_value('mygroup', 'bar', 'foo')
|
||||
value = kbsettings.get_value('mygroup', 'bar', 'baz')
|
||||
assert value == 'foo'
|
||||
|
||||
|
||||
def test_keyboardsettings_get_value_default(kbsettings):
|
||||
assert kbsettings.get_value('mygroup', 'bar', 'baz') == 'baz'
|
||||
|
||||
|
||||
def test_keyboardsettings_set_list(kbsettings):
|
||||
kbsettings.set_list('mygroup', 'foo', ['Ctrl+F'])
|
||||
assert kbsettings.get_list('mygroup', 'foo') == ['Ctrl+F']
|
||||
|
||||
|
||||
def test_keyboardsettings_set_list_multiple(kbsettings):
|
||||
kbsettings.set_list('mygroup', 'foo', ['Ctrl+F', 'Alt+O'])
|
||||
assert kbsettings.get_list('mygroup', 'foo') == ['Ctrl+F', 'Alt+O']
|
||||
|
||||
|
||||
def test_keyboardsettings_set_list_default_value(kbsettings):
|
||||
kbsettings.setValue('mygroup/foo', 'Ctrl+F')
|
||||
kbsettings.set_list('mygroup', 'foo', 'Ctrl+B', 'Ctrl+B')
|
||||
assert kbsettings.value('mygroup/foo') is None
|
||||
|
||||
|
||||
def test_keyboardsettings_get_list_existing(kbsettings):
|
||||
kbsettings.set_list('mygroup', 'bar', ['Ctrl+R'])
|
||||
shortcuts = kbsettings.get_list('mygroup', 'bar', ['Ctrl+B'])
|
||||
assert shortcuts == ['Ctrl+R']
|
||||
|
||||
|
||||
def test_keyboardsettings_get_list_existing_empty_list(kbsettings):
|
||||
kbsettings.set_list('mygroup', 'bar', [])
|
||||
shortcuts = kbsettings.get_list('mygroup', 'bar', ['Ctrl+B'])
|
||||
assert shortcuts == []
|
||||
|
||||
|
||||
def test_keyboardsettings_get_list_default(kbsettings):
|
||||
shortcuts = kbsettings.get_list('mygroup', 'bar', ['Ctrl+B'])
|
||||
assert shortcuts == ['Ctrl+B']
|
||||
|
||||
|
||||
@patch('beeref.config.KeyboardSettings.setValue')
|
||||
@patch('beeref.config.KeyboardSettings.remove')
|
||||
def test_keyboardsettings_set_list_other_than_default_saves(
|
||||
remove_mock, set_mock, kbsettings):
|
||||
kbsettings.set_list('mygroup', 'bar', ['Ctrl+R'], ['Ctrl+Z'])
|
||||
set_mock.assert_called_once_with('mygroup/bar', 'Ctrl+R')
|
||||
remove_mock.assert_not_called()
|
||||
|
||||
|
||||
@patch('beeref.config.KeyboardSettings.setValue')
|
||||
@patch('beeref.config.KeyboardSettings.remove')
|
||||
def test_keyboardsettings_set_list_with_than_default_doesnt_save(
|
||||
remove_mock, set_mock, kbsettings):
|
||||
kbsettings.set_list('mygroup', 'bar', ['Ctrl+R'], ['Ctrl+R'])
|
||||
set_mock.assert_not_called()
|
||||
remove_mock.assert_called_once_with('mygroup/bar')
|
||||
|
||||
|
||||
@patch('PyQt6.QtGui.QAction.setShortcuts')
|
||||
def test_keyboardsettings_restore_defaults_restores(shortcut_mock, kbsettings):
|
||||
kbsettings.setValue('Actions/bar', 'Ctrl+R')
|
||||
kbsettings.restore_defaults()
|
||||
assert kbsettings.contains('Actions/bar') is False
|
||||
|
||||
|
||||
def test_keyboardsettings_mousewheel_action_for_event_finds(kbsettings):
|
||||
action = kbsettings.MOUSEWHEEL_ACTIONS['zoom1']
|
||||
action.set_modifiers(['Shift', 'Ctrl', 'Alt'])
|
||||
action.set_inverted(True)
|
||||
|
||||
event = MagicMock(
|
||||
modifiers=MagicMock(
|
||||
return_value=(Qt.KeyboardModifier.AltModifier
|
||||
| Qt.KeyboardModifier.ShiftModifier
|
||||
| Qt.KeyboardModifier.ControlModifier)))
|
||||
|
||||
group, inverted = kbsettings.mousewheel_action_for_event(event)
|
||||
assert group == 'zoom'
|
||||
assert inverted is True
|
||||
|
||||
|
||||
def test_keyboardsettings_mousewheel_action_for_event_empty(kbsettings):
|
||||
event = MagicMock(
|
||||
modifiers=MagicMock(
|
||||
return_value=(Qt.KeyboardModifier.AltModifier
|
||||
| Qt.KeyboardModifier.ShiftModifier
|
||||
| Qt.KeyboardModifier.ControlModifier)))
|
||||
|
||||
group, inverted = kbsettings.mousewheel_action_for_event(event)
|
||||
assert group is None
|
||||
assert inverted is None
|
||||
|
||||
|
||||
def test_keyboardsettings_mouse_action_for_event_finds(kbsettings):
|
||||
action = kbsettings.MOUSE_ACTIONS['zoom1']
|
||||
action.set_button('Middle')
|
||||
action.set_modifiers(['Shift', 'Ctrl', 'Alt'])
|
||||
action.set_inverted(True)
|
||||
|
||||
event = MagicMock(
|
||||
button=MagicMock(return_value=Qt.MouseButton.MiddleButton),
|
||||
modifiers=MagicMock(
|
||||
return_value=(Qt.KeyboardModifier.AltModifier
|
||||
| Qt.KeyboardModifier.ShiftModifier
|
||||
| Qt.KeyboardModifier.ControlModifier)))
|
||||
|
||||
group, inverted = kbsettings.mouse_action_for_event(event)
|
||||
assert group == 'zoom'
|
||||
assert inverted is True
|
||||
|
||||
|
||||
def test_keyboardsettings_mouse_action_for_event_empty(kbsettings):
|
||||
event = MagicMock(
|
||||
button=MagicMock(return_value=Qt.MouseButton.MiddleButton),
|
||||
modifiers=MagicMock(
|
||||
return_value=(Qt.KeyboardModifier.AltModifier
|
||||
| Qt.KeyboardModifier.ShiftModifier
|
||||
| Qt.KeyboardModifier.ControlModifier)))
|
||||
|
||||
group, inverted = kbsettings.mouse_action_for_event(event)
|
||||
assert group is None
|
||||
assert inverted is None
|
||||
|
|
@ -4,7 +4,7 @@ from unittest.mock import patch
|
|||
|
||||
import pytest
|
||||
|
||||
from beeref.config import CommandlineArgs
|
||||
from beeref.config.settings import CommandlineArgs
|
||||
|
||||
|
||||
def test_command_line_args_singleton():
|
||||
|
|
@ -13,7 +13,7 @@ def test_command_line_args_singleton():
|
|||
CommandlineArgs._instance = None
|
||||
|
||||
|
||||
@patch('beeref.config.parser.parse_args')
|
||||
@patch('beeref.config.settings.parser.parse_args')
|
||||
def test_command_line_args_with_check_forces_new_parsing(parse_mock):
|
||||
args1 = CommandlineArgs()
|
||||
args2 = CommandlineArgs(with_check=True)
|
||||
|
|
@ -117,48 +117,3 @@ def test_settings_recent_files_update_respects_max_num(settings):
|
|||
assert len(recent) == 10
|
||||
assert recent[0] == os.path.abspath('14.bee')
|
||||
assert recent[-1] == os.path.abspath('5.bee')
|
||||
|
||||
|
||||
def test_keyboardsettings_set_shortcuts(kbsettings):
|
||||
kbsettings.set_shortcuts('Actions', 'foo', ['Ctrl+F'])
|
||||
assert kbsettings.get_shortcuts('Actions', 'foo') == ['Ctrl+F']
|
||||
|
||||
|
||||
def test_keyboardsettings_set_shortcuts_multiple(kbsettings):
|
||||
kbsettings.set_shortcuts('Actions', 'foo', ['Ctrl+F', 'Alt+O'])
|
||||
assert kbsettings.get_shortcuts('Actions', 'foo') == ['Ctrl+F', 'Alt+O']
|
||||
|
||||
|
||||
def test_keyboardsettings_get_shortcuts_existing(kbsettings):
|
||||
kbsettings.set_shortcuts('Actions', 'bar', ['Ctrl+R'])
|
||||
shortcuts = kbsettings.get_shortcuts('Actions', 'bar', ['Ctrl+B'])
|
||||
assert shortcuts == ['Ctrl+R']
|
||||
|
||||
|
||||
def test_keyboardsettings_get_shortcuts_default(kbsettings):
|
||||
shortcuts = kbsettings.get_shortcuts('Actions', 'bar', ['Ctrl+B'])
|
||||
assert shortcuts == ['Ctrl+B']
|
||||
|
||||
|
||||
@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()
|
||||
|
||||
|
||||
@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
|
||||
|
|
@ -2,6 +2,7 @@ import pytest
|
|||
|
||||
from PyQt6 import QtCore, QtGui
|
||||
|
||||
from beeref.actions.actions import Action
|
||||
from beeref import utils
|
||||
|
||||
|
||||
|
|
@ -78,3 +79,19 @@ def test_get_file_extension_from_format(formatstr, expected):
|
|||
((255, 0, 0, 100), '#ff000064')])
|
||||
def test_qcolor_to_hex(rgba, expected):
|
||||
assert utils.qcolor_to_hex(QtGui.QColor(*rgba)) == expected
|
||||
|
||||
|
||||
def test_actionlist_inits_dict():
|
||||
action1 = Action(id='foo', text='Foo')
|
||||
action2 = Action(id='bar', text='Bar')
|
||||
actionlist = utils.ActionList([action1, action2])
|
||||
actionlist['foo'] == action1
|
||||
actionlist['bar'] == action2
|
||||
|
||||
|
||||
def test_actionlist_acts_as_list():
|
||||
action1 = Action(id='foo', text='Foo')
|
||||
action2 = Action(id='bar', text='Bar')
|
||||
actionlist = utils.ActionList([action1, action2])
|
||||
actionlist[0] == action1
|
||||
actionlist[1] == action2
|
||||
|
|
|
|||
|
|
@ -376,7 +376,7 @@ def test_on_action_settings(show_mock, view):
|
|||
show_mock.assert_called_once()
|
||||
|
||||
|
||||
@patch('beeref.widgets.settings.KeyboardSettingsDialog.show')
|
||||
@patch('beeref.widgets.controls.ControlsDialog.show')
|
||||
def test_on_action_keyboard_settings(show_mock, view):
|
||||
view.on_action_keyboard_settings()
|
||||
show_mock.assert_called_once()
|
||||
|
|
@ -940,6 +940,19 @@ def test_wheel_event_zoom(zoom_mock, view):
|
|||
event.accept.assert_called_once_with()
|
||||
|
||||
|
||||
@patch('beeref.view.BeeGraphicsView.zoom')
|
||||
def test_wheel_event_zoom_custom_inverted(zoom_mock, view, kbsettings):
|
||||
kbsettings.MOUSEWHEEL_ACTIONS['zoom2'].set_modifiers(['Alt'])
|
||||
kbsettings.MOUSEWHEEL_ACTIONS['zoom2'].set_inverted(True)
|
||||
event = MagicMock()
|
||||
event.angleDelta.return_value = QtCore.QPointF(0.0, 40.0)
|
||||
event.position.return_value = QtCore.QPointF(10.0, 20.0)
|
||||
event.modifiers.return_value = Qt.KeyboardModifier.AltModifier
|
||||
view.wheelEvent(event)
|
||||
zoom_mock.assert_called_once_with(-40, QtCore.QPointF(10.0, 20.0))
|
||||
event.accept.assert_called_once_with()
|
||||
|
||||
|
||||
@patch('beeref.view.BeeGraphicsView.pan')
|
||||
def test_wheel_event_pan_vertically(pan_mock, view):
|
||||
event = MagicMock()
|
||||
|
|
@ -948,7 +961,21 @@ def test_wheel_event_pan_vertically(pan_mock, view):
|
|||
event.modifiers.return_value = (Qt.KeyboardModifier.ShiftModifier
|
||||
| Qt.KeyboardModifier.ControlModifier)
|
||||
view.wheelEvent(event)
|
||||
pan_mock.assert_called_once_with(QtCore.QPointF(0, 20))
|
||||
pan_mock.assert_called_once_with(QtCore.QPointF(20, 0))
|
||||
event.accept.assert_called_once_with()
|
||||
|
||||
|
||||
@patch('beeref.view.BeeGraphicsView.pan')
|
||||
def test_wheel_event_pan_vertically_custom_inverted(
|
||||
pan_mock, view, kbsettings):
|
||||
kbsettings.MOUSEWHEEL_ACTIONS['pan_vertical2'].set_modifiers(['Alt'])
|
||||
kbsettings.MOUSEWHEEL_ACTIONS['pan_vertical2'].set_inverted(True)
|
||||
event = MagicMock()
|
||||
event.angleDelta.return_value = QtCore.QPointF(0.0, 40.0)
|
||||
event.position.return_value = QtCore.QPointF(10.0, 20.0)
|
||||
event.modifiers.return_value = Qt.KeyboardModifier.AltModifier
|
||||
view.wheelEvent(event)
|
||||
pan_mock.assert_called_once_with(QtCore.QPointF(-20, 0))
|
||||
event.accept.assert_called_once_with()
|
||||
|
||||
|
||||
|
|
@ -959,7 +986,21 @@ def test_wheel_event_pan_horizontally(pan_mock, view):
|
|||
event.position.return_value = QtCore.QPointF(10.0, 20.0)
|
||||
event.modifiers.return_value = Qt.KeyboardModifier.ShiftModifier
|
||||
view.wheelEvent(event)
|
||||
pan_mock.assert_called_once_with(QtCore.QPointF(20, 0))
|
||||
pan_mock.assert_called_once_with(QtCore.QPointF(0, 20))
|
||||
event.accept.assert_called_once_with()
|
||||
|
||||
|
||||
@patch('beeref.view.BeeGraphicsView.pan')
|
||||
def test_wheel_event_pan_horizontally_custom_inverted(
|
||||
pan_mock, view, kbsettings):
|
||||
kbsettings.MOUSEWHEEL_ACTIONS['pan_horizontal2'].set_modifiers(['Alt'])
|
||||
kbsettings.MOUSEWHEEL_ACTIONS['pan_horizontal2'].set_inverted(True)
|
||||
event = MagicMock()
|
||||
event.angleDelta.return_value = QtCore.QPointF(0.0, 40.0)
|
||||
event.position.return_value = QtCore.QPointF(10.0, 20.0)
|
||||
event.modifiers.return_value = Qt.KeyboardModifier.AltModifier
|
||||
view.wheelEvent(event)
|
||||
pan_mock.assert_called_once_with(QtCore.QPointF(0, -20))
|
||||
event.accept.assert_called_once_with()
|
||||
|
||||
|
||||
|
|
@ -973,6 +1014,26 @@ def test_mouse_press_zoom(mouse_event_mock, view):
|
|||
assert view.active_mode == view.ZOOM_MODE
|
||||
assert view.event_start == QtCore.QPointF(10.0, 20.0)
|
||||
assert view.event_anchor == QtCore.QPointF(10.0, 20.0)
|
||||
assert view.event_inverted is False
|
||||
mouse_event_mock.assert_not_called()
|
||||
event.accept.assert_called_once_with()
|
||||
|
||||
|
||||
@patch('PyQt6.QtWidgets.QGraphicsView.mousePressEvent')
|
||||
def test_mouse_press_zoom_custom_inverted(mouse_event_mock, view, kbsettings):
|
||||
kbsettings.MOUSE_ACTIONS['zoom1'].set_button('Left')
|
||||
kbsettings.MOUSE_ACTIONS['zoom1'].set_modifiers(['Alt', 'Shift'])
|
||||
kbsettings.MOUSE_ACTIONS['zoom1'].set_inverted(True)
|
||||
event = MagicMock()
|
||||
event.position.return_value = QtCore.QPointF(10.0, 20.0)
|
||||
event.button.return_value = Qt.MouseButton.LeftButton
|
||||
event.modifiers.return_value = (
|
||||
Qt.KeyboardModifier.AltModifier | Qt.KeyboardModifier.ShiftModifier)
|
||||
view.mousePressEvent(event)
|
||||
assert view.active_mode == view.ZOOM_MODE
|
||||
assert view.event_start == QtCore.QPointF(10.0, 20.0)
|
||||
assert view.event_anchor == QtCore.QPointF(10.0, 20.0)
|
||||
assert view.event_inverted is True
|
||||
mouse_event_mock.assert_not_called()
|
||||
event.accept.assert_called_once_with()
|
||||
|
||||
|
|
@ -982,7 +1043,7 @@ def test_mouse_press_pan_middle_drag(mouse_event_mock, view):
|
|||
event = MagicMock()
|
||||
event.position.return_value = QtCore.QPointF(10.0, 20.0)
|
||||
event.button.return_value = Qt.MouseButton.MiddleButton
|
||||
event.modifiers.return_value = None
|
||||
event.modifiers.return_value = Qt.KeyboardModifier.NoModifier
|
||||
view.mousePressEvent(event)
|
||||
assert view.active_mode == view.PAN_MODE
|
||||
assert view.event_start == QtCore.QPointF(10.0, 20.0)
|
||||
|
|
@ -1136,6 +1197,7 @@ def test_mouse_move_zoom(zoom_mock, mouse_event_mock, view):
|
|||
view.active_mode = view.ZOOM_MODE
|
||||
view.event_anchor = QtCore.QPointF(55.0, 66.0)
|
||||
view.event_start = QtCore.QPointF(10.0, 20.0)
|
||||
view.event_inverted = False
|
||||
event = MagicMock()
|
||||
event.position.return_value = QtCore.QPointF(10.0, 18.0)
|
||||
view.mouseMoveEvent(event)
|
||||
|
|
@ -1144,6 +1206,21 @@ def test_mouse_move_zoom(zoom_mock, mouse_event_mock, view):
|
|||
event.accept.assert_called_once_with()
|
||||
|
||||
|
||||
@patch('PyQt6.QtWidgets.QGraphicsView.mouseMoveEvent')
|
||||
@patch('beeref.view.BeeGraphicsView.zoom')
|
||||
def test_mouse_move_zoom_inverted(zoom_mock, mouse_event_mock, view):
|
||||
view.active_mode = view.ZOOM_MODE
|
||||
view.event_anchor = QtCore.QPointF(55.0, 66.0)
|
||||
view.event_start = QtCore.QPointF(10.0, 20.0)
|
||||
view.event_inverted = True
|
||||
event = MagicMock()
|
||||
event.position.return_value = QtCore.QPointF(10.0, 18.0)
|
||||
view.mouseMoveEvent(event)
|
||||
zoom_mock.assert_called_once_with(-40, QtCore.QPointF(55.0, 66.0))
|
||||
mouse_event_mock.assert_not_called()
|
||||
event.accept.assert_called_once_with()
|
||||
|
||||
|
||||
@patch('PyQt6.QtWidgets.QGraphicsView.mouseMoveEvent')
|
||||
def test_mouse_move_sample_color(mouse_event_mock, view):
|
||||
view.active_mode = view.SAMPLE_COLOR_MODE
|
||||
|
|
|
|||
0
tests/widgets/__init__.py
Normal file
0
tests/widgets/__init__.py
Normal file
16
tests/widgets/controls/test_controls.py
Normal file
16
tests/widgets/controls/test_controls.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
from unittest.mock import patch
|
||||
|
||||
from PyQt6 import QtWidgets
|
||||
|
||||
from beeref.widgets.controls import ControlsDialog
|
||||
|
||||
|
||||
@patch('PyQt6.QtWidgets.QMessageBox.question',
|
||||
return_value=QtWidgets.QMessageBox.StandardButton.Yes)
|
||||
@patch('beeref.config.KeyboardSettings.restore_defaults')
|
||||
def test_controls_dialog_on_restore_defaults(
|
||||
restore_mock, msg_mock, kbsettings, view):
|
||||
dialog = ControlsDialog(view)
|
||||
dialog.on_restore_defaults()
|
||||
msg_mock.assert_called_once()
|
||||
restore_mock.assert_called()
|
||||
422
tests/widgets/controls/test_keyboard.py
Normal file
422
tests/widgets/controls/test_keyboard.py
Normal file
|
|
@ -0,0 +1,422 @@
|
|||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from PyQt6 import QtWidgets, QtCore, QtGui
|
||||
|
||||
from beeref.actions.actions import Action
|
||||
from beeref.widgets.controls.keyboard import (
|
||||
KeyboardShortcutsDelegate,
|
||||
KeyboardShortcutsEditor,
|
||||
KeyboardShortcutsModel,
|
||||
KeyboardShortcutsProxy,
|
||||
)
|
||||
from beeref.utils import ActionList
|
||||
|
||||
|
||||
def test_keyboard_shortcuts_editor_on_save_no_conflicts(view):
|
||||
a1 = Action(id='foo', text='Foo', shortcuts=['Ctrl+F'])
|
||||
a2 = Action(id='bar', text='Bar', shortcuts=['Ctrl+B'])
|
||||
with patch('beeref.widgets.controls.keyboard.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_on_save_reenter_existing_shortcut(view):
|
||||
a1 = Action(id='foo', text='Foo', shortcuts=['Ctrl+F'])
|
||||
a2 = Action(id='bar', text='bar', shortcuts=['Ctrl+B'])
|
||||
with patch('beeref.widgets.controls.keyboard.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_on_save_conflicts_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.controls.keyboard.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_on_save_conflicts_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.controls.keyboard.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_on_save_conflicts_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.controls.keyboard.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.controls.keyboard.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.controls.keyboard.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.controls.keyboard.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.controls.keyboard.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.controls.keyboard.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.controls.keyboard.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.controls.keyboard.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.controls.keyboard.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.controls.keyboard.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.controls.keyboard.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.controls.keyboard.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.controls.keyboard.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: Not set'
|
||||
|
||||
|
||||
def test_keyboard_shortcuts_model_setdata_saves():
|
||||
action = Action(id='foo', text='Foo', shortcuts=['Ctrl+F'])
|
||||
with patch('beeref.widgets.controls.keyboard.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', text='Foo', shortcuts=['Ctrl+F'])
|
||||
with patch('beeref.widgets.controls.keyboard.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', text='Foo')
|
||||
with patch('beeref.widgets.controls.keyboard.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', text='Foo', shortcuts=['Ctrl+B'])
|
||||
with patch('beeref.widgets.controls.keyboard.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', text='Foo', shortcuts=['Ctrl+F'])
|
||||
a2 = Action(id='bar', text='Bar', shortcuts=['Ctrl+B'])
|
||||
with patch('beeref.widgets.controls.keyboard.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.controls.keyboard.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()
|
||||
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.controls.keyboard.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')
|
||||
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.controls.keyboard.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', text='Foo', shortcuts=['Ctrl+F'])
|
||||
a2 = Action(id='bar', text='Bar', shortcuts=['Ctrl+B'])
|
||||
with patch('beeref.widgets.controls.keyboard.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() == []
|
||||
1257
tests/widgets/controls/test_mouse.py
Normal file
1257
tests/widgets/controls/test_mouse.py
Normal file
File diff suppressed because it is too large
Load diff
966
tests/widgets/controls/test_mousewheel.py
Normal file
966
tests/widgets/controls/test_mousewheel.py
Normal file
|
|
@ -0,0 +1,966 @@
|
|||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from PyQt6 import QtWidgets, QtCore
|
||||
from PyQt6.QtCore import Qt
|
||||
|
||||
from beeref.config.controls import MouseWheelConfig
|
||||
from beeref.widgets.controls.mousewheel import (
|
||||
MouseWheelDelegate,
|
||||
MouseWheelModifiersEditor,
|
||||
MouseWheelModel,
|
||||
MouseWheelProxy,
|
||||
)
|
||||
from beeref.utils import ActionList
|
||||
|
||||
|
||||
def test_mousewheel_editor_inits_modifiers_when_not_configured(view):
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=[],
|
||||
invertible=True)
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
editor = MouseWheelModifiersEditor(
|
||||
view, index=MagicMock(row=MagicMock(return_value=0)))
|
||||
|
||||
assert len(editor.checkboxes) == 6
|
||||
for checkbox in editor.checkboxes.values():
|
||||
assert checkbox.isChecked() is False
|
||||
|
||||
|
||||
def test_mousewheel_editor_inits_modifiers_when_configured(view):
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt', 'Ctrl'],
|
||||
invertible=True)
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
editor = MouseWheelModifiersEditor(
|
||||
view, index=MagicMock(row=MagicMock(return_value=0)))
|
||||
|
||||
assert len(editor.checkboxes) == 6
|
||||
for key, checkbox in editor.checkboxes.items():
|
||||
if key in ('Alt', 'Ctrl'):
|
||||
assert checkbox.isChecked() is True
|
||||
else:
|
||||
assert checkbox.isChecked() is False
|
||||
|
||||
|
||||
def test_mousewheel_editor_set_modifiers_no_modifier(view):
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt', 'Ctrl'],
|
||||
invertible=True)
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
editor = MouseWheelModifiersEditor(
|
||||
view, index=MagicMock(row=MagicMock(return_value=0)))
|
||||
|
||||
editor.set_modifiers_no_modifier()
|
||||
for key, checkbox in editor.checkboxes.items():
|
||||
if key == 'No Modifier':
|
||||
assert checkbox.isChecked() is True
|
||||
else:
|
||||
assert checkbox.isChecked() is False
|
||||
|
||||
|
||||
def test_mousewheel_editor_on_modifiers_changed_no_modifiers_checked(view):
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt', 'Ctrl'],
|
||||
invertible=True)
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
editor = MouseWheelModifiersEditor(
|
||||
view, index=MagicMock(row=MagicMock(return_value=0)))
|
||||
|
||||
editor.on_modifiers_changed('No Modifier', Qt.CheckState.Checked.value)
|
||||
for key, checkbox in editor.checkboxes.items():
|
||||
if key == 'No Modifier':
|
||||
assert checkbox.isChecked() is True
|
||||
else:
|
||||
assert checkbox.isChecked() is False
|
||||
|
||||
|
||||
def test_mousewheel_editor_on_modifiers_changed_when_a_modifier_checked(view):
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['No Modifier'],
|
||||
invertible=True)
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
editor = MouseWheelModifiersEditor(
|
||||
view, index=MagicMock(row=MagicMock(return_value=0)))
|
||||
|
||||
editor.on_modifiers_changed('Alt', Qt.CheckState.Checked.value)
|
||||
for key, checkbox in editor.checkboxes.items():
|
||||
if key == 'No Modifier':
|
||||
assert checkbox.isChecked() is False
|
||||
|
||||
|
||||
def test_mousewheel_editor_on_modifiers_changed_everything_unchecked(view):
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=[],
|
||||
invertible=True)
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
editor = MouseWheelModifiersEditor(
|
||||
view, index=MagicMock(row=MagicMock(return_value=0)))
|
||||
|
||||
editor.on_modifiers_changed('Alt', Qt.CheckState.Unchecked.value)
|
||||
for key, checkbox in editor.checkboxes.items():
|
||||
if key == 'No Modifier':
|
||||
assert checkbox.isChecked() is True
|
||||
else:
|
||||
assert checkbox.isChecked() is False
|
||||
|
||||
|
||||
def test_mousewheel_editor_get_modifiers(view):
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=[],
|
||||
invertible=True)
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
editor = MouseWheelModifiersEditor(
|
||||
view, index=MagicMock(row=MagicMock(return_value=0)))
|
||||
|
||||
editor.checkboxes['Alt'].setChecked(True)
|
||||
editor.get_modifiers() == ['Alt']
|
||||
|
||||
|
||||
def test_mousewheel_editor_get_modifiers_when_no_modifiers(view):
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=[],
|
||||
invertible=True)
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
editor = MouseWheelModifiersEditor(
|
||||
view, index=MagicMock(row=MagicMock(return_value=0)))
|
||||
|
||||
editor.ignore_on_changed = True
|
||||
editor.checkboxes['No Modifier'].setChecked(True)
|
||||
editor.checkboxes['Alt'].setChecked(True)
|
||||
editor.get_modifiers() == ['No Modifier']
|
||||
|
||||
|
||||
def test_mousewheel_editor_get_modifiers_when_no_modifiers_cleaned_false(view):
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=[],
|
||||
invertible=True)
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
editor = MouseWheelModifiersEditor(
|
||||
view, index=MagicMock(row=MagicMock(return_value=0)))
|
||||
|
||||
editor.ignore_on_changed = True
|
||||
editor.checkboxes['Shift'].setChecked(True)
|
||||
editor.checkboxes['Alt'].setChecked(True)
|
||||
editor.ignore_on_changed = False
|
||||
|
||||
assert editor.get_modifiers(cleaned=False) == ['Shift', 'Alt']
|
||||
|
||||
|
||||
def test_mousewheel_editor_set_modifiers(view):
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=[],
|
||||
invertible=True)
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
editor = MouseWheelModifiersEditor(
|
||||
view, index=MagicMock(row=MagicMock(return_value=0)))
|
||||
|
||||
editor.set_modifiers(['Alt', 'Shift'])
|
||||
for key, checkbox in editor.checkboxes.items():
|
||||
if key in ['Alt', 'Shift']:
|
||||
assert checkbox.isChecked() is True
|
||||
else:
|
||||
assert checkbox.isChecked() is False
|
||||
|
||||
|
||||
def test_mousewheel_editor_get_temp_action(view):
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=True)
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
editor = MouseWheelModifiersEditor(
|
||||
view, index=MagicMock(row=MagicMock(return_value=0)))
|
||||
|
||||
tmp = editor.get_temp_action()
|
||||
assert tmp.get_modifiers() == ['Alt']
|
||||
|
||||
|
||||
def test_mousewheel_editor_reset_inputs(view):
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=True)
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
editor = MouseWheelModifiersEditor(
|
||||
view, index=MagicMock(row=MagicMock(return_value=0)))
|
||||
|
||||
editor.set_modifiers(['Ctrl'])
|
||||
editor.reset_inputs()
|
||||
assert editor.get_modifiers() == ['Alt']
|
||||
|
||||
|
||||
@patch('beeref.widgets.controls.mousewheel.MouseWheelModifiersEditor.accept')
|
||||
def test_mousewheel_editor_on_save_no_conflicts(accept_mock, view):
|
||||
a1 = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=True)
|
||||
a2 = MouseWheelConfig(
|
||||
id='bar1',
|
||||
group='bar',
|
||||
text='Bar',
|
||||
modifiers=['Ctrl'],
|
||||
invertible=True)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([a1, a2])):
|
||||
editor = MouseWheelModifiersEditor(
|
||||
view, index=MagicMock(row=MagicMock(return_value=0)))
|
||||
|
||||
editor.set_modifiers(['Shift'])
|
||||
editor.on_save()
|
||||
assert editor.get_modifiers() == ['Shift']
|
||||
assert editor.remove_from_other is None
|
||||
accept_mock.assert_called_once_with()
|
||||
|
||||
|
||||
@patch('beeref.widgets.controls.mousewheel.MouseWheelModifiersEditor.accept')
|
||||
def test_mousewheel_editor_on_save_reenter_existing_shortcut(
|
||||
accept_mock, view):
|
||||
a1 = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=True)
|
||||
a2 = MouseWheelConfig(
|
||||
id='bar1',
|
||||
group='bar',
|
||||
text='Bar',
|
||||
modifiers=['Ctrl'],
|
||||
invertible=True)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([a1, a2])):
|
||||
editor = MouseWheelModifiersEditor(
|
||||
view, index=MagicMock(row=MagicMock(return_value=0)))
|
||||
|
||||
editor.on_save()
|
||||
assert editor.get_modifiers() == ['Alt']
|
||||
assert editor.remove_from_other is None
|
||||
accept_mock.assert_called_once_with()
|
||||
|
||||
|
||||
@patch('PyQt6.QtWidgets.QMessageBox.question',
|
||||
return_value=QtWidgets.QMessageBox.StandardButton.No)
|
||||
@patch('beeref.widgets.controls.mousewheel.MouseWheelModifiersEditor.accept')
|
||||
def test_mousewheel_editor_on_save_conflicts_cancel(
|
||||
accept_mock, msg_mock, view):
|
||||
a1 = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=True)
|
||||
a2 = MouseWheelConfig(
|
||||
id='bar1',
|
||||
group='bar',
|
||||
text='Bar',
|
||||
modifiers=['Ctrl'],
|
||||
invertible=True)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([a1, a2])):
|
||||
editor = MouseWheelModifiersEditor(
|
||||
view, index=MagicMock(row=MagicMock(return_value=0)))
|
||||
|
||||
editor.set_modifiers(['Ctrl'])
|
||||
editor.on_save()
|
||||
assert editor.get_modifiers() == ['Alt']
|
||||
assert editor.remove_from_other is None
|
||||
accept_mock.assert_not_called()
|
||||
|
||||
|
||||
@patch('PyQt6.QtWidgets.QMessageBox.question',
|
||||
return_value=QtWidgets.QMessageBox.StandardButton.Yes)
|
||||
@patch('beeref.widgets.controls.mousewheel.MouseWheelModifiersEditor.accept')
|
||||
def test_mousewheel_editor_on_save_conflicts_confirm(
|
||||
accept_mock, msg_mock, view):
|
||||
a1 = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=True)
|
||||
a2 = MouseWheelConfig(
|
||||
id='bar1',
|
||||
group='bar',
|
||||
text='Bar',
|
||||
modifiers=['Ctrl'],
|
||||
invertible=True)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([a1, a2])):
|
||||
editor = MouseWheelModifiersEditor(
|
||||
view, index=MagicMock(row=MagicMock(return_value=0)))
|
||||
|
||||
editor.set_modifiers(['Ctrl'])
|
||||
editor.on_save()
|
||||
assert editor.get_modifiers() == ['Ctrl']
|
||||
assert editor.remove_from_other == a2
|
||||
accept_mock.assert_called_once_with()
|
||||
|
||||
|
||||
def test_mousewheel_delegate_create_editor(view):
|
||||
delegate = MouseWheelDelegate()
|
||||
model = MouseWheelModel()
|
||||
widget = delegate.createEditor(
|
||||
view, QtWidgets.QStyleOptionViewItem(), index=model.index(0, 3))
|
||||
assert isinstance(widget.editor, MouseWheelModifiersEditor)
|
||||
|
||||
|
||||
def test_mousewheel_delegate_setmodeldata(view):
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=True)
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
delegate = MouseWheelDelegate()
|
||||
model = MouseWheelModel()
|
||||
widget = delegate.createEditor(
|
||||
view, QtWidgets.QStyleOptionViewItem(), index=model.index(0, 2))
|
||||
widget.editor.set_modifiers(['Ctrl', 'Shift'])
|
||||
|
||||
with patch.object(widget.editor, 'result',
|
||||
return_value=QtWidgets.QDialog.DialogCode.Accepted):
|
||||
delegate.setModelData(widget, model, index=model.index(0, 2))
|
||||
assert action.get_modifiers() == ['Shift', 'Ctrl']
|
||||
|
||||
|
||||
def test_mousewheel_model_columncount():
|
||||
model = MouseWheelModel()
|
||||
model.columnCount(None) == 3
|
||||
|
||||
|
||||
def test_mousewheel_model_rowcount():
|
||||
a1 = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=True)
|
||||
a2 = MouseWheelConfig(
|
||||
id='bar1',
|
||||
group='bar',
|
||||
text='Bar',
|
||||
modifiers=['Ctrl'],
|
||||
invertible=True)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([a1, a2])):
|
||||
model = MouseWheelModel()
|
||||
model.rowCount(None) == 2
|
||||
|
||||
|
||||
def test_mousewheel_model_headerdata():
|
||||
model = MouseWheelModel()
|
||||
header = model.headerData(
|
||||
0,
|
||||
QtCore.Qt.Orientation.Horizontal,
|
||||
QtCore.Qt.ItemDataRole.DisplayRole)
|
||||
assert header == 'Action'
|
||||
|
||||
|
||||
def test_flags_first_column():
|
||||
model = MouseWheelModel()
|
||||
flags = model.flags(model.index(0, 0))
|
||||
assert flags == (QtCore.Qt.ItemFlag.ItemIsEnabled
|
||||
| QtCore.Qt.ItemFlag.ItemNeverHasChildren)
|
||||
|
||||
|
||||
def test_flags_modifiers_column():
|
||||
model = MouseWheelModel()
|
||||
flags = model.flags(model.index(0, 2))
|
||||
assert flags == (QtCore.Qt.ItemFlag.ItemIsEnabled
|
||||
| QtCore.Qt.ItemFlag.ItemNeverHasChildren
|
||||
| QtCore.Qt.ItemFlag.ItemIsEditable)
|
||||
|
||||
|
||||
def test_flags_inverted_column_when_invertible():
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=True)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
model = MouseWheelModel()
|
||||
|
||||
flags = model.flags(model.index(0, 4))
|
||||
assert flags == (QtCore.Qt.ItemFlag.ItemIsEnabled
|
||||
| QtCore.Qt.ItemFlag.ItemNeverHasChildren
|
||||
| QtCore.Qt.ItemFlag.ItemIsEditable
|
||||
| QtCore.Qt.ItemFlag.ItemIsUserCheckable)
|
||||
|
||||
|
||||
def test_flags_inverted_column_when_not_invertible():
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=False)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
model = MouseWheelModel()
|
||||
|
||||
flags = model.flags(model.index(0, 4))
|
||||
assert flags == (QtCore.Qt.ItemFlag.ItemIsEnabled
|
||||
| QtCore.Qt.ItemFlag.ItemNeverHasChildren)
|
||||
|
||||
|
||||
def test_mousewheel_model_data_gets_text():
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=False)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
model = MouseWheelModel()
|
||||
|
||||
value = model.data(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=0),
|
||||
row=MagicMock(return_value=0)),
|
||||
role=QtCore.Qt.ItemDataRole.DisplayRole)
|
||||
assert value == 'Foo'
|
||||
|
||||
|
||||
def test_mousewheel_model_data_gets_changed_when_not_changed():
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=False)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
model = MouseWheelModel()
|
||||
|
||||
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_mousewheel_model_data_gets_changed_when_changed():
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=False)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
model = MouseWheelModel()
|
||||
|
||||
action.set_modifiers(['Shift'])
|
||||
value = model.data(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=1),
|
||||
row=MagicMock(return_value=0)),
|
||||
role=QtCore.Qt.ItemDataRole.DisplayRole)
|
||||
assert value == '✎'
|
||||
|
||||
|
||||
def test_mousewheel_model_data_gets_modifiers():
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Ctrl', 'Alt'],
|
||||
invertible=False)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
model = MouseWheelModel()
|
||||
|
||||
value = model.data(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=2),
|
||||
row=MagicMock(return_value=0)),
|
||||
role=QtCore.Qt.ItemDataRole.DisplayRole)
|
||||
assert value == 'Ctrl + Alt'
|
||||
|
||||
|
||||
def test_mousewheel_model_data_gets_inverted_when_invertible():
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Ctrl', 'Alt'],
|
||||
invertible=True)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
model = MouseWheelModel()
|
||||
|
||||
value = model.data(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=3),
|
||||
row=MagicMock(return_value=0)),
|
||||
role=QtCore.Qt.ItemDataRole.DisplayRole)
|
||||
assert value == 'No'
|
||||
|
||||
|
||||
def test_mousewheel_model_data_gets_inverted_when_not_invertible():
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Ctrl', 'Alt'],
|
||||
invertible=False)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
model = MouseWheelModel()
|
||||
|
||||
value = model.data(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=3),
|
||||
row=MagicMock(return_value=0)),
|
||||
role=QtCore.Qt.ItemDataRole.DisplayRole)
|
||||
assert value is None
|
||||
|
||||
|
||||
def test_mousewheel_model_data_tooltip_changed_when_not_changed():
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Ctrl', 'Alt'],
|
||||
invertible=False)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
model = MouseWheelModel()
|
||||
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_mousewheel_model_data_tooltip_changed_when_changed():
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=False)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
model = MouseWheelModel()
|
||||
|
||||
action.set_modifiers(['Shift'])
|
||||
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_mousewheel_model_data_tooltip_modifiers_changed_from_not_configured():
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=[],
|
||||
invertible=False)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
model = MouseWheelModel()
|
||||
|
||||
action.set_modifiers(['Shift'])
|
||||
value = model.data(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=2),
|
||||
row=MagicMock(return_value=0)),
|
||||
role=QtCore.Qt.ItemDataRole.ToolTipRole)
|
||||
assert value == 'Default: Not configured'
|
||||
|
||||
|
||||
def test_mousewheel_model_data_tooltip_modifiers_when_changed():
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Ctrl', 'Alt'],
|
||||
invertible=False)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
model = MouseWheelModel()
|
||||
|
||||
action.set_modifiers(['Shift'])
|
||||
value = model.data(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=2),
|
||||
row=MagicMock(return_value=0)),
|
||||
role=QtCore.Qt.ItemDataRole.ToolTipRole)
|
||||
assert value == 'Default: Ctrl + Alt'
|
||||
|
||||
|
||||
def test_mousewheel_model_data_tooltip_inverted_when_changed_and_invertible():
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=True)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
model = MouseWheelModel()
|
||||
|
||||
action.set_inverted(True)
|
||||
value = model.data(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=3),
|
||||
row=MagicMock(return_value=0)),
|
||||
role=QtCore.Qt.ItemDataRole.ToolTipRole)
|
||||
assert value == 'Default: No'
|
||||
|
||||
|
||||
def test_mousewheel_model_data_tooltip_inverted_changed_and_not_invertible():
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=False)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
model = MouseWheelModel()
|
||||
|
||||
action.set_modifiers(['Shift'])
|
||||
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_mousewheel_model_data_checkstaterole_invertible_invertcol_inverted():
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=True)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
model = MouseWheelModel()
|
||||
|
||||
action.set_inverted(True)
|
||||
value = model.data(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=3),
|
||||
row=MagicMock(return_value=0)),
|
||||
role=QtCore.Qt.ItemDataRole.CheckStateRole)
|
||||
assert value == Qt.CheckState.Checked
|
||||
|
||||
|
||||
def test_mousewheel_model_data_checkstaterole_invertible_invcol_not_inverted():
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=True)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
model = MouseWheelModel()
|
||||
|
||||
value = model.data(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=3),
|
||||
row=MagicMock(return_value=0)),
|
||||
role=QtCore.Qt.ItemDataRole.CheckStateRole)
|
||||
assert value == Qt.CheckState.Unchecked
|
||||
|
||||
|
||||
def test_mousewheel_model_data_checkstaterole_other_column():
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=True)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
model = MouseWheelModel()
|
||||
|
||||
value = model.data(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=2),
|
||||
row=MagicMock(return_value=0)),
|
||||
role=QtCore.Qt.ItemDataRole.CheckStateRole)
|
||||
assert value is None
|
||||
|
||||
|
||||
def test_mousewheel_model_setdate_saves_inverted():
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=True)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
model = MouseWheelModel()
|
||||
|
||||
model.setData(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=3),
|
||||
row=MagicMock(return_value=0)),
|
||||
value=Qt.CheckState.Checked.value,
|
||||
role=None)
|
||||
assert action.get_inverted() is True
|
||||
|
||||
|
||||
def test_mousewheel_model_setdata_saves_controls():
|
||||
action = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=True)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([action])):
|
||||
model = MouseWheelModel()
|
||||
|
||||
model.setData(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=2),
|
||||
row=MagicMock(return_value=0)),
|
||||
value=['Ctrl'],
|
||||
role=None)
|
||||
|
||||
assert action.get_modifiers() == ['Ctrl']
|
||||
|
||||
|
||||
def test_mousewheel_model_setdata_saves_controls_and_removes_from_other():
|
||||
a1 = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=True)
|
||||
a2 = MouseWheelConfig(
|
||||
id='bar1',
|
||||
group='bar',
|
||||
text='Bar',
|
||||
modifiers=['Ctrl'],
|
||||
invertible=True)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([a1, a2])):
|
||||
model = MouseWheelModel()
|
||||
|
||||
model.setData(
|
||||
index=MagicMock(
|
||||
column=MagicMock(return_value=2),
|
||||
row=MagicMock(return_value=0)),
|
||||
value=['Ctrl'],
|
||||
role=None,
|
||||
remove_from_other=a2)
|
||||
|
||||
assert a1.get_modifiers() == ['Ctrl']
|
||||
assert a2.get_modifiers() == []
|
||||
|
||||
|
||||
def test_mousewheel_proxy_data_unfiltered():
|
||||
a1 = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=True)
|
||||
a2 = MouseWheelConfig(
|
||||
id='bar1',
|
||||
group='bar',
|
||||
text='Bar',
|
||||
modifiers=['Ctrl'],
|
||||
invertible=True)
|
||||
a3 = MouseWheelConfig(
|
||||
id='baz1',
|
||||
group='baz',
|
||||
text='Baz',
|
||||
modifiers=['Shift'],
|
||||
invertible=True)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([a2, a1, a3])):
|
||||
proxy = MouseWheelProxy()
|
||||
|
||||
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'
|
||||
|
||||
|
||||
def test_mousewheel_proxy_data_filtered():
|
||||
a1 = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=True)
|
||||
a2 = MouseWheelConfig(
|
||||
id='bar1',
|
||||
group='bar',
|
||||
text='Bar',
|
||||
modifiers=['Ctrl'],
|
||||
invertible=True)
|
||||
a3 = MouseWheelConfig(
|
||||
id='baz1',
|
||||
group='baz',
|
||||
text='Baz',
|
||||
modifiers=['Shift'],
|
||||
invertible=True)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([a2, a1, a3])):
|
||||
proxy = MouseWheelProxy()
|
||||
|
||||
proxy.setFilterFixedString('b')
|
||||
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_mousewheel_proxy_setdata_saves_correct_filtered_index():
|
||||
a1 = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=True)
|
||||
a2 = MouseWheelConfig(
|
||||
id='bar1',
|
||||
group='bar',
|
||||
text='Bar',
|
||||
modifiers=['Ctrl'],
|
||||
invertible=True)
|
||||
a3 = MouseWheelConfig(
|
||||
id='baz1',
|
||||
group='baz',
|
||||
text='Baz',
|
||||
modifiers=['Shift'],
|
||||
invertible=True)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([a2, a1, a3])):
|
||||
proxy = MouseWheelProxy()
|
||||
|
||||
proxy.setFilterFixedString('b')
|
||||
proxy.setData(
|
||||
index=proxy.index(1, 3),
|
||||
value={'modifiers': ['Ctrl', 'Alt']},
|
||||
role=None)
|
||||
|
||||
a3.get_modifiers() == ['Ctrl', 'Alt']
|
||||
|
||||
|
||||
def test_mousewheel_proxy_setdata_remove_from_other():
|
||||
a1 = MouseWheelConfig(
|
||||
id='foo1',
|
||||
group='foo',
|
||||
text='Foo',
|
||||
modifiers=['Alt'],
|
||||
invertible=True)
|
||||
a2 = MouseWheelConfig(
|
||||
id='bar1',
|
||||
group='bar',
|
||||
text='Bar',
|
||||
modifiers=['Ctrl'],
|
||||
invertible=True)
|
||||
|
||||
with patch('beeref.config.controls.KeyboardSettings.MOUSEWHEEL_ACTIONS',
|
||||
ActionList([a2, a1])):
|
||||
proxy = MouseWheelProxy()
|
||||
|
||||
proxy.setData(
|
||||
index=proxy.index(0, 3),
|
||||
value={'modifiers': ['Ctrl', 'Alt']},
|
||||
role=None,
|
||||
remove_from_other=a2)
|
||||
|
||||
a1.get_modifiers() == ['Ctrl', 'Alt']
|
||||
a2.get_modifiers() == []
|
||||
|
|
@ -1,16 +1,10 @@
|
|||
from unittest.mock import patch, MagicMock
|
||||
from unittest.mock import patch
|
||||
|
||||
from PyQt6 import QtWidgets, QtCore, QtGui
|
||||
from PyQt6 import QtWidgets
|
||||
|
||||
from beeref.actions.actions import Action, ActionList
|
||||
from beeref.widgets.settings import (
|
||||
ArrangeGapWidget,
|
||||
ImageStorageFormatWidget,
|
||||
KeyboardSettingsDialog,
|
||||
KeyboardShortcutsDelegate,
|
||||
KeyboardShortcutsEditor,
|
||||
KeyboardShortcutsModel,
|
||||
KeyboardShortcutsProxy,
|
||||
SettingsDialog,
|
||||
)
|
||||
|
||||
|
|
@ -100,401 +94,3 @@ 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()
|
||||
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')
|
||||
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