hydrus/hydrus/client/gui/ClientGUIShortcuts.py

1435 lines
55 KiB
Python

import typing
from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
from qtpy import QtGui as QG
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 ClientApplicationCommand as CAC
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
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_MODIFIER_META = 5 # This is 'Control' in macOS, which is for system level stuff. They use 'Command' for Control stuff, which is helpfully mapped to Control in Qt. Just name nonsense
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:
DELETE_KEYS_QT = ( QC.Qt.Key_Backspace, QC.Qt.Key_Delete )
DELETE_KEYS_HYDRUS = ( SHORTCUT_KEY_SPECIAL_BACKSPACE, SHORTCUT_KEY_SPECIAL_DELETE )
else:
DELETE_KEYS_QT = ( QC.Qt.Key_Delete, )
DELETE_KEYS_HYDRUS = ( SHORTCUT_KEY_SPECIAL_DELETE, )
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
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
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'
shortcut_mouse_string_lookup[ SHORTCUT_MOUSE_BACK ] = 'back'
shortcut_mouse_string_lookup[ SHORTCUT_MOUSE_FORWARD ] = 'forward'
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'
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[ 'tags_autocomplete' ] = 'tag autocomplete'
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_sorted = [
'global',
'main_gui',
'tags_autocomplete',
'media',
'media_viewer',
'media_viewer_browser',
'archive_delete_filter',
'duplicate_filter',
'preview_media_window',
'media_viewer_media_window'
]
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[ 'tags_autocomplete' ] = 'Actions to control tag autocomplete when its input text box is focused.'
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', 'tags_autocomplete', 'main_gui', 'media_viewer_browser', 'media_viewer', 'media_viewer_media_window', 'preview_media_window' ]
SHORTCUTS_GLOBAL_ACTIONS = [ CAC.SIMPLE_GLOBAL_AUDIO_MUTE, CAC.SIMPLE_GLOBAL_AUDIO_UNMUTE, CAC.SIMPLE_GLOBAL_AUDIO_MUTE_FLIP, CAC.SIMPLE_EXIT_APPLICATION, CAC.SIMPLE_EXIT_APPLICATION_FORCE_MAINTENANCE, CAC.SIMPLE_RESTART_APPLICATION, CAC.SIMPLE_HIDE_TO_SYSTEM_TRAY ]
SHORTCUTS_MEDIA_ACTIONS = [ CAC.SIMPLE_MANAGE_FILE_TAGS, CAC.SIMPLE_MANAGE_FILE_RATINGS, CAC.SIMPLE_MANAGE_FILE_URLS, CAC.SIMPLE_MANAGE_FILE_NOTES, CAC.SIMPLE_ARCHIVE_FILE, CAC.SIMPLE_INBOX_FILE, CAC.SIMPLE_DELETE_FILE, CAC.SIMPLE_UNDELETE_FILE, CAC.SIMPLE_EXPORT_FILES, CAC.SIMPLE_EXPORT_FILES_QUICK_AUTO_EXPORT, CAC.SIMPLE_REMOVE_FILE_FROM_VIEW, CAC.SIMPLE_OPEN_FILE_IN_EXTERNAL_PROGRAM, CAC.SIMPLE_OPEN_SELECTION_IN_NEW_PAGE, CAC.SIMPLE_LAUNCH_THE_ARCHIVE_DELETE_FILTER, CAC.SIMPLE_COPY_BMP, CAC.SIMPLE_COPY_BMP_OR_FILE_IF_NOT_BMPABLE, CAC.SIMPLE_COPY_FILE, CAC.SIMPLE_COPY_PATH, CAC.SIMPLE_COPY_SHA256_HASH, CAC.SIMPLE_COPY_MD5_HASH, CAC.SIMPLE_COPY_SHA1_HASH, CAC.SIMPLE_COPY_SHA512_HASH, CAC.SIMPLE_GET_SIMILAR_TO_EXACT, CAC.SIMPLE_GET_SIMILAR_TO_VERY_SIMILAR, CAC.SIMPLE_GET_SIMILAR_TO_SIMILAR, CAC.SIMPLE_GET_SIMILAR_TO_SPECULATIVE, CAC.SIMPLE_DUPLICATE_MEDIA_SET_ALTERNATE, CAC.SIMPLE_DUPLICATE_MEDIA_SET_ALTERNATE_COLLECTIONS, CAC.SIMPLE_DUPLICATE_MEDIA_SET_CUSTOM, CAC.SIMPLE_DUPLICATE_MEDIA_SET_FOCUSED_BETTER, CAC.SIMPLE_DUPLICATE_MEDIA_SET_FOCUSED_KING, CAC.SIMPLE_DUPLICATE_MEDIA_SET_SAME_QUALITY, CAC.SIMPLE_OPEN_KNOWN_URL ]
SHORTCUTS_MEDIA_VIEWER_ACTIONS = [ CAC.SIMPLE_PAUSE_MEDIA, CAC.SIMPLE_PAUSE_PLAY_MEDIA, CAC.SIMPLE_MOVE_ANIMATION_TO_PREVIOUS_FRAME, CAC.SIMPLE_MOVE_ANIMATION_TO_NEXT_FRAME, CAC.SIMPLE_SWITCH_BETWEEN_FULLSCREEN_BORDERLESS_AND_REGULAR_FRAMED_WINDOW, CAC.SIMPLE_PAN_UP, CAC.SIMPLE_PAN_DOWN, CAC.SIMPLE_PAN_LEFT, CAC.SIMPLE_PAN_RIGHT, CAC.SIMPLE_PAN_TOP_EDGE, CAC.SIMPLE_PAN_BOTTOM_EDGE, CAC.SIMPLE_PAN_LEFT_EDGE, CAC.SIMPLE_PAN_RIGHT_EDGE, CAC.SIMPLE_PAN_VERTICAL_CENTER, CAC.SIMPLE_PAN_HORIZONTAL_CENTER, CAC.SIMPLE_ZOOM_IN, CAC.SIMPLE_ZOOM_OUT, CAC.SIMPLE_SWITCH_BETWEEN_100_PERCENT_AND_CANVAS_ZOOM, CAC.SIMPLE_FLIP_DARKMODE, CAC.SIMPLE_CLOSE_MEDIA_VIEWER ]
SHORTCUTS_MEDIA_VIEWER_BROWSER_ACTIONS = [ CAC.SIMPLE_VIEW_NEXT, CAC.SIMPLE_VIEW_FIRST, CAC.SIMPLE_VIEW_LAST, CAC.SIMPLE_VIEW_PREVIOUS, CAC.SIMPLE_PAUSE_PLAY_SLIDESHOW, CAC.SIMPLE_SHOW_MENU, CAC.SIMPLE_CLOSE_MEDIA_VIEWER ]
SHORTCUTS_MAIN_GUI_ACTIONS = [ CAC.SIMPLE_REFRESH, CAC.SIMPLE_REFRESH_ALL_PAGES, CAC.SIMPLE_REFRESH_PAGE_OF_PAGES_PAGES, CAC.SIMPLE_NEW_PAGE, CAC.SIMPLE_NEW_PAGE_OF_PAGES, CAC.SIMPLE_NEW_DUPLICATE_FILTER_PAGE, CAC.SIMPLE_NEW_GALLERY_DOWNLOADER_PAGE, CAC.SIMPLE_NEW_URL_DOWNLOADER_PAGE, CAC.SIMPLE_NEW_SIMPLE_DOWNLOADER_PAGE, CAC.SIMPLE_NEW_WATCHER_DOWNLOADER_PAGE, CAC.SIMPLE_SET_MEDIA_FOCUS, CAC.SIMPLE_SHOW_HIDE_SPLITTERS, CAC.SIMPLE_SET_SEARCH_FOCUS, CAC.SIMPLE_UNCLOSE_PAGE, CAC.SIMPLE_CLOSE_PAGE, CAC.SIMPLE_REDO, CAC.SIMPLE_UNDO, CAC.SIMPLE_FLIP_DARKMODE, CAC.SIMPLE_RUN_ALL_EXPORT_FOLDERS, CAC.SIMPLE_CHECK_ALL_IMPORT_FOLDERS, CAC.SIMPLE_FLIP_DEBUG_FORCE_IDLE_MODE_DO_NOT_SET_THIS, CAC.SIMPLE_SHOW_AND_FOCUS_MANAGE_TAGS_FAVOURITE_TAGS, CAC.SIMPLE_SHOW_AND_FOCUS_MANAGE_TAGS_RELATED_TAGS, CAC.SIMPLE_REFRESH_RELATED_TAGS, CAC.SIMPLE_SHOW_AND_FOCUS_MANAGE_TAGS_FILE_LOOKUP_SCRIPT_TAGS, CAC.SIMPLE_SHOW_AND_FOCUS_MANAGE_TAGS_RECENT_TAGS, CAC.SIMPLE_FOCUS_MEDIA_VIEWER, CAC.SIMPLE_MOVE_PAGES_SELECTION_LEFT, CAC.SIMPLE_MOVE_PAGES_SELECTION_RIGHT, CAC.SIMPLE_MOVE_PAGES_SELECTION_HOME, CAC.SIMPLE_MOVE_PAGES_SELECTION_END ]
SHORTCUTS_TAGS_AUTOCOMPLETE_ACTIONS = [ CAC.SIMPLE_SYNCHRONISED_WAIT_SWITCH, CAC.SIMPLE_AUTOCOMPLETE_FORCE_FETCH, CAC.SIMPLE_AUTOCOMPLETE_IME_MODE ]
SHORTCUTS_DUPLICATE_FILTER_ACTIONS = [ CAC.SIMPLE_DUPLICATE_FILTER_THIS_IS_BETTER_AND_DELETE_OTHER, CAC.SIMPLE_DUPLICATE_FILTER_THIS_IS_BETTER_BUT_KEEP_BOTH, CAC.SIMPLE_DUPLICATE_FILTER_EXACTLY_THE_SAME, CAC.SIMPLE_DUPLICATE_FILTER_ALTERNATES, CAC.SIMPLE_DUPLICATE_FILTER_FALSE_POSITIVE, CAC.SIMPLE_DUPLICATE_FILTER_CUSTOM_ACTION, CAC.SIMPLE_DUPLICATE_FILTER_SKIP, CAC.SIMPLE_DUPLICATE_FILTER_BACK, CAC.SIMPLE_CLOSE_MEDIA_VIEWER ]
SHORTCUTS_ARCHIVE_DELETE_FILTER_ACTIONS = [ CAC.SIMPLE_ARCHIVE_DELETE_FILTER_KEEP, CAC.SIMPLE_ARCHIVE_DELETE_FILTER_DELETE, CAC.SIMPLE_ARCHIVE_DELETE_FILTER_SKIP, CAC.SIMPLE_ARCHIVE_DELETE_FILTER_BACK, CAC.SIMPLE_CLOSE_MEDIA_VIEWER ]
SHORTCUTS_MEDIA_VIEWER_VIDEO_AUDIO_PLAYER_ACTIONS = [ CAC.SIMPLE_PAUSE_MEDIA, CAC.SIMPLE_PAUSE_PLAY_MEDIA, CAC.SIMPLE_OPEN_FILE_IN_EXTERNAL_PROGRAM, CAC.SIMPLE_CLOSE_MEDIA_VIEWER ]
SHORTCUTS_PREVIEW_VIDEO_AUDIO_PLAYER_ACTIONS = [ CAC.SIMPLE_PAUSE_MEDIA, CAC.SIMPLE_PAUSE_PLAY_MEDIA, CAC.SIMPLE_OPEN_FILE_IN_EXTERNAL_PROGRAM, CAC.SIMPLE_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[ 'tags_autocomplete' ] = SHORTCUTS_TAGS_AUTOCOMPLETE_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
# 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 ):
if key_qt in special_key_shortcut_enum_lookup:
key_ord = special_key_shortcut_enum_lookup[ key_qt ]
return ( SHORTCUT_TYPE_KEYBOARD_SPECIAL, key_ord )
else:
try:
key_ord = int( key_qt )
key_chr = chr( key_ord )
# this is turbo lower() that converts Scharfes S (beta) to 'ss'
key_chr = key_chr.casefold()[0]
casefold_key_ord = ord( key_chr )
return ( SHORTCUT_TYPE_KEYBOARD_CHARACTER, casefold_key_ord )
except:
return ( SHORTCUT_TYPE_NOT_ALLOWED, key_ord )
def ConvertKeyEventToShortcut( event ):
key_qt = event.key()
( shortcut_type, key_ord ) = ConvertQtKeyToShortcutKey( key_qt )
if shortcut_type != SHORTCUT_TYPE_NOT_ALLOWED:
modifiers = []
if event.modifiers() & QC.Qt.AltModifier:
modifiers.append( SHORTCUT_MODIFIER_ALT )
if event.modifiers() & QC.Qt.ControlModifier:
modifiers.append( SHORTCUT_MODIFIER_CTRL )
if event.modifiers() & QC.Qt.MetaModifier:
modifiers.append( SHORTCUT_MODIFIER_META )
if event.modifiers() & QC.Qt.ShiftModifier:
modifiers.append( SHORTCUT_MODIFIER_SHIFT )
if event.modifiers() & QC.Qt.GroupSwitchModifier:
modifiers.append( SHORTCUT_MODIFIER_GROUP_SWITCH )
if event.modifiers() & QC.Qt.KeypadModifier:
modifiers.append( SHORTCUT_MODIFIER_KEYPAD )
shortcut_press_type = SHORTCUT_PRESS_TYPE_PRESS
shortcut = Shortcut( shortcut_type, key_ord, shortcut_press_type, modifiers )
if HG.gui_report_mode:
HydrusData.ShowText( 'key event caught: ' + repr( shortcut ) )
return shortcut
return None
def ConvertKeyEventToSimpleTuple( event ):
modifier = QC.Qt.NoModifier
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
elif event.modifiers() & QC.Qt.MetaModifier: modifier = QC.Qt.MetaModifier
key = event.key()
return ( modifier, key )
GLOBAL_MOUSE_SCROLL_DELTA_FOR_TRACKPADS = 0
ONE_TICK_ON_A_NORMAL_MOUSE_IN_DEGREES = 15 * 8 # fifteen degrees, in eighths of a degree
def ConvertMouseEventToShortcut( event: QG.QMouseEvent ):
key = None
shortcut_press_type = SHORTCUT_PRESS_TYPE_PRESS
if event.type() == QC.QEvent.MouseButtonPress:
for ( qt_button, hydrus_button ) in qt_mouse_buttons_to_hydrus_mouse_buttons.items():
if event.buttons() & qt_button:
key = hydrus_button
break
elif event.type() in ( QC.QEvent.MouseButtonDblClick, QC.QEvent.MouseButtonRelease ):
if event.type() == QC.QEvent.MouseButtonRelease:
shortcut_press_type = SHORTCUT_PRESS_TYPE_RELEASE
elif event.type() == QC.QEvent.MouseButtonDblClick:
shortcut_press_type = SHORTCUT_PRESS_TYPE_DOUBLE_CLICK
for ( qt_button, hydrus_button ) in qt_mouse_buttons_to_hydrus_mouse_buttons.items():
if event.button() == qt_button:
key = hydrus_button
break
elif event.type() == QC.QEvent.Wheel:
angle_delta_point = event.angleDelta()
if angle_delta_point is None:
return None
angle_delta = angle_delta_point.y()
if event.source() == QC.Qt.MouseEventSynthesizedBySystem:
if abs( angle_delta ) < ONE_TICK_ON_A_NORMAL_MOUSE_IN_DEGREES:
# likely using a trackpad to generate artificial wheel events
global GLOBAL_MOUSE_SCROLL_DELTA_FOR_TRACKPADS
GLOBAL_MOUSE_SCROLL_DELTA_FOR_TRACKPADS += angle_delta
if abs( GLOBAL_MOUSE_SCROLL_DELTA_FOR_TRACKPADS ) > ONE_TICK_ON_A_NORMAL_MOUSE_IN_DEGREES:
angle_delta = GLOBAL_MOUSE_SCROLL_DELTA_FOR_TRACKPADS
GLOBAL_MOUSE_SCROLL_DELTA_FOR_TRACKPADS = 0
else:
return None
if angle_delta > 0:
key = SHORTCUT_MOUSE_SCROLL_UP
elif angle_delta < 0:
key = SHORTCUT_MOUSE_SCROLL_DOWN
if key is not None:
modifiers = []
if event.modifiers() & QC.Qt.AltModifier:
modifiers.append( SHORTCUT_MODIFIER_ALT )
if event.modifiers() & QC.Qt.ControlModifier:
modifiers.append( SHORTCUT_MODIFIER_CTRL )
if event.modifiers() & QC.Qt.MetaModifier:
modifiers.append( SHORTCUT_MODIFIER_META )
if event.modifiers() & QC.Qt.ShiftModifier:
modifiers.append( SHORTCUT_MODIFIER_SHIFT )
if event.modifiers() & QC.Qt.GroupSwitchModifier:
modifiers.append( SHORTCUT_MODIFIER_GROUP_SWITCH )
if event.modifiers() & QC.Qt.KeypadModifier:
modifiers.append( SHORTCUT_MODIFIER_KEYPAD )
shortcut = Shortcut( SHORTCUT_TYPE_MOUSE, key, shortcut_press_type, modifiers )
if HG.gui_report_mode:
HydrusData.ShowText( 'mouse event caught: ' + repr( shortcut ) )
return shortcut
return None
def AncestorShortcutsHandlers( widget: QW.QWidget ):
shortcuts_handlers = []
window = widget.window()
if window == widget:
return shortcuts_handlers
widget = widget.parentWidget()
if widget is None:
return shortcuts_handlers
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
widget = widget.parentWidget()
if widget is None:
break
return shortcuts_handlers
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.Collection[ type ] ] = None ):
do_focus_test = True
if event is not None:
# 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
if event.type() == QC.QEvent.Wheel:
do_focus_test = False
do_focus_test = False # lmao, why this here? I guess it got turned off
if do_focus_test:
if not ClientGUIFunctions.TLWIsActive( event_handler_owner ):
if child_tlw_classes_who_can_pass_up is not None:
child_tlw_has_focus = ClientGUIFunctions.WidgetOrAnyTLWChildHasFocus( event_handler_owner ) and isinstance( QW.QApplication.activeWindow(), child_tlw_classes_who_can_pass_up )
if not child_tlw_has_focus:
return False
else:
return False
return True
class Shortcut( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUT
SERIALISABLE_NAME = 'Shortcut'
SERIALISABLE_VERSION = 3
def __init__( self, shortcut_type = None, shortcut_key = None, shortcut_press_type = None, modifiers = None ):
if shortcut_type is None:
shortcut_type = SHORTCUT_TYPE_KEYBOARD_SPECIAL
if shortcut_key is None:
shortcut_key = SHORTCUT_KEY_SPECIAL_F7
if shortcut_press_type is None:
shortcut_press_type = SHORTCUT_PRESS_TYPE_PRESS
if modifiers is None:
modifiers = []
if shortcut_type == SHORTCUT_TYPE_KEYBOARD_CHARACTER and ClientData.OrdIsAlphaUpper( shortcut_key ):
shortcut_key += 32 # convert A to a
modifiers = sorted( modifiers )
HydrusSerialisable.SerialisableBase.__init__( self )
self.shortcut_type = shortcut_type
self.shortcut_key = shortcut_key
self.shortcut_press_type = shortcut_press_type
self.modifiers = modifiers
def __eq__( self, other ):
if isinstance( other, Shortcut ):
return self.__hash__() == other.__hash__()
return NotImplemented
def __hash__( self ):
return ( self.shortcut_type, self.shortcut_key, self.shortcut_press_type, tuple( self.modifiers ) ).__hash__()
def __repr__( self ):
return 'Shortcut: ' + self.ToString()
def _GetSerialisableInfo( self ):
return ( self.shortcut_type, self.shortcut_key, self.shortcut_press_type, self.modifiers )
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
( self.shortcut_type, self.shortcut_key, self.shortcut_press_type, self.modifiers ) = serialisable_info
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 = {
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
}
# 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' ),
333 : ord( '9' ),
388 : ord( '+' ),
392 : ord( '/' ),
390 : ord( '-' ),
387 : ord( '*' ),
391 : ord( '.' )
}
wx_to_qt_numpad_conversion = {
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
}
( shortcut_type, shortcut_key, modifiers ) = old_serialisable_info
if shortcut_type == SHORTCUT_TYPE_KEYBOARD_CHARACTER:
if shortcut_key in wx_to_qt_flat_conversion:
shortcut_type = SHORTCUT_TYPE_KEYBOARD_SPECIAL
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 )
modifiers.append( SHORTCUT_MODIFIER_KEYPAD )
modifiers.sort()
elif shortcut_key in wx_to_qt_numpad_conversion:
shortcut_type = SHORTCUT_TYPE_KEYBOARD_SPECIAL
shortcut_key = wx_to_qt_numpad_conversion[ shortcut_key ]
modifiers = list( modifiers )
modifiers.append( SHORTCUT_MODIFIER_KEYPAD )
modifiers.sort()
if shortcut_type == SHORTCUT_TYPE_KEYBOARD_CHARACTER:
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 )
if version == 2:
( shortcut_type, shortcut_key, modifiers ) = old_serialisable_info
shortcut_press_type = SHORTCUT_PRESS_TYPE_PRESS
new_serialisable_info = ( shortcut_type, shortcut_key, shortcut_press_type, modifiers )
return ( 3, new_serialisable_info )
def ConvertToSingleClick( self ):
if self.IsDoubleClick():
new_shortcut = self.Duplicate()
new_shortcut.shortcut_press_type = SHORTCUT_PRESS_TYPE_PRESS
return new_shortcut
return self
def GetShortcutType( self ):
return self.shortcut_type
def IsAppropriateForPressRelease( self ):
return self.shortcut_key in SHORTCUT_MOUSE_CLICKS and self.shortcut_press_type != SHORTCUT_PRESS_TYPE_DOUBLE_CLICK
def IsDoubleClick( self ):
return self.shortcut_type == SHORTCUT_TYPE_MOUSE and self.shortcut_press_type == SHORTCUT_PRESS_TYPE_DOUBLE_CLICK
def ToString( self ):
components = []
if SHORTCUT_MODIFIER_META in self.modifiers:
components.append( 'control' )
if SHORTCUT_MODIFIER_CTRL in self.modifiers:
if HC.PLATFORM_MACOS:
components.append( 'command' )
else:
components.append( 'ctrl' )
if SHORTCUT_MODIFIER_ALT in self.modifiers:
components.append( 'alt' )
if SHORTCUT_MODIFIER_SHIFT in self.modifiers:
components.append( 'shift' )
if SHORTCUT_MODIFIER_GROUP_SWITCH in self.modifiers:
components.append( 'Mode_switch' )
if self.shortcut_press_type != SHORTCUT_PRESS_TYPE_PRESS:
action_name = '{} '.format( shortcut_press_type_str_lookup[ self.shortcut_press_type ] )
else:
action_name = ''
if self.shortcut_type == SHORTCUT_TYPE_MOUSE and self.shortcut_key in shortcut_mouse_string_lookup:
action_name += shortcut_mouse_string_lookup[ self.shortcut_key ]
elif self.shortcut_type == SHORTCUT_TYPE_KEYBOARD_SPECIAL and self.shortcut_key in special_key_shortcut_str_lookup:
action_name += special_key_shortcut_str_lookup[ self.shortcut_key ]
elif self.shortcut_type == SHORTCUT_TYPE_KEYBOARD_CHARACTER:
try:
if ClientData.OrdIsAlphaUpper( self.shortcut_key ):
action_name += chr( self.shortcut_key + 32 ) # + 32 for converting ascii A -> a
else:
action_name += chr( self.shortcut_key )
except:
action_name += 'unknown key: {}'.format( repr( self.shortcut_key ) )
else:
action_name += 'unknown key: {}'.format( repr( self.shortcut_key ) )
components.append( action_name )
s = '+'.join( components )
if SHORTCUT_MODIFIER_KEYPAD in self.modifiers:
s += ' (on numpad)'
return s
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUT ] = Shortcut
class ShortcutSet( HydrusSerialisable.SerialisableBaseNamed ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUT_SET
SERIALISABLE_NAME = 'Shortcut Set'
SERIALISABLE_VERSION = 2
def __init__( self, name ):
HydrusSerialisable.SerialisableBaseNamed.__init__( self, name )
self._shortcuts_to_commands = {}
def __iter__( self ):
for ( shortcut, command ) in list( self._shortcuts_to_commands.items() ):
yield ( shortcut, command )
def __len__( self ):
return len( self._shortcuts_to_commands )
def _GetSerialisableInfo( self ):
return [ ( shortcut.GetSerialisableTuple(), command.GetSerialisableTuple() ) for ( shortcut, command ) in list(self._shortcuts_to_commands.items()) ]
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:
# no longer updating modifier, as that was wx legacy
modifiers = []
shortcut = Shortcut( SHORTCUT_TYPE_KEYBOARD_CHARACTER, key, SHORTCUT_PRESS_TYPE_PRESS, modifiers )
if serialisable_service_key is None:
command = CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, data )
else:
service_key = bytes.fromhex( serialisable_service_key )
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()
if service_type in HC.REAL_TAG_SERVICES:
content_type = HC.CONTENT_TYPE_MAPPINGS
elif service_type in HC.RATINGS_SERVICES:
content_type = HC.CONTENT_TYPE_RATINGS
else:
continue
command = CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_CONTENT, ( service_key, content_type, action, value ) )
shortcuts_to_commands[ shortcut ] = command
new_serialisable_info = ( ( shortcut.GetSerialisableTuple(), command.GetSerialisableTuple() ) for ( shortcut, command ) in list(shortcuts_to_commands.items()) )
return ( 2, new_serialisable_info )
def DeleteShortcut( self, shortcut ):
if shortcut in self._shortcuts_to_commands:
del self._shortcuts_to_commands[ shortcut ]
def GetCommand( self, shortcut ):
if shortcut in self._shortcuts_to_commands:
return self._shortcuts_to_commands[ shortcut ]
else:
return None
def GetShortcuts( self, simple_command: int ):
shortcuts = []
for ( shortcut, command ) in self._shortcuts_to_commands.items():
if command.IsSimpleCommand() and command.GetData() == simple_command:
shortcuts.append( shortcut )
return shortcuts
def SetCommand( self, shortcut, command ):
self._shortcuts_to_commands[ shortcut ] = command
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUT_SET ] = ShortcutSet
class ShortcutsHandler( QC.QObject ):
def __init__( self, parent: QW.QWidget, initial_shortcuts_names: typing.Collection[ str ], alternate_filter_target = None, catch_mouse = False, ignore_activating_mouse_click = False ):
QC.QObject.__init__( self, parent )
self._catch_mouse = catch_mouse
filter_target = parent
if alternate_filter_target is not None:
filter_target = alternate_filter_target
self._filter_target = filter_target
self._parent = parent
self._filter_target.installEventFilter( self )
self._shortcuts_names = list( initial_shortcuts_names )
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 )
def _ProcessShortcut( self, shortcut: Shortcut ):
shortcut_processed = False
command = shortcuts_manager().GetCommand( self._shortcuts_names, shortcut )
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 ] )
ancestor_command = shortcuts_manager().GetCommand( all_ancestor_shortcut_names, shortcut )
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()
command = shortcuts_manager().GetCommand( self._shortcuts_names, shortcut )
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 )
if command is not None:
command_processed = self._parent.ProcessApplicationCommand( command )
if command_processed:
shortcut_processed = True
if HG.shortcut_report_mode:
message = 'Shortcut "{}" matched to command "{}" on {}.'.format( shortcut.ToString(), command.ToString(), repr( self._parent ) )
if command_processed:
message += ' It was processed.'
else:
message += ' It was not processed.'
HydrusData.ShowText( message )
return shortcut_processed
def AddWindowToFilter( self, win: QW.QWidget ):
win.installEventFilter( self )
def eventFilter( self, watched, event ):
if event.type() == QC.QEvent.KeyPress:
i_should_catch_shortcut_event = IShouldCatchShortcutEvent( self._filter_target, watched, event = event )
shortcut = ConvertKeyEventToShortcut( event )
if shortcut is not None:
if HG.shortcut_report_mode:
message = 'Key shortcut "{}" passing through {}.'.format( shortcut.ToString(), 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
elif self._catch_mouse:
if event.type() in ( QC.QEvent.MouseButtonPress, QC.QEvent.MouseButtonRelease, QC.QEvent.MouseButtonDblClick, QC.QEvent.Wheel ):
if event.type() != QC.QEvent.Wheel and self._ignore_activating_mouse_click and not HydrusData.TimeHasPassedPrecise( self._frame_activated_time + 0.017 ):
if event.type() == QC.QEvent.MouseButtonRelease:
self._frame_activated_time = 0.0
return False
i_should_catch_shortcut_event = IShouldCatchShortcutEvent( self._filter_target, watched, event = event )
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
return False
def AddShortcuts( self, shortcut_set_name ):
if shortcut_set_name not in self._shortcuts_names:
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 )
def GetCustomShortcutNames( self ):
custom_names = sorted( ( name for name in self._shortcuts_names if name not in SHORTCUTS_RESERVED_NAMES ) )
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 )
def RemoveShortcuts( self, shortcut_set_name ):
if shortcut_set_name in self._shortcuts_names:
self._shortcuts_names.remove( shortcut_set_name )
def SetShortcuts( self, shortcut_set_names ):
self._shortcuts_names = list( shortcut_set_names )
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
class ShortcutsManager( QC.QObject ):
shortcutsChanged = QC.Signal()
my_instance = None
def __init__( self, shortcut_sets = None ):
parent = CGC.core()
QC.QObject.__init__( self, parent )
self._names_to_shortcut_sets = {}
if shortcut_sets is not None:
self.SetShortcutSets( shortcut_sets )
ShortcutsManager.my_instance = self
@staticmethod
def instance() -> 'ShortcutsManager':
if ShortcutsManager.my_instance is None:
raise Exception( 'ShortcutsManager is not yet initialised!' )
else:
return ShortcutsManager.my_instance
def GetCommand( self, shortcuts_names: typing.Iterable[ str ], shortcut: Shortcut ):
# process more specific shortcuts with higher priority
shortcuts_names = list( shortcuts_names )
shortcuts_names.reverse()
for name in shortcuts_names:
if name in self._names_to_shortcut_sets:
command = self._names_to_shortcut_sets[ name ].GetCommand( shortcut )
if command is not None:
if HG.shortcut_report_mode:
HydrusData.ShowText( 'Shortcut "{}" matched on "{}" set to "{}" command.'.format( shortcut.ToString(), name, repr( command ) ) )
return command
return None
def GetNamesToShortcuts( self, simple_command: int ):
names_to_shortcuts = {}
for ( name, shortcut_set ) in self._names_to_shortcut_sets.items():
shortcuts = shortcut_set.GetShortcuts( simple_command )
if len( shortcuts ) > 0:
names_to_shortcuts[ name ] = shortcuts
return names_to_shortcuts
def GetShortcutSets( self ) -> typing.List[ ShortcutSet ]:
return list( self._names_to_shortcut_sets.values() )
def SetShortcutSets( self, shortcut_sets: typing.Iterable[ ShortcutSet ] ):
self._names_to_shortcut_sets = { shortcut_set.GetName() : shortcut_set for shortcut_set in shortcut_sets }
self.shortcutsChanged.emit()
shortcuts_manager = ShortcutsManager.instance
shortcuts_manager_initialised = lambda: ShortcutsManager.my_instance is not None