Fix focus for items inside a QQuickWidget in a QGraphicsProxyWidget

QQuickWidgetRenderControl::renderWindowFor() did not take the proxy
widget into account, making it impossible to give focus to items
inside a QGraphicsProxyWidget by clicking on the item.
This patch is based on a patch by Giuseppe D'Angelo <giuseppe.dangelo@kdab.com>,
which fixed renderWindowFor(), but did not handle the case where a proxy widget
was in multiple views.

This version of the patch adds QQuickRenderControlPrivate::isRenderWindowFor(),
which allows all the views of the proxy widget to handle focus.

This patch also carefully preserves the non-obvious feature of the
previous implementation where all windows are considered to have focus
if QGuiApplication::focusWindow() == nullptr.

[ChangeLog][QuickWidget][Quick items inside a QuickWidget that is inside a
QGraphicsProxyWidget can now get focus by clicking.]

Fixes: QTBUG-91479
Pick-to: 6.2
Change-Id: I4a6fbbbeda2d14b5a6d8eb8218d5b14a3404d9c3
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Paul Olav Tvete 2021-11-29 17:23:18 +01:00
parent 61e325460c
commit 8c0b1e06d9
7 changed files with 223 additions and 8 deletions

View File

@ -181,6 +181,14 @@ QQuickRenderControl::QQuickRenderControl(QObject *parent)
{
}
/*!
\internal
*/
QQuickRenderControl::QQuickRenderControl(QQuickRenderControlPrivate &dd, QObject * parent)
: QObject(dd, parent)
{
}
/*!
Destroys the instance. Releases all scenegraph resources.
@ -555,6 +563,14 @@ QWindow *QQuickRenderControl::renderWindowFor(QQuickWindow *win, QPoint *offset)
return nullptr;
}
bool QQuickRenderControlPrivate::isRenderWindowFor(QQuickWindow *quickWin, const QWindow *renderWin)
{
QQuickRenderControl *rc = QQuickWindowPrivate::get(quickWin)->renderControl;
if (rc)
return QQuickRenderControlPrivate::get(rc)->isRenderWindow(renderWin);
return false;
}
/*!
\return the QQuickWindow this QQuickRenderControl is associated with.

View File

@ -80,6 +80,9 @@ public:
QQuickWindow *window() const;
protected:
QQuickRenderControl(QQuickRenderControlPrivate &dd, QObject * parent);
Q_SIGNALS:
void renderRequested();
void sceneChanged();

View File

@ -71,6 +71,9 @@ public:
return renderControl->d_func();
}
static bool isRenderWindowFor(QQuickWindow *quickWin, const QWindow *renderWin);
virtual bool isRenderWindow(const QWindow *w) { Q_UNUSED(w); return false; }
static void cleanup();
void windowDestroyed();

View File

@ -49,7 +49,7 @@
#include <QtQuick/private/qquickdrag_p.h>
#endif
#include <QtQuick/private/qquickprofiler_p.h>
#include <QtQuick/qquickrendercontrol.h>
#include <QtQuick/private/qquickrendercontrol_p.h>
#include <QtQuick/private/qquickwindow_p.h>
QT_BEGIN_NAMESPACE
@ -316,7 +316,7 @@ void QQuickDeliveryAgentPrivate::translateTouchEvent(QTouchEvent *touchEvent)
static inline bool windowHasFocus(QQuickWindow *win)
{
const QWindow *focusWindow = QGuiApplication::focusWindow();
return win == focusWindow || QQuickRenderControl::renderWindowFor(win) == focusWindow;
return win == focusWindow || QQuickRenderControlPrivate::isRenderWindowFor(win, focusWindow) || !focusWindow;
}
#ifdef Q_OS_WEBOS

View File

@ -83,6 +83,11 @@
#include <QtQuick/qquickgraphicsdevice.h>
#include <QtQuick/qquickrendertarget.h>
#include "private/qwidget_p.h"
#include <QtWidgets/qgraphicsscene.h>
#include <QtWidgets/qgraphicsview.h>
QT_BEGIN_NAMESPACE
QQuickWidgetOffscreenWindow::QQuickWidgetOffscreenWindow(QQuickWindowPrivate &dd, QQuickRenderControl *control)
@ -104,19 +109,80 @@ public:
}
};
class QQuickWidgetRenderControlPrivate;
class QQuickWidgetRenderControl : public QQuickRenderControl
{
Q_DECLARE_PRIVATE(QQuickWidgetRenderControl)
public:
QQuickWidgetRenderControl(QQuickWidget *quickwidget) : m_quickWidget(quickwidget) {}
QWindow *renderWindow(QPoint *offset) override {
if (offset)
*offset = m_quickWidget->mapTo(m_quickWidget->window(), QPoint());
return m_quickWidget->window()->windowHandle();
QQuickWidgetRenderControl(QQuickWidget *quickwidget);
QWindow *renderWindow(QPoint *offset) override;
};
class QQuickWidgetRenderControlPrivate : public QQuickRenderControlPrivate
{
public:
Q_DECLARE_PUBLIC(QQuickWidgetRenderControl)
QQuickWidgetRenderControlPrivate(QQuickWidgetRenderControl *renderControl, QQuickWidget *qqw)
: QQuickRenderControlPrivate(renderControl)
, m_quickWidget(qqw)
{
}
bool isRenderWindow(const QWindow *w) override {
#if QT_CONFIG(graphicsview)
QWidgetPrivate *widgetd = QWidgetPrivate::get(m_quickWidget);
auto *proxy = (widgetd && widgetd->extra) ? widgetd->extra->proxyWidget : nullptr;
auto *scene = proxy ? proxy->scene() : nullptr;
if (scene) {
for (const auto &view : scene->views()) {
if (view->window()->windowHandle() == w)
return true;
}
}
return m_quickWidget->window()->windowHandle() == w;
#endif
}
private:
QQuickWidget *m_quickWidget;
};
QQuickWidgetRenderControl::QQuickWidgetRenderControl(QQuickWidget *quickWidget)
: QQuickRenderControl(*(new QQuickWidgetRenderControlPrivate(this, quickWidget)), nullptr)
{
}
QWindow *QQuickWidgetRenderControl::renderWindow(QPoint *offset)
{
Q_D(QQuickWidgetRenderControl);
if (offset)
*offset = d->m_quickWidget->mapTo(d->m_quickWidget->window(), QPoint());
QWindow *result = nullptr;
#if QT_CONFIG(graphicsview)
QWidgetPrivate *widgetd = QWidgetPrivate::get(d->m_quickWidget);
if (widgetd->extra) {
if (auto proxy = widgetd->extra->proxyWidget) {
auto scene = proxy->scene();
if (scene) {
const auto views = scene->views();
if (!views.isEmpty()) {
// Get the first QGV containing the proxy. Not ideal, but the callers
// of this function aren't prepared to handle more than one render window.
auto candidateView = views.first();
result = candidateView->window()->windowHandle();
}
}
}
}
#endif
if (!result)
result = d->m_quickWidget->window()->windowHandle();
return result;
}
void QQuickWidgetPrivate::initOffscreenWindow()
{
Q_Q(QQuickWidget);

View File

@ -0,0 +1,33 @@
import QtQuick
Rectangle {
width: 300
height: 100
color: "lightblue"
Column {
Rectangle {
width: 150
height: 50
color: text1.activeFocus ? "pink" : "gray"
TextInput
{
id: text1
objectName: "text1"
anchors.fill: parent
text: "Enter text"
}
}
Rectangle {
width: 150
height: 50
color: text2.activeFocus ? "yellow" : "lightgreen"
TextInput
{
id: text2
objectName: "text2"
anchors.fill: parent
text: "Enter text"
}
}
}
}

View File

@ -48,6 +48,10 @@
#include <QtQuickWidgets/QQuickWidget>
#if QT_CONFIG(graphicsview)
# include <QtWidgets/QGraphicsView>
# include <QtWidgets/QGraphicsProxyWidget>
#endif
Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests")
class MouseRecordingQQWidget : public QQuickWidget
@ -145,6 +149,10 @@ private slots:
void tabKey();
void resizeOverlay();
void controls();
void focusOnClick();
#if QT_CONFIG(graphicsview)
void focusOnClickInProxyWidget();
#endif
private:
QPointingDevice *device = QTest::createTouchDevice();
@ -767,6 +775,92 @@ void tst_qquickwidget::controls()
QVERIFY(QTest::qWaitForWindowExposed(&widget));
}
void tst_qquickwidget::focusOnClick()
{
QQuickWidget quick;
quick.setSource(testFileUrl("FocusOnClick.qml"));
quick.show();
QVERIFY(QTest::qWaitForWindowExposed(&quick));
QQuickItem *rootItem = quick.rootObject();
QVERIFY(rootItem);
QWindow *window = quick.windowHandle();
QVERIFY(window);
QQuickItem *text1 = rootItem->findChild<QQuickItem *>("text1");
QVERIFY(text1);
QQuickItem *text2 = rootItem->findChild<QQuickItem *>("text2");
QVERIFY(text2);
QTest::mouseClick(window, Qt::LeftButton, Qt::KeyboardModifiers(), QPoint(75, 25));
QTRY_VERIFY(text1->hasActiveFocus());
QVERIFY(!text2->hasActiveFocus());
QTest::mouseClick(window, Qt::LeftButton, Qt::KeyboardModifiers(), QPoint(75, 75));
QTRY_VERIFY(text2->hasActiveFocus());
QVERIFY(!text1->hasActiveFocus());
}
#if QT_CONFIG(graphicsview)
void tst_qquickwidget::focusOnClickInProxyWidget()
{
QGraphicsScene scene(0,0,400,400);
QGraphicsView view1(&scene);
view1.setFrameStyle(QFrame::NoFrame);
view1.resize(400,400);
view1.show();
QQuickWidget* quick = new QQuickWidget(testFileUrl("FocusOnClick.qml"));
quick->resize(300,100);
quick->setAttribute(Qt::WA_AcceptTouchEvents);
QGraphicsProxyWidget* proxy = scene.addWidget(quick);
proxy->setAcceptTouchEvents(true);
// QTRY_VERIFY(quick->rootObject());
QQuickItem *rootItem = quick->rootObject();
QVERIFY(rootItem);
QQuickItem *text1 = rootItem->findChild<QQuickItem *>("text1");
QVERIFY(text1);
QQuickItem *text2 = rootItem->findChild<QQuickItem *>("text2");
QVERIFY(text2);
QVERIFY(QTest::qWaitForWindowExposed(&view1));
QWindow *window1 = view1.windowHandle();
QVERIFY(window1);
// Click in the QGraphicsView, outside the QuickWidget
QTest::mouseClick(window1, Qt::LeftButton, Qt::KeyboardModifiers(), QPoint(300, 300));
QTRY_VERIFY(!text1->hasActiveFocus());
QTRY_VERIFY(!text2->hasActiveFocus());
// Click on text1
QTest::mouseClick(window1, Qt::LeftButton, Qt::KeyboardModifiers(), QPoint(75, 25));
QTRY_VERIFY(text1->hasActiveFocus());
QVERIFY(!text2->hasActiveFocus());
// Now create a second view and repeat, in order to verify that we handle one QQuickItem being in multiple windows
QGraphicsView view2(&scene);
view2.resize(400,400);
view2.show();
QVERIFY(QTest::qWaitForWindowExposed(&view2));
QWindow *window2 = view2.windowHandle();
QVERIFY(window2);
QTest::mouseClick(window2, Qt::LeftButton, Qt::KeyboardModifiers(), QPoint(300, 300));
QTRY_VERIFY(!text1->hasActiveFocus());
QTRY_VERIFY(!text2->hasActiveFocus());
QTest::mouseClick(window2, Qt::LeftButton, Qt::KeyboardModifiers(), QPoint(75, 25));
QTRY_VERIFY(text1->hasActiveFocus());
QVERIFY(!text2->hasActiveFocus());
}
#endif
QTEST_MAIN(tst_qquickwidget)
#include "tst_qquickwidget.moc"