I have a QMainWindow with an event filter installed.
After I open and close a QDialog the keyboard arrow keys don't respond since QMainWindow receives only ShortcutOverride events instead of KeyPress events.
When I changed the QMainWindow's event filter to handle ShortcutOverride events I got a strange behavior since in my program each key press event is preceded by two shortcut override events (why??).
This doesn't work - events are handled more than once:
bool eventFilter(QObject *, QEvent *event) {
if(type == QEvent::KeyPress || type == QEvent::ShortcutOverride) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
switch(keyEvent->key()) {
case Qt::Key_Up:
case Qt::Key_Down:
case Qt::Key_Left:
case Qt::Key_Right:
// Do something here
break;
default:
break;
}
return true;
}
return false;
}
Why is this happening? Where do those ShortcutOverride events come from?
Edit:
As it turns out, QMainwindow loses focus when the QDialog is opened and never gains it back when the QDialog is closed.
I used setFocus() in the eventFilter of QMainwindow and it seems to do the trick.
Lost focus is exactly the reason for not getting any keyboard events after a dialog opens up. The solution you suggested is probably the best fix for this issue.
As for ShortcutOverride events, they come for every keypress because any key or combination of keys can be registered as a shortcut. This event gives you an option to cancel shortcut handling, if needed. It has to be processed at the earliest possible moment when the engine doesn't know yet if the pressed key is going to be used as a shortcut or not. Thus the event is generated for all key presses "just in case". You may read more here: https://wiki.qt.io/ShortcutOverride
It is happens when a key press in child is made. It is used to for overriding shortcut key handling (QKeyEvent).
refer http://qt-project.org/doc/qt-4.8/qevent.html#Type-enum
Related
I've implemented a Qt3DWidget which works fairly well by letting Qt3D draw to an offscreen texture and using the texture's ID to draw on a quad in a QOpenGLWidget. The input source on the QInputSettings is set to this, i.e. the widget itself.
One issue that remains is that mouse hovering events (without clicking) are not properly processed and a QObjectPicker never fires its moved event when only hovering over an object. Clicking and dragging works on the other hand. For a couple of hours now I tried to track down where the event gets eaten - I'm sure this happens somewhere because clicking and moving emits the moved event. The latter indicates (in my view) that the event filter (a PickEventFilter - private Qt3D class) has been successfully installed. One failure case would have been that the event filter doesn't get installed.
Now I'm kind of stuck because it just seems impossible trying to figure out where the event dies. I've got the Qt debugging symbols and stepped through the code (which is sometimes a bit buggy because maybe due to code optimization). I figured out that the PickEventFilter gets the leaving event when moving the mouse by setting a breakpoint in this method:
bool PickEventFilter::eventFilter(QObject *obj, QEvent *e)
{
Q_UNUSED(obj);
switch (e->type()) {
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseMove: {
QMutexLocker locker(&m_mutex);
m_pendingMouseEvents.push_back({obj, QMouseEvent(*static_cast<QMouseEvent *>(e))});
} break;
case QEvent::HoverMove: {
QMutexLocker locker(&m_mutex);
QHoverEvent *he = static_cast<QHoverEvent *>(e);
m_pendingMouseEvents.push_back({obj, QMouseEvent(QEvent::MouseMove,
he->pos(), Qt::NoButton, Qt::NoButton,
he->modifiers())});
} break;
case QEvent::KeyPress:
case QEvent::KeyRelease: {
QMutexLocker locker(&m_mutex);
m_pendingKeyEvents.push_back(QKeyEvent(*static_cast<QKeyEvent *>(e)));
}
default:
break;
}
return false;
}
And also on this line in QWidgetWindow:
QApplicationPrivate::sendMouseEvent(receiver, &translated, widget, m_widget,
&qt_button_down, qt_last_mouse_receiver);
When sendMouseEvent sends the moving event it never reaches the event filter. The only thing that arrives is a leave event. When you use createWindowContainer and Qt3DWindow the mouse events work. I really don't know what the difference is.
I don't think it's feasible to post code related to this issue but I'd hope that some of you can provide some ideas what to try out.
Thanks to Scheff in the comments I was able to make it work by setting setMouseTracking in the QWidget class to true. This was blocking the hovering events.
So, I have an application where if a particular button is kept pressed it plays an audio device, when the button is released it stops the audio device. I use keyPressEvent and KeyReleaseEvent to implement this which is similar to the code below:
void MainWindow::keyPressEvent(QKeyEvent *event)
{
if(event->isAutoRepeat())
{
event->ignore();
}
else
{
if(event->key() == Qt::Key_0)
{
qDebug()<<"key_0 pressed"<<endl;
}
else
{
QWidget::keyPressEvent(event);
}
}
}
void MainWindow::keyReleaseEvent(QKeyEvent *event)
{
if(event->isAutoRepeat())
{
event->ignore();
}
else
{
if(event->key() == Qt::Key_0)
{
qDebug()<<"key_0 released"<<endl;
}
else
{
QWidget::keyReleaseEvent(event);
}
}
}
But apparently isAutoRepeat function isn't working as I can see continuous print out of key_0 pressed and key_0 released despite the fact I haven't released the 0 key after I have pressed it. Is my code wrong or something else is wrong?
Thanks.
EDIT
I think this is happening because the MainWindow loses the keyboard focus. How can I actually find out which widget has the focus? I'm actually using some widgets when Qt::Key_0 pressed, but I thought I set all those possible widgets to Qt::NoFocus, I guess it's not working.
I'm trying to know which widget has the focus by doing the following:
QWidget * wigdet = QApplication::activeWindow();
qDebug()<<wigdet->accessibleName()<<endl;
but it always prints an empty string. How can I make it print the name of the widget which has the keyboard focus?
So as I also stumbled over this issue (and grabKeyboard didn't really help), I begun digging in qtbase. It is connected to X11 via xcb, and by default, in case of repeated keys, X11 sends for each repeated key a release-event immediately followed by a key-press-event. So holding down a key results in a sequence of XCB_BUTTON_RELEASE/XCB_BUTTON_PRESS-events beeing sent to the client (try it out with xev or the source at the end of this page).
Then, qt (qtbase/src/plugins/platforms/xcb/qxcbkeyboard.cpp) tries to figure out from these events whether its an autorepeat case: when a release is received, it uses a lookahead feature to figure if its followed by a press (with timestamps close enough), and if so it assumes autorepeat.
This does not always work, at least not on all platforms. For my case (old and outworn slow laptop (Intel® Celeron(R) CPU N2830 # 2.16GHz × 2) running ubuntu 16.04), it helped to just put a usleep (500) before that check, allowing the press event following the release event to arrive... it's around line 1525 of qxcbkeyboard.cpp:
// look ahead for auto-repeat
KeyChecker checker(source->xcb_window(), code, time, state);
usleep(500); // Added, 100 is to small, 200 is ok (for me)
xcb_generic_event_t *event = connection()->checkEvent(checker);
if (event) {
...
Filed this as QTBUG-57335.
Nb: The behaviour of X can be changed by using
Display *dpy=...;
Bool result;
XkbSetDetectableAutoRepeat (dpy, true, &result);
Then it wont send this release-press-sequences in case of a hold down key, but using it would require more changes to the autorepeat-detection-logic.
Anyway solved it.
The problem was that I have a widget which is a subclass of QGLWidget which I use to show some augmented reality images from Kinect. This widget takes over the keyboard focus whenever a keyboard button is pressed.
To solve this problem, I needed to call grabKeyboard function from the MainWindow class (MainWindow is a subclass of QMainWindow), so this->grabKeyboard() is the line I needed to add when key_0 button is pressed so that MainWindow doesn't lose the keyboard focus, and then when the key is released I needed to add the line this->releaseKeyboard() to resume normal behaviour, that is, other widgets can have the keyboard focus.
I want to intercept Tab key press in my main window to prevent Qt from switching focus. Here's what I've tried so far:
bool CMainWindow::event(QEvent * e)
{
if (e && e->type() == QEvent::KeyPress)
{
QKeyEvent * keyEvent = dynamic_cast<QKeyEvent*>(e);
if (keyEvent && keyEvent->key() == Qt::Key_Tab)
return true;
}
return QMainWindow::event(e);
}
This doesn't work, event isn't called when I press Tab. How to achieve what I want?
The most elegant way I found to avoid focus change is to reimplement in your class derived from QWidget the method bool focusNextPrevChild(bool next) and simply return FALSE. In case you want to allow it, return TRUE.
Like other keys you get now also the key Qt::Key_Tab in keyPressEvent(QKeyEvent* event)
Reimplementing virtual bool QApplication::notify(QObject * receiver, QEvent * e) and pasting the code from my question there works.
You can achieve by using setFocusPolicy( Qt::NoFocus) property of QWidget. You can set Focus policy on widget which doesn't require tab focus. I think the reason why event handler is not calling, because Tab is managed by Qt framework internally. Please see QWidget::setTabOrder API, which is static.
You'll need to install an event filter on your main window in order to receive the events. You can use installEventFilter method for this.
Another option is to override the keyPressEvent method to handle the key presses.
I'm experiencing some difficulties with catching application-scope events in Qt.
My purpose is to catch every user action in some GUI application. Of course I've overrided QApplication::notify handler and processing different types of application-scope events there. Something like that:
bool CoolApplication::notify(QObject *obj, QEvent *e)
{
switch (e->type()) {
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
// Handle mouse
break;
case QEvent::Wheel:
// Handle wheel
break;
case QEvent::KeyPress:
case QEvent::KeyRelease:
// Handle keyboard and so on
break;
default:
break;
}
return QApplication::notify(obj, e);
}
That works pretty well until user starts drag-and-drop operation. Mouse press event is handled well, it is catched in my handler, life is beautiful there. The problems start when I try to catch mouse release event on Windows.
When entering drag operation mouse and keyboard events are no longer handled by QApplication::notify. It is possible to catch drop event when it was successfull (I can add case QEvent::Drop branch into my handler), but how to catch ignored drop or canceling drop with Esc? It seems impossible to be done directly, maybe I can catch drag event loop termination event or something like that? Or even maybe someone knows a direct way to do it?
Edit:
Once more turning the attention that I need to catch application-scope event, not the widget one
Please, try this
bool QtCoreApplication::notify(QObject *obj, QEvent *e)
{
switch (e->type()) {
case QEvent::QDropEvent:
QDropEvent* dropEvent = (QDropEvent*)e;
dropEvent->keyboardModifiers(); // keys pressed
dropEvent->dropAction(); // Drop result
break;
return QApplication::notify(obj, e);
}
As this remained an open item to us, we checked the usage of the destroyed signal.
This is fired at the moment the element is discarded, which seems to happen a the moment the QDrag is killed by the Escape key pressure
void dragDestroyed()
{
qDebug("drag and drop cancled by Escape key");
}
QDrag *drag = new QDrag(this);
connect( drag, SIGNAL(destroyed()), this, SLOT(dragDestroyed()) );
drag->exec(Qt::MoveAction);
probably this is relevant for anyone facing the issue.
How to make Esc key to minimize a dialog? By default it closes. Should I process KeyEvent or there is a better way?
I think you may use this:
void MyDialog::keyPressEvent(QKeyEvent *e) {
if(e->key() != Qt::Key_Escape)
QDialog::keyPressEvent(e);
else {/* minimize */}
}
Also have a look at Events and Event Filters docs.
Escape calls reject(). I override this function (in my case not to minimize the dialog but to prompt to save)
void MyDialog::reject() {if(cleanupIsOK()) done(0);}
Al_
Renaming the reject is correct. But be careful because if you want to close the dialog in other way you cannot call close.
MyDialog::reject(){
if(some_closing_condition)
{
QDialog::reject() //calls the default close.
}
else
{
//skip reject operation
}
}
I think that to do this, you would basically have to avoid inheriting from QDialog. The documentation for QDialog says:
Escape Key
If the user presses the Esc key in a
dialog, QDialog::reject() will be
called. This will cause the window to
close: The close event cannot be
ignored.
Interestingly Qt docs state ESC calls reject()
Escape Key
If the user presses the Esc key in a dialog, QDialog::reject() will be
called. This will cause the window to close: The close event cannot be
ignored
yet QDialog::reject() documentation says hides. i.e closeEvent() is not called, which I have confirmed to be the case.
void QDialog::reject()
Hides the modal dialog and sets the result code to Rejected