5

I'm using PyQt5. When I write a keyPressEvent handler, I would like to be able to print, for debugging purposes, a human-readable description of what keys were pressed. I want to be able to print such a thing no matter what, regardless of how many keys were pressed in the event, or whether they were modifiers or "regular" keys.

I have seen this previous question in which the accepted answer (using C++) suggests creating a QKeySequence and using its .toString method. I can do this like this:

def keyPressEvent(self, event):
    print("got a key event of ", QKeySequence(event.key()).toString())

However, this does not always work. For instance, if I press the Shift key, it will result in an encoding error when I try to output (or if I try to encode it to UTF-8). This seems to be because QKeySequence does not work on isolated modifier keys:

>>> QKeySequence(Qt.Key_Shift).toString().encode('unicode-escape')
b'\\u17c0\\udc20'

It gives gibberish instead of what I would expect, namely "Shift". It works if I use Qt.SHIFT (sort of, in that it gives "Shift+"), but that is of no use, because Qt.SHIFT is not what I get in event.key() if I press the Shift key.

How can I get Qt to give me a printable representation of anything that might ever be the value of event.key(), where event is a QKeyEvent?

BrenBarn
  • 242,874
  • 37
  • 412
  • 384
  • That's what the metaobject system is for - the [*other* answer to cited question](https://stackoverflow.com/a/21767101/1329652) is exactly what you need. – Kuba hasn't forgotten Monica Jun 14 '18 at 07:21
  • @KubaOber: It is not obvious to me how to adapt that code to Python. Can you show how to do that In PyQt5? I had a similar problem with the first half of the accepted answer; I cannot see how, in PyQt, to get access to the "reverse" mapping described, which in theory would map the numeric values of things like `Key_Shift` to their textual names. – BrenBarn Jun 14 '18 at 07:28
  • I think you need to look into `Qt.ShiftModifier` and `Qt.ControlModifier` take a look here: https://stackoverflow.com/questions/8772595/how-to-check-if-a-key-modifier-is-pressed-shift-ctrl-alt – Christoffer Jun 14 '18 at 07:43
  • @ChristofferGøthgen: Those appear to be aliases for Qt.SHIFT and Qt.CTRL, which as I said are not useful because they are not present in the key value. The starting point for me is the value of `event.key()`; I want to convert *that* into a string representation. I know I could do it manually by checking all the modifiers from `event.modifiers()` and mapping them manually to strings, but what I want to know is if there is a way to get Qt to give it me without having to do all that myself. – BrenBarn Jun 14 '18 at 07:46
  • Qt to give you anything? Why? It's Python, it's way simpler than in C++. The `Qt` object has `Key_foo` properties. It's trivial to enumerate them and find all that match. You can even pre-cache a reverse map of them for faster lookup. There's also the `Qt.KeyboardModifierMask`, etc. Forget about C++. Think Python. Python has ubiquitous introspection. If you know that there's an attribute of an object, you certainly can enumerate it, introspect it, etc. That's very, very different from C++. In C++, you need moc to introspect enums. In Python, it's in the language. – Kuba hasn't forgotten Monica Jun 14 '18 at 07:59
  • And modifiers certainly are in the key "code". The code in my answer works, after all. It's shorter in Python, and I encourage you to figure it out, keeping in mind that it's Python you're dealing with. And yes, you will have to write those meager 20-30 lines of code, Qt doesn't provide any ready-made way, unfortunately. – Kuba hasn't forgotten Monica Jun 14 '18 at 08:02
  • In any case, for `QKeySequence`, it looks like all you need to tweak is the modifier keys to modifiers conversion, i.e. map `Qt.Key_Shift` to `Qt.ShiftModifier`. There's a whooping six (6) of those. – Kuba hasn't forgotten Monica Jun 14 '18 at 08:10
  • There's a slim chance that PyQt folks mapped all of `Qt` object to dynamic attributes via `__getattr__`, and didn't implement `__dir__`, in which case of course no introspection will work - but even then, you need to deal with 6 special cases only. No biggie there. – Kuba hasn't forgotten Monica Jun 14 '18 at 08:16

1 Answers1

14

To deal with the specific question:

How can I get Qt to give me a printable representation of anything that might ever be the value of event.key(), where event is a QKeyEvent?

The first things to note is that event.key() returns an int, rather than a Qt.Key. This is simply because the key can be any unicode value whatsoever. As a consequnce of this, it is not really feasible to give a printable representation of literally anything, since not every unicode key is printable, and it is impractical to enumerate them all.

The only API Qt provides for this is the QKeySequnce class. However, as you have found, it does not handle all inputs in the way you want. So you will have to roll your own solution.

It might be tempting to use QMetaEnum to convert key values to their names wherever possible. However, this won't work here, because there is no staticMetaObject for the Qt object, and PyQt does not provide anything like qt_getQtMetaObject. It also doesn't currently implement QMetaEnum.fromType (although this is likely to change in future versions).

So the only available solution is to use normal python introspection to build a mapping, and build up a printable representation from that.

Here is a basic demo (only tested on Linux):

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget

keymap = {}
for key, value in vars(Qt).items():
    if isinstance(value, Qt.Key):
        keymap[value] = key.partition('_')[2]

modmap = {
    Qt.ControlModifier: keymap[Qt.Key_Control],
    Qt.AltModifier: keymap[Qt.Key_Alt],
    Qt.ShiftModifier: keymap[Qt.Key_Shift],
    Qt.MetaModifier: keymap[Qt.Key_Meta],
    Qt.GroupSwitchModifier: keymap[Qt.Key_AltGr],
    Qt.KeypadModifier: keymap[Qt.Key_NumLock],
    }

def keyevent_to_string(event):
    sequence = []
    for modifier, text in modmap.items():
        if event.modifiers() & modifier:
            sequence.append(text)
    key = keymap.get(event.key(), event.text())
    if key not in sequence:
        sequence.append(key)
    return '+'.join(sequence)

class Window(QWidget):
    def keyPressEvent(self, event):
        print(keyevent_to_string(event))

if __name__ == '__main__':

    app = QApplication(sys.argv)
    window = Window()
    window.setGeometry(600, 100, 300, 200)
    window.show()
    sys.exit(app.exec_())
ekhumoro
  • 115,249
  • 20
  • 229
  • 336