hydrus/hydrus/client/gui/ClientGUIShortcuts.py

1366 lines
51 KiB
Python
Raw Normal View History

2020-04-22 21:00:35 +00:00
import typing
2019-11-14 03:56:30 +00:00
from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
from qtpy import QtGui as QG
2020-04-22 21:00:35 +00:00
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusSerialisable
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientData
from hydrus.client.gui import ClientGUICore as CGC
from hydrus.client.gui import ClientGUIFunctions
2019-12-11 23:18:37 +00:00
2020-02-19 21:48:36 +00:00
SHORTCUT_TYPE_KEYBOARD_CHARACTER = 0
SHORTCUT_TYPE_MOUSE = 1
SHORTCUT_TYPE_KEYBOARD_SPECIAL = 2
SHORTCUT_TYPE_NOT_ALLOWED = 3
SHORTCUT_PRESS_TYPE_PRESS = 0
SHORTCUT_PRESS_TYPE_RELEASE = 1
SHORTCUT_PRESS_TYPE_DOUBLE_CLICK = 2
SHORTCUT_PRESS_TYPE_DRAG = 3
shortcut_press_type_str_lookup = {}
shortcut_press_type_str_lookup[ SHORTCUT_PRESS_TYPE_PRESS ] = 'press'
shortcut_press_type_str_lookup[ SHORTCUT_PRESS_TYPE_RELEASE ] = 'release'
shortcut_press_type_str_lookup[ SHORTCUT_PRESS_TYPE_DOUBLE_CLICK ] = 'double'
shortcut_press_type_str_lookup[ SHORTCUT_PRESS_TYPE_DRAG ] = 'drag'
SHORTCUT_MODIFIER_CTRL = 0
SHORTCUT_MODIFIER_ALT = 1
SHORTCUT_MODIFIER_SHIFT = 2
SHORTCUT_MODIFIER_KEYPAD = 3
SHORTCUT_MODIFIER_GROUP_SWITCH = 4
SHORTCUT_KEY_SPECIAL_SPACE = 0
SHORTCUT_KEY_SPECIAL_BACKSPACE = 1
SHORTCUT_KEY_SPECIAL_TAB = 2
SHORTCUT_KEY_SPECIAL_RETURN = 3
SHORTCUT_KEY_SPECIAL_ENTER = 4
SHORTCUT_KEY_SPECIAL_PAUSE = 5
SHORTCUT_KEY_SPECIAL_ESCAPE = 6
SHORTCUT_KEY_SPECIAL_INSERT = 7
SHORTCUT_KEY_SPECIAL_DELETE = 8
SHORTCUT_KEY_SPECIAL_UP = 9
SHORTCUT_KEY_SPECIAL_DOWN = 10
SHORTCUT_KEY_SPECIAL_LEFT = 11
SHORTCUT_KEY_SPECIAL_RIGHT = 12
SHORTCUT_KEY_SPECIAL_HOME = 13
SHORTCUT_KEY_SPECIAL_END = 14
SHORTCUT_KEY_SPECIAL_PAGE_UP = 15
SHORTCUT_KEY_SPECIAL_PAGE_DOWN = 16
SHORTCUT_KEY_SPECIAL_F1 = 17
SHORTCUT_KEY_SPECIAL_F2 = 18
SHORTCUT_KEY_SPECIAL_F3 = 19
SHORTCUT_KEY_SPECIAL_F4 = 20
SHORTCUT_KEY_SPECIAL_F5 = 21
SHORTCUT_KEY_SPECIAL_F6 = 22
SHORTCUT_KEY_SPECIAL_F7 = 23
SHORTCUT_KEY_SPECIAL_F8 = 24
SHORTCUT_KEY_SPECIAL_F9 = 25
SHORTCUT_KEY_SPECIAL_F10 = 26
SHORTCUT_KEY_SPECIAL_F11 = 27
SHORTCUT_KEY_SPECIAL_F12 = 28
if HC.PLATFORM_MACOS:
2020-05-27 21:27:52 +00:00
DELETE_KEYS_QT = ( QC.Qt.Key_Backspace, QC.Qt.Key_Delete )
DELETE_KEYS_HYDRUS = ( SHORTCUT_KEY_SPECIAL_BACKSPACE, SHORTCUT_KEY_SPECIAL_DELETE )
2020-02-19 21:48:36 +00:00
else:
2020-05-27 21:27:52 +00:00
DELETE_KEYS_QT = ( QC.Qt.Key_Delete, )
DELETE_KEYS_HYDRUS = ( SHORTCUT_KEY_SPECIAL_DELETE, )
2020-02-19 21:48:36 +00:00
special_key_shortcut_enum_lookup = {}
special_key_shortcut_enum_lookup[ QC.Qt.Key_Space ] = SHORTCUT_KEY_SPECIAL_SPACE
special_key_shortcut_enum_lookup[ QC.Qt.Key_Backspace ] = SHORTCUT_KEY_SPECIAL_BACKSPACE
special_key_shortcut_enum_lookup[ QC.Qt.Key_Tab ] = SHORTCUT_KEY_SPECIAL_TAB
special_key_shortcut_enum_lookup[ QC.Qt.Key_Return ] = SHORTCUT_KEY_SPECIAL_RETURN
special_key_shortcut_enum_lookup[ QC.Qt.Key_Enter ] = SHORTCUT_KEY_SPECIAL_ENTER
special_key_shortcut_enum_lookup[ QC.Qt.Key_Pause ] = SHORTCUT_KEY_SPECIAL_PAUSE
special_key_shortcut_enum_lookup[ QC.Qt.Key_Escape ] = SHORTCUT_KEY_SPECIAL_ESCAPE
special_key_shortcut_enum_lookup[ QC.Qt.Key_Insert ] = SHORTCUT_KEY_SPECIAL_INSERT
special_key_shortcut_enum_lookup[ QC.Qt.Key_Delete ] = SHORTCUT_KEY_SPECIAL_DELETE
special_key_shortcut_enum_lookup[ QC.Qt.Key_Up ] = SHORTCUT_KEY_SPECIAL_UP
special_key_shortcut_enum_lookup[ QC.Qt.Key_Down ] = SHORTCUT_KEY_SPECIAL_DOWN
special_key_shortcut_enum_lookup[ QC.Qt.Key_Left ] = SHORTCUT_KEY_SPECIAL_LEFT
special_key_shortcut_enum_lookup[ QC.Qt.Key_Right ] = SHORTCUT_KEY_SPECIAL_RIGHT
special_key_shortcut_enum_lookup[ QC.Qt.Key_Home ] = SHORTCUT_KEY_SPECIAL_HOME
special_key_shortcut_enum_lookup[ QC.Qt.Key_End ] = SHORTCUT_KEY_SPECIAL_END
special_key_shortcut_enum_lookup[ QC.Qt.Key_PageUp ] = SHORTCUT_KEY_SPECIAL_PAGE_UP
special_key_shortcut_enum_lookup[ QC.Qt.Key_PageDown ] = SHORTCUT_KEY_SPECIAL_PAGE_DOWN
special_key_shortcut_enum_lookup[ QC.Qt.Key_F1 ] = SHORTCUT_KEY_SPECIAL_F1
special_key_shortcut_enum_lookup[ QC.Qt.Key_F2 ] = SHORTCUT_KEY_SPECIAL_F2
special_key_shortcut_enum_lookup[ QC.Qt.Key_F3 ] = SHORTCUT_KEY_SPECIAL_F3
special_key_shortcut_enum_lookup[ QC.Qt.Key_F4 ] = SHORTCUT_KEY_SPECIAL_F4
special_key_shortcut_enum_lookup[ QC.Qt.Key_F5 ] = SHORTCUT_KEY_SPECIAL_F5
special_key_shortcut_enum_lookup[ QC.Qt.Key_F6 ] = SHORTCUT_KEY_SPECIAL_F6
special_key_shortcut_enum_lookup[ QC.Qt.Key_F7 ] = SHORTCUT_KEY_SPECIAL_F7
special_key_shortcut_enum_lookup[ QC.Qt.Key_F8 ] = SHORTCUT_KEY_SPECIAL_F8
special_key_shortcut_enum_lookup[ QC.Qt.Key_F9 ] = SHORTCUT_KEY_SPECIAL_F9
special_key_shortcut_enum_lookup[ QC.Qt.Key_F10 ] = SHORTCUT_KEY_SPECIAL_F10
special_key_shortcut_enum_lookup[ QC.Qt.Key_F11 ] = SHORTCUT_KEY_SPECIAL_F11
special_key_shortcut_enum_lookup[ QC.Qt.Key_F12 ] = SHORTCUT_KEY_SPECIAL_F12
special_key_shortcut_str_lookup = {}
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_SPACE ] = 'space'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_BACKSPACE ] = 'backspace'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_TAB ] = 'tab'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_RETURN ] = 'return'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_ENTER ] = 'enter'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_PAUSE ] = 'pause'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_ESCAPE ] = 'escape'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_INSERT ] = 'insert'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_DELETE ] = 'delete'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_UP ] = 'up'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_DOWN ] = 'down'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_LEFT ] = 'left'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_RIGHT ] = 'right'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_HOME ] = 'home'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_END ] = 'end'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_PAGE_DOWN ] = 'page down'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_PAGE_UP ] = 'page up'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_F1 ] = 'f1'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_F2 ] = 'f2'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_F3 ] = 'f3'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_F4 ] = 'f4'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_F5 ] = 'f5'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_F6 ] = 'f6'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_F7 ] = 'f7'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_F8 ] = 'f8'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_F9 ] = 'f9'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_F10 ] = 'f10'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_F11 ] = 'f11'
special_key_shortcut_str_lookup[ SHORTCUT_KEY_SPECIAL_F12 ] = 'f12'
SHORTCUT_MOUSE_LEFT = 0
SHORTCUT_MOUSE_RIGHT = 1
SHORTCUT_MOUSE_MIDDLE = 2
SHORTCUT_MOUSE_SCROLL_UP = 3
SHORTCUT_MOUSE_SCROLL_DOWN = 4
SHORTCUT_MOUSE_SCROLL_LEFT = 5
SHORTCUT_MOUSE_SCROLL_RIGHT = 6
2020-04-29 21:44:12 +00:00
SHORTCUT_MOUSE_BACK = 7
SHORTCUT_MOUSE_FORWARD = 8
SHORTCUT_MOUSE_CLICKS = { SHORTCUT_MOUSE_LEFT, SHORTCUT_MOUSE_MIDDLE, SHORTCUT_MOUSE_RIGHT, SHORTCUT_MOUSE_BACK, SHORTCUT_MOUSE_FORWARD }
qt_mouse_buttons_to_hydrus_mouse_buttons = {}
qt_mouse_buttons_to_hydrus_mouse_buttons[ QC.Qt.LeftButton ] = SHORTCUT_MOUSE_LEFT
qt_mouse_buttons_to_hydrus_mouse_buttons[ QC.Qt.MiddleButton ] = SHORTCUT_MOUSE_MIDDLE
qt_mouse_buttons_to_hydrus_mouse_buttons[ QC.Qt.RightButton ] = SHORTCUT_MOUSE_RIGHT
qt_mouse_buttons_to_hydrus_mouse_buttons[ QC.Qt.BackButton ] = SHORTCUT_MOUSE_BACK
qt_mouse_buttons_to_hydrus_mouse_buttons[ QC.Qt.ForwardButton ] = SHORTCUT_MOUSE_FORWARD
2020-02-19 21:48:36 +00:00
shortcut_mouse_string_lookup = {}
shortcut_mouse_string_lookup[ SHORTCUT_MOUSE_LEFT ] = 'left-click'
shortcut_mouse_string_lookup[ SHORTCUT_MOUSE_RIGHT ] = 'right-click'
shortcut_mouse_string_lookup[ SHORTCUT_MOUSE_MIDDLE ] = 'middle-click'
2020-04-29 21:44:12 +00:00
shortcut_mouse_string_lookup[ SHORTCUT_MOUSE_BACK ] = 'back'
shortcut_mouse_string_lookup[ SHORTCUT_MOUSE_FORWARD ] = 'forward'
2020-02-19 21:48:36 +00:00
shortcut_mouse_string_lookup[ SHORTCUT_MOUSE_SCROLL_UP ] = 'scroll up'
shortcut_mouse_string_lookup[ SHORTCUT_MOUSE_SCROLL_DOWN ] = 'scroll down'
shortcut_mouse_string_lookup[ SHORTCUT_MOUSE_SCROLL_LEFT ] = 'scroll left'
shortcut_mouse_string_lookup[ SHORTCUT_MOUSE_SCROLL_RIGHT ] = 'scroll right'
2020-05-06 21:31:41 +00:00
shortcut_names_to_pretty_names = {}
shortcut_names_to_pretty_names[ 'global' ] = 'global'
shortcut_names_to_pretty_names[ 'main_gui' ] = 'the main window'
shortcut_names_to_pretty_names[ 'media' ] = 'media actions, either thumbnails or the viewer'
shortcut_names_to_pretty_names[ 'media_viewer' ] = 'media viewers - all (zoom and pan)'
shortcut_names_to_pretty_names[ 'media_viewer_browser' ] = 'media viewer - \'normal\' browser'
shortcut_names_to_pretty_names[ 'archive_delete_filter' ] = 'media viewer - archive/delete filter'
shortcut_names_to_pretty_names[ 'duplicate_filter' ] = 'media viewer - duplicate filter'
shortcut_names_to_pretty_names[ 'preview_media_window' ] = 'media viewer - the preview window'
shortcut_names_to_pretty_names[ 'media_viewer_media_window' ] = 'the actual media in a media viewer'
shortcut_names_to_sort_order = {}
shortcut_names_to_sort_order[ 'global' ] = 0
shortcut_names_to_sort_order[ 'main_gui' ] = 1
shortcut_names_to_sort_order[ 'media' ] = 2
shortcut_names_to_sort_order[ 'media_viewer' ] = 3
shortcut_names_to_sort_order[ 'media_viewer_browser' ] = 4
shortcut_names_to_sort_order[ 'archive_delete_filter' ] = 5
shortcut_names_to_sort_order[ 'duplicate_filter' ] = 6
shortcut_names_to_sort_order[ 'preview_media_window' ] = 7
shortcut_names_to_sort_order[ 'media_viewer_media_window' ] = 8
2020-02-19 21:48:36 +00:00
shortcut_names_to_descriptions = {}
shortcut_names_to_descriptions[ 'global' ] = 'Actions for the whole program. Should work in the main gui or a media viewer.'
shortcut_names_to_descriptions[ 'archive_delete_filter' ] = 'Navigation actions for the media viewer during an archive/delete filter. Mouse shortcuts should work.'
shortcut_names_to_descriptions[ 'duplicate_filter' ] = 'Navigation actions for the media viewer during a duplicate filter. Mouse shortcuts should work.'
shortcut_names_to_descriptions[ 'media' ] = 'Actions to alter metadata for media in the media viewer or the thumbnail grid.'
shortcut_names_to_descriptions[ 'main_gui' ] = 'Actions to control pages in the main window of the program.'
shortcut_names_to_descriptions[ 'media_viewer_browser' ] = 'Navigation actions for the regular browsable media viewer.'
shortcut_names_to_descriptions[ 'media_viewer' ] = 'Zoom and pan and player actions for any media viewer.'
shortcut_names_to_descriptions[ 'media_viewer_media_window' ] = 'Actions for any video or audio player in a media viewer window.'
shortcut_names_to_descriptions[ 'preview_media_window' ] = 'Actions for any video or audio player in a preview window.'
# shortcut commands
SHORTCUTS_RESERVED_NAMES = [ 'global', 'archive_delete_filter', 'duplicate_filter', 'media', 'main_gui', 'media_viewer_browser', 'media_viewer', 'media_viewer_media_window', 'preview_media_window' ]
2020-04-01 21:51:42 +00:00
SHORTCUTS_GLOBAL_ACTIONS = [ 'global_audio_mute', 'global_audio_unmute', 'global_audio_mute_flip', 'exit_application', 'exit_application_force_maintenance', 'restart_application', 'hide_to_system_tray' ]
SHORTCUTS_MEDIA_ACTIONS = [ 'manage_file_tags', 'manage_file_ratings', 'manage_file_urls', 'manage_file_notes', 'archive_file', 'inbox_file', 'delete_file', 'undelete_file', 'export_files', 'export_files_quick_auto_export', 'remove_file_from_view', 'open_file_in_external_program', 'open_selection_in_new_page', 'launch_the_archive_delete_filter', 'copy_bmp', 'copy_bmp_or_file_if_not_bmpable', 'copy_file', 'copy_path', 'copy_sha256_hash', 'copy_md5_hash', 'copy_sha1_hash', 'copy_sha512_hash', 'get_similar_to_exact', 'get_similar_to_very_similar', 'get_similar_to_similar', 'get_similar_to_speculative', 'duplicate_media_set_alternate', 'duplicate_media_set_alternate_collections', 'duplicate_media_set_custom', 'duplicate_media_set_focused_better', 'duplicate_media_set_focused_king', 'duplicate_media_set_same_quality', 'open_known_url' ]
2020-02-19 21:48:36 +00:00
SHORTCUTS_MEDIA_VIEWER_ACTIONS = [ 'pause_media', 'pause_play_media', 'move_animation_to_previous_frame', 'move_animation_to_next_frame', 'switch_between_fullscreen_borderless_and_regular_framed_window', 'pan_up', 'pan_down', 'pan_left', 'pan_right', 'pan_top_edge', 'pan_bottom_edge', 'pan_left_edge', 'pan_right_edge', 'pan_vertical_center', 'pan_horizontal_center', 'zoom_in', 'zoom_out', 'switch_between_100_percent_and_canvas_zoom', 'flip_darkmode', 'close_media_viewer' ]
SHORTCUTS_MEDIA_VIEWER_BROWSER_ACTIONS = [ 'view_next', 'view_first', 'view_last', 'view_previous', 'pause_play_slideshow', 'show_menu', 'close_media_viewer' ]
SHORTCUTS_MAIN_GUI_ACTIONS = [ 'refresh', 'refresh_all_pages', 'refresh_page_of_pages_pages', 'new_page', 'new_page_of_pages', 'new_duplicate_filter_page', 'new_gallery_downloader_page', 'new_url_downloader_page', 'new_simple_downloader_page', 'new_watcher_downloader_page', 'synchronised_wait_switch', 'set_media_focus', 'show_hide_splitters', 'set_search_focus', 'unclose_page', 'close_page', 'redo', 'undo', 'flip_darkmode', 'check_all_import_folders', 'flip_debug_force_idle_mode_do_not_set_this', 'show_and_focus_manage_tags_favourite_tags', 'show_and_focus_manage_tags_related_tags', 'show_and_focus_manage_tags_file_lookup_script_tags', 'show_and_focus_manage_tags_recent_tags', 'focus_media_viewer' ]
SHORTCUTS_DUPLICATE_FILTER_ACTIONS = [ 'duplicate_filter_this_is_better_and_delete_other', 'duplicate_filter_this_is_better_but_keep_both', 'duplicate_filter_exactly_the_same', 'duplicate_filter_alternates', 'duplicate_filter_false_positive', 'duplicate_filter_custom_action', 'duplicate_filter_skip', 'duplicate_filter_back', 'close_media_viewer' ]
SHORTCUTS_ARCHIVE_DELETE_FILTER_ACTIONS = [ 'archive_delete_filter_keep', 'archive_delete_filter_delete', 'archive_delete_filter_skip', 'archive_delete_filter_back', 'close_media_viewer' ]
SHORTCUTS_MEDIA_VIEWER_VIDEO_AUDIO_PLAYER_ACTIONS = [ 'pause_media', 'pause_play_media', 'open_file_in_external_program', 'close_media_viewer' ]
SHORTCUTS_PREVIEW_VIDEO_AUDIO_PLAYER_ACTIONS = [ 'pause_media', 'pause_play_media', 'open_file_in_external_program', 'launch_media_viewer' ]
simple_shortcut_name_to_action_lookup = {}
simple_shortcut_name_to_action_lookup[ 'global' ] = SHORTCUTS_GLOBAL_ACTIONS
simple_shortcut_name_to_action_lookup[ 'media' ] = SHORTCUTS_MEDIA_ACTIONS
simple_shortcut_name_to_action_lookup[ 'media_viewer' ] = SHORTCUTS_MEDIA_VIEWER_ACTIONS
simple_shortcut_name_to_action_lookup[ 'media_viewer_browser' ] = SHORTCUTS_MEDIA_VIEWER_BROWSER_ACTIONS
simple_shortcut_name_to_action_lookup[ 'main_gui' ] = SHORTCUTS_MAIN_GUI_ACTIONS
simple_shortcut_name_to_action_lookup[ 'duplicate_filter' ] = SHORTCUTS_DUPLICATE_FILTER_ACTIONS + SHORTCUTS_MEDIA_ACTIONS + SHORTCUTS_MEDIA_VIEWER_ACTIONS
simple_shortcut_name_to_action_lookup[ 'archive_delete_filter' ] = SHORTCUTS_ARCHIVE_DELETE_FILTER_ACTIONS
simple_shortcut_name_to_action_lookup[ 'media_viewer_media_window' ] = SHORTCUTS_MEDIA_VIEWER_VIDEO_AUDIO_PLAYER_ACTIONS
simple_shortcut_name_to_action_lookup[ 'preview_media_window' ] = SHORTCUTS_PREVIEW_VIDEO_AUDIO_PLAYER_ACTIONS
simple_shortcut_name_to_action_lookup[ 'custom' ] = SHORTCUTS_MEDIA_ACTIONS + SHORTCUTS_MEDIA_VIEWER_ACTIONS
2019-12-11 23:18:37 +00:00
# ok, the problem here is that I get key codes that are converted, so if someone does shift+1 on a US keyboard, this ends up with Shift+! same with ctrl+alt+ to get accented characters
# it isn't really a big deal since everything still lines up, but the QGuiApplicationPrivate::platformIntegration()->possibleKeys(e) to get some variant of 'yeah this is just !' seems unavailable for python
# it is basically a display bug, but it'd be nice to have it working right
def ConvertQtKeyToShortcutKey( key_qt ):
2017-05-31 21:50:53 +00:00
2020-02-19 21:48:36 +00:00
if key_qt in special_key_shortcut_enum_lookup:
2019-11-14 03:56:30 +00:00
2020-02-19 21:48:36 +00:00
key_ord = special_key_shortcut_enum_lookup[ key_qt ]
2019-12-11 23:18:37 +00:00
2020-02-19 21:48:36 +00:00
return ( SHORTCUT_TYPE_KEYBOARD_SPECIAL, key_ord )
2019-12-11 23:18:37 +00:00
else:
try:
2019-11-14 03:56:30 +00:00
2019-12-11 23:18:37 +00:00
key_ord = int( key_qt )
2019-11-14 03:56:30 +00:00
2019-12-11 23:18:37 +00:00
key_chr = chr( key_ord )
2019-11-14 03:56:30 +00:00
2019-12-11 23:18:37 +00:00
# this is turbo lower() that converts Scharfes S (beta) to 'ss'
key_chr = key_chr.casefold()[0]
2019-11-14 03:56:30 +00:00
2019-12-11 23:18:37 +00:00
casefold_key_ord = ord( key_chr )
2020-02-19 21:48:36 +00:00
return ( SHORTCUT_TYPE_KEYBOARD_CHARACTER, casefold_key_ord )
2019-12-11 23:18:37 +00:00
except:
2020-02-19 21:48:36 +00:00
return ( SHORTCUT_TYPE_NOT_ALLOWED, key_ord )
2019-12-11 23:18:37 +00:00
def ConvertKeyEventToShortcut( event ):
key_qt = event.key()
( shortcut_type, key_ord ) = ConvertQtKeyToShortcutKey( key_qt )
2020-02-19 21:48:36 +00:00
if shortcut_type != SHORTCUT_TYPE_NOT_ALLOWED:
2018-05-16 20:09:50 +00:00
modifiers = []
2019-11-14 03:56:30 +00:00
if event.modifiers() & QC.Qt.AltModifier:
2018-05-16 20:09:50 +00:00
2020-02-19 21:48:36 +00:00
modifiers.append( SHORTCUT_MODIFIER_ALT )
2018-05-16 20:09:50 +00:00
2019-11-20 23:10:46 +00:00
if HC.PLATFORM_MACOS:
2019-11-14 03:56:30 +00:00
ctrl = QC.Qt.MetaModifier
else:
ctrl = QC.Qt.ControlModifier
if event.modifiers() & ctrl:
2018-05-16 20:09:50 +00:00
2020-02-19 21:48:36 +00:00
modifiers.append( SHORTCUT_MODIFIER_CTRL )
2018-05-16 20:09:50 +00:00
2019-11-14 03:56:30 +00:00
if event.modifiers() & QC.Qt.ShiftModifier:
2018-05-16 20:09:50 +00:00
2020-02-19 21:48:36 +00:00
modifiers.append( SHORTCUT_MODIFIER_SHIFT )
2018-05-16 20:09:50 +00:00
2019-11-14 03:56:30 +00:00
if event.modifiers() & QC.Qt.GroupSwitchModifier:
2020-02-19 21:48:36 +00:00
modifiers.append( SHORTCUT_MODIFIER_GROUP_SWITCH )
2019-11-14 03:56:30 +00:00
if event.modifiers() & QC.Qt.KeypadModifier:
2020-02-19 21:48:36 +00:00
modifiers.append( SHORTCUT_MODIFIER_KEYPAD )
2019-11-14 03:56:30 +00:00
2020-02-19 21:48:36 +00:00
shortcut_press_type = SHORTCUT_PRESS_TYPE_PRESS
2020-02-12 22:50:37 +00:00
shortcut = Shortcut( shortcut_type, key_ord, shortcut_press_type, modifiers )
2018-05-16 20:09:50 +00:00
if HG.gui_report_mode:
HydrusData.ShowText( 'key event caught: ' + repr( shortcut ) )
return shortcut
return None
def ConvertKeyEventToSimpleTuple( event ):
2019-11-14 03:56:30 +00:00
modifier = QC.Qt.NoModifier
2018-05-16 20:09:50 +00:00
2019-11-14 03:56:30 +00:00
if event.modifiers() & QC.Qt.AltModifier: modifier = QC.Qt.AltModifier
elif event.modifiers() & QC.Qt.ControlModifier: modifier = QC.Qt.ControlModifier
elif event.modifiers() & QC.Qt.ShiftModifier: modifier = QC.Qt.ShiftModifier
elif event.modifiers() & QC.Qt.KeypadModifier: modifier = QC.Qt.KeypadModifier
elif event.modifiers() & QC.Qt.GroupSwitchModifier: modifier = QC.Qt.GroupSwitchModifier
2018-05-16 20:09:50 +00:00
2019-11-14 03:56:30 +00:00
key = event.key()
2018-05-16 20:09:50 +00:00
return ( modifier, key )
2020-04-29 21:44:12 +00:00
def ConvertMouseEventToShortcut( event: QG.QMouseEvent ):
2018-05-16 20:09:50 +00:00
key = None
2020-02-19 21:48:36 +00:00
shortcut_press_type = SHORTCUT_PRESS_TYPE_PRESS
2020-02-12 22:50:37 +00:00
if event.type() == QC.QEvent.MouseButtonPress:
2018-05-16 20:09:50 +00:00
2020-04-29 21:44:12 +00:00
for ( qt_button, hydrus_button ) in qt_mouse_buttons_to_hydrus_mouse_buttons.items():
2020-02-12 22:50:37 +00:00
2020-04-29 21:44:12 +00:00
if event.buttons() & qt_button:
key = hydrus_button
break
2020-02-12 22:50:37 +00:00
2018-05-16 20:09:50 +00:00
2020-02-12 22:50:37 +00:00
elif event.type() in ( QC.QEvent.MouseButtonDblClick, QC.QEvent.MouseButtonRelease ):
2018-05-16 20:09:50 +00:00
2020-02-12 22:50:37 +00:00
if event.type() == QC.QEvent.MouseButtonRelease:
2020-02-19 21:48:36 +00:00
shortcut_press_type = SHORTCUT_PRESS_TYPE_RELEASE
2020-02-12 22:50:37 +00:00
elif event.type() == QC.QEvent.MouseButtonDblClick:
2020-02-19 21:48:36 +00:00
shortcut_press_type = SHORTCUT_PRESS_TYPE_DOUBLE_CLICK
2020-02-12 22:50:37 +00:00
2018-05-16 20:09:50 +00:00
2020-04-29 21:44:12 +00:00
for ( qt_button, hydrus_button ) in qt_mouse_buttons_to_hydrus_mouse_buttons.items():
2020-02-12 22:50:37 +00:00
2020-04-29 21:44:12 +00:00
if event.button() == qt_button:
key = hydrus_button
break
2020-02-12 22:50:37 +00:00
2018-05-16 20:09:50 +00:00
2020-02-12 22:50:37 +00:00
elif event.type() == QC.QEvent.Wheel:
2018-05-16 20:09:50 +00:00
2020-02-12 22:50:37 +00:00
if event.angleDelta().y() > 0:
2020-02-19 21:48:36 +00:00
key = SHORTCUT_MOUSE_SCROLL_UP
2020-02-12 22:50:37 +00:00
elif event.angleDelta().y() < 0:
2020-02-19 21:48:36 +00:00
key = SHORTCUT_MOUSE_SCROLL_DOWN
2020-02-12 22:50:37 +00:00
2018-05-16 20:09:50 +00:00
if key is not None:
modifiers = []
2019-11-14 03:56:30 +00:00
if event.modifiers() & QC.Qt.AltModifier:
2018-05-16 20:09:50 +00:00
2020-02-19 21:48:36 +00:00
modifiers.append( SHORTCUT_MODIFIER_ALT )
2018-05-16 20:09:50 +00:00
2019-11-14 03:56:30 +00:00
if event.modifiers() & QC.Qt.ControlModifier:
2018-05-16 20:09:50 +00:00
2020-02-19 21:48:36 +00:00
modifiers.append( SHORTCUT_MODIFIER_CTRL )
2018-05-16 20:09:50 +00:00
2019-11-14 03:56:30 +00:00
if event.modifiers() & QC.Qt.ShiftModifier:
2018-05-16 20:09:50 +00:00
2020-02-19 21:48:36 +00:00
modifiers.append( SHORTCUT_MODIFIER_SHIFT )
2018-05-16 20:09:50 +00:00
2019-11-14 03:56:30 +00:00
if event.modifiers() & QC.Qt.GroupSwitchModifier:
2020-02-19 21:48:36 +00:00
modifiers.append( SHORTCUT_MODIFIER_GROUP_SWITCH )
2019-11-14 03:56:30 +00:00
if event.modifiers() & QC.Qt.KeypadModifier:
2020-02-19 21:48:36 +00:00
modifiers.append( SHORTCUT_MODIFIER_KEYPAD )
2019-11-14 03:56:30 +00:00
2020-02-19 21:48:36 +00:00
shortcut = Shortcut( SHORTCUT_TYPE_MOUSE, key, shortcut_press_type, modifiers )
2018-05-16 20:09:50 +00:00
if HG.gui_report_mode:
HydrusData.ShowText( 'mouse event caught: ' + repr( shortcut ) )
return shortcut
return None
2020-02-19 21:48:36 +00:00
def AncestorShortcutsHandlers( widget: QW.QWidget ):
shortcuts_handlers = []
window = widget.window()
if window == widget:
return shortcuts_handlers
2020-04-08 21:10:11 +00:00
widget = widget.parentWidget()
if widget is None:
return shortcuts_handlers
2020-02-19 21:48:36 +00:00
while True:
child_shortcuts_handlers = [ child for child in widget.children() if isinstance( child, ShortcutsHandler ) ]
shortcuts_handlers.extend( child_shortcuts_handlers )
if widget == window:
break
2020-04-08 21:10:11 +00:00
widget = widget.parentWidget()
2020-02-19 21:48:36 +00:00
if widget is None:
break
return shortcuts_handlers
2020-05-27 21:27:52 +00:00
def IShouldCatchShortcutEvent( event_handler_owner: QC.QObject, event_catcher: QW.QWidget, event: typing.Optional[ QC.QEvent ] = None, child_tlw_classes_who_can_pass_up: typing.Optional[ typing.Iterable[ type ] ] = None ):
2017-04-19 20:58:30 +00:00
2019-07-03 22:49:27 +00:00
do_focus_test = True
2020-05-27 21:27:52 +00:00
if event is not None:
2019-11-20 23:10:46 +00:00
2020-05-27 21:27:52 +00:00
# the event happened to somewhere else, most likely a hover window of a media viewer
# should we intercept that event that happened somewhere else?
if event_handler_owner != event_catcher:
# don't pass clicks up
if event.type() in ( QC.QEvent.MouseButtonPress, QC.QEvent.MouseButtonRelease, QC.QEvent.MouseButtonDblClick ):
return False
# don't pass wheels that happen to legit controls that want to eat it, like a list, when the catcher is a window
if event.type() == QC.QEvent.Wheel:
widget_under_mouse = event_catcher.childAt( event_catcher.mapFromGlobal( QG.QCursor.pos() ) )
if widget_under_mouse is not None:
mouse_scroll_over_window_greyspace = widget_under_mouse == event_catcher and event_catcher.isWindow()
if not mouse_scroll_over_window_greyspace:
return False
2019-11-20 23:10:46 +00:00
2020-05-27 21:27:52 +00:00
if event.type() == QC.QEvent.Wheel:
do_focus_test = False
2019-07-03 22:49:27 +00:00
2020-05-27 21:27:52 +00:00
do_focus_test = False
2019-07-03 22:49:27 +00:00
if do_focus_test:
2020-05-27 21:27:52 +00:00
if not ClientGUIFunctions.TLWIsActive( event_handler_owner ):
2019-06-26 21:27:18 +00:00
2019-12-11 23:18:37 +00:00
if child_tlw_classes_who_can_pass_up is not None:
2019-07-03 22:49:27 +00:00
2020-05-27 21:27:52 +00:00
child_tlw_has_focus = ClientGUIFunctions.WidgetOrAnyTLWChildHasFocus( event_handler_owner ) and isinstance( QW.QApplication.activeWindow(), child_tlw_classes_who_can_pass_up )
2019-07-03 22:49:27 +00:00
2019-12-11 23:18:37 +00:00
if not child_tlw_has_focus:
2019-07-03 22:49:27 +00:00
return False
else:
2019-06-26 21:27:18 +00:00
return False
2017-04-19 20:58:30 +00:00
return True
2018-05-16 20:09:50 +00:00
class Shortcut( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUT
SERIALISABLE_NAME = 'Shortcut'
2020-02-12 22:50:37 +00:00
SERIALISABLE_VERSION = 3
2018-05-16 20:09:50 +00:00
2020-02-12 22:50:37 +00:00
def __init__( self, shortcut_type = None, shortcut_key = None, shortcut_press_type = None, modifiers = None ):
2018-05-16 20:09:50 +00:00
if shortcut_type is None:
2020-02-19 21:48:36 +00:00
shortcut_type = SHORTCUT_TYPE_KEYBOARD_SPECIAL
2018-05-16 20:09:50 +00:00
if shortcut_key is None:
2020-02-19 21:48:36 +00:00
shortcut_key = SHORTCUT_KEY_SPECIAL_F7
2018-05-16 20:09:50 +00:00
2020-02-12 22:50:37 +00:00
if shortcut_press_type is None:
2020-02-19 21:48:36 +00:00
shortcut_press_type = SHORTCUT_PRESS_TYPE_PRESS
2020-02-12 22:50:37 +00:00
2018-05-16 20:09:50 +00:00
if modifiers is None:
modifiers = []
2020-02-19 21:48:36 +00:00
if shortcut_type == SHORTCUT_TYPE_KEYBOARD_CHARACTER and ClientData.OrdIsAlphaUpper( shortcut_key ):
2019-11-14 03:56:30 +00:00
shortcut_key += 32 # convert A to a
2020-05-13 19:03:16 +00:00
modifiers = sorted( modifiers )
2018-05-16 20:09:50 +00:00
HydrusSerialisable.SerialisableBase.__init__( self )
2020-02-12 22:50:37 +00:00
self.shortcut_type = shortcut_type
self.shortcut_key = shortcut_key
self.shortcut_press_type = shortcut_press_type
self.modifiers = modifiers
2018-05-16 20:09:50 +00:00
def __eq__( self, other ):
2020-01-22 21:04:43 +00:00
if isinstance( other, Shortcut ):
return self.__hash__() == other.__hash__()
return NotImplemented
2018-05-16 20:09:50 +00:00
def __hash__( self ):
2020-02-12 22:50:37 +00:00
return ( self.shortcut_type, self.shortcut_key, self.shortcut_press_type, tuple( self.modifiers ) ).__hash__()
2018-05-16 20:09:50 +00:00
def __repr__( self ):
return 'Shortcut: ' + self.ToString()
def _GetSerialisableInfo( self ):
2020-02-12 22:50:37 +00:00
return ( self.shortcut_type, self.shortcut_key, self.shortcut_press_type, self.modifiers )
2018-05-16 20:09:50 +00:00
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
2020-02-12 22:50:37 +00:00
( self.shortcut_type, self.shortcut_key, self.shortcut_press_type, self.modifiers ) = serialisable_info
2018-05-16 20:09:50 +00:00
2019-11-14 03:56:30 +00:00
def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
if version == 1:
# these are dicts that convert fixed wx enums to new stuff
wx_to_qt_flat_conversion = {
2020-02-19 21:48:36 +00:00
32 : SHORTCUT_KEY_SPECIAL_SPACE,
8 : SHORTCUT_KEY_SPECIAL_BACKSPACE,
9 : SHORTCUT_KEY_SPECIAL_TAB,
13 : SHORTCUT_KEY_SPECIAL_RETURN,
310 : SHORTCUT_KEY_SPECIAL_PAUSE,
27 : SHORTCUT_KEY_SPECIAL_ESCAPE,
322 : SHORTCUT_KEY_SPECIAL_INSERT,
127 : SHORTCUT_KEY_SPECIAL_DELETE,
315 : SHORTCUT_KEY_SPECIAL_UP,
317 : SHORTCUT_KEY_SPECIAL_DOWN,
314 : SHORTCUT_KEY_SPECIAL_LEFT,
316 : SHORTCUT_KEY_SPECIAL_RIGHT,
313 : SHORTCUT_KEY_SPECIAL_HOME,
312 : SHORTCUT_KEY_SPECIAL_END,
367 : SHORTCUT_KEY_SPECIAL_PAGE_DOWN,
366 : SHORTCUT_KEY_SPECIAL_PAGE_UP,
340 : SHORTCUT_KEY_SPECIAL_F1,
341 : SHORTCUT_KEY_SPECIAL_F2,
342 : SHORTCUT_KEY_SPECIAL_F3,
343 : SHORTCUT_KEY_SPECIAL_F4,
344 : SHORTCUT_KEY_SPECIAL_F5,
345 : SHORTCUT_KEY_SPECIAL_F6,
346 : SHORTCUT_KEY_SPECIAL_F7,
347 : SHORTCUT_KEY_SPECIAL_F8,
348 : SHORTCUT_KEY_SPECIAL_F9,
349 : SHORTCUT_KEY_SPECIAL_F10,
350 : SHORTCUT_KEY_SPECIAL_F11,
351 : SHORTCUT_KEY_SPECIAL_F12
2019-11-14 03:56:30 +00:00
}
# regular keys, but numpad, that are tracked in wx by combined unique enum
wx_to_qt_numpad_ascii_conversion = {
324 : ord( '0' ),
325 : ord( '1' ),
326 : ord( '2' ),
327 : ord( '3' ),
328 : ord( '4' ),
329 : ord( '5' ),
330 : ord( '6' ),
331 : ord( '7' ),
332 : ord( '8' ),
2019-12-11 23:18:37 +00:00
333 : ord( '9' ),
2019-11-14 03:56:30 +00:00
388 : ord( '+' ),
392 : ord( '/' ),
390 : ord( '-' ),
387 : ord( '*' ),
391 : ord( '.' )
}
wx_to_qt_numpad_conversion = {
2020-02-19 21:48:36 +00:00
377 : SHORTCUT_KEY_SPECIAL_UP,
379 : SHORTCUT_KEY_SPECIAL_DOWN,
376 : SHORTCUT_KEY_SPECIAL_LEFT,
378 : SHORTCUT_KEY_SPECIAL_RIGHT,
375 : SHORTCUT_KEY_SPECIAL_HOME,
382 : SHORTCUT_KEY_SPECIAL_END,
381 : SHORTCUT_KEY_SPECIAL_PAGE_DOWN,
380 : SHORTCUT_KEY_SPECIAL_PAGE_UP,
385 : SHORTCUT_KEY_SPECIAL_DELETE,
370 : SHORTCUT_KEY_SPECIAL_ENTER
2019-11-14 03:56:30 +00:00
}
( shortcut_type, shortcut_key, modifiers ) = old_serialisable_info
2020-02-19 21:48:36 +00:00
if shortcut_type == SHORTCUT_TYPE_KEYBOARD_CHARACTER:
2019-11-14 03:56:30 +00:00
if shortcut_key in wx_to_qt_flat_conversion:
2020-02-19 21:48:36 +00:00
shortcut_type = SHORTCUT_TYPE_KEYBOARD_SPECIAL
2019-11-14 03:56:30 +00:00
shortcut_key = wx_to_qt_flat_conversion[ shortcut_key ]
elif shortcut_key in wx_to_qt_numpad_ascii_conversion:
shortcut_key = wx_to_qt_numpad_ascii_conversion[ shortcut_key ]
modifiers = list( modifiers )
2020-02-19 21:48:36 +00:00
modifiers.append( SHORTCUT_MODIFIER_KEYPAD )
2019-11-14 03:56:30 +00:00
modifiers.sort()
elif shortcut_key in wx_to_qt_numpad_conversion:
2020-02-19 21:48:36 +00:00
shortcut_type = SHORTCUT_TYPE_KEYBOARD_SPECIAL
2019-11-14 03:56:30 +00:00
shortcut_key = wx_to_qt_numpad_conversion[ shortcut_key ]
modifiers = list( modifiers )
2020-02-19 21:48:36 +00:00
modifiers.append( SHORTCUT_MODIFIER_KEYPAD )
2019-11-14 03:56:30 +00:00
modifiers.sort()
2020-02-19 21:48:36 +00:00
if shortcut_type == SHORTCUT_TYPE_KEYBOARD_CHARACTER:
2019-11-14 03:56:30 +00:00
if ClientData.OrdIsAlphaUpper( shortcut_key ):
shortcut_key += 32 # convert 'A' to 'a'
new_serialisable_info = ( shortcut_type, shortcut_key, modifiers )
return ( 2, new_serialisable_info )
2020-02-12 22:50:37 +00:00
if version == 2:
( shortcut_type, shortcut_key, modifiers ) = old_serialisable_info
2020-02-19 21:48:36 +00:00
shortcut_press_type = SHORTCUT_PRESS_TYPE_PRESS
2020-02-12 22:50:37 +00:00
new_serialisable_info = ( shortcut_type, shortcut_key, shortcut_press_type, modifiers )
return ( 3, new_serialisable_info )
2019-11-14 03:56:30 +00:00
2020-02-19 21:48:36 +00:00
def ConvertToSingleClick( self ):
if self.IsDoubleClick():
new_shortcut = self.Duplicate()
new_shortcut.shortcut_press_type = SHORTCUT_PRESS_TYPE_PRESS
return new_shortcut
return self
2018-05-16 20:09:50 +00:00
def GetShortcutType( self ):
2020-02-12 22:50:37 +00:00
return self.shortcut_type
2018-05-16 20:09:50 +00:00
def IsAppropriateForPressRelease( self ):
return self.shortcut_key in SHORTCUT_MOUSE_CLICKS and self.shortcut_press_type != SHORTCUT_PRESS_TYPE_DOUBLE_CLICK
2020-02-19 21:48:36 +00:00
def IsDoubleClick( self ):
return self.shortcut_type == SHORTCUT_TYPE_MOUSE and self.shortcut_press_type == SHORTCUT_PRESS_TYPE_DOUBLE_CLICK
2018-05-16 20:09:50 +00:00
def ToString( self ):
components = []
2020-02-19 21:48:36 +00:00
if SHORTCUT_MODIFIER_CTRL in self.modifiers:
2018-05-16 20:09:50 +00:00
components.append( 'ctrl' )
2020-02-19 21:48:36 +00:00
if SHORTCUT_MODIFIER_ALT in self.modifiers:
2018-05-16 20:09:50 +00:00
components.append( 'alt' )
2020-02-19 21:48:36 +00:00
if SHORTCUT_MODIFIER_SHIFT in self.modifiers:
2018-05-16 20:09:50 +00:00
components.append( 'shift' )
2020-02-19 21:48:36 +00:00
if SHORTCUT_MODIFIER_GROUP_SWITCH in self.modifiers:
2018-05-16 20:09:50 +00:00
2019-11-14 03:56:30 +00:00
components.append( 'Mode_switch' )
2020-02-19 21:48:36 +00:00
if self.shortcut_press_type != SHORTCUT_PRESS_TYPE_PRESS:
2020-02-12 22:50:37 +00:00
2020-02-19 21:48:36 +00:00
action_name = '{} '.format( shortcut_press_type_str_lookup[ self.shortcut_press_type ] )
2019-11-14 03:56:30 +00:00
2020-02-12 22:50:37 +00:00
else:
2019-11-14 03:56:30 +00:00
2020-02-12 22:50:37 +00:00
action_name = ''
2020-02-19 21:48:36 +00:00
if self.shortcut_type == SHORTCUT_TYPE_MOUSE and self.shortcut_key in shortcut_mouse_string_lookup:
2019-11-14 03:56:30 +00:00
2020-02-19 21:48:36 +00:00
action_name += shortcut_mouse_string_lookup[ self.shortcut_key ]
2019-11-14 03:56:30 +00:00
2020-02-19 21:48:36 +00:00
elif self.shortcut_type == SHORTCUT_TYPE_KEYBOARD_SPECIAL and self.shortcut_key in special_key_shortcut_str_lookup:
2020-02-12 22:50:37 +00:00
2020-02-19 21:48:36 +00:00
action_name += special_key_shortcut_str_lookup[ self.shortcut_key ]
2020-02-12 22:50:37 +00:00
2020-02-19 21:48:36 +00:00
elif self.shortcut_type == SHORTCUT_TYPE_KEYBOARD_CHARACTER:
2019-11-14 03:56:30 +00:00
2019-12-18 22:06:34 +00:00
try:
2018-05-16 20:09:50 +00:00
2020-02-12 22:50:37 +00:00
if ClientData.OrdIsAlphaUpper( self.shortcut_key ):
2019-12-18 22:06:34 +00:00
2020-02-12 22:50:37 +00:00
action_name += chr( self.shortcut_key + 32 ) # + 32 for converting ascii A -> a
2019-12-18 22:06:34 +00:00
else:
2020-02-12 22:50:37 +00:00
action_name += chr( self.shortcut_key )
2019-12-18 22:06:34 +00:00
2018-05-16 20:09:50 +00:00
2019-12-18 22:06:34 +00:00
except:
2018-05-16 20:09:50 +00:00
2020-02-12 22:50:37 +00:00
action_name += 'unknown key: {}'.format( repr( self.shortcut_key ) )
2018-05-16 20:09:50 +00:00
2019-11-14 03:56:30 +00:00
else:
2018-05-16 20:09:50 +00:00
2020-02-12 22:50:37 +00:00
action_name += 'unknown key: {}'.format( repr( self.shortcut_key ) )
2018-05-16 20:09:50 +00:00
2020-02-12 22:50:37 +00:00
components.append( action_name )
2019-11-14 03:56:30 +00:00
s = '+'.join( components )
2020-02-19 21:48:36 +00:00
if SHORTCUT_MODIFIER_KEYPAD in self.modifiers:
2019-11-14 03:56:30 +00:00
s += ' (on numpad)'
return s
2018-05-16 20:09:50 +00:00
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUT ] = Shortcut
2019-06-05 19:42:39 +00:00
class ShortcutSet( HydrusSerialisable.SerialisableBaseNamed ):
2018-05-16 20:09:50 +00:00
2019-06-05 19:42:39 +00:00
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUT_SET
SERIALISABLE_NAME = 'Shortcut Set'
2018-05-16 20:09:50 +00:00
SERIALISABLE_VERSION = 2
def __init__( self, name ):
HydrusSerialisable.SerialisableBaseNamed.__init__( self, name )
self._shortcuts_to_commands = {}
def __iter__( self ):
2019-05-22 22:35:06 +00:00
for ( shortcut, command ) in list( self._shortcuts_to_commands.items() ):
2018-05-16 20:09:50 +00:00
yield ( shortcut, command )
def __len__( self ):
return len( self._shortcuts_to_commands )
def _GetSerialisableInfo( self ):
2019-01-09 22:59:03 +00:00
return [ ( shortcut.GetSerialisableTuple(), command.GetSerialisableTuple() ) for ( shortcut, command ) in list(self._shortcuts_to_commands.items()) ]
2018-05-16 20:09:50 +00:00
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
for ( serialisable_shortcut, serialisable_command ) in serialisable_info:
shortcut = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_shortcut )
command = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_command )
self._shortcuts_to_commands[ shortcut ] = command
def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
if version == 1:
( serialisable_mouse_actions, serialisable_keyboard_actions ) = old_serialisable_info
shortcuts_to_commands = {}
# this never stored mouse actions, so skip
services_manager = HG.client_controller.services_manager
for ( modifier, key, ( serialisable_service_key, data ) ) in serialisable_keyboard_actions:
2019-11-14 03:56:30 +00:00
# no longer updating modifier, as that was wx legacy
2018-05-16 20:09:50 +00:00
2019-11-14 03:56:30 +00:00
modifiers = []
2020-02-19 21:48:36 +00:00
shortcut = Shortcut( SHORTCUT_TYPE_KEYBOARD_CHARACTER, key, SHORTCUT_PRESS_TYPE_PRESS, modifiers )
2018-05-16 20:09:50 +00:00
if serialisable_service_key is None:
command = ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, data )
else:
2019-01-09 22:59:03 +00:00
service_key = bytes.fromhex( serialisable_service_key )
2018-05-16 20:09:50 +00:00
if not services_manager.ServiceExists( service_key ):
continue
action = HC.CONTENT_UPDATE_FLIP
value = data
service = services_manager.GetService( service_key )
service_type = service.GetServiceType()
2020-03-11 21:52:11 +00:00
if service_type in HC.REAL_TAG_SERVICES:
2018-05-16 20:09:50 +00:00
content_type = HC.CONTENT_TYPE_MAPPINGS
elif service_type in HC.RATINGS_SERVICES:
content_type = HC.CONTENT_TYPE_RATINGS
else:
continue
command = ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_CONTENT, ( service_key, content_type, action, value ) )
shortcuts_to_commands[ shortcut ] = command
2019-01-09 22:59:03 +00:00
new_serialisable_info = ( ( shortcut.GetSerialisableTuple(), command.GetSerialisableTuple() ) for ( shortcut, command ) in list(shortcuts_to_commands.items()) )
2018-05-16 20:09:50 +00:00
return ( 2, new_serialisable_info )
def GetCommand( self, shortcut ):
if shortcut in self._shortcuts_to_commands:
return self._shortcuts_to_commands[ shortcut ]
else:
return None
2019-06-05 19:42:39 +00:00
def GetShortcuts( self, simple_command ):
shortcuts = []
for ( shortcut, command ) in self._shortcuts_to_commands.items():
if command.GetCommandType() == CC.APPLICATION_COMMAND_TYPE_SIMPLE and command.GetData() == simple_command:
shortcuts.append( shortcut )
return shortcuts
2018-05-16 20:09:50 +00:00
def SetCommand( self, shortcut, command ):
self._shortcuts_to_commands[ shortcut ] = command
2019-06-05 19:42:39 +00:00
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUT_SET ] = ShortcutSet
2018-05-16 20:09:50 +00:00
2019-11-14 03:56:30 +00:00
class ShortcutsHandler( QC.QObject ):
2017-03-29 19:39:34 +00:00
2020-03-04 22:12:53 +00:00
def __init__( self, parent: QW.QWidget, initial_shortcuts_names = None, catch_mouse = False, ignore_activating_mouse_click = False ):
2017-03-29 19:39:34 +00:00
2019-11-14 03:56:30 +00:00
QC.QObject.__init__( self, parent )
2020-02-12 22:50:37 +00:00
self._catch_mouse = catch_mouse
2018-03-14 21:01:02 +00:00
if initial_shortcuts_names is None:
2017-03-29 19:39:34 +00:00
2018-03-14 21:01:02 +00:00
initial_shortcuts_names = []
2017-03-29 19:39:34 +00:00
self._parent = parent
2019-11-14 03:56:30 +00:00
self._parent.installEventFilter( self )
2018-03-14 21:01:02 +00:00
self._shortcuts_names = list( initial_shortcuts_names )
2017-03-29 19:39:34 +00:00
2020-03-04 22:12:53 +00:00
self._ignore_activating_mouse_click = ignore_activating_mouse_click
self._frame_activated_time = 0.0
if self._catch_mouse and self._ignore_activating_mouse_click:
self._deactivation_catcher = ShortcutsDeactivationCatcher( self, parent )
2018-03-14 21:01:02 +00:00
2020-02-19 21:48:36 +00:00
def _ProcessShortcut( self, shortcut: Shortcut ):
2018-03-14 21:01:02 +00:00
shortcut_processed = False
2020-03-25 21:15:57 +00:00
command = shortcuts_manager().GetCommand( self._shortcuts_names, shortcut )
2020-02-19 21:48:36 +00:00
if command is None and shortcut.IsDoubleClick():
# ok, so user double-clicked
# if a parent wants to catch this (for instance the media viewer when we double-click a video), then we want that parent to have it
# but if no parent wants it, we can try converting it to a single-click to see if that does anything
ancestor_shortcuts_handlers = AncestorShortcutsHandlers( self._parent )
all_ancestor_shortcut_names = HydrusData.MassUnion( [ ancestor_shortcuts_handler.GetShortcutNames() for ancestor_shortcuts_handler in ancestor_shortcuts_handlers ] )
2020-03-25 21:15:57 +00:00
ancestor_command = shortcuts_manager().GetCommand( all_ancestor_shortcut_names, shortcut )
2020-02-19 21:48:36 +00:00
if ancestor_command is None:
if HG.shortcut_report_mode:
message = 'Shortcut "' + shortcut.ToString() + '" did not match any command. The single click version is now being attempted.'
HydrusData.ShowText( message )
shortcut = shortcut.ConvertToSingleClick()
2020-03-25 21:15:57 +00:00
command = shortcuts_manager().GetCommand( self._shortcuts_names, shortcut )
2020-02-19 21:48:36 +00:00
else:
if HG.shortcut_report_mode:
message = 'Shortcut "' + shortcut.ToString() + '" did not match any command. A parent seems to want it, however, so the single click version will not be attempted.'
HydrusData.ShowText( message )
2018-03-14 21:01:02 +00:00
if command is not None:
command_processed = self._parent.ProcessApplicationCommand( command )
2017-03-29 19:39:34 +00:00
2018-03-14 21:01:02 +00:00
if command_processed:
shortcut_processed = True
2017-03-29 19:39:34 +00:00
2018-03-22 00:03:33 +00:00
if HG.shortcut_report_mode:
message = 'Shortcut "' + shortcut.ToString() + '" matched to command "' + command.ToString() + '" on ' + repr( self._parent ) + '.'
if command_processed:
message += ' It was processed.'
else:
message += ' It was not processed.'
HydrusData.ShowText( message )
2017-03-29 19:39:34 +00:00
2018-03-14 21:01:02 +00:00
return shortcut_processed
2017-03-29 19:39:34 +00:00
2020-04-29 21:44:12 +00:00
def AddWindowToFilter( self, win: QW.QWidget ):
win.installEventFilter( self )
2019-11-14 03:56:30 +00:00
def eventFilter( self, watched, event ):
2018-03-22 00:03:33 +00:00
2019-11-14 03:56:30 +00:00
if event.type() == QC.QEvent.KeyPress:
2018-03-14 21:01:02 +00:00
2020-05-27 21:27:52 +00:00
i_should_catch_shortcut_event = IShouldCatchShortcutEvent( self._parent, watched, event = event )
2020-02-12 22:50:37 +00:00
2019-11-14 03:56:30 +00:00
shortcut = ConvertKeyEventToShortcut( event )
if shortcut is not None:
2018-03-22 00:03:33 +00:00
2019-11-14 03:56:30 +00:00
if HG.shortcut_report_mode:
2018-03-22 00:03:33 +00:00
2019-11-14 03:56:30 +00:00
message = 'Key shortcut "' + shortcut.ToString() + '" passing through ' + repr( self._parent ) + '.'
2018-03-22 00:03:33 +00:00
2020-02-12 22:50:37 +00:00
if i_should_catch_shortcut_event:
2019-11-14 03:56:30 +00:00
message += ' I am in a state to catch it.'
else:
message += ' I am not in a state to catch it.'
2018-03-22 00:03:33 +00:00
2019-11-14 03:56:30 +00:00
HydrusData.ShowText( message )
2018-03-22 00:03:33 +00:00
2020-02-12 22:50:37 +00:00
if i_should_catch_shortcut_event:
2018-03-14 21:01:02 +00:00
2019-11-14 03:56:30 +00:00
shortcut_processed = self._ProcessShortcut( shortcut )
if shortcut_processed:
2020-02-12 22:50:37 +00:00
event.accept()
2019-11-14 03:56:30 +00:00
return True
2018-03-14 21:01:02 +00:00
2020-02-12 22:50:37 +00:00
elif self._catch_mouse:
if event.type() in ( QC.QEvent.MouseButtonPress, QC.QEvent.MouseButtonRelease, QC.QEvent.MouseButtonDblClick, QC.QEvent.Wheel ):
2020-05-27 21:27:52 +00:00
if event.type() != QC.QEvent.Wheel and self._ignore_activating_mouse_click and not HydrusData.TimeHasPassedPrecise( self._frame_activated_time + 0.017 ):
2020-03-04 22:12:53 +00:00
if event.type() == QC.QEvent.MouseButtonRelease:
self._frame_activated_time = 0.0
return False
2020-05-27 21:27:52 +00:00
i_should_catch_shortcut_event = IShouldCatchShortcutEvent( self._parent, watched, event = event )
2020-02-12 22:50:37 +00:00
shortcut = ConvertMouseEventToShortcut( event )
if shortcut is not None:
if HG.shortcut_report_mode:
message = 'Mouse Press shortcut "' + shortcut.ToString() + '" passing through ' + repr( self._parent ) + '.'
if i_should_catch_shortcut_event:
message += ' I am in a state to catch it.'
else:
message += ' I am not in a state to catch it.'
HydrusData.ShowText( message )
if i_should_catch_shortcut_event:
shortcut_processed = self._ProcessShortcut( shortcut )
if shortcut_processed:
event.accept()
return True
2019-11-14 03:56:30 +00:00
return False
2017-03-29 19:39:34 +00:00
2019-09-11 21:51:09 +00:00
def AddShortcuts( self, shortcut_set_name ):
2017-03-29 19:39:34 +00:00
2019-09-11 21:51:09 +00:00
if shortcut_set_name not in self._shortcuts_names:
2018-03-14 21:01:02 +00:00
2020-02-19 21:48:36 +00:00
reserved_names = [ name for name in self._shortcuts_names if name in SHORTCUTS_RESERVED_NAMES ]
custom_names = [ name for name in self._shortcuts_names if name not in SHORTCUTS_RESERVED_NAMES ]
if shortcut_set_name in SHORTCUTS_RESERVED_NAMES:
reserved_names.append( shortcut_set_name )
else:
custom_names.append( shortcut_set_name )
self._shortcuts_names = reserved_names + custom_names
def FlipShortcuts( self, shortcut_set_name ):
if shortcut_set_name in self._shortcuts_names:
self.RemoveShortcuts( shortcut_set_name )
else:
self.AddShortcuts( shortcut_set_name )
2018-03-14 21:01:02 +00:00
2017-03-29 19:39:34 +00:00
2020-02-19 21:48:36 +00:00
def GetCustomShortcutNames( self ):
2020-05-13 19:03:16 +00:00
custom_names = sorted( ( name for name in self._shortcuts_names if name not in SHORTCUTS_RESERVED_NAMES ) )
2020-02-19 21:48:36 +00:00
return custom_names
def GetShortcutNames( self ):
return list( self._shortcuts_names )
def HasShortcuts( self, shortcut_set_name ):
return shortcut_set_name in self._shortcuts_names
def ProcessShortcut( self, shortcut ):
return self._ProcessShortcut( shortcut )
2019-09-11 21:51:09 +00:00
def RemoveShortcuts( self, shortcut_set_name ):
2017-03-29 19:39:34 +00:00
2019-09-11 21:51:09 +00:00
if shortcut_set_name in self._shortcuts_names:
2017-03-29 19:39:34 +00:00
2019-09-11 21:51:09 +00:00
self._shortcuts_names.remove( shortcut_set_name )
2017-03-29 19:39:34 +00:00
2018-05-16 20:09:50 +00:00
2020-02-12 22:50:37 +00:00
def SetShortcuts( self, shortcut_set_names ):
self._shortcuts_names = list( shortcut_set_names )
2020-03-04 22:12:53 +00:00
def FrameActivated( self ):
self._frame_activated_time = HydrusData.GetNowPrecise()
class ShortcutsDeactivationCatcher( QC.QObject ):
def __init__( self, shortcuts_handler: ShortcutsHandler, widget: QW.QWidget ):
QC.QObject.__init__( self, shortcuts_handler )
self._shortcuts_handler = shortcuts_handler
widget.window().installEventFilter( self )
def eventFilter( self, watched, event ):
if event.type() == QC.QEvent.WindowActivate:
self._shortcuts_handler.FrameActivated()
return False
2020-03-25 21:15:57 +00:00
class ShortcutsManager( QC.QObject ):
2019-06-05 19:42:39 +00:00
2020-03-25 21:15:57 +00:00
shortcutsChanged = QC.Signal()
my_instance = None
def __init__( self, shortcut_sets = None ):
2019-06-05 19:42:39 +00:00
2020-03-25 21:15:57 +00:00
parent = CGC.core()
2019-06-05 19:42:39 +00:00
2020-03-25 21:15:57 +00:00
QC.QObject.__init__( self, parent )
2019-06-05 19:42:39 +00:00
2020-03-25 21:15:57 +00:00
self._names_to_shortcut_sets = {}
2019-06-05 19:42:39 +00:00
2020-03-25 21:15:57 +00:00
if shortcut_sets is not None:
self.SetShortcutSets( shortcut_sets )
2019-06-05 19:42:39 +00:00
2020-03-25 21:15:57 +00:00
ShortcutsManager.my_instance = self
2019-06-05 19:42:39 +00:00
2020-03-25 21:15:57 +00:00
@staticmethod
def instance() -> 'ShortcutsManager':
2019-06-05 19:42:39 +00:00
2020-03-25 21:15:57 +00:00
if ShortcutsManager.my_instance is None:
raise Exception( 'ShortcutsManager is not yet initialised!' )
else:
2019-06-05 19:42:39 +00:00
2020-03-25 21:15:57 +00:00
return ShortcutsManager.my_instance
2019-06-05 19:42:39 +00:00
2020-04-29 21:44:12 +00:00
def GetCommand( self, shortcuts_names: typing.Iterable[ str ], shortcut: Shortcut ):
2019-06-05 19:42:39 +00:00
2020-02-19 21:48:36 +00:00
# process more specific shortcuts with higher priority
shortcuts_names = list( shortcuts_names )
shortcuts_names.reverse()
2019-06-05 19:42:39 +00:00
for name in shortcuts_names:
2020-03-25 21:15:57 +00:00
if name in self._names_to_shortcut_sets:
2019-06-05 19:42:39 +00:00
2020-03-25 21:15:57 +00:00
command = self._names_to_shortcut_sets[ name ].GetCommand( shortcut )
2019-06-05 19:42:39 +00:00
if command is not None:
2020-02-19 21:48:36 +00:00
if HG.shortcut_report_mode:
2019-06-05 19:42:39 +00:00
2020-02-19 21:48:36 +00:00
HydrusData.ShowText( 'Shortcut "{}" matched on "{}" set to "{}" command.'.format( shortcut.ToString(), name, repr( command ) ) )
2019-06-05 19:42:39 +00:00
return command
return None
2020-03-25 21:15:57 +00:00
def GetNamesToShortcuts( self, simple_command: ClientData.ApplicationCommand ):
2019-06-05 19:42:39 +00:00
names_to_shortcuts = {}
2020-03-25 21:15:57 +00:00
for ( name, shortcut_set ) in self._names_to_shortcut_sets.items():
2019-06-05 19:42:39 +00:00
shortcuts = shortcut_set.GetShortcuts( simple_command )
if len( shortcuts ) > 0:
names_to_shortcuts[ name ] = shortcuts
return names_to_shortcuts
2020-03-25 21:15:57 +00:00
def GetShortcutSets( self ) -> typing.List[ ShortcutSet ]:
return list( self._names_to_shortcut_sets.values() )
2020-04-29 21:44:12 +00:00
def SetShortcutSets( self, shortcut_sets: typing.Iterable[ ShortcutSet ] ):
2019-06-05 19:42:39 +00:00
2020-03-25 21:15:57 +00:00
self._names_to_shortcut_sets = { shortcut_set.GetName() : shortcut_set for shortcut_set in shortcut_sets }
2019-06-05 19:42:39 +00:00
2020-03-25 21:15:57 +00:00
self.shortcutsChanged.emit()
2019-06-05 19:42:39 +00:00
2020-03-25 21:15:57 +00:00
shortcuts_manager = ShortcutsManager.instance
shortcuts_manager_initialised = lambda: ShortcutsManager.my_instance is not None