Browse Source

re-implement the qt-broken hover shortcuts by using our own event filter

master
Nils 4 years ago
parent
commit
19a7a05169
  1. 44
      template/qtgui/mainwindow.py
  2. 47
      template/qtgui/menu.py

44
template/qtgui/mainwindow.py

@ -129,6 +129,8 @@ class MainWindow(QtWidgets.QMainWindow):
self.ui.menubar.setNativeMenuBar(False) #Force a real menu bar. Qt on wayland will not display it otherwise. self.ui.menubar.setNativeMenuBar(False) #Force a real menu bar. Qt on wayland will not display it otherwise.
self.menu = Menu(mainWindow=self) #needs the about dialog, save file and the api.session ready. self.menu = Menu(mainWindow=self) #needs the about dialog, save file and the api.session ready.
self.installEventFilter(self) #Bottom of the file. Works around the hover numpad bug that is in Qt for years.
self.initiGuiSharedDataToSave() self.initiGuiSharedDataToSave()
def start(self): def start(self):
@ -148,6 +150,7 @@ class MainWindow(QtWidgets.QMainWindow):
self._zoom() #enable zoom factor loaded from save file self._zoom() #enable zoom factor loaded from save file
#def event(self, event): #def event(self, event):
# print (event.type()) # print (event.type())
# return super().event(event) # return super().event(event)
@ -333,5 +336,46 @@ class MainWindow(QtWidgets.QMainWindow):
self.show() self.show()
self.nsmClient.announceGuiVisibility(True) self.nsmClient.announceGuiVisibility(True)
def eventFilter(self, obj, event):
"""Qt has a known but unresolved bug (for many years now)
that shortcuts with the numpad don't trigger. This has little
chance of ever getting fixed, despite getting reported multiple
times.
Try to uninstall this filter (self.__init__) after a new qt release
and check if it works without. It should (it did already in the past)
but so far no luck...
What do we do?
We intercept every key and see if it was with a numpad modifier.
If yes we trigger the menu action ourselves and discard the event
We also need to separate the action of assigning a hover shortcut
to a menu and actually triggering it. Since we are in the template
part of the program we can't assume there is a main widget,
as in Patroneo and Laborejos case.
The obj is always MainWindow, so we can't detect if the menu was open or not.
self.qtApp.focusWidget() is also NOT the menu.
The menu is only triggered AFTER the filter. But we need to
detect if a menu is open while we are running the filter.
self.menu.ui.menubar.activeAction()
For each key press/release we receive 3 keypress events, not
counting autoRepeats. event.type() which is different from type(event)
returns 51 (press), 6(release), 7(accept) for the three. This is key press, release and event accept(?).
This event filter somehow does not differentiate between numpad
on or numpad off. There maybe is another qt check for that.
"""
if (not self.menu.ui.menubar.activeAction()) and event.type() == 51 and type(event) is QtGui.QKeyEvent and event.modifiers() == QtCore.Qt.KeypadModifier and event.text() and event.text() in "0123456789":
action = self.menu.hoverShortcutDict[event.text()]
if action:
action.trigger()
event.accept() #despite what Qt docs say it is not enough to return True to "eat" the event.
return True
else:
#No keypad shortcut. Just use the normal handling.
return super().eventFilter(obj, event)

47
template/qtgui/menu.py

@ -30,7 +30,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
#Template Modules #Template Modules
from .about import About from .about import About
from .resources import * from .resources import *
#User Modules #User Modules
import engine.api as api import engine.api as api
@ -41,12 +41,12 @@ class Menu(object):
def __init__(self, mainWindow): def __init__(self, mainWindow):
"""We can receive a dict of extra menu actions, which also override our hardcoded ones""" """We can receive a dict of extra menu actions, which also override our hardcoded ones"""
self.mainWindow = mainWindow self.mainWindow = mainWindow
self.ui = mainWindow.ui #just shorter to write. self.ui = mainWindow.ui #just shorter to write.
#Hover Shortcuts #Hover Shortcuts
self.hoverShortcutDict = HoverActionDictionary(self.mainWindow) #for temporary hover shortcuts. key=actual key, value=QAction self.hoverShortcutDict = HoverActionDictionary(self.mainWindow) #for temporary hover shortcuts. key=actual key, value=QAction
self.lastHoverAction = None #the last time the mouse hovered over a menu it was this QAction self.lastHoverAction = None #the last time the mouse hovered over a menu it was this QAction. This gets never empty again, but works because we only check it during menu KeyPressEvent
def _rememberHover(action): self.lastHoverAction = action def _rememberHover(action): self.lastHoverAction = action
self.ui.menubar.hovered.connect(_rememberHover) self.ui.menubar.hovered.connect(_rememberHover)
self._originalMenuKeyPressEvent = self.ui.menubar.keyPressEvent self._originalMenuKeyPressEvent = self.ui.menubar.keyPressEvent
@ -64,7 +64,7 @@ class Menu(object):
def renameUndoRedoByHistory(self, undoList, redoList): def renameUndoRedoByHistory(self, undoList, redoList):
undoString = QtCore.QCoreApplication.translate("TemplateMainWindow", "Undo") undoString = QtCore.QCoreApplication.translate("TemplateMainWindow", "Undo")
redoString = QtCore.QCoreApplication.translate("TemplateMainWindow", "Redo") redoString = QtCore.QCoreApplication.translate("TemplateMainWindow", "Redo")
if undoList: if undoList:
undoInfo = QtCore.QCoreApplication.translate("NOOPengineHistory", undoList[-1]) undoInfo = QtCore.QCoreApplication.translate("NOOPengineHistory", undoList[-1])
self.ui.actionUndo.setText(undoString +": {}".format(undoInfo)) self.ui.actionUndo.setText(undoString +": {}".format(undoInfo))
@ -81,18 +81,18 @@ class Menu(object):
self.ui.actionRedo.setText(redoString) self.ui.actionRedo.setText(redoString)
self.ui.actionRedo.setEnabled(False) self.ui.actionRedo.setEnabled(False)
def _setupHelpMenu(self): def _setupHelpMenu(self):
self.addSubmenu("menuHelp", QtCore.QCoreApplication.translate("TemplateMainWindow", "Help")) self.addSubmenu("menuHelp", QtCore.QCoreApplication.translate("TemplateMainWindow", "Help"))
self.addMenuEntry("menuHelp", "actionUser_Manual", QtCore.QCoreApplication.translate("TemplateMainWindow", "User Manual"), connectedFunction=self.mainWindow.userManual.show) self.addMenuEntry("menuHelp", "actionUser_Manual", QtCore.QCoreApplication.translate("TemplateMainWindow", "User Manual"), connectedFunction=self.mainWindow.userManual.show)
self.addSeparator("menuHelp") self.addSeparator("menuHelp")
self.addMenuEntry("menuHelp", "actionAbout", QtCore.QCoreApplication.translate("TemplateMainWindow", "About and Tips"), connectedFunction=self.mainWindow.about.show) self.addMenuEntry("menuHelp", "actionAbout", QtCore.QCoreApplication.translate("TemplateMainWindow", "About and Tips"), connectedFunction=self.mainWindow.about.show)
def _setupEditMenu(self): def _setupEditMenu(self):
self.addSubmenu("menuEdit", QtCore.QCoreApplication.translate("TemplateMainWindow", "Edit")) self.addSubmenu("menuEdit", QtCore.QCoreApplication.translate("TemplateMainWindow", "Edit"))
self.addMenuEntry("menuEdit", "actionUndo", QtCore.QCoreApplication.translate("TemplateMainWindow", "Undo"), connectedFunction=api.undo, shortcut="Ctrl+Z") self.addMenuEntry("menuEdit", "actionUndo", QtCore.QCoreApplication.translate("TemplateMainWindow", "Undo"), connectedFunction=api.undo, shortcut="Ctrl+Z")
self.addMenuEntry("menuEdit", "actionRedo", QtCore.QCoreApplication.translate("TemplateMainWindow", "Redo"), connectedFunction=api.redo, shortcut="Ctrl+Shift+Z") self.addMenuEntry("menuEdit", "actionRedo", QtCore.QCoreApplication.translate("TemplateMainWindow", "Redo"), connectedFunction=api.redo, shortcut="Ctrl+Shift+Z")
def _setupFileMenu(self): def _setupFileMenu(self):
self.addSubmenu("menuFile", QtCore.QCoreApplication.translate("TemplateMainWindow", "File")) self.addSubmenu("menuFile", QtCore.QCoreApplication.translate("TemplateMainWindow", "File"))
self.addMenuEntry("menuFile", "actionSave", QtCore.QCoreApplication.translate("TemplateMainWindow", "Save"), connectedFunction=api.save, shortcut="Ctrl+S") self.addMenuEntry("menuFile", "actionSave", QtCore.QCoreApplication.translate("TemplateMainWindow", "Save"), connectedFunction=api.save, shortcut="Ctrl+S")
self.addMenuEntry("menuFile", "actionQuit", QtCore.QCoreApplication.translate("TemplateMainWindow", "Quit (without saving)"), connectedFunction=self.mainWindow.nsmClient.serverSendExitToSelf, shortcut="Ctrl+Q") self.addMenuEntry("menuFile", "actionQuit", QtCore.QCoreApplication.translate("TemplateMainWindow", "Quit (without saving)"), connectedFunction=self.mainWindow.nsmClient.serverSendExitToSelf, shortcut="Ctrl+Q")
@ -121,12 +121,14 @@ class Menu(object):
self.ui.menuDebug.menuAction().setVisible(state) #...therefore we hide manually. self.ui.menuDebug.menuAction().setVisible(state) #...therefore we hide manually.
def menuKeyPressInterceptor(self, event): def menuKeyPressInterceptor(self, event):
"""Menu Entries without their own shortcut can be temporarily assigned one of the ten """
Triggered when a menu is open and mouse hovers over a menu.
Menu Entries without their own shortcut can be temporarily assigned one of the ten
number keypad keys as shortcuts. number keypad keys as shortcuts.
If the numpad key is already in use it will be rerouted (freed first). If the numpad key is already in use it will be rerouted (freed first).
""" """
strings = set(("Num+1", "Num+2", "Num+3", "Num+4", "Num+5", "Num+6", "Num+7", "Num+8", "Num+9", "Num+0")) strings = set(("Num+1", "Num+2", "Num+3", "Num+4", "Num+5", "Num+6", "Num+7", "Num+8", "Num+9", "Num+0"))
if self.lastHoverAction and (not self.lastHoverAction.menu()) and event.modifiers() == QtCore.Qt.KeypadModifier and ((not self.lastHoverAction.shortcut().toString()) or self.lastHoverAction.shortcut().toString() in strings): if self.lastHoverAction and (not self.lastHoverAction.menu()) and event.modifiers() == QtCore.Qt.KeypadModifier and ((not self.lastHoverAction.shortcut().toString()) or self.lastHoverAction.shortcut().toString() in strings):
key = event.text() #just number 0-9 #event.key() is 49, 50... key = event.text() #just number 0-9 #event.key() is 49, 50...
@ -135,10 +137,11 @@ class Menu(object):
else: else:
self._originalMenuKeyPressEvent(event) self._originalMenuKeyPressEvent(event)
def addSubmenu(self, submenuActionAsString, text): def addSubmenu(self, submenuActionAsString, text):
""" """
Returns the submenu. Returns the submenu.
submenuActionAsString is something like menuHelp submenuActionAsString is something like menuHelp
@ -213,7 +216,7 @@ class Menu(object):
def addMenuEntry(self, submenu, actionAsString:str, text:str, connectedFunction=None, shortcut:str="", tooltip:str="", iconResource:str="", checkable=False): def addMenuEntry(self, submenu, actionAsString:str, text:str, connectedFunction=None, shortcut:str="", tooltip:str="", iconResource:str="", checkable=False):
""" """
parameterstrings must already be translated. parameterstrings must already be translated.
Returns the QAction Returns the QAction
Add a new menu entry to an existing submenu. Add a new menu entry to an existing submenu.
@ -242,7 +245,7 @@ class Menu(object):
action.setText(text) #Text must already be translated action.setText(text) #Text must already be translated
action.setToolTip(tooltip) #Text must already be translated action.setToolTip(tooltip) #Text must already be translated
if iconResource: if iconResource:
assert QtCore.QFile.exists(iconResource) assert QtCore.QFile.exists(iconResource)
ico = QtGui.QIcon(iconResource) ico = QtGui.QIcon(iconResource)
action.setIcon(ico) action.setIcon(ico)
if checkable: if checkable:
@ -257,14 +260,14 @@ class Menu(object):
def hideMenuEntry(self, menuAction:str): def hideMenuEntry(self, menuAction:str):
action = getattr(self.ui, menuAction) action = getattr(self.ui, menuAction)
action.setVisible(False) action.setVisible(False)
def removeMenuEntry(self, menuAction:str): def removeMenuEntry(self, menuAction:str):
raise NotImplementedError #TODO raise NotImplementedError #TODO
def addSeparator(self, submenu): def addSeparator(self, submenu):
submenu = getattr(self.ui, submenu) submenu = getattr(self.ui, submenu)
submenu.addSeparator() submenu.addSeparator()
def connectMenuEntry(self, actionAsString, connectedFunction, shortcut=""): def connectMenuEntry(self, actionAsString, connectedFunction, shortcut=""):
""" """
@ -295,15 +298,15 @@ class Menu(object):
def disable(self): def disable(self):
"""This is meant for a short temporary disabling.""" """This is meant for a short temporary disabling."""
for a in self.mainWindow.findChildren(QtWidgets.QAction): for a in self.mainWindow.findChildren(QtWidgets.QAction):
if not a.menu(): if not a.menu():
a.setEnabled(False) a.setEnabled(False)
def enable(self): def enable(self):
for a in self.mainWindow.findChildren(QtWidgets.QAction): for a in self.mainWindow.findChildren(QtWidgets.QAction):
a.setEnabled(True) a.setEnabled(True)
self.renameUndoRedoByHistory(*api.getUndoLists()) self.renameUndoRedoByHistory(*api.getUndoLists())

Loading…
Cancel
Save