PDA

View Full Version : keys map (cross platform)



junior-root
15th September 2017, 21:40
Hello,
I'm working on a cross platform application in which user must be able to check whether a particular key is pressed or not.

So, I create a map of keys like this:

std::unordered_set<int> pressedKeys;

... and store each key when is pressed


void MyAapp::keyPressEvent(QKeyEvent *e) {
e->accept();
mymutex->lock();
qDebug() << "+key:" << e->key() << "=" << e->text() <<", nativeVirtualKey:" << e->nativeVirtualKey() <<", isAutoRepeat:" << e->isAutoRepeat() <<", modifiers" << e->modifiers();;
if(!e->isAutoRepeat()){
// Note that if the event is a multiple-key compressed event that is partly due to auto-repeat,
// isAutoRepeat() function could return either true or false indeterminately.
if( e->modifiers() & Qt::ShiftModifier )
{
pressedKeys.insert(Qt::Key_Shift);
}else{
pressedKeys.erase(Qt::Key_Shift);
}
if( e->modifiers() & Qt::ControlModifier )
{
pressedKeys.insert(Qt::Key_Control);
}else{
pressedKeys.erase(Qt::Key_Control);
}
if( e->modifiers() & Qt::AltModifier )
{
pressedKeys.insert(Qt::Key_Alt);
}else{
pressedKeys.erase(Qt::Key_Alt);
}
if( e->modifiers() & Qt::MetaModifier )
{
pressedKeys.insert(Qt::Key_Meta);
}else{
pressedKeys.erase(Qt::Key_Meta);
}
pressedKeys.insert(e->key());
}
mymutex->unlock();
}

... and delete a key when key is released:


void MyApp::keyReleaseEvent(QKeyEvent *e) {
e->accept();
mymutex->lock();
qDebug() << "-key:" << e->key() << "=" << e->text() <<", nativeVirtualKey:" << e->nativeVirtualKey() <<", isAutoRepeat:" << e->isAutoRepeat() <<", modifiers" << e->modifiers();;
if(!e->isAutoRepeat()){
pressedKeys.erase(e->key());
if( e->modifiers() & Qt::ShiftModifier )
{
pressedKeys.insert(Qt::Key_Shift);
}else{
pressedKeys.erase(Qt::Key_Shift);
}
if( e->modifiers() & Qt::ControlModifier )
{
pressedKeys.insert(Qt::Key_Control);
}else{
pressedKeys.erase(Qt::Key_Control);
}
if( e->modifiers() & Qt::AltModifier )
{
pressedKeys.insert(Qt::Key_Alt);
}else{
pressedKeys.erase(Qt::Key_Alt);
}
if( e->modifiers() & Qt::MetaModifier )
{
pressedKeys.insert(Qt::Key_Meta);
}else{
pressedKeys.erase(Qt::Key_Meta);
}
}
mymutex->unlock();
}


Everything is perfect.
To be a cross platform I use Qt Key-enum values for keys (http://doc.qt.io/qt-4.8/qt.html#Key-enum).

The problem appears when I press a sequence like this:
(notation: + is keyPressEvent, - is keyReleaseEvent):


user press SHIFT
+key: 16777248 = "" , nativeVirtualKey: 16 , isAutoRepeat: false , modifiers QFlags<Qt::KeyboardModifiers>(ShiftModifier)
user press "2", but because SHIFT is pressed, then Qt returns Qt::Key_At "@"
+key: 64 = "@" , nativeVirtualKey: 50 , isAutoRepeat: false , modifiers QFlags<Qt::KeyboardModifiers>(ShiftModifier)
-key: 64 = "@" , nativeVirtualKey: 50 , isAutoRepeat: true , modifiers QFlags<Qt::KeyboardModifiers>(ShiftModifier)
... keep pressing SHIFT+2 = "@" but in fact user press "2" key
+key: 64 = "@" , nativeVirtualKey: 50 , isAutoRepeat: true , modifiers QFlags<Qt::KeyboardModifiers>(ShiftModifier)
release SHIFT
-key: 16777248 = "" , nativeVirtualKey: 16 , isAutoRepeat: false , modifiers QFlags<Qt::KeyboardModifiers>(NoModifier)
now the key is "2" but the original key "@"is in map as pressed
+key: 50 = "2" , nativeVirtualKey: 50 , isAutoRepeat: false , modifiers QFlags<Qt::KeyboardModifiers>(NoModifier)
-key: 50 = "2" , nativeVirtualKey: 50 , isAutoRepeat: true , modifiers QFlags<Qt::KeyboardModifiers>(NoModifier)
...


I do not like the fact that Qt return "@" as a key instead of "2".
One of the flaw is in the example above: the "@" key remains in pressedKeys map as pressed even after the key is actually released.
I want to receive the real key, not the combination.
And I need in cross platform way.

Remember that user must be able to check if a specific key is pressed. For this I check if key is in pressedKeys map and return true/false. User use Key-enum values.

Any ideas?
Thanks!

d_stranz
16th September 2017, 05:30
I do not like the fact that Qt return "@" as a key instead of "2".

Sorry, but you didn't press "2". You pressed "Shift + 2", and the text for that is "@" on your keyboard. Qt (and every other UI system that will track key actions) told you correctly. Too bad if you don't agree with what the OS tells you. Did you notice that Qt reports "native virtual key" as being "50" in both cases though? That's an ASCII "2". The "Shift" modifier turns it into a "@".


And I need in cross platform way.

Maybe you should go online and look at some pictures of keyboards for other countries besides your own. You'll be surprised to learn that not all of them look like the keyboard in front of you. Different letters and symbols on different physical keys for almost every different language. Ergonomic keyboards often have different layouts than standard keyboards, even for the same language and country. So, you can't be guaranteed that any keyboard except your own will report "@" for "Shift + 2".

So, Qt is already giving you "cross-platform" as well as "cross-keyboard" and "cross-country".


One of the flaw is in the example above: the "@" key remains in pressedKeys map as pressed even after the key is actually released.

Then you need to fix the logic flaw in your code. Your combinations of if clauses in the two key events don't correctly handle all of the possibilities.

In this particular example, Qt has told you that the "@" key was pressed with the "Shift" modifier. If you then release the Shift key, then the shift condition no longer applies. It also told you that autorepeat for this key is true, so that additionally tells you that if you keep the key pressed, you will get another key press event from it eventually at the end of the autorepeat delay timeout. You got that one as well, except that before the autorepeat delay timed out, you released the Shift key, so it gets reported (correctly) as a "2". You never got a key release for the "@" key because you didn't release it.

So your logic needs to take into account the modifiers that were in effect when a key was pressed, whether the key is autorepeat, and what the native virtual key is for that key. If you get another key press event for the same native key and there wasn't a corresponding release event for that native key in between, then you know that is an autorepeat. And then if you can't find a match for the new e->text() in the map, you delete whichever entry is in there that has the same native key value instead, and add the new one.

The fact that Qt reports both the key text and native value allow you to handle exactly this situation.

junior-root
16th September 2017, 10:10
First of all, I thank for the answer and the time spent for me.
I also noticed that nativeVirtualKey() returns the same value in both cases: SHIFT+2="2" and key "2".
I also played with several layouts for keyboard before that, and I saw that in many cases nativeVirtualKey() is the same (the same physical key is pressed), but the key() value is different.
The solution is the same as you say: to keep both values in map (key() and nativeVirtualKey()) and to ckeck for one of those values when a key is pressed or released.
Ok.

The problem is: inside the event keyPressEvent() or keyReleaseEvent() I can get the corresponding nativeVirtualKey... but:



Remember that user must be able to check if a specific key is pressed. For this I check if key is in pressedKeys map and return true/false. User use Key-enum values.


In this case user wants to check if key "2" is pressed.
To do this, user's input is the value QT::Key_2 (50).
But if you press SHIFT+2 and then release it, you will never get the QT::Key_2 value, even the fact that you pressed the key. You wil receive only QT::Key_At value.
The solution may be the one pointed above and keep both values: key() and keyPressEvent()...

But to do this I need to know the corresponding nativeVirtualKey value of user input. Ok, inside keyPressEvent() or keyReleaseEvent() is easy to convert a key() into nativeVirtualKey(). How can I convert a Qt::Key (eg. Qt::Key_At) into nativeVirtualKey code outside of a keyPressEvent() or a keyReleaseEvent()?

d_stranz
16th September 2017, 17:26
But to do this I need to know the corresponding nativeVirtualKey value of user input.

The trouble is, I don't think the native virtual key codes are platform-independent. The VK_xxx codes on Windows that are returned as the "native virtual key code" in Qt are Windows-specific and defined by Microsoft (https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx). You might want to read this discussion (https://stackoverflow.com/questions/8737566) of the problem.