QWidget: fix regression when changing focus proxy while it has focus

This follows up on commits 3e7463411e and
947883141d.

The changing of the pointer of QApplicationPrivate does not transfer
focus properly. It updates the pointer, but it doesn't deliver events
or update the widget hierarchy's focus chain. The result is that
multiple line edits might show a blinking cursor.

Instead, use QWidget::setFocus when the focus proxy has changed while
it had focus, and pass OtherFocusReason rather than NoFocusReason.

Add a basic test for QWidget::focusProxy, which exercises this code
path and verifies that pointers are consistent when focus changes as
a side effect of modifying the focusProxy.

Change-Id: I15a4d868bab2b590cfe4a1daa6a3c8cebc9c9ca2
Fixes: QTBUG-83720
Fixes: QTBUG-79707
Pick-to: 5.15
Reviewed-by: David Faure <david.faure@kdab.com>
bb10
Volker Hilsheimer 2020-04-23 13:12:06 +02:00
parent 574898c9eb
commit 23b998fa45
2 changed files with 99 additions and 6 deletions

View File

@ -6197,18 +6197,17 @@ void QWidget::setFocusProxy(QWidget * w)
}
}
QWidget *oldDeepestFocusProxy = d_func()->deepestFocusProxy();
QWidget *oldDeepestFocusProxy = d->deepestFocusProxy();
if (!oldDeepestFocusProxy)
oldDeepestFocusProxy = this;
const bool changingAppFocusWidget = (QApplicationPrivate::focus_widget == oldDeepestFocusProxy);
const bool focusProxyHadFocus = (QApplicationPrivate::focus_widget == oldDeepestFocusProxy);
d->createExtra();
d->extra->focus_proxy = w;
if (changingAppFocusWidget) {
QWidget *newDeepestFocusProxy = d_func()->deepestFocusProxy();
QApplicationPrivate::setFocusWidget(newDeepestFocusProxy ? newDeepestFocusProxy : this, Qt::NoFocusReason);
}
if (focusProxyHadFocus)
setFocus(Qt::OtherFocusReason);
}

View File

@ -373,6 +373,7 @@ private slots:
void openModal_taskQTBUG_5804();
void focusProxy();
void focusProxyAndInputMethods();
#ifdef QT_BUILD_INTERNAL
void scrollWithoutBackingStore();
@ -10078,6 +10079,99 @@ void tst_QWidget::openModal_taskQTBUG_5804()
QVERIFY(QTest::qWaitForWindowExposed(win.data()));
}
/*!
Test that the focus proxy receives focus, and that changing the
focus proxy of a widget that has focus passes focus on correctly.
The test uses a single window, so we can rely on the window's focus
widget and the QApplication focus widget to be the same.
*/
void tst_QWidget::focusProxy()
{
QWidget window;
class Container : public QWidget
{
public:
Container()
{
edit = new QLineEdit;
edit->installEventFilter(this);
setFocusProxy(edit);
QHBoxLayout *layout = new QHBoxLayout;
layout->addWidget(edit);
setLayout(layout);
}
QLineEdit *edit;
int focusInCount = 0;
int focusOutCount = 0;
protected:
bool eventFilter(QObject *receiver, QEvent *event)
{
if (receiver == edit) {
switch (event->type()) {
case QEvent::FocusIn:
++focusInCount;
break;
case QEvent::FocusOut:
++focusOutCount;
break;
default:
break;
}
}
return QWidget::eventFilter(receiver, event);
}
};
auto container1 = new Container;
container1->edit->setObjectName("edit1");
auto container2 = new Container;
container2->edit->setObjectName("edit2");
QHBoxLayout *layout = new QHBoxLayout;
layout->addWidget(container1);
layout->addWidget(container2);
window.setLayout(layout);
window.show();
window.activateWindow();
if (!QTest::qWaitForWindowExposed(&window) || !QTest::qWaitForWindowActive(&window))
QSKIP("Window activation failed");
QCOMPARE(container1->focusInCount, 1);
QCOMPARE(container1->focusOutCount, 0);
// given a widget with a nested focus proxy
window.setFocusProxy(container1);
QCOMPARE(window.focusWidget(), container1->edit);
QCOMPARE(window.focusWidget(), QApplication::focusWidget());
QVERIFY(container1->edit->hasFocus());
QCOMPARE(container1->focusInCount, 1);
// changing the focus proxy should move focus to the new proxy
window.setFocusProxy(container2);
QCOMPARE(window.focusWidget(), container2->edit);
QCOMPARE(window.focusWidget(), QApplication::focusWidget());
QVERIFY(!container1->edit->hasFocus());
QVERIFY(container2->edit->hasFocus());
QCOMPARE(container1->focusInCount, 1);
QCOMPARE(container1->focusOutCount, 1);
QCOMPARE(container2->focusInCount, 1);
QCOMPARE(container2->focusOutCount, 0);
// clearing the focus proxy moves focus
window.setFocusProxy(nullptr);
QCOMPARE(window.focusWidget(), &window);
QCOMPARE(window.focusWidget(), QApplication::focusWidget());
QCOMPARE(container1->focusInCount, 1);
QCOMPARE(container1->focusOutCount, 1);
QCOMPARE(container2->focusInCount, 1);
QCOMPARE(container2->focusOutCount, 1);
}
void tst_QWidget::focusProxyAndInputMethods()
{
if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation))