Skip to content

Commit

Permalink
Deep refactor of the QShortcut code used for arrow key detection, wit…
Browse files Browse the repository at this point in the history
…h a custom eventFilter to stop it from messing with our primary UI tabs (effects, transitions, files, emojis)
  • Loading branch information
jonoomph committed Apr 1, 2023
1 parent 65ce747 commit 6ff5f4b
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 50 deletions.
89 changes: 87 additions & 2 deletions src/windows/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@
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 (
QMainWindow, QWidget, QDockWidget,
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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
49 changes: 1 addition & 48 deletions src/windows/views/webview.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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)

0 comments on commit 6ff5f4b

Please sign in to comment.