From 6ff5f4bc896b2083706e864e470dea72aafc2df0 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 1 Apr 2023 02:04:04 -0500 Subject: [PATCH] Deep refactor of the QShortcut code used for arrow key detection, with a custom eventFilter to stop it from messing with our primary UI tabs (effects, transitions, files, emojis) --- src/windows/main_window.py | 89 +++++++++++++++++++++++++++++++++++- src/windows/views/webview.py | 49 +------------------- 2 files changed, 88 insertions(+), 50 deletions(-) diff --git a/src/windows/main_window.py b/src/windows/main_window.py index 1b729e706d..4fb0bff5a3 100644 --- a/src/windows/main_window.py +++ b/src/windows/main_window.py @@ -39,7 +39,7 @@ import openshot # Python module for libopenshot (required video editing module installed separately) from PyQt5.QtCore import ( Qt, pyqtSignal, pyqtSlot, QCoreApplication, PYQT_VERSION_STR, - QTimer, QDateTime, QFileInfo, QUrl, + QTimer, QDateTime, QFileInfo, QUrl, QEvent ) from PyQt5.QtGui import QIcon, QCursor, QKeySequence, QTextCursor from PyQt5.QtWidgets import ( @@ -47,7 +47,7 @@ QMessageBox, QDialog, QFileDialog, QInputDialog, QAction, QActionGroup, QSizePolicy, QStatusBar, QToolBar, QToolButton, - QLineEdit, QComboBox, QTextEdit + QLineEdit, QComboBox, QTextEdit, QShortcut ) from classes import exceptions, info, qt_types, sentry, ui_util, updates @@ -98,6 +98,9 @@ class MainWindow(updates.UpdateWatcher, QMainWindow): StopSignal = pyqtSignal() SeekSignal = pyqtSignal(int) SpeedSignal = pyqtSignal(float) + SeekPreviousFrame = pyqtSignal() + SeekNextFrame = pyqtSignal() + PlayPauseToggleSignal = pyqtSignal() RecoverBackup = pyqtSignal() FoundVersionSignal = pyqtSignal(str) TransformSignal = pyqtSignal(str) @@ -1554,6 +1557,43 @@ def actionCenterOnPlayhead_trigger(self, checked=True): """ Center the timeline on the current playhead position """ self.timeline.centerOnPlayhead() + def handleSeekPreviousFrame(self): + """Handle previous-frame keypress""" + player = get_app().window.preview_thread.player + frame_num = player.Position() - 1 + + # Seek to previous frame + get_app().window.PauseSignal.emit() + get_app().window.SpeedSignal.emit(0) + get_app().window.previewFrameSignal.emit(frame_num) + + # Notify properties dialog + get_app().window.propertyTableView.select_frame(frame_num) + + def handleSeekNextFrame(self): + """Handle next-frame keypress""" + player = get_app().window.preview_thread.player + frame_num = player.Position() + 1 + + # Seek to next frame + get_app().window.PauseSignal.emit() + get_app().window.SpeedSignal.emit(0) + get_app().window.previewFrameSignal.emit(frame_num) + + # Notify properties dialog + get_app().window.propertyTableView.select_frame(frame_num) + + def handlePlayPauseToggleSignal(self): + """Handle play-pause-toggle keypress""" + player = get_app().window.preview_thread.player + frame_num = player.Position() + + # Toggle Play/Pause + get_app().window.actionPlay.trigger() + + # Notify properties dialog + get_app().window.propertyTableView.select_frame(frame_num) + def getShortcutByName(self, setting_name): """ Get a key sequence back from the setting name """ s = get_app().get_settings() @@ -3090,12 +3130,44 @@ def initModels(self): self.emojiListView = EmojisListView(self.emojis_model) self.tabEmojis.layout().addWidget(self.emojiListView) + def seekPreviousFrame(self): + """Handle previous-frame keypress""" + # Ignore certain focused widgets + get_app().window.SeekPreviousFrame.emit() + + def seekNextFrame(self): + """Handle next-frame keypress""" + get_app().window.SeekNextFrame.emit() + + def playToggle(self): + """Handle play-pause-toggle keypress""" + get_app().window.PlayPauseToggleSignal.emit() + + def eventFilter(self, obj, event): + """Filter out certain QShortcuts - for example, arrow keys used + in our files, transitions, effects, and emojis views.""" + if event.type() == QEvent.ShortcutOverride: + if self.emojiListView.hasFocus() or self.filesView.hasFocus() or \ + self.transitionsView.hasFocus() or self.effectsView.hasFocus(): + # Mark event as 'handled' so it stops propagating + event.accept() + + elif self.propertyTableView.hasFocus() and \ + (event.key() == get_app().window.getShortcutByName("playToggle") or + event.key() == get_app().window.getShortcutByName("playToggle1") or + event.key() == get_app().window.getShortcutByName("playToggle2") or + event.key() == get_app().window.getShortcutByName("playToggle3")): + # Mark event as 'handled' so it stops propagating + event.accept() + return super(MainWindow, self).eventFilter(obj, event) + def __init__(self, *args): # Create main window base class super().__init__(*args) self.initialized = False self.shutting_down = False + self.installEventFilter(self) # set window on app for reference during initialization of children app = get_app() @@ -3177,6 +3249,9 @@ def __init__(self, *args): # Connect signals self.RecoverBackup.connect(self.recover_backup) + self.SeekPreviousFrame.connect(self.handleSeekPreviousFrame) + self.SeekNextFrame.connect(self.handleSeekNextFrame) + self.PlayPauseToggleSignal.connect(self.handlePlayPauseToggleSignal) # Create the timeline sync object (used for previewing timeline) self.timeline_sync = TimelineSync(self) @@ -3367,3 +3442,13 @@ def __init__(self, *args): # Main window is initialized self.initialized = True + + # Use shortcuts to override keypress capturing for arrow keys + # These keys are a bit special, and other approaches fail on certain + # combinations of OS and Webview backend + QShortcut(app.window.getShortcutByName("seekPreviousFrame"), self, activated=self.seekPreviousFrame, context=Qt.WindowShortcut) + QShortcut(app.window.getShortcutByName("seekNextFrame"), self, activated=self.seekNextFrame, context=Qt.WindowShortcut) + QShortcut(app.window.getShortcutByName("playToggle"), self, activated=self.playToggle, context=Qt.WindowShortcut) + QShortcut(app.window.getShortcutByName("playToggle1"), self, activated=self.playToggle, context=Qt.WindowShortcut) + QShortcut(app.window.getShortcutByName("playToggle2"), self, activated=self.playToggle, context=Qt.WindowShortcut) + QShortcut(app.window.getShortcutByName("playToggle3"), self, activated=self.playToggle, context=Qt.WindowShortcut) \ No newline at end of file diff --git a/src/windows/views/webview.py b/src/windows/views/webview.py index 1827791e6b..fb46c11e1a 100644 --- a/src/windows/views/webview.py +++ b/src/windows/views/webview.py @@ -39,7 +39,7 @@ from PyQt5.QtCore import QFileInfo, pyqtSlot, QUrl, Qt, QCoreApplication, QTimer, pyqtSignal from PyQt5.QtGui import QCursor, QKeySequence, QColor -from PyQt5.QtWidgets import QMenu, QDialog, QShortcut +from PyQt5.QtWidgets import QMenu, QDialog from classes import info, updates from classes.app import get_app @@ -3309,43 +3309,6 @@ def render_cache_json(self): # Log the exception and ignore log.warning("Exception processing timeline cache: %s", ex) - def seekPreviousFrame(self): - """Handle previous-frame keypress""" - player = get_app().window.preview_thread.player - frame_num = player.Position() - 1 - - # Seek to prevoius frame - get_app().window.PauseSignal.emit() - get_app().window.SpeedSignal.emit(0) - get_app().window.previewFrameSignal.emit(frame_num) - - # Notify properties dialog - get_app().window.propertyTableView.select_frame(frame_num) - - def seekNextFrame(self): - """Handle next-frame keypress""" - player = get_app().window.preview_thread.player - frame_num = player.Position() + 1 - - # Seek to next frame - get_app().window.PauseSignal.emit() - get_app().window.SpeedSignal.emit(0) - get_app().window.previewFrameSignal.emit(frame_num) - - # Notify properties dialog - get_app().window.propertyTableView.select_frame(frame_num) - - def playToggle(self): - """Handle play-pause-toggle keypress""" - player = get_app().window.preview_thread.player - frame_num = player.Position() - - # Toggle Play/Pause - get_app().window.actionPlay.trigger() - - # Notify properties dialog - get_app().window.propertyTableView.select_frame(frame_num) - def __init__(self, window): super().__init__() self.setObjectName("TimelineWebView") @@ -3403,13 +3366,3 @@ def __init__(self, window): # connect signal to receive waveform data self.clipAudioDataReady.connect(self.clipAudioDataReady_Triggered) self.fileAudioDataReady.connect(self.fileAudioDataReady_Triggered) - - # Use shortcuts to override keypress capturing for arrow keys - # This is needed mostly due to WebEngine backend eating keypress events - # This approach works well for ALL backends though - QShortcut(app.window.getShortcutByName("seekPreviousFrame"), self, activated=self.seekPreviousFrame) - QShortcut(app.window.getShortcutByName("seekNextFrame"), self, activated=self.seekNextFrame) - QShortcut(app.window.getShortcutByName("playToggle"), self, activated=self.playToggle) - QShortcut(app.window.getShortcutByName("playToggle1"), self, activated=self.playToggle) - QShortcut(app.window.getShortcutByName("playToggle2"), self, activated=self.playToggle) - QShortcut(app.window.getShortcutByName("playToggle3"), self, activated=self.playToggle)