1435 lines
55 KiB
Python
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
|