2022-05-13 13:12:05 +00:00
|
|
|
// Copyright (C) 2016 The Qt Company Ltd.
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2014-03-19 08:21:11 +00:00
|
|
|
|
|
|
|
#include <qtest.h>
|
2017-11-30 10:36:42 +00:00
|
|
|
#include <qtesttouch.h>
|
2014-03-19 08:21:11 +00:00
|
|
|
#include <QtTest/QSignalSpy>
|
2022-04-08 15:35:03 +00:00
|
|
|
#include <QtTest/private/qtesthelpers_p.h>
|
2014-03-19 08:21:11 +00:00
|
|
|
#include <QtQml/qqmlcomponent.h>
|
|
|
|
#include <QtQml/qqmlcontext.h>
|
|
|
|
#include <QtQuick/qquickview.h>
|
|
|
|
#include <QtQuick/qquickitem.h>
|
Resize offscreen window when QQuickWidget is resized
In a typical Qt Quick application, when a window is resized, the
contentItem of that window is resized with it, and then the root item.
QQuickOverlay in qtquickcontrols2 listens to size changes in the
contentItem (QQuickRootItem) via addItemChangeListener(), as a cheap
way (e.g. no signals) of knowing when to resize background dimming
effects. It resizes the dimmer item to the size of the window.
The first problem with QQuickWidget is that it only ever resizes the root item
when using the SizeRootObjectToView resize mode, and not the contentItem.
The second problem is that the root item is resized (via updateSize()) before
the window itself even has a size (which happens in
QQuickWidget::createFramebufferObject() via the call to
d->offscreenWindow->setGeometry()).
To demonstrate the second problem in detail, consider the following widget
hierarchy (written in everybody's favorite language: QML):
QMainWindow {
QQuickWidget {
QQuickWindow { // QQuickWidgetPrivate::offscreenWindow
QQuickRootItem { // QQuickWindowPrivate::contentItem
Page {} // QQuickWidgetPrivate::root
}
}
}
}
The QMainWindow starts off as 200x200. When the window is resized,
QQuickWidget::resizeEvent() is called. The first thing it does is call
updateSize(), which in the case of SizeRootObjectToView, resizes the root item
to 300x300. This causes QQuickOverlayPrivate::itemGeometryChanged() to be
called, and the dimmers are resized to the size of the window, but the window
still has its 200x200 size, as it is only updated later, when
QQuickWidget::createFramebufferObject() is called.
This patch fixes these issues by ensuring that contentItem and the window
itself are resized along with the root item.
As to why such manual intervention is necessary: from what I can see, it is
because it's an "offscreen" window. This means that
QWindowPrivate::platformWindow is null, and setGeometry() takes a different
path that presumably results in no QResizeEvent being sent to the QQuickWindow.
As QQuickWindow relies on resizeEvent() being called to resize its contentItem,
the contentItem is never resized. With a typical Qt Quick application, all of
this works as expected.
Change-Id: I7401aa7a9b209096183416ab53014f67cceccbe4
Fixes: QTBUG-78323
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io>
Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
2020-03-05 15:00:04 +00:00
|
|
|
#include <QtQuick/private/qquickitem_p.h>
|
2022-03-18 12:13:24 +00:00
|
|
|
#include <QtQuick/private/qquickmousearea_p.h>
|
2022-12-14 08:52:51 +00:00
|
|
|
#include <QtQuickTemplates2/private/qquickbutton_p.h>
|
2021-08-06 10:27:35 +00:00
|
|
|
#include <QtQuickTestUtils/private/qmlutils_p.h>
|
2014-03-19 08:21:11 +00:00
|
|
|
#include <QtGui/QWindow>
|
2018-12-05 10:28:47 +00:00
|
|
|
#include <QtGui/QScreen>
|
2016-06-22 10:17:15 +00:00
|
|
|
#include <QtGui/QImage>
|
2014-03-19 08:21:11 +00:00
|
|
|
#include <QtCore/QDebug>
|
|
|
|
#include <QtQml/qqmlengine.h>
|
|
|
|
|
2017-11-30 10:36:42 +00:00
|
|
|
#include <QtCore/QLoggingCategory>
|
2017-11-01 14:40:14 +00:00
|
|
|
#include <QtGui/qstylehints.h>
|
2017-08-14 08:29:53 +00:00
|
|
|
#include <QtWidgets/QBoxLayout>
|
|
|
|
#include <QtWidgets/QLabel>
|
2023-04-05 15:12:07 +00:00
|
|
|
#include <QtWidgets/private/qapplication_p.h>
|
2017-08-14 08:29:53 +00:00
|
|
|
|
2014-03-19 08:21:11 +00:00
|
|
|
#include <QtQuickWidgets/QQuickWidget>
|
|
|
|
|
2021-11-29 16:23:18 +00:00
|
|
|
#if QT_CONFIG(graphicsview)
|
|
|
|
# include <QtWidgets/QGraphicsView>
|
|
|
|
# include <QtWidgets/QGraphicsProxyWidget>
|
|
|
|
#endif
|
2017-11-30 10:36:42 +00:00
|
|
|
Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests")
|
|
|
|
|
|
|
|
class MouseRecordingQQWidget : public QQuickWidget
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
explicit MouseRecordingQQWidget(QWidget *parent = nullptr) : QQuickWidget(parent) {
|
|
|
|
setAttribute(Qt::WA_AcceptTouchEvents);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
void mousePressEvent(QMouseEvent *event) override {
|
|
|
|
qCDebug(lcTests) << event;
|
2020-11-11 12:51:35 +00:00
|
|
|
m_mouseEvents << event->source();
|
2017-11-30 10:36:42 +00:00
|
|
|
QQuickWidget::mousePressEvent(event);
|
|
|
|
}
|
|
|
|
void mouseMoveEvent(QMouseEvent *event) override {
|
|
|
|
qCDebug(lcTests) << event;
|
2020-11-11 12:51:35 +00:00
|
|
|
m_mouseEvents << event->source();
|
2017-11-30 10:36:42 +00:00
|
|
|
QQuickWidget::mouseMoveEvent(event);
|
|
|
|
}
|
|
|
|
void mouseReleaseEvent(QMouseEvent *event) override {
|
|
|
|
qCDebug(lcTests) << event;
|
2020-11-11 12:51:35 +00:00
|
|
|
m_mouseEvents << event->source();
|
2017-11-30 10:36:42 +00:00
|
|
|
QQuickWidget::mouseReleaseEvent(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
public:
|
2020-11-11 12:51:35 +00:00
|
|
|
QList<Qt::MouseEventSource> m_mouseEvents;
|
2017-11-30 10:36:42 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
class MouseRecordingItem : public QQuickItem
|
|
|
|
{
|
|
|
|
public:
|
2022-03-17 16:41:59 +00:00
|
|
|
MouseRecordingItem(bool acceptTouch, bool acceptTouchPress, QQuickItem *parent = nullptr)
|
2017-11-30 10:36:42 +00:00
|
|
|
: QQuickItem(parent)
|
2022-03-17 16:41:59 +00:00
|
|
|
, m_acceptTouchPress(acceptTouchPress)
|
2017-11-30 10:36:42 +00:00
|
|
|
{
|
|
|
|
setSize(QSizeF(300, 300));
|
|
|
|
setAcceptedMouseButtons(Qt::LeftButton);
|
2022-03-17 16:41:59 +00:00
|
|
|
setAcceptTouchEvents(acceptTouch);
|
2017-11-30 10:36:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
void touchEvent(QTouchEvent* event) override {
|
2022-03-17 16:41:59 +00:00
|
|
|
event->setAccepted(m_acceptTouchPress);
|
2020-11-11 12:51:35 +00:00
|
|
|
m_touchEvents << event->type();
|
2017-11-30 10:36:42 +00:00
|
|
|
qCDebug(lcTests) << "accepted?" << event->isAccepted() << event;
|
|
|
|
}
|
|
|
|
void mousePressEvent(QMouseEvent *event) override {
|
|
|
|
qCDebug(lcTests) << event;
|
2020-11-11 12:51:35 +00:00
|
|
|
m_mouseEvents << event->source();
|
2017-11-30 10:36:42 +00:00
|
|
|
}
|
|
|
|
void mouseMoveEvent(QMouseEvent *event) override {
|
|
|
|
qCDebug(lcTests) << event;
|
2020-11-11 12:51:35 +00:00
|
|
|
m_mouseEvents << event->source();
|
2017-11-30 10:36:42 +00:00
|
|
|
}
|
|
|
|
void mouseReleaseEvent(QMouseEvent *event) override {
|
|
|
|
qCDebug(lcTests) << event;
|
2020-11-11 12:51:35 +00:00
|
|
|
m_mouseEvents << event->source();
|
2017-11-30 10:36:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public:
|
2020-11-11 12:51:35 +00:00
|
|
|
QList<Qt::MouseEventSource> m_mouseEvents;
|
|
|
|
QList<QEvent::Type> m_touchEvents;
|
2017-11-30 10:36:42 +00:00
|
|
|
|
|
|
|
private:
|
2022-03-17 16:41:59 +00:00
|
|
|
bool m_acceptTouchPress;
|
2017-11-30 10:36:42 +00:00
|
|
|
};
|
|
|
|
|
2014-03-19 08:21:11 +00:00
|
|
|
class tst_qquickwidget : public QQmlDataTest
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
public:
|
|
|
|
tst_qquickwidget();
|
|
|
|
|
|
|
|
private slots:
|
|
|
|
void showHide();
|
|
|
|
void reparentAfterShow();
|
|
|
|
void changeGeometry();
|
|
|
|
void resizemodeitem();
|
2017-08-14 08:29:53 +00:00
|
|
|
void layoutSizeChange();
|
2014-03-19 08:21:11 +00:00
|
|
|
void errors();
|
|
|
|
void engine();
|
|
|
|
void readback();
|
2015-03-26 20:34:42 +00:00
|
|
|
void renderingSignals();
|
2017-06-20 20:15:48 +00:00
|
|
|
void grab();
|
2015-12-14 08:44:40 +00:00
|
|
|
void grabBeforeShow();
|
2016-06-22 10:17:15 +00:00
|
|
|
void reparentToNewWindow();
|
2016-06-24 08:22:47 +00:00
|
|
|
void nullEngine();
|
2017-02-22 15:16:14 +00:00
|
|
|
void keyEvents();
|
2017-05-23 12:34:23 +00:00
|
|
|
void shortcuts();
|
2015-12-02 08:51:35 +00:00
|
|
|
void enterLeave();
|
2018-01-16 13:29:40 +00:00
|
|
|
void mouseEventWindowPos();
|
2017-11-30 10:36:42 +00:00
|
|
|
void synthMouseFromTouch_data();
|
|
|
|
void synthMouseFromTouch();
|
2022-03-18 12:13:24 +00:00
|
|
|
void touchTapMouseArea();
|
2022-12-14 08:52:51 +00:00
|
|
|
void touchTapButton();
|
|
|
|
void touchMultipleWidgets();
|
2017-11-01 14:40:14 +00:00
|
|
|
void tabKey();
|
Resize offscreen window when QQuickWidget is resized
In a typical Qt Quick application, when a window is resized, the
contentItem of that window is resized with it, and then the root item.
QQuickOverlay in qtquickcontrols2 listens to size changes in the
contentItem (QQuickRootItem) via addItemChangeListener(), as a cheap
way (e.g. no signals) of knowing when to resize background dimming
effects. It resizes the dimmer item to the size of the window.
The first problem with QQuickWidget is that it only ever resizes the root item
when using the SizeRootObjectToView resize mode, and not the contentItem.
The second problem is that the root item is resized (via updateSize()) before
the window itself even has a size (which happens in
QQuickWidget::createFramebufferObject() via the call to
d->offscreenWindow->setGeometry()).
To demonstrate the second problem in detail, consider the following widget
hierarchy (written in everybody's favorite language: QML):
QMainWindow {
QQuickWidget {
QQuickWindow { // QQuickWidgetPrivate::offscreenWindow
QQuickRootItem { // QQuickWindowPrivate::contentItem
Page {} // QQuickWidgetPrivate::root
}
}
}
}
The QMainWindow starts off as 200x200. When the window is resized,
QQuickWidget::resizeEvent() is called. The first thing it does is call
updateSize(), which in the case of SizeRootObjectToView, resizes the root item
to 300x300. This causes QQuickOverlayPrivate::itemGeometryChanged() to be
called, and the dimmers are resized to the size of the window, but the window
still has its 200x200 size, as it is only updated later, when
QQuickWidget::createFramebufferObject() is called.
This patch fixes these issues by ensuring that contentItem and the window
itself are resized along with the root item.
As to why such manual intervention is necessary: from what I can see, it is
because it's an "offscreen" window. This means that
QWindowPrivate::platformWindow is null, and setGeometry() takes a different
path that presumably results in no QResizeEvent being sent to the QQuickWindow.
As QQuickWindow relies on resizeEvent() being called to resize its contentItem,
the contentItem is never resized. With a typical Qt Quick application, all of
this works as expected.
Change-Id: I7401aa7a9b209096183416ab53014f67cceccbe4
Fixes: QTBUG-78323
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io>
Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
2020-03-05 15:00:04 +00:00
|
|
|
void resizeOverlay();
|
Handle redirected rendering better in styles
Unlike in the QWidget-based desktop world, Qt Quick scenes can
be rendered in a variety of ways, some completely offscreen
wthout any native windows on screen, whereas some (most notably,
QQuickWidget) work offscreen but in association with an on-screen
window that is not the QQuickWindow. Therefore, every time a
QQuickWindow is accessed, typically from QQuickStyleItem, it needs
to be considered if further resolution is needed.
For devicePixelRatio, there is a handy helper available in form of
QQuickWindow::effectiveDevicePixelRatio(). This picks up the dpr
from either the QQuickWindow or the QQuickWidget's associated
top-level QWidget window (or whatever window a custom
QQuickRenderControl implementation reports).
Elsewhere, where we need a QWindow in order to do native window
things, QQuickRenderControl::renderWindowFor() must be called to see
if there is another QWindow we should be using in place of the
QQuickWindow.
Pick-to: 6.2
Fixes: QTBUG-95937
Change-Id: I0690915d995ebb5f5cc0c48f565dfaf978e849ea
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
Reviewed-by: Samuel Ghinet <samuel.ghinet@qt.io>
2021-08-20 08:43:34 +00:00
|
|
|
void controls();
|
2021-11-29 16:23:18 +00:00
|
|
|
void focusOnClick();
|
|
|
|
#if QT_CONFIG(graphicsview)
|
|
|
|
void focusOnClickInProxyWidget();
|
|
|
|
#endif
|
2023-04-05 15:12:07 +00:00
|
|
|
void focusPreserved();
|
QQuickWidget: don't crash in accessibility when reparenting
QAccessibleQuickWidget delegates all calls to the QAccessibleQuickWindow,
which it had as a member that was initialized at construction time to
the offscreenWindow backing the QQuickWidget. Both are QAccessibleObject
subclasses, and QAccessibleObject stores the object it wraps as a
QPointer. The QAccessibleQuickWindow's object becomes null when that
offscreen window gets destroyed (for instance, when reparenting).
We might get called by the accessibility framework in that situation, as
we are clicking a button and the hierarchy changes.
To prevent crashes, we need to test for nullptr in QAccessibleQuickWindow.
However, that alone would leave us with a useless QAccessibleQuickWindow,
and in turn with a useless QAccessibleQuickWidget instance.
The QAccessibleQuickWindow is not directly exposed to the
accessibility framework, and all calls to it are dispatched through its
QAccessibleQuickWidget owner. We can't repair the QAccessibleQuickWindow
but we can replace it entirely if we manage it as a heap-allocated
object. Use a std::unique_ptr for that, which we can reset with a new
instance created from a new offscreen window in order to repair things.
We can now either test in all functions whether the window's window is
still alive. Or we can handle the destroyed() signal of the offscreen
window. The latter solution is a bit more involved, but generally more
scalable as we don't have to remember to check, and possibly repair, in
each QAccessibleQuickWidget function.
Pick-to: 6.5
Fixes: QTBUG-108226
Change-Id: Ib19c07d3679c0af28cb5aab4c80691cc57c4e514
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Jan Arve Sæther <jan-arve.saether@qt.io>
2023-04-08 16:53:14 +00:00
|
|
|
void accessibilityHandlesViewChange();
|
2023-12-19 20:52:05 +00:00
|
|
|
void cleanupRhi();
|
2014-03-19 08:21:11 +00:00
|
|
|
|
2017-11-30 10:36:42 +00:00
|
|
|
private:
|
2020-03-26 15:50:40 +00:00
|
|
|
QPointingDevice *device = QTest::createTouchDevice();
|
2018-12-05 10:28:47 +00:00
|
|
|
const QRect m_availableGeometry = QGuiApplication::primaryScreen()->availableGeometry();
|
2017-11-30 10:36:42 +00:00
|
|
|
};
|
2014-03-19 08:21:11 +00:00
|
|
|
|
|
|
|
tst_qquickwidget::tst_qquickwidget()
|
2021-08-06 10:27:35 +00:00
|
|
|
: QQmlDataTest(QT_QMLTEST_DATADIR)
|
2014-03-19 08:21:11 +00:00
|
|
|
{
|
2021-11-08 15:16:16 +00:00
|
|
|
}
|
|
|
|
|
2014-03-19 08:21:11 +00:00
|
|
|
void tst_qquickwidget::showHide()
|
|
|
|
{
|
|
|
|
QWidget window;
|
|
|
|
|
|
|
|
QQuickWidget *childView = new QQuickWidget(&window);
|
|
|
|
childView->setSource(testFileUrl("rectangle.qml"));
|
|
|
|
|
|
|
|
window.show();
|
2018-01-31 19:05:38 +00:00
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(&window));
|
2019-04-02 14:07:34 +00:00
|
|
|
QVERIFY(!childView->quickWindow()->isVisible()); // this window is always not visible see QTBUG-65761
|
2015-11-16 10:21:00 +00:00
|
|
|
QVERIFY(childView->quickWindow()->visibility() != QWindow::Hidden);
|
2014-03-19 08:21:11 +00:00
|
|
|
|
2015-11-16 10:21:00 +00:00
|
|
|
window.hide();
|
|
|
|
QVERIFY(!childView->quickWindow()->isVisible());
|
|
|
|
QCOMPARE(childView->quickWindow()->visibility(), QWindow::Hidden);
|
2014-03-19 08:21:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void tst_qquickwidget::reparentAfterShow()
|
|
|
|
{
|
|
|
|
QWidget window;
|
|
|
|
|
|
|
|
QQuickWidget *childView = new QQuickWidget(&window);
|
|
|
|
childView->setSource(testFileUrl("rectangle.qml"));
|
|
|
|
window.show();
|
2018-01-31 19:05:38 +00:00
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(&window));
|
2014-03-19 08:21:11 +00:00
|
|
|
|
|
|
|
QScopedPointer<QQuickWidget> toplevelView(new QQuickWidget);
|
|
|
|
toplevelView->setParent(&window);
|
|
|
|
toplevelView->setSource(testFileUrl("rectangle.qml"));
|
|
|
|
toplevelView->show();
|
2018-01-31 19:05:38 +00:00
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(&window));
|
2014-03-19 08:21:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void tst_qquickwidget::changeGeometry()
|
|
|
|
{
|
|
|
|
QWidget window;
|
|
|
|
|
|
|
|
QQuickWidget *childView = new QQuickWidget(&window);
|
|
|
|
childView->setSource(testFileUrl("rectangle.qml"));
|
|
|
|
|
|
|
|
window.show();
|
2018-01-31 19:05:38 +00:00
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(&window));
|
2014-03-19 08:21:11 +00:00
|
|
|
|
|
|
|
childView->setGeometry(100,100,100,100);
|
|
|
|
}
|
|
|
|
|
|
|
|
void tst_qquickwidget::resizemodeitem()
|
|
|
|
{
|
|
|
|
QWidget window;
|
2018-12-05 10:28:47 +00:00
|
|
|
window.setGeometry(m_availableGeometry.left(), m_availableGeometry.top(), 400, 400);
|
2014-03-19 08:21:11 +00:00
|
|
|
|
|
|
|
QScopedPointer<QQuickWidget> view(new QQuickWidget);
|
|
|
|
view->setParent(&window);
|
|
|
|
view->setResizeMode(QQuickWidget::SizeRootObjectToView);
|
|
|
|
QCOMPARE(QSize(0,0), view->initialSize());
|
|
|
|
view->setSource(testFileUrl("resizemodeitem.qml"));
|
|
|
|
QQuickItem* item = qobject_cast<QQuickItem*>(view->rootObject());
|
|
|
|
QVERIFY(item);
|
|
|
|
window.show();
|
|
|
|
|
|
|
|
view->showNormal();
|
|
|
|
// initial size from root object
|
|
|
|
QCOMPARE(item->width(), 200.0);
|
|
|
|
QCOMPARE(item->height(), 200.0);
|
|
|
|
QCOMPARE(view->size(), QSize(200, 200));
|
|
|
|
QCOMPARE(view->size(), view->sizeHint());
|
|
|
|
QCOMPARE(view->size(), view->initialSize());
|
|
|
|
|
|
|
|
// size update from view
|
|
|
|
view->resize(QSize(80,100));
|
|
|
|
|
|
|
|
QTRY_COMPARE(item->width(), 80.0);
|
|
|
|
QCOMPARE(item->height(), 100.0);
|
|
|
|
QCOMPARE(view->size(), QSize(80, 100));
|
|
|
|
QCOMPARE(view->size(), view->sizeHint());
|
|
|
|
|
|
|
|
view->setResizeMode(QQuickWidget::SizeViewToRootObject);
|
|
|
|
|
|
|
|
// size update from view disabled
|
|
|
|
view->resize(QSize(60,80));
|
|
|
|
QCOMPARE(item->width(), 80.0);
|
|
|
|
QCOMPARE(item->height(), 100.0);
|
|
|
|
QTRY_COMPARE(view->size(), QSize(60, 80));
|
|
|
|
|
|
|
|
// size update from root object
|
|
|
|
item->setWidth(250);
|
|
|
|
item->setHeight(350);
|
|
|
|
QCOMPARE(item->width(), 250.0);
|
|
|
|
QCOMPARE(item->height(), 350.0);
|
|
|
|
QTRY_COMPARE(view->size(), QSize(250, 350));
|
|
|
|
QCOMPARE(view->size(), QSize(250, 350));
|
|
|
|
QCOMPARE(view->size(), view->sizeHint());
|
|
|
|
|
|
|
|
// reset window
|
|
|
|
window.hide();
|
|
|
|
view.reset(new QQuickWidget(&window));
|
|
|
|
view->setResizeMode(QQuickWidget::SizeViewToRootObject);
|
|
|
|
view->setSource(testFileUrl("resizemodeitem.qml"));
|
|
|
|
item = qobject_cast<QQuickItem*>(view->rootObject());
|
|
|
|
QVERIFY(item);
|
|
|
|
window.show();
|
|
|
|
|
|
|
|
view->showNormal();
|
|
|
|
|
|
|
|
// initial size for root object
|
|
|
|
QCOMPARE(item->width(), 200.0);
|
|
|
|
QCOMPARE(item->height(), 200.0);
|
|
|
|
QCOMPARE(view->size(), view->sizeHint());
|
|
|
|
QCOMPARE(view->size(), view->initialSize());
|
|
|
|
|
|
|
|
// size update from root object
|
|
|
|
item->setWidth(80);
|
|
|
|
item->setHeight(100);
|
|
|
|
QCOMPARE(item->width(), 80.0);
|
|
|
|
QCOMPARE(item->height(), 100.0);
|
|
|
|
QTRY_COMPARE(view->size(), QSize(80, 100));
|
|
|
|
QCOMPARE(view->size(), view->sizeHint());
|
|
|
|
|
|
|
|
// size update from root object disabled
|
|
|
|
view->setResizeMode(QQuickWidget::SizeRootObjectToView);
|
|
|
|
item->setWidth(60);
|
|
|
|
item->setHeight(80);
|
|
|
|
QCOMPARE(view->width(), 80);
|
|
|
|
QCOMPARE(view->height(), 100);
|
|
|
|
QCOMPARE(QSize(item->width(), item->height()), view->sizeHint());
|
|
|
|
|
|
|
|
// size update from view
|
|
|
|
view->resize(QSize(200,300));
|
|
|
|
QTRY_COMPARE(item->width(), 200.0);
|
|
|
|
QCOMPARE(item->height(), 300.0);
|
|
|
|
QCOMPARE(view->size(), QSize(200, 300));
|
|
|
|
QCOMPARE(view->size(), view->sizeHint());
|
|
|
|
|
|
|
|
window.hide();
|
|
|
|
|
|
|
|
// if we set a specific size for the view then it should keep that size
|
|
|
|
// for SizeRootObjectToView mode.
|
|
|
|
view.reset(new QQuickWidget(&window));
|
|
|
|
view->resize(300, 300);
|
|
|
|
view->setResizeMode(QQuickWidget::SizeRootObjectToView);
|
|
|
|
QCOMPARE(QSize(0,0), view->initialSize());
|
|
|
|
view->setSource(testFileUrl("resizemodeitem.qml"));
|
|
|
|
view->resize(300, 300);
|
|
|
|
item = qobject_cast<QQuickItem*>(view->rootObject());
|
|
|
|
QVERIFY(item);
|
|
|
|
window.show();
|
|
|
|
|
|
|
|
view->showNormal();
|
|
|
|
|
|
|
|
// initial size from root object
|
|
|
|
QCOMPARE(item->width(), 300.0);
|
|
|
|
QCOMPARE(item->height(), 300.0);
|
|
|
|
QTRY_COMPARE(view->size(), QSize(300, 300));
|
|
|
|
QCOMPARE(view->size(), view->sizeHint());
|
|
|
|
QCOMPARE(view->initialSize(), QSize(200, 200)); // initial object size
|
|
|
|
}
|
|
|
|
|
2017-08-14 08:29:53 +00:00
|
|
|
void tst_qquickwidget::layoutSizeChange()
|
|
|
|
{
|
|
|
|
QWidget window;
|
|
|
|
window.resize(400, 400);
|
|
|
|
|
|
|
|
QVBoxLayout *layout = new QVBoxLayout(&window);
|
|
|
|
layout->setContentsMargins(0,0,0,0);
|
|
|
|
layout->setSpacing(0);
|
|
|
|
QScopedPointer<QQuickWidget> view(new QQuickWidget);
|
|
|
|
layout->addWidget(view.data());
|
|
|
|
QLabel *label = new QLabel("Label");
|
|
|
|
layout->addWidget(label);
|
|
|
|
layout->addStretch(1);
|
|
|
|
|
|
|
|
|
|
|
|
view->resize(300,300);
|
|
|
|
view->setResizeMode(QQuickWidget::SizeViewToRootObject);
|
|
|
|
QCOMPARE(QSize(0,0), view->initialSize());
|
|
|
|
view->setSource(testFileUrl("rectangle.qml"));
|
|
|
|
QQuickItem* item = qobject_cast<QQuickItem*>(view->rootObject());
|
|
|
|
QVERIFY(item);
|
|
|
|
QCOMPARE(item->height(), 200.0);
|
|
|
|
window.show();
|
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(&window, 5000));
|
|
|
|
QTRY_COMPARE(view->height(), 200);
|
|
|
|
QTRY_COMPARE(label->y(), 200);
|
|
|
|
|
|
|
|
item->setSize(QSizeF(100,100));
|
|
|
|
QCOMPARE(item->height(), 100.0);
|
|
|
|
QTRY_COMPARE(view->height(), 100);
|
|
|
|
QTRY_COMPARE(label->y(), 100);
|
|
|
|
}
|
|
|
|
|
2014-03-19 08:21:11 +00:00
|
|
|
void tst_qquickwidget::errors()
|
|
|
|
{
|
|
|
|
QQuickWidget *view = new QQuickWidget;
|
|
|
|
QScopedPointer<QQuickWidget> cleanupView(view);
|
2015-09-01 16:04:44 +00:00
|
|
|
QVERIFY(view->errors().isEmpty()); // don't crash
|
2014-03-19 08:21:11 +00:00
|
|
|
|
|
|
|
QQmlTestMessageHandler messageHandler;
|
|
|
|
view->setSource(testFileUrl("error1.qml"));
|
2015-07-24 13:32:22 +00:00
|
|
|
QCOMPARE(view->status(), QQuickWidget::Error);
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(view->errors().size(), 1);
|
2014-03-19 08:21:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void tst_qquickwidget::engine()
|
|
|
|
{
|
|
|
|
QScopedPointer<QQmlEngine> engine(new QQmlEngine);
|
2018-02-21 09:41:54 +00:00
|
|
|
QScopedPointer<QQuickWidget> view(new QQuickWidget(engine.data(), nullptr));
|
|
|
|
QScopedPointer<QQuickWidget> view2(new QQuickWidget(view->engine(), nullptr));
|
2014-03-19 08:21:11 +00:00
|
|
|
|
|
|
|
QVERIFY(view->engine());
|
|
|
|
QVERIFY(view2->engine());
|
|
|
|
QCOMPARE(view->engine(), view2->engine());
|
|
|
|
}
|
|
|
|
|
|
|
|
void tst_qquickwidget::readback()
|
|
|
|
{
|
|
|
|
QScopedPointer<QQuickWidget> view(new QQuickWidget);
|
|
|
|
view->setSource(testFileUrl("rectangle.qml"));
|
|
|
|
|
|
|
|
view->show();
|
2018-01-31 19:05:38 +00:00
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(view.data()));
|
2014-03-19 08:21:11 +00:00
|
|
|
|
|
|
|
QImage img = view->grabFramebuffer();
|
|
|
|
QVERIFY(!img.isNull());
|
2020-08-06 19:40:01 +00:00
|
|
|
QCOMPARE(img.width(), qCeil(view->width() * view->devicePixelRatio()));
|
|
|
|
QCOMPARE(img.height(), qCeil(view->height() * view->devicePixelRatio()));
|
2014-03-19 08:21:11 +00:00
|
|
|
|
|
|
|
QRgb pix = img.pixel(5, 5);
|
|
|
|
QCOMPARE(pix, qRgb(255, 0, 0));
|
|
|
|
}
|
|
|
|
|
2015-03-26 20:34:42 +00:00
|
|
|
void tst_qquickwidget::renderingSignals()
|
|
|
|
{
|
|
|
|
QQuickWidget widget;
|
|
|
|
QQuickWindow *window = widget.quickWindow();
|
|
|
|
QVERIFY(window);
|
|
|
|
|
|
|
|
QSignalSpy beforeRenderingSpy(window, &QQuickWindow::beforeRendering);
|
|
|
|
QSignalSpy beforeSyncSpy(window, &QQuickWindow::beforeSynchronizing);
|
|
|
|
QSignalSpy afterRenderingSpy(window, &QQuickWindow::afterRendering);
|
|
|
|
|
|
|
|
QVERIFY(beforeRenderingSpy.isValid());
|
|
|
|
QVERIFY(beforeSyncSpy.isValid());
|
|
|
|
QVERIFY(afterRenderingSpy.isValid());
|
|
|
|
|
|
|
|
QCOMPARE(beforeRenderingSpy.size(), 0);
|
|
|
|
QCOMPARE(beforeSyncSpy.size(), 0);
|
|
|
|
QCOMPARE(afterRenderingSpy.size(), 0);
|
|
|
|
|
|
|
|
widget.setSource(testFileUrl("rectangle.qml"));
|
|
|
|
|
|
|
|
QCOMPARE(beforeRenderingSpy.size(), 0);
|
|
|
|
QCOMPARE(beforeSyncSpy.size(), 0);
|
|
|
|
QCOMPARE(afterRenderingSpy.size(), 0);
|
|
|
|
|
|
|
|
widget.show();
|
2018-01-31 19:05:38 +00:00
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(&widget));
|
2015-03-26 20:34:42 +00:00
|
|
|
|
|
|
|
QTRY_VERIFY(beforeRenderingSpy.size() > 0);
|
|
|
|
QTRY_VERIFY(beforeSyncSpy.size() > 0);
|
|
|
|
QTRY_VERIFY(afterRenderingSpy.size() > 0);
|
|
|
|
}
|
|
|
|
|
2017-06-20 20:15:48 +00:00
|
|
|
void tst_qquickwidget::grab()
|
|
|
|
{
|
|
|
|
QQuickWidget view;
|
|
|
|
view.setSource(testFileUrl("rectangle.qml"));
|
|
|
|
QPixmap pixmap = view.grab();
|
|
|
|
QRgb pixel = pixmap.toImage().pixel(5, 5);
|
|
|
|
QCOMPARE(pixel, qRgb(255, 0, 0));
|
|
|
|
}
|
|
|
|
|
2015-12-14 08:44:40 +00:00
|
|
|
// QTBUG-49929, verify that Qt Designer grabbing the contents before drag
|
|
|
|
// does not crash due to missing GL contexts or similar.
|
|
|
|
void tst_qquickwidget::grabBeforeShow()
|
|
|
|
{
|
|
|
|
QQuickWidget widget;
|
|
|
|
QVERIFY(!widget.grab().isNull());
|
|
|
|
}
|
|
|
|
|
2016-06-22 10:17:15 +00:00
|
|
|
void tst_qquickwidget::reparentToNewWindow()
|
|
|
|
{
|
2022-04-08 15:35:03 +00:00
|
|
|
#ifdef Q_OS_ANDROID
|
|
|
|
QSKIP("This test crashes on Android (see QTBUG-100173)");
|
|
|
|
#endif
|
2016-06-22 10:17:15 +00:00
|
|
|
QWidget window1;
|
|
|
|
QWidget window2;
|
|
|
|
|
|
|
|
QQuickWidget *qqw = new QQuickWidget(&window1);
|
|
|
|
qqw->setSource(testFileUrl("rectangle.qml"));
|
|
|
|
window1.show();
|
2018-01-31 19:05:38 +00:00
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(&window1));
|
2016-06-22 10:17:15 +00:00
|
|
|
window2.show();
|
2018-01-31 19:05:38 +00:00
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(&window2));
|
2016-06-22 10:17:15 +00:00
|
|
|
|
|
|
|
qqw->setParent(&window2);
|
2020-04-30 15:37:27 +00:00
|
|
|
|
|
|
|
QSignalSpy afterRenderingSpy(qqw->quickWindow(), &QQuickWindow::afterRendering);
|
2016-06-22 10:17:15 +00:00
|
|
|
qqw->show();
|
2017-08-30 12:39:28 +00:00
|
|
|
|
2016-06-22 10:17:15 +00:00
|
|
|
QTRY_VERIFY(afterRenderingSpy.size() > 0);
|
|
|
|
|
|
|
|
QImage img = qqw->grabFramebuffer();
|
2020-04-30 15:37:27 +00:00
|
|
|
|
2016-06-22 10:17:15 +00:00
|
|
|
QCOMPARE(img.pixel(5, 5), qRgb(255, 0, 0));
|
|
|
|
}
|
|
|
|
|
2016-06-24 08:22:47 +00:00
|
|
|
void tst_qquickwidget::nullEngine()
|
|
|
|
{
|
|
|
|
QQuickWidget widget;
|
2016-06-27 09:55:33 +00:00
|
|
|
// Default should have no errors, even with a null qml engine
|
2016-06-24 08:22:47 +00:00
|
|
|
QVERIFY(widget.errors().isEmpty());
|
|
|
|
QCOMPARE(widget.status(), QQuickWidget::Null);
|
2016-06-27 09:55:33 +00:00
|
|
|
|
|
|
|
// A QML engine should be created lazily.
|
|
|
|
QVERIFY(widget.rootContext());
|
|
|
|
QVERIFY(widget.engine());
|
2016-06-24 08:22:47 +00:00
|
|
|
}
|
|
|
|
|
2017-02-22 15:16:14 +00:00
|
|
|
class KeyHandlingWidget : public QQuickWidget
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
void keyPressEvent(QKeyEvent *e) override {
|
|
|
|
if (e->key() == Qt::Key_A)
|
|
|
|
ok = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ok = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
void tst_qquickwidget::keyEvents()
|
|
|
|
{
|
|
|
|
// A QQuickWidget should behave like a normal widget when it comes to event handling.
|
|
|
|
// Verify that key events actually reach the widget. (QTBUG-45757)
|
|
|
|
KeyHandlingWidget widget;
|
|
|
|
widget.setSource(testFileUrl("rectangle.qml"));
|
|
|
|
widget.show();
|
2018-01-31 19:05:38 +00:00
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(widget.window()));
|
2017-02-22 15:16:14 +00:00
|
|
|
|
|
|
|
// Note: send the event to the QWindow, not the QWidget, in order
|
|
|
|
// to simulate the full event processing chain.
|
|
|
|
QTest::keyClick(widget.window()->windowHandle(), Qt::Key_A);
|
|
|
|
|
|
|
|
QTRY_VERIFY(widget.ok);
|
|
|
|
}
|
|
|
|
|
2017-05-23 12:34:23 +00:00
|
|
|
class ShortcutEventFilter : public QObject
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
bool eventFilter(QObject *obj, QEvent *e) override {
|
|
|
|
if (e->type() == QEvent::ShortcutOverride)
|
|
|
|
shortcutOk = true;
|
|
|
|
|
|
|
|
return QObject::eventFilter(obj, e);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool shortcutOk = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
void tst_qquickwidget::shortcuts()
|
|
|
|
{
|
|
|
|
// Verify that ShortcutOverride events do not get lost. (QTBUG-60988)
|
|
|
|
KeyHandlingWidget widget;
|
|
|
|
widget.setSource(testFileUrl("rectangle.qml"));
|
|
|
|
widget.show();
|
2018-01-31 19:05:38 +00:00
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(widget.window()));
|
2017-05-23 12:34:23 +00:00
|
|
|
|
|
|
|
// Send to the widget, verify that the QQuickWindow sees it.
|
|
|
|
|
|
|
|
ShortcutEventFilter filter;
|
|
|
|
widget.quickWindow()->installEventFilter(&filter);
|
|
|
|
|
|
|
|
QKeyEvent e(QEvent::ShortcutOverride, Qt::Key_A, Qt::ControlModifier);
|
|
|
|
QCoreApplication::sendEvent(&widget, &e);
|
|
|
|
|
|
|
|
QTRY_VERIFY(filter.shortcutOk);
|
|
|
|
}
|
|
|
|
|
2015-12-02 08:51:35 +00:00
|
|
|
void tst_qquickwidget::enterLeave()
|
|
|
|
{
|
2022-04-08 15:35:03 +00:00
|
|
|
#ifdef Q_OS_ANDROID
|
|
|
|
QSKIP("Android has no cursor");
|
|
|
|
#endif
|
2015-12-02 08:51:35 +00:00
|
|
|
QQuickWidget view;
|
|
|
|
view.setSource(testFileUrl("enterleave.qml"));
|
|
|
|
|
2018-01-31 19:05:38 +00:00
|
|
|
// Ensure the cursor is away from the window first
|
2018-12-05 10:28:47 +00:00
|
|
|
const auto outside = m_availableGeometry.topLeft() + QPoint(50, 50);
|
|
|
|
QCursor::setPos(outside);
|
|
|
|
QTRY_VERIFY(QCursor::pos() == outside);
|
2015-12-02 08:51:35 +00:00
|
|
|
|
2018-12-05 10:28:47 +00:00
|
|
|
view.move(m_availableGeometry.topLeft() + QPoint(100, 100));
|
2015-12-02 08:51:35 +00:00
|
|
|
view.show();
|
2018-01-31 19:05:38 +00:00
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(&view));
|
2015-12-02 08:51:35 +00:00
|
|
|
QQuickItem *rootItem = view.rootObject();
|
|
|
|
QVERIFY(rootItem);
|
2018-12-05 10:28:47 +00:00
|
|
|
const QPoint frameOffset = view.geometry().topLeft() - view.frameGeometry().topLeft();
|
2015-12-02 08:51:35 +00:00
|
|
|
|
|
|
|
QTRY_VERIFY(!rootItem->property("hasMouse").toBool());
|
|
|
|
// Check the enter
|
2018-12-05 10:28:47 +00:00
|
|
|
QCursor::setPos(view.pos() + QPoint(50, 50) + frameOffset);
|
2015-12-02 08:51:35 +00:00
|
|
|
QTRY_VERIFY(rootItem->property("hasMouse").toBool());
|
|
|
|
// Now check the leave
|
2018-01-31 19:05:38 +00:00
|
|
|
QCursor::setPos(outside);
|
2015-12-02 08:51:35 +00:00
|
|
|
QTRY_VERIFY(!rootItem->property("hasMouse").toBool());
|
|
|
|
}
|
|
|
|
|
2018-01-16 13:29:40 +00:00
|
|
|
void tst_qquickwidget::mouseEventWindowPos()
|
|
|
|
{
|
|
|
|
QWidget widget;
|
|
|
|
widget.resize(100, 100);
|
|
|
|
QQuickWidget *quick = new QQuickWidget(&widget);
|
|
|
|
quick->setSource(testFileUrl("mouse.qml"));
|
|
|
|
quick->move(50, 50);
|
|
|
|
widget.show();
|
2018-01-31 19:05:38 +00:00
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(&widget));
|
2018-01-16 13:29:40 +00:00
|
|
|
QQuickItem *rootItem = quick->rootObject();
|
|
|
|
QVERIFY(rootItem);
|
|
|
|
|
|
|
|
QVERIFY(!rootItem->property("wasClicked").toBool());
|
|
|
|
QVERIFY(!rootItem->property("wasDoubleClicked").toBool());
|
2018-08-23 12:00:01 +00:00
|
|
|
// Moving an item under the mouse cursor will trigger a mouse move event.
|
|
|
|
// The above quick->move() will trigger a mouse move event on macOS.
|
|
|
|
// Discard that in order to get a clean slate for the actual tests.
|
|
|
|
rootItem->setProperty("wasMoved", QVariant(false));
|
2018-01-16 13:29:40 +00:00
|
|
|
|
|
|
|
QWindow *window = widget.windowHandle();
|
|
|
|
QVERIFY(window);
|
|
|
|
|
|
|
|
QTest::mouseMove(window, QPoint(60, 60));
|
|
|
|
QTest::mouseClick(window, Qt::LeftButton, Qt::KeyboardModifiers(), QPoint(60, 60));
|
|
|
|
QTRY_VERIFY(rootItem->property("wasClicked").toBool());
|
|
|
|
QTest::mouseDClick(window, Qt::LeftButton, Qt::KeyboardModifiers(), QPoint(60, 60));
|
|
|
|
QTRY_VERIFY(rootItem->property("wasDoubleClicked").toBool());
|
|
|
|
QTest::mouseMove(window, QPoint(70, 70));
|
|
|
|
QTRY_VERIFY(rootItem->property("wasMoved").toBool());
|
|
|
|
}
|
|
|
|
|
2017-11-30 10:36:42 +00:00
|
|
|
void tst_qquickwidget::synthMouseFromTouch_data()
|
|
|
|
{
|
|
|
|
QTest::addColumn<bool>("synthMouse"); // AA_SynthesizeMouseForUnhandledTouchEvents
|
|
|
|
QTest::addColumn<bool>("acceptTouch"); // QQuickItem::touchEvent: setAccepted()
|
|
|
|
|
|
|
|
QTest::newRow("no synth, accept") << false << true; // suitable for touch-capable UIs
|
|
|
|
QTest::newRow("no synth, don't accept") << false << false;
|
|
|
|
QTest::newRow("synth and accept") << true << true;
|
|
|
|
QTest::newRow("synth, don't accept") << true << false; // the default
|
|
|
|
}
|
|
|
|
|
|
|
|
void tst_qquickwidget::synthMouseFromTouch()
|
|
|
|
{
|
|
|
|
QFETCH(bool, synthMouse);
|
|
|
|
QFETCH(bool, acceptTouch);
|
|
|
|
|
|
|
|
QCoreApplication::setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, synthMouse);
|
|
|
|
QWidget window;
|
|
|
|
window.setAttribute(Qt::WA_AcceptTouchEvents);
|
|
|
|
QScopedPointer<MouseRecordingQQWidget> childView(new MouseRecordingQQWidget(&window));
|
2022-03-17 16:41:59 +00:00
|
|
|
MouseRecordingItem *item = new MouseRecordingItem(!synthMouse, acceptTouch, nullptr);
|
2017-11-30 10:36:42 +00:00
|
|
|
childView->setContent(QUrl(), nullptr, item);
|
|
|
|
window.resize(300, 300);
|
|
|
|
childView->resize(300, 300);
|
|
|
|
window.show();
|
|
|
|
QVERIFY(QTest::qWaitForWindowActive(&window));
|
2019-04-02 14:07:34 +00:00
|
|
|
QVERIFY(!childView->quickWindow()->isVisible()); // this window is always not visible see QTBUG-65761
|
2017-11-30 10:36:42 +00:00
|
|
|
QVERIFY(item->isVisible());
|
|
|
|
|
|
|
|
QPoint p1 = QPoint(20, 20);
|
|
|
|
QPoint p2 = QPoint(30, 30);
|
|
|
|
QTest::touchEvent(&window, device).press(0, p1, &window);
|
|
|
|
QTest::touchEvent(&window, device).move(0, p2, &window);
|
|
|
|
QTest::touchEvent(&window, device).release(0, p2, &window);
|
|
|
|
|
2022-03-18 12:13:24 +00:00
|
|
|
qCDebug(lcTests) << item->m_touchEvents << item->m_mouseEvents;
|
2022-10-05 05:29:16 +00:00
|
|
|
QCOMPARE(item->m_touchEvents.size(), synthMouse ? 0 : (acceptTouch ? 3 : 1));
|
|
|
|
QCOMPARE(item->m_mouseEvents.size(), synthMouse ? 3 : 0);
|
|
|
|
QCOMPARE(childView->m_mouseEvents.size(), 0);
|
2020-11-11 12:51:35 +00:00
|
|
|
for (const auto &ev : item->m_mouseEvents)
|
|
|
|
QCOMPARE(ev, Qt::MouseEventSynthesizedByQt);
|
2017-11-30 10:36:42 +00:00
|
|
|
}
|
|
|
|
|
2022-03-18 12:13:24 +00:00
|
|
|
void tst_qquickwidget::touchTapMouseArea()
|
|
|
|
{
|
|
|
|
QWidget window;
|
|
|
|
window.resize(100, 100);
|
|
|
|
window.setObjectName("window widget");
|
|
|
|
window.setAttribute(Qt::WA_AcceptTouchEvents);
|
|
|
|
QVERIFY(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents));
|
|
|
|
QQuickWidget *quick = new QQuickWidget(&window);
|
|
|
|
quick->setSource(testFileUrl("mouse.qml"));
|
|
|
|
quick->move(50, 50);
|
|
|
|
quick->setObjectName("quick widget");
|
|
|
|
window.show();
|
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(&window));
|
|
|
|
QQuickItem *rootItem = quick->rootObject();
|
|
|
|
QVERIFY(rootItem);
|
|
|
|
QQuickMouseArea *ma = rootItem->findChild<QQuickMouseArea *>();
|
|
|
|
QVERIFY(ma);
|
|
|
|
|
|
|
|
QPoint p1 = QPoint(70, 70);
|
|
|
|
QTest::touchEvent(&window, device).press(0, p1, &window);
|
2023-01-11 09:37:21 +00:00
|
|
|
QTRY_COMPARE(ma->isPressed(), true);
|
2022-03-18 12:13:24 +00:00
|
|
|
QTest::touchEvent(&window, device).move(0, p1, &window);
|
|
|
|
QTest::touchEvent(&window, device).release(0, p1, &window);
|
2023-01-11 09:37:21 +00:00
|
|
|
QTRY_COMPARE(ma->isPressed(), false);
|
2022-03-18 12:13:24 +00:00
|
|
|
QVERIFY(rootItem->property("wasClicked").toBool());
|
|
|
|
}
|
|
|
|
|
2022-12-14 08:52:51 +00:00
|
|
|
void tst_qquickwidget::touchTapButton()
|
|
|
|
{
|
|
|
|
QWidget window;
|
|
|
|
QQuickWidget *quick = new QQuickWidget;
|
|
|
|
quick->setSource(testFileUrl("button.qml"));
|
|
|
|
|
|
|
|
QHBoxLayout hbox;
|
|
|
|
hbox.addWidget(quick);
|
|
|
|
window.setLayout(&hbox);
|
|
|
|
window.show();
|
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(&window));
|
|
|
|
|
|
|
|
QQuickItem *rootItem = quick->rootObject();
|
|
|
|
QVERIFY(rootItem);
|
|
|
|
QQuickButton *button = rootItem->findChild<QQuickButton *>("button");
|
|
|
|
QVERIFY(button);
|
|
|
|
|
|
|
|
const QPoint point = quick->mapTo(&window, button->mapToScene(button->boundingRect().center()).toPoint());
|
|
|
|
QTest::touchEvent(&window, device).press(0, point, &window).commit();
|
|
|
|
QTRY_VERIFY(rootItem->property("wasPressed").toBool());
|
|
|
|
QTest::touchEvent(&window, device).release(0, point, &window).commit();
|
|
|
|
QTRY_VERIFY(rootItem->property("wasReleased").toBool());
|
|
|
|
QTRY_VERIFY(rootItem->property("wasClicked").toBool());
|
|
|
|
}
|
|
|
|
|
|
|
|
void tst_qquickwidget::touchMultipleWidgets()
|
|
|
|
{
|
|
|
|
QWidget window;
|
|
|
|
QQuickWidget *leftQuick = new QQuickWidget;
|
|
|
|
leftQuick->setSource(testFileUrl("button.qml"));
|
|
|
|
QQuickWidget *rightQuick = new QQuickWidget;
|
|
|
|
rightQuick->setSource(testFileUrl("button.qml"));
|
|
|
|
|
|
|
|
QHBoxLayout hbox;
|
|
|
|
hbox.addWidget(leftQuick);
|
|
|
|
hbox.addWidget(rightQuick);
|
|
|
|
window.setLayout(&hbox);
|
|
|
|
window.show();
|
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(&window));
|
|
|
|
|
|
|
|
QQuickItem *leftRootItem = leftQuick->rootObject();
|
|
|
|
QQuickItem *rightRootItem = rightQuick->rootObject();
|
|
|
|
QVERIFY(leftRootItem);
|
|
|
|
QVERIFY(rightRootItem);
|
|
|
|
QQuickButton *leftButton = leftRootItem->findChild<QQuickButton *>("button");
|
|
|
|
QQuickButton *rightButton = rightRootItem->findChild<QQuickButton *>("button");
|
|
|
|
QVERIFY(leftButton);
|
|
|
|
QVERIFY(rightButton);
|
|
|
|
|
|
|
|
const QPoint leftPoint = leftQuick->mapTo(&window, leftButton->mapToScene(
|
|
|
|
leftButton->boundingRect().center()).toPoint());
|
|
|
|
const QPoint rightPoint = rightQuick->mapTo(&window, rightButton->mapToScene(
|
|
|
|
rightButton->boundingRect().center()).toPoint());
|
|
|
|
QTest::touchEvent(&window, device).press(0, leftPoint, &window).commit();
|
|
|
|
QTRY_VERIFY(leftRootItem->property("wasPressed").toBool());
|
|
|
|
QTest::touchEvent(&window, device).press(1, rightPoint, &window).commit();
|
|
|
|
QTRY_VERIFY(rightRootItem->property("wasPressed").toBool());
|
|
|
|
QTest::touchEvent(&window, device).release(1, rightPoint, &window).commit();
|
|
|
|
QTRY_VERIFY(rightRootItem->property("wasReleased").toBool());
|
|
|
|
QVERIFY(rightRootItem->property("wasClicked").toBool());
|
|
|
|
QTest::touchEvent(&window, device).release(0, leftPoint, &window).commit();
|
|
|
|
QTRY_VERIFY(leftRootItem->property("wasReleased").toBool());
|
|
|
|
QVERIFY(leftRootItem->property("wasClicked").toBool());
|
|
|
|
}
|
|
|
|
|
2017-11-01 14:40:14 +00:00
|
|
|
void tst_qquickwidget::tabKey()
|
|
|
|
{
|
|
|
|
if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls)
|
|
|
|
QSKIP("This function doesn't support NOT iterating all.");
|
|
|
|
QWidget window1;
|
|
|
|
QQuickWidget *qqw = new QQuickWidget(&window1);
|
|
|
|
qqw->setSource(testFileUrl("activeFocusOnTab.qml"));
|
|
|
|
QQuickWidget *qqw2 = new QQuickWidget(&window1);
|
|
|
|
qqw2->setSource(testFileUrl("noActiveFocusOnTab.qml"));
|
|
|
|
qqw2->move(100, 0);
|
|
|
|
window1.show();
|
|
|
|
qqw->setFocus();
|
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(&window1, 5000));
|
|
|
|
QVERIFY(qqw->hasFocus());
|
|
|
|
QQuickItem *item = qobject_cast<QQuickItem *>(qqw->rootObject());
|
|
|
|
QQuickItem *topItem = item->findChild<QQuickItem *>("topRect");
|
|
|
|
QQuickItem *middleItem = item->findChild<QQuickItem *>("middleRect");
|
|
|
|
QQuickItem *bottomItem = item->findChild<QQuickItem *>("bottomRect");
|
|
|
|
topItem->forceActiveFocus();
|
|
|
|
QVERIFY(topItem->property("activeFocus").toBool());
|
|
|
|
QTest::keyClick(qqw, Qt::Key_Tab);
|
|
|
|
QTRY_VERIFY(middleItem->property("activeFocus").toBool());
|
|
|
|
QTest::keyClick(qqw, Qt::Key_Tab);
|
|
|
|
QTRY_VERIFY(bottomItem->property("activeFocus").toBool());
|
|
|
|
QTest::keyClick(qqw, Qt::Key_Backtab);
|
|
|
|
QTRY_VERIFY(middleItem->property("activeFocus").toBool());
|
|
|
|
|
|
|
|
qqw2->setFocus();
|
|
|
|
QQuickItem *item2 = qobject_cast<QQuickItem *>(qqw2->rootObject());
|
|
|
|
QQuickItem *topItem2 = item2->findChild<QQuickItem *>("topRect2");
|
|
|
|
QTRY_VERIFY(qqw2->hasFocus());
|
|
|
|
QVERIFY(topItem2->property("activeFocus").toBool());
|
|
|
|
QTest::keyClick(qqw2, Qt::Key_Tab);
|
|
|
|
QTRY_VERIFY(qqw->hasFocus());
|
|
|
|
QVERIFY(middleItem->property("activeFocus").toBool());
|
|
|
|
}
|
|
|
|
|
Resize offscreen window when QQuickWidget is resized
In a typical Qt Quick application, when a window is resized, the
contentItem of that window is resized with it, and then the root item.
QQuickOverlay in qtquickcontrols2 listens to size changes in the
contentItem (QQuickRootItem) via addItemChangeListener(), as a cheap
way (e.g. no signals) of knowing when to resize background dimming
effects. It resizes the dimmer item to the size of the window.
The first problem with QQuickWidget is that it only ever resizes the root item
when using the SizeRootObjectToView resize mode, and not the contentItem.
The second problem is that the root item is resized (via updateSize()) before
the window itself even has a size (which happens in
QQuickWidget::createFramebufferObject() via the call to
d->offscreenWindow->setGeometry()).
To demonstrate the second problem in detail, consider the following widget
hierarchy (written in everybody's favorite language: QML):
QMainWindow {
QQuickWidget {
QQuickWindow { // QQuickWidgetPrivate::offscreenWindow
QQuickRootItem { // QQuickWindowPrivate::contentItem
Page {} // QQuickWidgetPrivate::root
}
}
}
}
The QMainWindow starts off as 200x200. When the window is resized,
QQuickWidget::resizeEvent() is called. The first thing it does is call
updateSize(), which in the case of SizeRootObjectToView, resizes the root item
to 300x300. This causes QQuickOverlayPrivate::itemGeometryChanged() to be
called, and the dimmers are resized to the size of the window, but the window
still has its 200x200 size, as it is only updated later, when
QQuickWidget::createFramebufferObject() is called.
This patch fixes these issues by ensuring that contentItem and the window
itself are resized along with the root item.
As to why such manual intervention is necessary: from what I can see, it is
because it's an "offscreen" window. This means that
QWindowPrivate::platformWindow is null, and setGeometry() takes a different
path that presumably results in no QResizeEvent being sent to the QQuickWindow.
As QQuickWindow relies on resizeEvent() being called to resize its contentItem,
the contentItem is never resized. With a typical Qt Quick application, all of
this works as expected.
Change-Id: I7401aa7a9b209096183416ab53014f67cceccbe4
Fixes: QTBUG-78323
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io>
Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
2020-03-05 15:00:04 +00:00
|
|
|
class Overlay : public QQuickItem, public QQuickItemChangeListener
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
public:
|
|
|
|
Overlay() = default;
|
|
|
|
|
|
|
|
~Overlay()
|
|
|
|
{
|
|
|
|
QQuickItemPrivate::get(parentItem())->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
|
|
|
|
}
|
|
|
|
|
|
|
|
// componentCompleted() is too early to add the listener, as parentItem()
|
|
|
|
// is still null by that stage, so we use this function instead.
|
|
|
|
void startListening()
|
|
|
|
{
|
|
|
|
QQuickItemPrivate::get(parentItem())->addItemChangeListener(this, QQuickItemPrivate::Geometry);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2021-03-25 13:44:04 +00:00
|
|
|
void itemGeometryChanged(QQuickItem *, QQuickGeometryChange, const QRectF &/*oldGeometry*/) override
|
Resize offscreen window when QQuickWidget is resized
In a typical Qt Quick application, when a window is resized, the
contentItem of that window is resized with it, and then the root item.
QQuickOverlay in qtquickcontrols2 listens to size changes in the
contentItem (QQuickRootItem) via addItemChangeListener(), as a cheap
way (e.g. no signals) of knowing when to resize background dimming
effects. It resizes the dimmer item to the size of the window.
The first problem with QQuickWidget is that it only ever resizes the root item
when using the SizeRootObjectToView resize mode, and not the contentItem.
The second problem is that the root item is resized (via updateSize()) before
the window itself even has a size (which happens in
QQuickWidget::createFramebufferObject() via the call to
d->offscreenWindow->setGeometry()).
To demonstrate the second problem in detail, consider the following widget
hierarchy (written in everybody's favorite language: QML):
QMainWindow {
QQuickWidget {
QQuickWindow { // QQuickWidgetPrivate::offscreenWindow
QQuickRootItem { // QQuickWindowPrivate::contentItem
Page {} // QQuickWidgetPrivate::root
}
}
}
}
The QMainWindow starts off as 200x200. When the window is resized,
QQuickWidget::resizeEvent() is called. The first thing it does is call
updateSize(), which in the case of SizeRootObjectToView, resizes the root item
to 300x300. This causes QQuickOverlayPrivate::itemGeometryChanged() to be
called, and the dimmers are resized to the size of the window, but the window
still has its 200x200 size, as it is only updated later, when
QQuickWidget::createFramebufferObject() is called.
This patch fixes these issues by ensuring that contentItem and the window
itself are resized along with the root item.
As to why such manual intervention is necessary: from what I can see, it is
because it's an "offscreen" window. This means that
QWindowPrivate::platformWindow is null, and setGeometry() takes a different
path that presumably results in no QResizeEvent being sent to the QQuickWindow.
As QQuickWindow relies on resizeEvent() being called to resize its contentItem,
the contentItem is never resized. With a typical Qt Quick application, all of
this works as expected.
Change-Id: I7401aa7a9b209096183416ab53014f67cceccbe4
Fixes: QTBUG-78323
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io>
Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
2020-03-05 15:00:04 +00:00
|
|
|
{
|
|
|
|
auto window = QQuickItemPrivate::get(this)->window;
|
|
|
|
if (!window)
|
|
|
|
return;
|
|
|
|
|
|
|
|
setSize(window->size());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Test that an item that resizes itself based on the window size can use a
|
|
|
|
// Geometry item change listener to respond to changes in size. This is a
|
|
|
|
// simplified test to mimic a use case involving Overlay from Qt Quick Controls 2.
|
|
|
|
void tst_qquickwidget::resizeOverlay()
|
|
|
|
{
|
|
|
|
QWidget widget;
|
|
|
|
auto contentVerticalLayout = new QVBoxLayout(&widget);
|
2020-04-14 12:38:13 +00:00
|
|
|
contentVerticalLayout->setContentsMargins(0, 0, 0, 0);
|
Resize offscreen window when QQuickWidget is resized
In a typical Qt Quick application, when a window is resized, the
contentItem of that window is resized with it, and then the root item.
QQuickOverlay in qtquickcontrols2 listens to size changes in the
contentItem (QQuickRootItem) via addItemChangeListener(), as a cheap
way (e.g. no signals) of knowing when to resize background dimming
effects. It resizes the dimmer item to the size of the window.
The first problem with QQuickWidget is that it only ever resizes the root item
when using the SizeRootObjectToView resize mode, and not the contentItem.
The second problem is that the root item is resized (via updateSize()) before
the window itself even has a size (which happens in
QQuickWidget::createFramebufferObject() via the call to
d->offscreenWindow->setGeometry()).
To demonstrate the second problem in detail, consider the following widget
hierarchy (written in everybody's favorite language: QML):
QMainWindow {
QQuickWidget {
QQuickWindow { // QQuickWidgetPrivate::offscreenWindow
QQuickRootItem { // QQuickWindowPrivate::contentItem
Page {} // QQuickWidgetPrivate::root
}
}
}
}
The QMainWindow starts off as 200x200. When the window is resized,
QQuickWidget::resizeEvent() is called. The first thing it does is call
updateSize(), which in the case of SizeRootObjectToView, resizes the root item
to 300x300. This causes QQuickOverlayPrivate::itemGeometryChanged() to be
called, and the dimmers are resized to the size of the window, but the window
still has its 200x200 size, as it is only updated later, when
QQuickWidget::createFramebufferObject() is called.
This patch fixes these issues by ensuring that contentItem and the window
itself are resized along with the root item.
As to why such manual intervention is necessary: from what I can see, it is
because it's an "offscreen" window. This means that
QWindowPrivate::platformWindow is null, and setGeometry() takes a different
path that presumably results in no QResizeEvent being sent to the QQuickWindow.
As QQuickWindow relies on resizeEvent() being called to resize its contentItem,
the contentItem is never resized. With a typical Qt Quick application, all of
this works as expected.
Change-Id: I7401aa7a9b209096183416ab53014f67cceccbe4
Fixes: QTBUG-78323
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io>
Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
2020-03-05 15:00:04 +00:00
|
|
|
|
|
|
|
qmlRegisterType<Overlay>("Test", 1, 0, "Overlay");
|
|
|
|
|
|
|
|
auto quickWidget = new QQuickWidget(testFileUrl("resizeOverlay.qml"), &widget);
|
|
|
|
QCOMPARE(quickWidget->status(), QQuickWidget::Ready);
|
|
|
|
quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
|
|
|
|
contentVerticalLayout->addWidget(quickWidget);
|
|
|
|
|
|
|
|
auto rootItem = qobject_cast<QQuickItem*>(quickWidget->rootObject());
|
|
|
|
QVERIFY(rootItem);
|
|
|
|
|
|
|
|
auto overlay = rootItem->property("overlay").value<Overlay*>();
|
|
|
|
QVERIFY(overlay);
|
|
|
|
QVERIFY(overlay->parentItem());
|
|
|
|
overlay->startListening();
|
|
|
|
|
|
|
|
widget.resize(200, 200);
|
2022-04-08 15:35:03 +00:00
|
|
|
QTestPrivate::androidCompatibleShow(&widget);
|
Resize offscreen window when QQuickWidget is resized
In a typical Qt Quick application, when a window is resized, the
contentItem of that window is resized with it, and then the root item.
QQuickOverlay in qtquickcontrols2 listens to size changes in the
contentItem (QQuickRootItem) via addItemChangeListener(), as a cheap
way (e.g. no signals) of knowing when to resize background dimming
effects. It resizes the dimmer item to the size of the window.
The first problem with QQuickWidget is that it only ever resizes the root item
when using the SizeRootObjectToView resize mode, and not the contentItem.
The second problem is that the root item is resized (via updateSize()) before
the window itself even has a size (which happens in
QQuickWidget::createFramebufferObject() via the call to
d->offscreenWindow->setGeometry()).
To demonstrate the second problem in detail, consider the following widget
hierarchy (written in everybody's favorite language: QML):
QMainWindow {
QQuickWidget {
QQuickWindow { // QQuickWidgetPrivate::offscreenWindow
QQuickRootItem { // QQuickWindowPrivate::contentItem
Page {} // QQuickWidgetPrivate::root
}
}
}
}
The QMainWindow starts off as 200x200. When the window is resized,
QQuickWidget::resizeEvent() is called. The first thing it does is call
updateSize(), which in the case of SizeRootObjectToView, resizes the root item
to 300x300. This causes QQuickOverlayPrivate::itemGeometryChanged() to be
called, and the dimmers are resized to the size of the window, but the window
still has its 200x200 size, as it is only updated later, when
QQuickWidget::createFramebufferObject() is called.
This patch fixes these issues by ensuring that contentItem and the window
itself are resized along with the root item.
As to why such manual intervention is necessary: from what I can see, it is
because it's an "offscreen" window. This means that
QWindowPrivate::platformWindow is null, and setGeometry() takes a different
path that presumably results in no QResizeEvent being sent to the QQuickWindow.
As QQuickWindow relies on resizeEvent() being called to resize its contentItem,
the contentItem is never resized. With a typical Qt Quick application, all of
this works as expected.
Change-Id: I7401aa7a9b209096183416ab53014f67cceccbe4
Fixes: QTBUG-78323
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io>
Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
2020-03-05 15:00:04 +00:00
|
|
|
QCOMPARE(rootItem->width(), 200);
|
|
|
|
QCOMPARE(rootItem->height(), 200);
|
|
|
|
QCOMPARE(overlay->width(), rootItem->width());
|
|
|
|
QCOMPARE(overlay->height(), rootItem->height());
|
|
|
|
|
|
|
|
widget.resize(300, 300);
|
|
|
|
QCOMPARE(rootItem->width(), 300);
|
|
|
|
QCOMPARE(rootItem->height(), 300);
|
|
|
|
QCOMPARE(overlay->width(), rootItem->width());
|
|
|
|
QCOMPARE(overlay->height(), rootItem->height());
|
|
|
|
}
|
|
|
|
|
Handle redirected rendering better in styles
Unlike in the QWidget-based desktop world, Qt Quick scenes can
be rendered in a variety of ways, some completely offscreen
wthout any native windows on screen, whereas some (most notably,
QQuickWidget) work offscreen but in association with an on-screen
window that is not the QQuickWindow. Therefore, every time a
QQuickWindow is accessed, typically from QQuickStyleItem, it needs
to be considered if further resolution is needed.
For devicePixelRatio, there is a handy helper available in form of
QQuickWindow::effectiveDevicePixelRatio(). This picks up the dpr
from either the QQuickWindow or the QQuickWidget's associated
top-level QWidget window (or whatever window a custom
QQuickRenderControl implementation reports).
Elsewhere, where we need a QWindow in order to do native window
things, QQuickRenderControl::renderWindowFor() must be called to see
if there is another QWindow we should be using in place of the
QQuickWindow.
Pick-to: 6.2
Fixes: QTBUG-95937
Change-Id: I0690915d995ebb5f5cc0c48f565dfaf978e849ea
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
Reviewed-by: Samuel Ghinet <samuel.ghinet@qt.io>
2021-08-20 08:43:34 +00:00
|
|
|
void tst_qquickwidget::controls()
|
|
|
|
{
|
|
|
|
// Smoke test for having some basic Quick Controls in a scene in a QQuickWidget.
|
|
|
|
QWidget widget;
|
|
|
|
QVBoxLayout *contentVerticalLayout = new QVBoxLayout(&widget);
|
|
|
|
QQuickWidget *quickWidget = new QQuickWidget(testFileUrl("controls.qml"), &widget);
|
|
|
|
QCOMPARE(quickWidget->status(), QQuickWidget::Ready);
|
|
|
|
quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
|
|
|
|
contentVerticalLayout->addWidget(quickWidget);
|
|
|
|
|
|
|
|
widget.resize(400, 400);
|
|
|
|
widget.show();
|
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(&widget));
|
|
|
|
|
|
|
|
QQuickItem *rootItem = qobject_cast<QQuickItem *>(quickWidget->rootObject());
|
|
|
|
QVERIFY(rootItem);
|
|
|
|
QCOMPARE(rootItem->size(), quickWidget->size());
|
|
|
|
QSize oldSize = quickWidget->size();
|
|
|
|
|
|
|
|
// Verify that QTBUG-95937 no longer occurs. (on Windows with the default
|
|
|
|
// native windows style this used to assert in debug builds)
|
|
|
|
widget.resize(300, 300);
|
|
|
|
QTRY_VERIFY(quickWidget->width() < oldSize.width());
|
|
|
|
QTRY_COMPARE(rootItem->size(), quickWidget->size());
|
|
|
|
|
|
|
|
widget.hide();
|
|
|
|
widget.show();
|
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(&widget));
|
|
|
|
}
|
|
|
|
|
2021-11-29 16:23:18 +00:00
|
|
|
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);
|
2022-04-08 15:35:03 +00:00
|
|
|
QTestPrivate::androidCompatibleShow(&view1);
|
2021-11-29 16:23:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
2022-04-08 15:35:03 +00:00
|
|
|
QTestPrivate::androidCompatibleShow(&view2);
|
2021-11-29 16:23:18 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2023-04-05 15:12:07 +00:00
|
|
|
void tst_qquickwidget::focusPreserved()
|
|
|
|
{
|
|
|
|
if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation))
|
|
|
|
QSKIP("Window Activation is not supported.");
|
|
|
|
if (QGuiApplication::platformName() == "android")
|
|
|
|
QSKIP("Test doesn't exit cleanly on Android and generates many warnings - QTBUG-112696");
|
|
|
|
|
|
|
|
QScopedPointer<QWidget> widget(new QWidget());
|
|
|
|
QScopedPointer<QQuickWidget> quick(new QQuickWidget());
|
|
|
|
QQuickItem *root = new QQuickItem(); // will be owned by quick after setContent
|
|
|
|
QScopedPointer<QQuickItem> content(new QQuickItem());
|
|
|
|
content->setActiveFocusOnTab(true);
|
|
|
|
content->setFocus(true);
|
|
|
|
quick->setFocusPolicy(Qt::StrongFocus);
|
|
|
|
quick->setContent(QUrl(), nullptr, root);
|
|
|
|
root->setFlag(QQuickItem::ItemHasContents);
|
|
|
|
content->setParentItem(root);
|
|
|
|
|
|
|
|
quick->setGeometry(0, 0, 200, 200);
|
|
|
|
quick->show();
|
|
|
|
quick->setFocus();
|
|
|
|
quick->activateWindow();
|
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(quick.get()));
|
|
|
|
QTRY_VERIFY(quick->hasFocus());
|
|
|
|
QTRY_VERIFY(content->hasFocus());
|
|
|
|
QTRY_VERIFY(content->hasActiveFocus());
|
|
|
|
|
|
|
|
widget->show();
|
|
|
|
widget->setFocus();
|
|
|
|
widget->activateWindow();
|
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(widget.get()));
|
|
|
|
QTRY_VERIFY(widget->hasFocus());
|
|
|
|
|
|
|
|
quick->setParent(widget.get());
|
|
|
|
|
|
|
|
quick->show();
|
|
|
|
quick->setFocus();
|
|
|
|
quick->activateWindow();
|
|
|
|
QTRY_VERIFY(quick->hasFocus());
|
|
|
|
QTRY_VERIFY(content->hasFocus());
|
|
|
|
QTRY_VERIFY(content->hasActiveFocus());
|
|
|
|
}
|
|
|
|
|
QQuickWidget: don't crash in accessibility when reparenting
QAccessibleQuickWidget delegates all calls to the QAccessibleQuickWindow,
which it had as a member that was initialized at construction time to
the offscreenWindow backing the QQuickWidget. Both are QAccessibleObject
subclasses, and QAccessibleObject stores the object it wraps as a
QPointer. The QAccessibleQuickWindow's object becomes null when that
offscreen window gets destroyed (for instance, when reparenting).
We might get called by the accessibility framework in that situation, as
we are clicking a button and the hierarchy changes.
To prevent crashes, we need to test for nullptr in QAccessibleQuickWindow.
However, that alone would leave us with a useless QAccessibleQuickWindow,
and in turn with a useless QAccessibleQuickWidget instance.
The QAccessibleQuickWindow is not directly exposed to the
accessibility framework, and all calls to it are dispatched through its
QAccessibleQuickWidget owner. We can't repair the QAccessibleQuickWindow
but we can replace it entirely if we manage it as a heap-allocated
object. Use a std::unique_ptr for that, which we can reset with a new
instance created from a new offscreen window in order to repair things.
We can now either test in all functions whether the window's window is
still alive. Or we can handle the destroyed() signal of the offscreen
window. The latter solution is a bit more involved, but generally more
scalable as we don't have to remember to check, and possibly repair, in
each QAccessibleQuickWidget function.
Pick-to: 6.5
Fixes: QTBUG-108226
Change-Id: Ib19c07d3679c0af28cb5aab4c80691cc57c4e514
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Jan Arve Sæther <jan-arve.saether@qt.io>
2023-04-08 16:53:14 +00:00
|
|
|
/*
|
|
|
|
Reparenting the QQuickWidget recreates the offscreen QQuickWindow.
|
|
|
|
Since the accessible interface that is cached for the QQuickWidget dispatches
|
|
|
|
all calls to the offscreen QQuickWindow, it must fix itself when the offscreen
|
|
|
|
view changes. QTBUG-108226
|
|
|
|
*/
|
|
|
|
void tst_qquickwidget::accessibilityHandlesViewChange()
|
|
|
|
{
|
|
|
|
if (QGuiApplication::platformName() == "offscreen")
|
|
|
|
QSKIP("Doesn't test anything on offscreen platform.");
|
|
|
|
if (QGuiApplication::platformName() == "android")
|
|
|
|
QSKIP("Test doesn't exit cleanly on Android and generates many warnings - QTBUG-112696");
|
|
|
|
|
|
|
|
QWidget window;
|
|
|
|
|
|
|
|
QPointer<QQuickWindow> backingScene;
|
|
|
|
|
|
|
|
QQuickWidget *childView = new QQuickWidget(&window);
|
|
|
|
childView->setSource(testFileUrl("rectangle.qml"));
|
|
|
|
window.show();
|
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(&window));
|
|
|
|
backingScene = childView->quickWindow();
|
|
|
|
QVERIFY(backingScene);
|
|
|
|
|
|
|
|
QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(childView);
|
|
|
|
QVERIFY(iface);
|
|
|
|
(void)iface->child(0);
|
|
|
|
|
|
|
|
std::unique_ptr<QQuickWidget> quickWidget(childView);
|
|
|
|
childView->setParent(nullptr);
|
|
|
|
childView->show();
|
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(childView));
|
|
|
|
QVERIFY(!backingScene); // the old QQuickWindow should be gone now
|
|
|
|
QVERIFY(childView->quickWindow()); // long live the new QQuickWindow
|
|
|
|
|
|
|
|
iface = QAccessible::queryAccessibleInterface(childView);
|
|
|
|
QVERIFY(iface);
|
|
|
|
// this would crash if QAccessibleQuickWidget hadn't repaired itself to
|
|
|
|
// delegate calls to the new (or at least not the old, destroyed) QQuickWindow.
|
|
|
|
(void)iface->child(0);
|
|
|
|
}
|
|
|
|
|
2023-12-19 20:52:05 +00:00
|
|
|
class CreateDestroyWidget : public QWidget
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
using QWidget::create;
|
|
|
|
using QWidget::destroy;
|
|
|
|
};
|
|
|
|
|
|
|
|
void tst_qquickwidget::cleanupRhi()
|
|
|
|
{
|
|
|
|
CreateDestroyWidget topLevel;
|
|
|
|
QQuickWidget quickWidget(&topLevel);
|
|
|
|
quickWidget.setSource(testFileUrl("rectangle.qml"));
|
|
|
|
topLevel.show();
|
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(&topLevel));
|
|
|
|
|
|
|
|
topLevel.destroy();
|
|
|
|
topLevel.create();
|
|
|
|
}
|
QQuickWidget: don't crash in accessibility when reparenting
QAccessibleQuickWidget delegates all calls to the QAccessibleQuickWindow,
which it had as a member that was initialized at construction time to
the offscreenWindow backing the QQuickWidget. Both are QAccessibleObject
subclasses, and QAccessibleObject stores the object it wraps as a
QPointer. The QAccessibleQuickWindow's object becomes null when that
offscreen window gets destroyed (for instance, when reparenting).
We might get called by the accessibility framework in that situation, as
we are clicking a button and the hierarchy changes.
To prevent crashes, we need to test for nullptr in QAccessibleQuickWindow.
However, that alone would leave us with a useless QAccessibleQuickWindow,
and in turn with a useless QAccessibleQuickWidget instance.
The QAccessibleQuickWindow is not directly exposed to the
accessibility framework, and all calls to it are dispatched through its
QAccessibleQuickWidget owner. We can't repair the QAccessibleQuickWindow
but we can replace it entirely if we manage it as a heap-allocated
object. Use a std::unique_ptr for that, which we can reset with a new
instance created from a new offscreen window in order to repair things.
We can now either test in all functions whether the window's window is
still alive. Or we can handle the destroyed() signal of the offscreen
window. The latter solution is a bit more involved, but generally more
scalable as we don't have to remember to check, and possibly repair, in
each QAccessibleQuickWidget function.
Pick-to: 6.5
Fixes: QTBUG-108226
Change-Id: Ib19c07d3679c0af28cb5aab4c80691cc57c4e514
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Jan Arve Sæther <jan-arve.saether@qt.io>
2023-04-08 16:53:14 +00:00
|
|
|
|
2014-03-19 08:21:11 +00:00
|
|
|
QTEST_MAIN(tst_qquickwidget)
|
|
|
|
|
|
|
|
#include "tst_qquickwidget.moc"
|