3

Consider this PySide 6 example:

from PySide6.QtCore import *  # noqa
from PySide6.QtGui import *  # noqa
from PySide6.QtWidgets import *  # noqa


class TableWidget(QTableWidget):
    def __init__(self):
        super().__init__()
        self.setColumnCount(2)
        self.setHorizontalHeaderLabels(["Key", "Value"])
        self.setColumnWidth(1, 800)
        self._sequence_format = QKeySequence.PortableText
        self._modifier_order = [
            self.modifier_to_string(modifier)
            for modifier in (Qt.ControlModifier, Qt.AltModifier, Qt.ShiftModifier)
        ]

    def modifier_to_string(self, modifier):
        return QKeySequence(modifier.value).toString(self._sequence_format).lower()

    def key_to_string(self, key):
        if key in (
            Qt.Key_Shift,
            Qt.Key_Control,
            Qt.Key_Alt,
            Qt.Key_Meta,
            Qt.Key_AltGr,
        ):
            return None
        else:
            return QKeySequence(key).toString(self._sequence_format).lower()

    def shortcut_from_event(self, event):
        key = event.key()
        modifiers = event.modifiers()
        vk = event.nativeVirtualKey()
        key_string = self.key_to_string(key)
        vk_string = self.key_to_string(vk)
        modifier_strings = tuple(
            self.modifier_to_string(modifier) for modifier in modifiers
        )
        shortcut_tpl = (key_string, modifier_strings)

        shortcut_lst = []
        for modifier_string in self._modifier_order:
            if modifier_string in shortcut_tpl[1]:
                shortcut_lst.append(modifier_string)
        shortcut_lst.append(shortcut_tpl[0])
        if None in shortcut_lst:
            shortcut = None  # noqa
        else:
            shortcut = "".join(shortcut_lst)  # noqa

        return {
            "shortcut": shortcut,
            "key": key,
            "modifiers": modifiers,
            "vk": vk,
            "key_string": key_string,
            "vk_string": vk_string,
            "modifier_strings": modifier_strings,
        }

    def keyPressEvent(self, event):
        table = self
        item = self.shortcut_from_event(event)

        if item:
            table.clearContents()
            table.setRowCount(0)

            row_position = 0
            for k, v in sorted(item.items(), reverse=True):
                table.insertRow(row_position)
                table.setItem(row_position, 0, QTableWidgetItem(k))
                table.setItem(row_position, 1, QTableWidgetItem(str(v)))
            # table.resizeColumnsToContents()

        # return super().keyPressEvent(event)


if __name__ == "__main__":
    import sys

    app = QApplication(sys.argv)
    w = TableWidget()
    w.resize(800, 600)
    w.show()
    sys.exit(app.exec())

I'd like to know: How can I create a shortcut string using the modifiers that follows the modifier_order and the unaffected key string? For instance, right now:

  • If I press key +

    Enter image description here

  • If I press Shift + +

    Enter image description here

  • If I press AltGr + +

    Enter image description here

But I'd like to modify the code so the shortcut created will be +, Shift + plus and Ctrl + Alt + plus, respectively.

How can I achieve this behaviour? Similar to all keys, I'd like to get the string of the unaffected key without any modifiers on top.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
BPL
  • 9,632
  • 9
  • 59
  • 117
  • 1
    AFAIK, it's not directly possible from Qt, as it normally receives an already "processed" event that already "translates" the result with basic modifiers. So, basically, when you press "shift+plus", it's almost like pressing a key that *naturally* has the asterisk along with the shift modifier. While not optimal for lower level implementations, it's what most developers are interested in and it's probably also related to the compatibility layer used by Qt. What you could eventually do is to look up scancode tables and use `nativeScanCode()` from the event to properly translate the result. – musicamante Aug 12 '23 at 22:50
  • @musicamante A little bit more context, what I'm trying to achieve is basically create a shortcut editor with similar behaviour to wt, the way that one behaves is really intuitive. I've tried using QKeySequenceEdit but that's not good neither... For more info, I've opened a thread asking about it [https://github.com/microsoft/terminal/issues/15825](https://github.com/microsoft/terminal/issues/15825) . So even if it's not in the keyPressEvent, the idea is coming up with a code that help me to achieve wt's behaviour – BPL Aug 13 '23 at 06:05
  • What's your keyboard layout? Anyway, why not use virtual key codes since you are on Windows? – relent95 Aug 14 '23 at 02:51
  • @relent95 It's a QWERTY layout, when you say using virtual key codes, what do you mean? Just to let you know few days ago I'd tried using event.nativeVirtualKey() and the results were not good – BPL Aug 14 '23 at 06:36
  • Run ```powershell -c "(Get-Culture).keyboardLayoutId.toString('x')"``` in the cmd, and find the number in [Windows keyboard layouts](https://learn.microsoft.com/en-us/globalization/windows-keyboard-layouts). And for the virtual key codes, I mean converting codes to characters by yourself refering [Virtual-Key Codes](https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes). You can do only for a few codes - one with the ```KeypadModifier``` flag for example. – relent95 Aug 14 '23 at 07:01
  • Found the relevant code https://github.com/microsoft/terminal/blob/main/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp – BPL Aug 14 '23 at 20:15
  • It looks like you need to use a key scan code that is actually provided by event.nativeScanCode() in your shortcut_from_event() method. Each scan code corresponds to a physical key on your keyboard. But I was surprised to see that nativeScanCode() provides rather strange values that I can't match with anything else... Maybe it is something with my PC but I might suspect even a bug in Qt... – StarterKit Aug 15 '23 at 14:57
  • @StarterKit I remember last test I did few days ago that was the case, maybe rather than using keyPressEvent at all we should use a similar approach like wt. They've got routines that converts between KeyChord<->json<->string . Once you've got that routines in place I guess it's just a matter of capturing the keySequence string – BPL Aug 15 '23 at 22:17
  • What is the result of the powershell command(keyboardLayoutId)? The ```QKeyEvent.key()``` works fine for the 0x409(US). – relent95 Aug 16 '23 at 00:37
  • I'm not a python expert, so I only know how to do this with C++, but have you looked at making a [QAbstractNativeEventFilter](https://doc.qt.io/qt-6/qabstractnativeeventfilter.html)? – JarMan Aug 16 '23 at 16:08
  • 1
    The bounty attracted a [ChatGPT](https://meta.stackoverflow.com/questions/421831/temporary-policy-chatgpt-is-banned) plagiariser (for more than one question). – Peter Mortensen Aug 20 '23 at 12:19
  • 2
    ChatGPT is always worth a try, but it is very unreliable. A response will often contain sections that are completely made up (that simply isn't so). It can be used for generating ideas or leads, but not for anything factual. – Peter Mortensen Aug 20 '23 at 12:22
  • @StarterKit It's not impossible that it's a bug, but knowing how the Qt event is created (which is based on the OS API) I sincerely doubt that. Remember that scan codes do **not** relate to a specific "character": the OS maps the scancode based on the current keyboard layout that is normally detected or selected by the user, meaning that the same key (which always has the same scancode) may result in different characters depending on the keyboard layout currently set on the OS. – musicamante Aug 24 '23 at 14:06

1 Answers1

0

From the Qt documentation:

Warning: This function cannot always be trusted. The user can confuse it by pressing both Shift keys simultaneously and releasing one of them, for example.

The alternative suggested there is to use the keyboard modifiers from the application itself.

Following this answer, it does seem that this approach works better. So, in your case just switch this line

modifiers = event.modifiers()

with this one:

modifiers = Qt.QtWidgets.QApplication.keyboardModifiers()

I also suggest for you to map the Qt types to your own readable strings in the shortcut tuple in order to produce the desirable output value.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ymz
  • 6,602
  • 1
  • 20
  • 39