[SOLVED] Qt 4.8.5 Focus bug, keyPresses routed via menu, OSX only



  • Hi,

    I've got a cross platform app which had a repaint issue on OS X. I fixed it by adding the attribute Qt::WA_NativeWindow to the problem window.

    I've now discovered this is causing a new problem.

    I've got a QStackedWidget with two child widgets. One is a QTextEdit, the other just a pretty standard widget.

    I can click the QTextEdit and type normally, then I switch to the other widget and back and text input has broken.

    Some characters work, eg 'q' but others eg 'w' do not. What's more I can see the menu bar flashing when I press q. Q has a menu action!

    I'm debugging Qt source and sure enough in the working state, my QTextEdit gets keyPressEvents as expected. NSWindow sendEvent -> QCocoaView keyDown:
    @0 MyInputWidget::keyPressEvent MyInputWidget.cpp 115 0x101715e37
    1 QWidget::event qwidget.cpp 8422 0x1076927ef
    2 QFrame::event qframe.cpp 557 0x107b02f55
    3 QAbstractScrollArea::event qabstractscrollarea.cpp 996 0x107bb5177
    4 QTextEdit::event qtextedit.cpp 1070 0x107b97efd
    5 QApplicationPrivate::notify_helper qapplication.cpp 4562 0x10761f665
    6 QApplication::notify qapplication.cpp 4003 0x107627b15
    7 MyQApplication::notify MyQApplication.cpp 278 0x1010c9f64
    8 QCoreApplication::notifyInternal qcoreapplication.cpp 949 0x108c56310
    9 QCoreApplication::sendSpontaneousEvent qcoreapplication.h 234 0x107584ed7
    10 qt_sendSpontaneousEvent qapplication.cpp 5560 0x10761c72b
    11 QKeyMapper::sendKeyEvent qkeymapper_mac.cpp 1014 0x1076c60c0
    12 QKeyMapperPrivate::translateKeyEvent qkeymapper_mac.cpp 886 0x1076c760d
    13 qt_dispatchKeyEvent qt_cocoa_helpers_mac.mm 839 0x1075ae40b
    14 -[QCocoaView keyDown:] qcocoaview_mac.mm 935 0x10759ca49
    15 -[NSWindow sendEvent:] 0x7fff8c5647ac
    16 -[QCocoaWindow sendEvent:] qcocoasharedwindowmethods_mac_p.h 183 0x1075a2338
    17 -[NSApplication sendEvent:] 0x7fff8c4fda55
    18 -[QNSApplication sendEvent:] qcocoaapplication_mac.mm 186 0x1075a945a
    19 -[NSApplication run] 0x7fff8c4940c6
    20 QEventDispatcherMac::processEvents qeventdispatcher_mac.mm 615 0x1075b737e
    21 QEventLoop::processEvents qeventloop.cpp 149 0x108c543d4
    22 QEventLoop::exec qeventloop.cpp 204 0x108c54814
    23 QCoreApplication::exec qcoreapplication.cpp 1221 0x108c59d82 @

    In the broken state, the keys which do make it to the QTextEdit::keyPressEvent have a different stack, and the events are now routed via NSWindow keyDown -> NSWindow keyDown -> NSMenu performKeyEquivalent:
    @0 MyInputWidget::keyPressEvent MyInputWidget.cpp 115 0x101715e37
    1 QWidget::event qwidget.cpp 8422 0x1076927ef
    2 QFrame::event qframe.cpp 557 0x107b02f55
    3 QAbstractScrollArea::event qabstractscrollarea.cpp 996 0x107bb5177
    4 QTextEdit::event qtextedit.cpp 1070 0x107b97efd
    5 QApplicationPrivate::notify_helper qapplication.cpp 4562 0x10761f665
    6 QApplication::notify qapplication.cpp 4003 0x107627b15
    7 MyQApplication::notify MyQApplication.cpp 278 0x1010c9f64
    8 QCoreApplication::notifyInternal qcoreapplication.cpp 949 0x108c56310
    9 QCoreApplication::sendSpontaneousEvent qcoreapplication.h 234 0x107584ed7
    10 qt_sendSpontaneousEvent qapplication.cpp 5560 0x10761c72b
    11 QKeyMapper::sendKeyEvent qkeymapper_mac.cpp 1014 0x1076c60c0
    12 QKeyMapperPrivate::translateKeyEvent qkeymapper_mac.cpp 886 0x1076c760d
    13 qt_dispatchKeyEvent qt_cocoa_helpers_mac.mm 839 0x1075ae40b
    14 -[QCocoaMenu menuHasKeyEquivalent:forEvent:target:action:] qcocoamenu_mac.mm 189 0x1075ff873
    15 -[NSMenu _performKeyEquivalentWithDelegate:] 0x7fff8c60575b
    16 -[NSMenu _performKeyEquivalentWithDelegate:] 0x7fff8c6059b6
    17 -[NSMenu performKeyEquivalent:] 0x7fff8c605452
    18 -[NSApplication _handleKeyEquivalent:] 0x7fff8c604175
    19 -[NSWindow keyDown:] 0x7fff8cb2fb82
    20 -[NSWindow sendEvent:] 0x7fff8c5647ac
    21 -[QCocoaWindow sendEvent:] qcocoasharedwindowmethods_mac_p.h 183 0x1075a2338
    22 -[NSApplication sendEvent:] 0x7fff8c4fda55
    23 -[QNSApplication sendEvent:] qcocoaapplication_mac.mm 186 0x1075a945a
    24 -[NSApplication run] 0x7fff8c4940c6
    25 QEventDispatcherMac::processEvents qeventdispatcher_mac.mm 615 0x1075b737e
    26 QEventLoop::processEvents qeventloop.cpp 149 0x108c543d4
    27 QEventLoop::exec qeventloop.cpp 204 0x108c54814
    28 QCoreApplication::exec qcoreapplication.cpp 1221 0x108c59d82 @

    What on earth? This only happens on OSX.

    I've checked what during MyInputWidget::keyPressEvent and MyInputWidget is indeed QApplication::focusWidget() in both cases. Why the switch to query menu actions?

    Thanks for any advice.


  • Lifetime Qt Champion

    Hi,

    Can you create a minimal compile example of this ?



  • Here's a minimal example:

    TestWidget.h
    @#ifndef TESTWIDGET_H
    #define TESTWIDGET_H

    #include <QtGui/QtGui>
    #include <QtCore/QObject>

    class TestWidget: public QWidget
    {
    Q_OBJECT
    public:
    TestWidget();

    public Q_SLOTS:
    void onPressP1();
    void onPressP2();

    private:
    QStackedWidget* _stack;
    };

    #endif // TESTWIDGET_H
    @

    TestWidget.cpp
    @#include "testpanel.h"

    #include <QtGui/QtGui>
    #include <QtOpenGL/QGLWidget>

    TestWidget::TestWidget() : QWidget()
    {
    QVBoxLayout* layout = new QVBoxLayout();
    setLayout(layout);
    _stack = new QStackedWidget();
    QPushButton* b1 = new QPushButton("p1");
    QPushButton* b2 = new QPushButton("p2");

    layout->addWidget(b1);
    layout->addWidget(b2);
    layout->addWidget(_stack);

    //QLabel* page1 = new QLabel("PAGE 1");
    QGLWidget* page1 = new QGLWidget();
    _stack->addWidget(page1);

    QTextEdit* page2 = new QTextEdit();
    _stack->addWidget(page2);

    connect(b1, SIGNAL(pressed()), this, SLOT(onPressP1()));
    connect(b2, SIGNAL(pressed()), this, SLOT(onPressP2()));
    }

    void TestWidget::onPressP1()
    {
    qDebug() << "onPressP1";
    _stack->setCurrentIndex(0);
    }
    void TestWidget::onPressP2()
    {
    qDebug() << "onPressP2";
    _stack->setCurrentIndex(1);
    setFocusProxy(_stack->currentWidget());
    }
    @

    main.cpp
    @#include <QtCore/QCoreApplication>
    #include <QtGui/QtGui>

    #include "testpanel.h"

    int main(int argc, char *argv[])
    {
    QApplication app(argc, argv);

    QMainWindow* mw = new QMainWindow();
    mw->show();

    TestWidget* t = new TestWidget();
    t->setParent(mw);
    t->show();

    return app.exec();
    }@

    I dont have a .pro file - not using qmake projects, but its standard stuff.

    Once compiled, expand the window. There are 2 buttons and an uncleared QGLWidget. If you switch between the two pages of the QStackedWidget, you suddenly cannot type in the QTextEdit any more.

    If you recompile with a QLabel for page1 instead of a QGLWidget, it works fine. In fact setting any widget to use the WA_NativeWindow flag also breaks it.


  • Lifetime Qt Champion

    I just tested it with on 10.8.5 and can confirm the behavior for Qt 4.8.5 but it works on Qt 5.2. Can you open a bug report "here":http://bugreports.qt-project.com ?

    Posting the link to it would be great



  • Thanks for testing... I'll log a bug





  • I really need a patch or workaround for this. Can anyone help?



  • I've reported the bug, but it looks like no suggestion will be forthcoming any time soon. So I am trying to debug myself.

    More info. I've tried different combinations of widgets in the QStackedWidget:

    Working
    page1: QQLWidget
    page2: QLabel, QPushButton, QWidget or QGLWidget

    Working
    page1: QLineEdit, QPlainTextEdit or QTextEdit
    page2: QLineEdit, QPlainTextEdit or QTextEdit

    Broken
    page1: QGLWidget
    page2: QLineEdit, QPlainTextEdit, QTextEdit or QComboBox

    Broken
    page1: QLineEdit, QPlainTextEdit, QTextEdit or QComboBox
    page2: QGLWidget

    So to reproduce it you definitely need a QGLWidget and some sort of widget which accepts focus in the same stack.

    Significantly, you can switch between the two pages and see key presses received correctly (using an event filter). It works until you focus the focusable widget (the QTextEdit becomes the QApplication::focusWidget()). Then when you change pages and back, it is broken.

    EDIT - Found that QComboBox also exhibits the problem, so its not confined to text entry widgets



  • My debug efforts show that in the scenario where a QStackedWidget has a mix of Native and alien widgets in its stack, the focussing is broken. Exact repro steps:

    • start app which has a QStackedWidget showing page1. Page1 is a widget with WA_NativeWindow set. Page2 is a Widget which accepts focus, eg QLineEdit.
    • switch to page2. Focus the widget with the mouse. You can type in the QLineEdit .
    • switch to page1. QStackedLayout::setCurrentIndex switches focus from the QLineEdit to the Native widget.
    • switch to page2. QStackedLayout::setCurrentIndex tries to focus the QLineEdit, but this line returns NIL, qwidget_mac.mm:3914, QWidgetPrivate::setFocus_sys:

    @NSView *view = qt_mac_nativeview_for(q);@

    The next line then does makeFirstResponder:nil, which unfocuses widgets and routes further keyboard events to the top level application instead.

    My patch ensures we always get a valid native view by calling:

    @NSView *view = qt_mac_effectiveview_for(q);@

    Is this the right approach?



  • Further evidence that my fix is correct. It brings OSX into line with other platforms. On windows, we setFocus on the effectiveWinId. But on OSX, we were using internalWinID.

    On Windows, qwidget_win.cpp:

    @void QWidgetPrivate::setFocus_sys()
    {
    Q_Q(QWidget);
    if (q->testAttribute(Qt::WA_WState_Created) && q->window()->windowType() != Qt::Popup)
    SetFocus(q->effectiveWinId());
    }@

    On OSX, qwidget_mac.mm:
    @void QWidgetPrivate::setFocus_sys()
    {
    Q_Q(QWidget);
    if (q->testAttribute(Qt::WA_WState_Created)) {
    #ifdef QT_MAC_USE_COCOA
    QMacCocoaAutoReleasePool pool;
    //NSView *view = qt_mac_nativeview_for(q); // WRONG as it uses internalWinId
    NSView *view = qt_mac_effectiveview_for(q); // CORRECT as it uses effectiveWinId
    [[view window] makeFirstResponder:view];
    #else
    SetKeyboardFocus(qt_mac_window_for(q), qt_mac_nativeview_for(q), 1);
    #endif
    }
    }

    Q_GUI_EXPORT OSViewRef qt_mac_nativeview_for(const QWidget *w)
    {
    return reinterpret_cast<OSViewRef>(w->internalWinId());
    }

    Q_GUI_EXPORT OSViewRef qt_mac_effectiveview_for(const QWidget *w)
    {
    // Get the first non-alien (parent) widget for
    // w, and return its NSView (if it has one):
    return reinterpret_cast<OSViewRef>(w->effectiveWinId());
    }
    @



  • Thanks for submitting the patch, SGaist!


  • Lifetime Qt Champion

    You're welcome !

    Since it will be working now, please update the thread title prepending [solved] so other forum users may know a solution has been found :)


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.