4155 lines
152 KiB
C++
4155 lines
152 KiB
C++
// Copyright (C) 2020 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
#include <qtest.h>
|
|
#include <QDebug>
|
|
#include <QEvent>
|
|
#include <QMimeData>
|
|
#include <QTouchEvent>
|
|
#include <QtQuick/QQuickItem>
|
|
#include <QtQuick/QQuickView>
|
|
#include <QtQuick/QQuickWindow>
|
|
#include <QtQml/QQmlEngine>
|
|
#include <QtQml/QQmlComponent>
|
|
#include <QtQuick/private/qquickrectangle_p.h>
|
|
#include <QtQuick/private/qquickloader_p.h>
|
|
#include <QtQuick/private/qquickmousearea_p.h>
|
|
#include <QtQuickTestUtils/private/qmlutils_p.h>
|
|
#include <QtQuickTestUtils/private/visualtestutils_p.h>
|
|
#include <QtQuickTestUtils/private/viewtestutils_p.h>
|
|
#include <QSignalSpy>
|
|
#include <private/qquickwindow_p.h>
|
|
#include <private/qguiapplication_p.h>
|
|
#include <QtGui/qpa/qplatformintegration.h>
|
|
#include <QRunnable>
|
|
#include <QSGRendererInterface>
|
|
#include <QQuickRenderControl>
|
|
#include <QOperatingSystemVersion>
|
|
#include <functional>
|
|
#include <QtGui/private/qeventpoint_p.h>
|
|
#include <rhi/qrhi.h>
|
|
#if QT_CONFIG(opengl)
|
|
#include <QOpenGLContext>
|
|
#endif
|
|
#if QT_CONFIG(vulkan)
|
|
#include <QVulkanInstance>
|
|
#endif
|
|
|
|
Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests")
|
|
|
|
struct TouchEventData {
|
|
QEvent::Type type;
|
|
QWidget *widget;
|
|
QWindow *window;
|
|
QEventPoint::States states;
|
|
QList<QEventPoint> touchPoints;
|
|
};
|
|
|
|
static QEventPoint makeTouchPoint(QQuickItem *item, const QPointF &p, const QPointF &lastPoint = QPointF())
|
|
{
|
|
QPointF last = lastPoint.isNull() ? p : lastPoint;
|
|
|
|
QEventPoint tp;
|
|
|
|
QMutableEventPoint::setPosition(tp, p);
|
|
QMutableEventPoint::setScenePosition(tp, item->mapToScene(p));
|
|
QMutableEventPoint::setGlobalPosition(tp, item->mapToGlobal(p));
|
|
QMutableEventPoint::setGlobalLastPosition(tp, item->mapToGlobal(last));
|
|
return tp;
|
|
}
|
|
|
|
static TouchEventData makeTouchData(QEvent::Type type, QWindow *w, QEventPoint::States states = {},
|
|
const QList<QEventPoint>& touchPoints = QList<QEventPoint>())
|
|
{
|
|
TouchEventData d = { type, nullptr, w, states, touchPoints };
|
|
for (auto &pt : d.touchPoints)
|
|
QMutableEventPoint::detach(pt);
|
|
return d;
|
|
}
|
|
static TouchEventData makeTouchData(QEvent::Type type, QWindow *w, QEventPoint::States states,
|
|
const QList<QEventPoint*>& touchPoints)
|
|
{
|
|
QList <QEventPoint> pts;
|
|
for (auto pt : touchPoints)
|
|
pts << *pt;
|
|
TouchEventData d = { type, nullptr, w, states, pts };
|
|
return d;
|
|
}
|
|
static TouchEventData makeTouchData(QEvent::Type type, QWindow *w, QEventPoint::States states, const QEventPoint &touchPoint)
|
|
{
|
|
QList<QEventPoint*> points;
|
|
points << const_cast<QEventPoint *>(&touchPoint);
|
|
return makeTouchData(type, w, states, points);
|
|
}
|
|
|
|
#define COMPARE_TOUCH_POINTS(tp1, tp2) \
|
|
{ \
|
|
QCOMPARE(tp1.position(), tp2.position()); \
|
|
QCOMPARE(tp1.lastPosition(), tp2.lastPosition()); \
|
|
QCOMPARE(tp1.scenePosition(), tp2.scenePosition()); \
|
|
QCOMPARE(tp1.sceneLastPosition(), tp2.sceneLastPosition()); \
|
|
QCOMPARE(tp1.globalPosition(), tp2.globalPosition()); \
|
|
QCOMPARE(tp1.globalLastPosition(), tp2.globalLastPosition()); \
|
|
}
|
|
|
|
#define COMPARE_TOUCH_DATA(d1, d2) \
|
|
{ \
|
|
QCOMPARE((int)d1.type, (int)d2.type); \
|
|
QCOMPARE(d1.widget, d2.widget); \
|
|
QCOMPARE((int)d1.states, (int)d2.states); \
|
|
QCOMPARE(d1.touchPoints.count(), d2.touchPoints.count()); \
|
|
for (int i=0; i<d1.touchPoints.count(); i++) { \
|
|
COMPARE_TOUCH_POINTS(d1.touchPoints[i], d2.touchPoints[i]); \
|
|
} \
|
|
}
|
|
|
|
|
|
class RootItemAccessor : public QQuickItem
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
RootItemAccessor()
|
|
: m_rootItemDestroyed(false)
|
|
, m_rootItem(nullptr)
|
|
{
|
|
}
|
|
Q_INVOKABLE QQuickItem *contentItem()
|
|
{
|
|
if (!m_rootItem) {
|
|
QQuickWindowPrivate *c = QQuickWindowPrivate::get(window());
|
|
m_rootItem = c->contentItem;
|
|
QObject::connect(m_rootItem, SIGNAL(destroyed()), this, SLOT(rootItemDestroyed()));
|
|
}
|
|
return m_rootItem;
|
|
}
|
|
bool isRootItemDestroyed() {return m_rootItemDestroyed;}
|
|
public slots:
|
|
void rootItemDestroyed() {
|
|
m_rootItemDestroyed = true;
|
|
}
|
|
|
|
private:
|
|
bool m_rootItemDestroyed;
|
|
QQuickItem *m_rootItem;
|
|
};
|
|
|
|
class TestTouchItem : public QQuickRectangle
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
TestTouchItem(QQuickItem *parent = nullptr)
|
|
: QQuickRectangle(parent), acceptTouchEvents(true), acceptMouseEvents(true),
|
|
mousePressCount(0), mouseMoveCount(0),
|
|
spinLoopWhenPressed(false), touchEventCount(0),
|
|
mouseUngrabEventCount(0)
|
|
{
|
|
border()->setWidth(1);
|
|
setAcceptedMouseButtons(Qt::LeftButton);
|
|
setAcceptTouchEvents(true);
|
|
setFiltersChildMouseEvents(true);
|
|
}
|
|
|
|
void reset() {
|
|
acceptTouchEvents = acceptMouseEvents = true;
|
|
setEnabled(true);
|
|
setVisible(true);
|
|
|
|
lastEvent = makeTouchData(QEvent::None, window());//CHECK_VALID
|
|
|
|
lastVelocity = lastVelocityFromMouseMove = QVector2D();
|
|
lastMousePos = QPointF();
|
|
lastMouseCapabilityFlags = {};
|
|
touchEventCount = 0;
|
|
mouseMoveCount = 0;
|
|
mouseUngrabEventCount = 0;
|
|
}
|
|
|
|
static void clearMouseEventCounters()
|
|
{
|
|
mousePressNum = mouseMoveNum = mouseReleaseNum = 0;
|
|
}
|
|
|
|
void clearTouchEventCounter()
|
|
{
|
|
touchEventCount = 0;
|
|
}
|
|
|
|
bool acceptTouchEvents;
|
|
bool acceptMouseEvents;
|
|
bool grabOnRelease = false;
|
|
TouchEventData lastEvent;
|
|
int mousePressCount;
|
|
int mouseMoveCount;
|
|
bool spinLoopWhenPressed;
|
|
int touchEventCount;
|
|
int mouseUngrabEventCount;
|
|
QVector2D lastVelocity;
|
|
QVector2D lastVelocityFromMouseMove;
|
|
QPointF lastMousePos;
|
|
QInputDevice::Capabilities lastMouseCapabilityFlags;
|
|
|
|
void touchEvent(QTouchEvent *event) override
|
|
{
|
|
if (!acceptTouchEvents) {
|
|
event->ignore();
|
|
return;
|
|
}
|
|
qCDebug(lcTests) << objectName() << event;
|
|
++touchEventCount;
|
|
lastEvent = makeTouchData(event->type(), nullptr, event->touchPointStates(), event->points());
|
|
if (event->device()->capabilities().testFlag(QPointingDevice::Capability::Velocity) && !event->points().isEmpty()) {
|
|
lastVelocity = event->points().first().velocity();
|
|
} else {
|
|
lastVelocity = QVector2D();
|
|
}
|
|
if (spinLoopWhenPressed && event->touchPointStates().testFlag(QEventPoint::State::Pressed)) {
|
|
QCoreApplication::processEvents();
|
|
}
|
|
}
|
|
|
|
void mousePressEvent(QMouseEvent *e) override
|
|
{
|
|
if (!acceptMouseEvents) {
|
|
e->ignore();
|
|
return;
|
|
}
|
|
mousePressCount = ++mousePressNum;
|
|
lastMousePos = e->position().toPoint();
|
|
lastMouseCapabilityFlags = e->device()->capabilities();
|
|
}
|
|
|
|
void mouseMoveEvent(QMouseEvent *e) override
|
|
{
|
|
if (!acceptMouseEvents) {
|
|
e->ignore();
|
|
return;
|
|
}
|
|
mouseMoveCount = ++mouseMoveNum;
|
|
lastVelocityFromMouseMove = e->points().first().velocity();
|
|
lastMouseCapabilityFlags = e->device()->capabilities();
|
|
lastMousePos = e->position().toPoint();
|
|
}
|
|
|
|
void mouseReleaseEvent(QMouseEvent *e) override
|
|
{
|
|
if (!acceptMouseEvents) {
|
|
e->ignore();
|
|
return;
|
|
}
|
|
++mouseReleaseNum;
|
|
lastMousePos = e->position().toPoint();
|
|
lastMouseCapabilityFlags = e->device()->capabilities();
|
|
}
|
|
|
|
void mouseUngrabEvent() override
|
|
{
|
|
qCDebug(lcTests) << objectName();
|
|
++mouseUngrabEventCount;
|
|
}
|
|
|
|
bool childMouseEventFilter(QQuickItem *item, QEvent *e) override
|
|
{
|
|
qCDebug(lcTests) << objectName() << "filtering" << e << "ahead of delivery to" << item->metaObject()->className() << item->objectName();
|
|
switch (e->type()) {
|
|
case QEvent::MouseButtonPress:
|
|
mousePressCount = ++mousePressNum;
|
|
break;
|
|
case QEvent::MouseButtonRelease:
|
|
if (grabOnRelease)
|
|
grabMouse();
|
|
break;
|
|
case QEvent::MouseMove:
|
|
mouseMoveCount = ++mouseMoveNum;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static int mousePressNum, mouseMoveNum, mouseReleaseNum;
|
|
};
|
|
|
|
int TestTouchItem::mousePressNum = 0;
|
|
int TestTouchItem::mouseMoveNum = 0;
|
|
int TestTouchItem::mouseReleaseNum = 0;
|
|
|
|
class EventFilter : public QObject
|
|
{
|
|
public:
|
|
bool eventFilter(QObject *watched, QEvent *event) override
|
|
{
|
|
Q_UNUSED(watched);
|
|
events.append(event->type());
|
|
return false;
|
|
}
|
|
|
|
QList<int> events;
|
|
};
|
|
|
|
class ConstantUpdateItem : public QQuickItem
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
ConstantUpdateItem(QQuickItem *parent = nullptr) : QQuickItem(parent), iterations(0) {setFlag(ItemHasContents);}
|
|
|
|
int iterations;
|
|
protected:
|
|
QSGNode* updatePaintNode(QSGNode *, UpdatePaintNodeData *) override
|
|
{
|
|
iterations++;
|
|
update();
|
|
return nullptr;
|
|
}
|
|
};
|
|
|
|
class PointerRecordingWindow : public QQuickWindow
|
|
{
|
|
public:
|
|
explicit PointerRecordingWindow(QWindow *parent = nullptr) : QQuickWindow(parent) { }
|
|
|
|
protected:
|
|
bool event(QEvent *event) override {
|
|
if (event->isPointerEvent()) {
|
|
qCDebug(lcTests) << event;
|
|
m_events << PointerEvent { event->type(), static_cast<QPointerEvent *>(event)->pointingDevice() };
|
|
}
|
|
return QQuickWindow::event(event);
|
|
}
|
|
|
|
void mousePressEvent(QMouseEvent *event) override {
|
|
qCDebug(lcTests) << event;
|
|
m_mouseEvents << PointerEvent { event->type(), event->pointingDevice() };
|
|
QQuickWindow::mousePressEvent(event);
|
|
}
|
|
void mouseMoveEvent(QMouseEvent *event) override {
|
|
qCDebug(lcTests) << event;
|
|
m_mouseEvents << PointerEvent { event->type(), event->pointingDevice() };
|
|
QQuickWindow::mouseMoveEvent(event);
|
|
}
|
|
void mouseReleaseEvent(QMouseEvent *event) override {
|
|
qCDebug(lcTests) << event;
|
|
m_mouseEvents << PointerEvent { event->type(), event->pointingDevice() };
|
|
QQuickWindow::mouseReleaseEvent(event);
|
|
}
|
|
|
|
void touchEvent(QTouchEvent * event) override {
|
|
qCDebug(lcTests) << event;
|
|
m_touchEvents << PointerEvent { event->type(), event->pointingDevice() };
|
|
QQuickWindow::touchEvent(event);
|
|
}
|
|
|
|
#if QT_CONFIG(tabletevent)
|
|
void tabletEvent(QTabletEvent * event) override {
|
|
qCDebug(lcTests) << event;
|
|
m_tabletEvents << PointerEvent { event->type(), event->pointingDevice() };
|
|
QQuickWindow::tabletEvent(event);
|
|
}
|
|
#endif
|
|
|
|
#if QT_CONFIG(wheelevent)
|
|
void wheelEvent(QWheelEvent * event) override {
|
|
qCDebug(lcTests) << event;
|
|
m_tabletEvents << PointerEvent { event->type(), event->pointingDevice() };
|
|
QQuickWindow::wheelEvent(event);
|
|
}
|
|
#endif
|
|
|
|
public:
|
|
struct PointerEvent
|
|
{
|
|
QEvent::Type type;
|
|
const QPointingDevice *device;
|
|
};
|
|
QList<PointerEvent> m_events;
|
|
QList<PointerEvent> m_mouseEvents;
|
|
QList<PointerEvent> m_touchEvents;
|
|
QList<PointerEvent> m_tabletEvents;
|
|
};
|
|
|
|
class MouseRecordingItem : public QQuickItem
|
|
{
|
|
public:
|
|
MouseRecordingItem(bool acceptTouch, QQuickItem *parent = nullptr)
|
|
: QQuickItem(parent)
|
|
{
|
|
setSize(QSizeF(300, 300));
|
|
setAcceptedMouseButtons(Qt::LeftButton);
|
|
setAcceptTouchEvents(acceptTouch);
|
|
}
|
|
|
|
protected:
|
|
void touchEvent(QTouchEvent* event) override {
|
|
m_touchEvents << event->type();
|
|
qCDebug(lcTests) << "accepted?" << event->isAccepted() << event;
|
|
}
|
|
void mousePressEvent(QMouseEvent *event) override {
|
|
qCDebug(lcTests) << event;
|
|
m_mouseEvents << MouseEvent{event->type(), event->source()};
|
|
}
|
|
void mouseMoveEvent(QMouseEvent *event) override {
|
|
qCDebug(lcTests) << event;
|
|
m_mouseEvents << MouseEvent{event->type(), event->source()};
|
|
}
|
|
void mouseReleaseEvent(QMouseEvent *event) override {
|
|
qCDebug(lcTests) << event;
|
|
m_mouseEvents << MouseEvent{event->type(), event->source()};
|
|
}
|
|
|
|
void mouseDoubleClickEvent(QMouseEvent *event) override {
|
|
qCDebug(lcTests) << event;
|
|
m_mouseEvents << MouseEvent{event->type(), event->source()};
|
|
}
|
|
|
|
public:
|
|
struct MouseEvent
|
|
{
|
|
QEvent::Type type;
|
|
Qt::MouseEventSource source;
|
|
};
|
|
QList<MouseEvent> m_mouseEvents;
|
|
QList<QEvent::Type> m_touchEvents;
|
|
};
|
|
|
|
class tst_qquickwindow : public QQmlDataTest
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
tst_qquickwindow()
|
|
: QQmlDataTest(QT_QMLTEST_DATADIR)
|
|
, touchDevice(QTest::createTouchDevice())
|
|
, touchDeviceWithVelocity(QTest::createTouchDevice(QInputDevice::DeviceType::TouchScreen,
|
|
QInputDevice::Capability::Position | QPointingDevice::Capability::Velocity))
|
|
, tabletStylusDevice(QPointingDevicePrivate::tabletDevice(QInputDevice::DeviceType::Stylus,
|
|
QPointingDevice::PointerType::Pen,
|
|
QPointingDeviceUniqueId::fromNumericId(1234567890)))
|
|
{
|
|
QQuickWindow::setDefaultAlphaBuffer(true);
|
|
}
|
|
|
|
private slots:
|
|
void initTestCase() override;
|
|
void cleanup();
|
|
void aboutToStopSignal();
|
|
|
|
void constantUpdates();
|
|
void constantUpdatesOnWindow_data();
|
|
void constantUpdatesOnWindow();
|
|
void mouseFiltering();
|
|
void headless();
|
|
void destroyShowWithoutHide();
|
|
|
|
void touchEvent_basic();
|
|
void touchEvent_propagation();
|
|
void touchEvent_propagation_data();
|
|
void touchEvent_cancel();
|
|
void touchEvent_cancelClearsMouseGrab();
|
|
void touchEvent_reentrant();
|
|
void touchEvent_velocity();
|
|
|
|
void mergeTouchPointLists_data();
|
|
void mergeTouchPointLists();
|
|
|
|
void mouseFromTouch_basic();
|
|
void synthMouseFromTouch_data();
|
|
void synthMouseFromTouch();
|
|
void synthMouseDoubleClickFromTouch_data();
|
|
void synthMouseDoubleClickFromTouch();
|
|
|
|
void clearWindow();
|
|
|
|
void qmlCreation();
|
|
void qmlCreationWithScreen();
|
|
void clearColor();
|
|
void defaultState();
|
|
|
|
void grab_data();
|
|
void grab();
|
|
void earlyGrab();
|
|
void multipleWindows();
|
|
|
|
void animationsWhileHidden();
|
|
|
|
void focusObject();
|
|
void focusReason();
|
|
|
|
void ignoreUnhandledMouseEvents();
|
|
|
|
void ownershipRootItem();
|
|
|
|
void hideThenDelete_data();
|
|
void hideThenDelete();
|
|
|
|
void showHideAnimate();
|
|
|
|
void testExpose();
|
|
|
|
void requestActivate();
|
|
|
|
void testWindowVisibilityOrder();
|
|
|
|
void blockClosing();
|
|
void blockCloseMethod();
|
|
|
|
void crashWhenHoverItemDeleted();
|
|
|
|
void unloadSubWindow();
|
|
void changeVisibilityInCompleted();
|
|
|
|
void qobjectEventFilter_touch();
|
|
void qobjectEventFilter_key();
|
|
void qobjectEventFilter_mouse();
|
|
|
|
#if QT_CONFIG(cursor)
|
|
void cursor();
|
|
#endif
|
|
|
|
void animatingSignal();
|
|
void frameSignals();
|
|
|
|
void contentItemSize();
|
|
|
|
void defaultSurfaceFormat();
|
|
|
|
void attachedProperty();
|
|
|
|
void testRenderJob();
|
|
|
|
void testHoverChildMouseEventFilter();
|
|
void testHoverTimestamp();
|
|
void test_circleMapItem();
|
|
|
|
void grabContentItemToImage();
|
|
|
|
void testDragEventPropertyPropagation();
|
|
|
|
void findChild();
|
|
|
|
void testChildMouseEventFilter();
|
|
void testChildMouseEventFilter_data();
|
|
void cleanupGrabsOnRelease();
|
|
|
|
void subclassWithPointerEventVirtualOverrides_data();
|
|
void subclassWithPointerEventVirtualOverrides();
|
|
|
|
#if QT_CONFIG(shortcut)
|
|
void testShortCut();
|
|
void shortcutOverride_data();
|
|
void shortcutOverride();
|
|
#endif
|
|
|
|
void rendererInterface();
|
|
|
|
void rendererInterfaceWithRenderControl_data();
|
|
void rendererInterfaceWithRenderControl();
|
|
|
|
void graphicsConfiguration();
|
|
|
|
void visibleVsVisibility_data();
|
|
void visibleVsVisibility();
|
|
|
|
void eventTypes();
|
|
|
|
private:
|
|
QPointingDevice *touchDevice; // TODO make const after fixing QTBUG-107864
|
|
const QPointingDevice *touchDeviceWithVelocity;
|
|
const QPointingDevice *tabletStylusDevice;
|
|
};
|
|
|
|
#if QT_CONFIG(opengl)
|
|
Q_DECLARE_METATYPE(QOpenGLContext *);
|
|
#endif
|
|
|
|
void tst_qquickwindow::initTestCase()
|
|
{
|
|
// for the graphicsConfiguration test
|
|
qunsetenv("QSG_NO_DEPTH_BUFFER");
|
|
qunsetenv("QSG_RHI_DEBUG_LAYER");
|
|
qunsetenv("QSG_RHI_PROFILE");
|
|
qunsetenv("QSG_RHI_PREFER_SOFTWARE_RENDERER");
|
|
qunsetenv("QT_DISABLE_SHADER_DISK_CACHE");
|
|
qunsetenv("QSG_RHI_DISABLE_DISK_CACHE");
|
|
qunsetenv("QSG_RHI_PIPELINE_CACHE_SAVE");
|
|
qunsetenv("QSG_RHI_PIPELINE_CACHE_LOAD");
|
|
|
|
QQmlDataTest::initTestCase();
|
|
}
|
|
|
|
void tst_qquickwindow::cleanup()
|
|
{
|
|
QVERIFY(QGuiApplication::topLevelWindows().isEmpty());
|
|
}
|
|
|
|
void tst_qquickwindow::aboutToStopSignal()
|
|
{
|
|
QQuickWindow window;
|
|
window.setTitle(QTest::currentTestFunction());
|
|
window.show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(&window));
|
|
|
|
QSignalSpy spy(&window, SIGNAL(sceneGraphAboutToStop()));
|
|
|
|
window.hide();
|
|
|
|
QTRY_VERIFY(spy.size() > 0);
|
|
}
|
|
|
|
//If the item calls update inside updatePaintNode, it should schedule another sync pass
|
|
void tst_qquickwindow::constantUpdates()
|
|
{
|
|
QQuickWindow window;
|
|
window.resize(250, 250);
|
|
ConstantUpdateItem item(window.contentItem());
|
|
window.setTitle(QTest::currentTestFunction());
|
|
window.show();
|
|
|
|
QSignalSpy beforeSpy(&window, SIGNAL(beforeSynchronizing()));
|
|
QSignalSpy afterSpy(&window, SIGNAL(afterSynchronizing()));
|
|
|
|
QTRY_VERIFY(item.iterations > 10);
|
|
QTRY_VERIFY(beforeSpy.size() > 10);
|
|
QTRY_VERIFY(afterSpy.size() > 10);
|
|
}
|
|
|
|
void tst_qquickwindow::constantUpdatesOnWindow_data()
|
|
{
|
|
QTest::addColumn<bool>("blockedGui");
|
|
QTest::addColumn<QByteArray>("signal");
|
|
|
|
QQuickWindow window;
|
|
window.setTitle(QTest::currentTestFunction());
|
|
window.setGeometry(100, 100, 300, 200);
|
|
window.show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(&window));
|
|
const bool threaded = QQuickWindowPrivate::get(&window)->context->thread() != QGuiApplication::instance()->thread();
|
|
if (threaded) {
|
|
QTest::newRow("blocked, beforeRender") << true << QByteArray(SIGNAL(beforeRendering()));
|
|
QTest::newRow("blocked, afterRender") << true << QByteArray(SIGNAL(afterRendering()));
|
|
QTest::newRow("blocked, swapped") << true << QByteArray(SIGNAL(frameSwapped()));
|
|
}
|
|
QTest::newRow("unblocked, beforeRender") << false << QByteArray(SIGNAL(beforeRendering()));
|
|
QTest::newRow("unblocked, afterRender") << false << QByteArray(SIGNAL(afterRendering()));
|
|
QTest::newRow("unblocked, swapped") << false << QByteArray(SIGNAL(frameSwapped()));
|
|
}
|
|
|
|
class FrameCounter : public QObject
|
|
{
|
|
Q_OBJECT
|
|
public slots:
|
|
void incr() { QMutexLocker locker(&m_mutex); ++m_counter; }
|
|
public:
|
|
FrameCounter() : m_counter(0) {}
|
|
int count() { QMutexLocker locker(&m_mutex); int x = m_counter; return x; }
|
|
private:
|
|
int m_counter;
|
|
QMutex m_mutex;
|
|
};
|
|
|
|
void tst_qquickwindow::constantUpdatesOnWindow()
|
|
{
|
|
QFETCH(bool, blockedGui);
|
|
QFETCH(QByteArray, signal);
|
|
|
|
QQuickWindow window;
|
|
window.setTitle(QTest::currentTestFunction());
|
|
window.setGeometry(100, 100, 300, 200);
|
|
|
|
bool ok = connect(&window, signal.constData(), &window, SLOT(update()), Qt::DirectConnection);
|
|
Q_ASSERT(ok);
|
|
window.show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(&window));
|
|
|
|
FrameCounter counter;
|
|
connect(&window, SIGNAL(frameSwapped()), &counter, SLOT(incr()), Qt::DirectConnection);
|
|
|
|
int frameCount = 10;
|
|
QElapsedTimer timer;
|
|
timer.start();
|
|
if (blockedGui) {
|
|
while (counter.count() < frameCount)
|
|
QTest::qSleep(100);
|
|
QVERIFY(counter.count() >= frameCount);
|
|
} else {
|
|
window.update();
|
|
QTRY_VERIFY(counter.count() > frameCount);
|
|
}
|
|
window.hide();
|
|
}
|
|
|
|
void tst_qquickwindow::touchEvent_basic()
|
|
{
|
|
TestTouchItem::clearMouseEventCounters();
|
|
|
|
QQuickWindow *window = new QQuickWindow;
|
|
QScopedPointer<QQuickWindow> cleanup(window);
|
|
window->setTitle(QTest::currentTestFunction());
|
|
|
|
window->resize(250, 250);
|
|
window->setPosition(100, 100);
|
|
window->show();
|
|
QVERIFY(QTest::qWaitForWindowActive(window));
|
|
|
|
TestTouchItem *bottomItem = new TestTouchItem(window->contentItem());
|
|
bottomItem->setObjectName("Bottom Item");
|
|
bottomItem->setSize(QSizeF(150, 150));
|
|
|
|
TestTouchItem *middleItem = new TestTouchItem(bottomItem);
|
|
middleItem->setObjectName("Middle Item");
|
|
middleItem->setPosition(QPointF(50, 50));
|
|
middleItem->setSize(QSizeF(150, 150));
|
|
|
|
TestTouchItem *topItem = new TestTouchItem(middleItem);
|
|
topItem->setObjectName("Top Item");
|
|
topItem->setPosition(QPointF(50, 50));
|
|
topItem->setSize(QSizeF(150, 150));
|
|
|
|
QPointF pos(10, 10);
|
|
QTest::QTouchEventSequence touchSeq = QTest::touchEvent(window, touchDevice, false);
|
|
|
|
// press single point
|
|
touchSeq.press(0, topItem->mapToScene(pos).toPoint(),window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
QTRY_COMPARE(topItem->lastEvent.touchPoints.size(), 1);
|
|
|
|
QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
|
|
QVERIFY(bottomItem->lastEvent.touchPoints.isEmpty());
|
|
// At one point this was failing with kwin (KDE window manager) because window->setPosition(100, 100)
|
|
// would put the decorated window at that position rather than the window itself.
|
|
COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, QEventPoint::State::Pressed, makeTouchPoint(topItem, pos)));
|
|
topItem->reset();
|
|
touchSeq.release(0, topItem->mapToScene(pos).toPoint(), window).commit();
|
|
|
|
// press multiple points
|
|
touchSeq.press(0, topItem->mapToScene(pos).toPoint(), window)
|
|
.press(1, bottomItem->mapToScene(pos).toPoint(), window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
QCOMPARE(topItem->lastEvent.touchPoints.size(), 1);
|
|
QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
|
|
QCOMPARE(bottomItem->lastEvent.touchPoints.size(), 1);
|
|
COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, QEventPoint::State::Pressed, makeTouchPoint(topItem, pos)));
|
|
COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, QEventPoint::State::Pressed, makeTouchPoint(bottomItem, pos)));
|
|
topItem->reset();
|
|
bottomItem->reset();
|
|
touchSeq.release(0, topItem->mapToScene(pos).toPoint(), window).release(1, bottomItem->mapToScene(pos).toPoint(), window).commit();
|
|
|
|
// touch point on top item moves to bottom item, but top item should still receive the event
|
|
touchSeq.press(0, topItem->mapToScene(pos).toPoint(), window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
touchSeq.move(0, bottomItem->mapToScene(pos).toPoint(), window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
QCOMPARE(topItem->lastEvent.touchPoints.size(), 1);
|
|
COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchUpdate, window, QEventPoint::State::Updated,
|
|
makeTouchPoint(topItem, topItem->mapFromItem(bottomItem, pos), pos)));
|
|
topItem->reset();
|
|
touchSeq.release(0, bottomItem->mapToScene(pos).toPoint(), window).commit();
|
|
|
|
// touch point on bottom item moves to top item, but bottom item should still receive the event
|
|
touchSeq.press(0, bottomItem->mapToScene(pos).toPoint(), window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
touchSeq.move(0, topItem->mapToScene(pos).toPoint(), window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
QCOMPARE(bottomItem->lastEvent.touchPoints.size(), 1);
|
|
COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchUpdate, window, QEventPoint::State::Updated,
|
|
makeTouchPoint(bottomItem, bottomItem->mapFromItem(topItem, pos), pos)));
|
|
bottomItem->reset();
|
|
touchSeq.release(0, bottomItem->mapToScene(pos).toPoint(), window).commit();
|
|
|
|
// a single stationary press on an item shouldn't cause an event
|
|
touchSeq.press(0, topItem->mapToScene(pos).toPoint(), window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
touchSeq.stationary(0)
|
|
.press(1, bottomItem->mapToScene(pos).toPoint(), window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
QCOMPARE(topItem->lastEvent.touchPoints.size(), 1); // received press and then stationary
|
|
QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
|
|
QCOMPARE(bottomItem->lastEvent.touchPoints.size(), 1);
|
|
COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchUpdate, window, QEventPoint::State::Stationary, makeTouchPoint(topItem, pos)));
|
|
COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, QEventPoint::State::Pressed, makeTouchPoint(bottomItem, pos)));
|
|
topItem->reset();
|
|
bottomItem->reset();
|
|
// cleanup: what is pressed must be released
|
|
// Otherwise you will get an assertion failure:
|
|
// ASSERT: "itemForTouchPointId.isEmpty()" in file items/qquickwindow.cpp
|
|
touchSeq.release(0, pos.toPoint(), window).release(1, pos.toPoint(), window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
// move touch point from top item to bottom, and release
|
|
touchSeq.press(0, topItem->mapToScene(pos).toPoint(),window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
touchSeq.release(0, bottomItem->mapToScene(pos).toPoint(),window).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
QCOMPARE(topItem->lastEvent.touchPoints.size(), 1);
|
|
COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchEnd, window, QEventPoint::State::Released,
|
|
makeTouchPoint(topItem, topItem->mapFromItem(bottomItem, pos), pos)));
|
|
topItem->reset();
|
|
|
|
// release while another point is pressed
|
|
touchSeq.press(0, topItem->mapToScene(pos).toPoint(),window) // seen and grabbed by topItem
|
|
.press(1, bottomItem->mapToScene(pos).toPoint(), window).commit(); // seen and grabbed by bottomItem
|
|
QQuickTouchUtils::flush(window);
|
|
touchSeq.move(0, bottomItem->mapToScene(pos).toPoint(), window).stationary(1).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
touchSeq.release(0, bottomItem->mapToScene(pos).toPoint(), window)
|
|
.stationary(1).commit();
|
|
QQuickTouchUtils::flush(window);
|
|
QCOMPARE(topItem->lastEvent.touchPoints.size(), 1);
|
|
QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
|
|
QCOMPARE(bottomItem->lastEvent.touchPoints.size(), 1);
|
|
// Since qtbase 2692237bb1b0c0f50b7cc5d920eb8ab065063d47, if the point didn't have a different position on release,
|
|
// then lastPosition is not changed. So in this case, it still holds the press position. I.e. on release,
|
|
// it's the last position that was actually different.
|
|
COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchEnd, window, QEventPoint::State::Released,
|
|
makeTouchPoint(topItem, topItem->mapFromItem(bottomItem, pos), pos)));
|
|
COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchUpdate, window, QEventPoint::State::Stationary,
|
|
makeTouchPoint(bottomItem, pos)));
|
|
topItem->reset();
|
|
bottomItem->reset();
|
|
|
|
delete topItem;
|
|
delete middleItem;
|
|
delete bottomItem;
|
|
}
|
|
|
|
void tst_qquickwindow::touchEvent_propagation()
|
|
{
|
|
TestTouchItem::clearMouseEventCounters();
|
|
|
|
QFETCH(bool, acceptTouchEvents);
|
|
QFETCH(bool, acceptMouseEvents);
|
|
QFETCH(bool, enableItem);
|
|
QFETCH(bool, showItem);
|
|
|
|
QQuickWindow *window = new QQuickWindow;
|
|
QScopedPointer<QQuickWindow> cleanup(window);
|
|
|
|
window->resize(250, 250);
|
|
window->setPosition(100, 100);
|
|
window->setTitle(QTest::currentTestFunction());
|
|
window->show();
|
|
QVERIFY(QTest::qWaitForWindowActive(window));
|
|
|
|
TestTouchItem *bottomItem = new TestTouchItem(window->contentItem());
|
|
bottomItem->setObjectName("Bottom Item");
|
|
bottomItem->setSize(QSizeF(150, 150));
|
|
|
|
TestTouchItem *middleItem = new TestTouchItem(bottomItem);
|
|
middleItem->setObjectName("Middle Item");
|
|
middleItem->setPosition(QPointF(50, 50));
|
|
middleItem->setSize(QSizeF(150, 150));
|
|
|
|
TestTouchItem *topItem = new TestTouchItem(middleItem);
|
|
topItem->setObjectName("Top Item");
|
|
topItem->setPosition(QPointF(50, 50));
|
|
topItem->setSize(QSizeF(150, 150));
|
|
|
|
QPointF pos(10, 10);
|
|
QPoint pointInBottomItem = bottomItem->mapToScene(pos).toPoint(); // (10, 10)
|
|
QPoint pointInMiddleItem = middleItem->mapToScene(pos).toPoint(); // (60, 60) overlaps with bottomItem
|
|
QPoint pointInTopItem = topItem->mapToScene(pos).toPoint(); // (110, 110) overlaps with bottom & top items
|
|
|
|
// disable topItem
|
|
topItem->acceptTouchEvents = acceptTouchEvents;
|
|
topItem->acceptMouseEvents = acceptMouseEvents;
|
|
topItem->setEnabled(enableItem);
|
|
topItem->setVisible(showItem);
|
|
|
|
// single touch to top item, should be received by middle item
|
|
QTest::touchEvent(window, touchDevice).press(0, pointInTopItem, window);
|
|
QTRY_COMPARE(middleItem->lastEvent.touchPoints.size(), 1);
|
|
QVERIFY(topItem->lastEvent.touchPoints.isEmpty());
|
|
QVERIFY(bottomItem->lastEvent.touchPoints.isEmpty());
|
|
COMPARE_TOUCH_DATA(middleItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, QEventPoint::State::Pressed,
|
|
makeTouchPoint(middleItem, middleItem->mapFromItem(topItem, pos))));
|
|
QTest::touchEvent(window, touchDevice).release(0, pointInTopItem, window);
|
|
|
|
// touch top and middle items, middle item should get both events
|
|
QTest::touchEvent(window, touchDevice).press(0, pointInTopItem, window)
|
|
.press(1, pointInMiddleItem, window);
|
|
QTRY_COMPARE(middleItem->lastEvent.touchPoints.size(), 2);
|
|
QVERIFY(topItem->lastEvent.touchPoints.isEmpty());
|
|
QVERIFY(bottomItem->lastEvent.touchPoints.isEmpty());
|
|
COMPARE_TOUCH_DATA(middleItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, QEventPoint::State::Pressed,
|
|
(QList<QEventPoint>() << makeTouchPoint(middleItem, middleItem->mapFromItem(topItem, pos))
|
|
<< makeTouchPoint(middleItem, pos) )));
|
|
QTest::touchEvent(window, touchDevice).release(0, pointInTopItem, window)
|
|
.release(1, pointInMiddleItem, window);
|
|
middleItem->reset();
|
|
|
|
// disable middleItem as well
|
|
middleItem->acceptTouchEvents = acceptTouchEvents;
|
|
middleItem->acceptMouseEvents = acceptMouseEvents;
|
|
middleItem->setEnabled(enableItem);
|
|
middleItem->setVisible(showItem);
|
|
|
|
// touch top and middle items, bottom item should get all events
|
|
QTest::touchEvent(window, touchDevice).press(0, pointInTopItem, window)
|
|
.press(1, pointInMiddleItem, window);
|
|
QTRY_COMPARE(bottomItem->lastEvent.touchPoints.size(), 2);
|
|
QVERIFY(topItem->lastEvent.touchPoints.isEmpty());
|
|
QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
|
|
COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, QEventPoint::State::Pressed,
|
|
(QList<QEventPoint>() << makeTouchPoint(bottomItem, bottomItem->mapFromItem(topItem, pos))
|
|
<< makeTouchPoint(bottomItem, bottomItem->mapFromItem(middleItem, pos)) )));
|
|
bottomItem->reset();
|
|
|
|
// disable bottom item as well
|
|
bottomItem->acceptTouchEvents = acceptTouchEvents;
|
|
bottomItem->setEnabled(enableItem);
|
|
bottomItem->setVisible(showItem);
|
|
QTest::touchEvent(window, touchDevice).release(0, pointInTopItem, window)
|
|
.release(1, pointInMiddleItem, window);
|
|
|
|
// no events should be received
|
|
QTest::touchEvent(window, touchDevice).press(0, pointInTopItem, window)
|
|
.press(1, pointInMiddleItem, window)
|
|
.press(2, pointInBottomItem, window);
|
|
QTest::qWait(50);
|
|
QVERIFY(topItem->lastEvent.touchPoints.isEmpty());
|
|
QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
|
|
QVERIFY(bottomItem->lastEvent.touchPoints.isEmpty());
|
|
QTest::touchEvent(window, touchDevice).release(0, pointInTopItem, window)
|
|
.release(1, pointInMiddleItem, window)
|
|
.release(2, pointInBottomItem, window);
|
|
topItem->reset();
|
|
middleItem->reset();
|
|
bottomItem->reset();
|
|
|
|
// disable middle item, touch on top item
|
|
middleItem->acceptTouchEvents = acceptTouchEvents;
|
|
middleItem->setEnabled(enableItem);
|
|
middleItem->setVisible(showItem);
|
|
QTest::touchEvent(window, touchDevice).press(0, pointInTopItem, window);
|
|
QTest::qWait(50);
|
|
if (!enableItem || !showItem) {
|
|
// middle item is disabled or has 0 opacity, bottom item receives the event
|
|
QVERIFY(topItem->lastEvent.touchPoints.isEmpty());
|
|
QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
|
|
QCOMPARE(bottomItem->lastEvent.touchPoints.size(), 1);
|
|
COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, QEventPoint::State::Pressed,
|
|
makeTouchPoint(bottomItem, bottomItem->mapFromItem(topItem, pos))));
|
|
} else {
|
|
// middle item ignores event, sends it to the top item (top-most child)
|
|
QCOMPARE(topItem->lastEvent.touchPoints.size(), 1);
|
|
QVERIFY(middleItem->lastEvent.touchPoints.isEmpty());
|
|
QVERIFY(bottomItem->lastEvent.touchPoints.isEmpty());
|
|
COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, QEventPoint::State::Pressed,
|
|
makeTouchPoint(topItem, pos)));
|
|
}
|
|
QTest::touchEvent(window, touchDevice).release(0, pointInTopItem, window);
|
|
|
|
delete topItem;
|
|
delete middleItem;
|
|
delete bottomItem;
|
|
}
|
|
|
|
void tst_qquickwindow::touchEvent_propagation_data()
|
|
{
|
|
QTest::addColumn<bool>("acceptTouchEvents");
|
|
QTest::addColumn<bool>("acceptMouseEvents");
|
|
QTest::addColumn<bool>("enableItem");
|
|
QTest::addColumn<bool>("showItem");
|
|
|
|
QTest::newRow("disable events") << false << false << true << true;
|
|
QTest::newRow("disable item") << true << true << false << true;
|
|
QTest::newRow("hide item") << true << true << true << false;
|
|
}
|
|
|
|
void tst_qquickwindow::touchEvent_cancel()
|
|
{
|
|
TestTouchItem::clearMouseEventCounters();
|
|
|
|
QQuickWindow *window = new QQuickWindow;
|
|
QScopedPointer<QQuickWindow> cleanup(window);
|
|
|
|
window->resize(250, 250);
|
|
window->setPosition(100, 100);
|
|
window->setTitle(QTest::currentTestFunction());
|
|
window->show();
|
|
QVERIFY(QTest::qWaitForWindowActive(window));
|
|
|
|
TestTouchItem *item = new TestTouchItem(window->contentItem());
|
|
item->setPosition(QPointF(50, 50));
|
|
item->setSize(QSizeF(150, 150));
|
|
|
|
QPointF pos(50, 50);
|
|
QTest::touchEvent(window, touchDevice).press(0, item->mapToScene(pos).toPoint(), window);
|
|
QCoreApplication::processEvents();
|
|
|
|
QTRY_COMPARE(item->lastEvent.touchPoints.size(), 1);
|
|
TouchEventData d = makeTouchData(QEvent::TouchBegin, window, QEventPoint::State::Pressed, makeTouchPoint(item, pos));
|
|
COMPARE_TOUCH_DATA(item->lastEvent, d);
|
|
item->reset();
|
|
|
|
QWindowSystemInterface::handleTouchCancelEvent(nullptr, touchDevice);
|
|
QCoreApplication::processEvents();
|
|
d = makeTouchData(QEvent::TouchCancel, window, QEventPoint::State::Pressed, makeTouchPoint(item, pos));
|
|
COMPARE_TOUCH_DATA(item->lastEvent, d);
|
|
|
|
delete item;
|
|
}
|
|
|
|
void tst_qquickwindow::touchEvent_cancelClearsMouseGrab()
|
|
{
|
|
TestTouchItem::clearMouseEventCounters();
|
|
|
|
QQuickWindow *window = new QQuickWindow;
|
|
QScopedPointer<QQuickWindow> cleanup(window);
|
|
|
|
window->resize(250, 250);
|
|
window->setPosition(100, 100);
|
|
window->setTitle(QTest::currentTestFunction());
|
|
window->show();
|
|
QVERIFY(QTest::qWaitForWindowActive(window));
|
|
|
|
TestTouchItem *item = new TestTouchItem(window->contentItem());
|
|
item->setPosition(QPointF(50, 50));
|
|
item->setSize(QSizeF(150, 150));
|
|
item->acceptMouseEvents = true;
|
|
item->setAcceptTouchEvents(false);
|
|
|
|
QPointF pos(50, 50);
|
|
QTest::touchEvent(window, touchDevice).press(0, item->mapToScene(pos).toPoint(), window);
|
|
QCoreApplication::processEvents();
|
|
|
|
QTRY_COMPARE(item->mousePressCount, 1);
|
|
QTRY_COMPARE(item->mouseUngrabEventCount, 0);
|
|
|
|
QWindowSystemInterface::handleTouchCancelEvent(nullptr, touchDevice);
|
|
QCoreApplication::processEvents();
|
|
|
|
QTRY_COMPARE(item->mouseUngrabEventCount, 1);
|
|
}
|
|
|
|
void tst_qquickwindow::touchEvent_reentrant()
|
|
{
|
|
TestTouchItem::clearMouseEventCounters();
|
|
|
|
QQuickWindow *window = new QQuickWindow;
|
|
QScopedPointer<QQuickWindow> cleanup(window);
|
|
|
|
window->resize(250, 250);
|
|
window->setPosition(100, 100);
|
|
window->setTitle(QTest::currentTestFunction());
|
|
window->show();
|
|
QVERIFY(QTest::qWaitForWindowActive(window));
|
|
|
|
TestTouchItem *item = new TestTouchItem(window->contentItem());
|
|
|
|
item->spinLoopWhenPressed = true; // will call processEvents() from the touch handler
|
|
|
|
item->setPosition(QPointF(50, 50));
|
|
item->setSize(QSizeF(150, 150));
|
|
QPointF pos(60, 60);
|
|
|
|
// None of these should commit from the dtor.
|
|
QTest::QTouchEventSequence press = QTest::touchEvent(window, touchDevice, false).press(0, pos.toPoint(), window);
|
|
pos += QPointF(2, 2);
|
|
QTest::QTouchEventSequence move = QTest::touchEvent(window, touchDevice, false).move(0, pos.toPoint(), window);
|
|
QTest::QTouchEventSequence release = QTest::touchEvent(window, touchDevice, false).release(0, pos.toPoint(), window);
|
|
|
|
// Now commit (i.e. call QWindowSystemInterface::handleTouchEvent), but do not process the events yet.
|
|
press.commit(false);
|
|
move.commit(false);
|
|
release.commit(false);
|
|
|
|
QCoreApplication::processEvents();
|
|
|
|
QTRY_COMPARE(item->touchEventCount, 3);
|
|
|
|
delete item;
|
|
}
|
|
|
|
void tst_qquickwindow::touchEvent_velocity()
|
|
{
|
|
TestTouchItem::clearMouseEventCounters();
|
|
|
|
QQuickWindow *window = new QQuickWindow;
|
|
QScopedPointer<QQuickWindow> cleanup(window);
|
|
window->resize(250, 250);
|
|
window->setPosition(100, 100);
|
|
window->setTitle(QTest::currentTestFunction());
|
|
window->show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(window));
|
|
QTest::qWait(10);
|
|
|
|
TestTouchItem *item = new TestTouchItem(window->contentItem());
|
|
item->setPosition(QPointF(50, 50));
|
|
item->setSize(QSizeF(150, 150));
|
|
|
|
QList<QEventPoint> points;
|
|
QEventPoint tp(1, QEventPoint::State::Pressed, {}, {});
|
|
const QPointF localPos = item->mapToScene(QPointF(10, 10));
|
|
const QPointF screenPos = window->mapToGlobal(localPos.toPoint());
|
|
QMutableEventPoint::setPosition(tp, localPos);
|
|
QMutableEventPoint::setGlobalPosition(tp, screenPos);
|
|
QMutableEventPoint::setEllipseDiameters(tp, QSizeF(4, 4));
|
|
points << tp;
|
|
QWindowSystemInterface::handleTouchEvent(window, touchDeviceWithVelocity,
|
|
QWindowSystemInterfacePrivate::toNativeTouchPoints(points, window));
|
|
QGuiApplication::processEvents();
|
|
QQuickTouchUtils::flush(window);
|
|
QCOMPARE(item->touchEventCount, 1);
|
|
|
|
QMutableEventPoint::setState(points[0], QEventPoint::State::Updated);
|
|
QMutableEventPoint::setPosition(points[0], localPos + QPointF(5, 5));
|
|
QMutableEventPoint::setGlobalPosition(points[0], screenPos + QPointF(5, 5));
|
|
QVector2D velocity(1.5, 2.5);
|
|
QMutableEventPoint::setVelocity(points[0], velocity);
|
|
QWindowSystemInterface::handleTouchEvent(window, touchDeviceWithVelocity,
|
|
QWindowSystemInterfacePrivate::toNativeTouchPoints(points, window));
|
|
QGuiApplication::processEvents();
|
|
QQuickTouchUtils::flush(window);
|
|
QCOMPARE(item->touchEventCount, 2);
|
|
QCOMPARE(item->lastEvent.touchPoints.size(), 1);
|
|
QCOMPARE(item->lastVelocity, velocity);
|
|
|
|
// Now have a transformation on the item and check if position is transformed accordingly.
|
|
// (In Qt 6, transforming the velocity is an exercise left to the user. This saves work
|
|
// during delivery. If we want it to be transformed, we should add QEventPoint::sceneVelocity
|
|
// so that we can keep transforming it repeatedly during Item-localization.)
|
|
item->setRotation(90); // clockwise
|
|
QMutableEventPoint::setPosition(points[0], points[0].position() + QPointF(5, 5));
|
|
QMutableEventPoint::setGlobalPosition(points[0], points[0].globalPosition() + QPointF(5, 5));
|
|
QWindowSystemInterface::handleTouchEvent(window, touchDeviceWithVelocity,
|
|
QWindowSystemInterfacePrivate::toNativeTouchPoints(points, window));
|
|
QGuiApplication::processEvents();
|
|
QQuickTouchUtils::flush(window);
|
|
QCOMPARE(item->lastVelocity, velocity);
|
|
QPoint itemLocalPos = item->mapFromScene(points[0].position()).toPoint();
|
|
QPoint itemLocalPosFromEvent = item->lastEvent.touchPoints[0].position().toPoint();
|
|
QCOMPARE(itemLocalPos, itemLocalPosFromEvent);
|
|
|
|
QMutableEventPoint::setState(points[0], QEventPoint::State::Released);
|
|
QWindowSystemInterface::handleTouchEvent(window, touchDeviceWithVelocity,
|
|
QWindowSystemInterfacePrivate::toNativeTouchPoints(points, window));
|
|
QGuiApplication::processEvents();
|
|
QQuickTouchUtils::flush(window);
|
|
delete item;
|
|
}
|
|
|
|
using ItemVector = QVector<std::shared_ptr<QQuickItem>>;
|
|
void tst_qquickwindow::mergeTouchPointLists_data()
|
|
{
|
|
QTest::addColumn<ItemVector>("list1");
|
|
QTest::addColumn<ItemVector>("list2");
|
|
QTest::addColumn<QVector<QQuickItem *>>("expected");
|
|
QTest::addColumn<bool>("showItem");
|
|
|
|
auto item1 = std::make_shared<QQuickItem>();
|
|
auto item2 = std::make_shared<QQuickItem>();
|
|
auto item3 = std::make_shared<QQuickItem>();
|
|
auto item4 = std::make_shared<QQuickItem>();
|
|
auto item5 = std::make_shared<QQuickItem>();
|
|
|
|
QTest::newRow("empty") << ItemVector() << ItemVector() << QVector<QQuickItem *>();
|
|
QTest::newRow("single list left")
|
|
<< (ItemVector() << item1 << item2 << item3)
|
|
<< ItemVector()
|
|
<< (QVector<QQuickItem *>() << item1.get() << item2.get() << item3.get());
|
|
QTest::newRow("single list right")
|
|
<< ItemVector()
|
|
<< (ItemVector() << item1 << item2 << item3)
|
|
<< (QVector<QQuickItem *>() << item1.get() << item2.get() << item3.get());
|
|
QTest::newRow("two lists identical")
|
|
<< (ItemVector() << item1 << item2 << item3)
|
|
<< (ItemVector() << item1 << item2 << item3)
|
|
<< (QVector<QQuickItem *>() << item1.get() << item2.get() << item3.get());
|
|
QTest::newRow("two lists 1")
|
|
<< (ItemVector() << item1 << item2 << item5)
|
|
<< (ItemVector() << item3 << item4 << item5)
|
|
<< (QVector<QQuickItem *>() << item1.get() << item2.get() << item3.get() << item4.get() << item5.get());
|
|
QTest::newRow("two lists 2")
|
|
<< (ItemVector() << item1 << item2 << item5)
|
|
<< (ItemVector() << item3 << item4 << item5)
|
|
<< (QVector<QQuickItem *>() << item1.get() << item2.get() << item3.get() << item4.get() << item5.get());
|
|
QTest::newRow("two lists 3")
|
|
<< (ItemVector() << item1 << item2 << item3)
|
|
<< (ItemVector() << item1 << item4 << item5)
|
|
<< (QVector<QQuickItem *>() << item1.get() << item2.get() << item3.get() << item4.get() << item5.get());
|
|
QTest::newRow("two lists 4")
|
|
<< (ItemVector() << item1 << item3 << item4)
|
|
<< (ItemVector() << item2 << item3 << item5)
|
|
<< (QVector<QQuickItem *>() << item1.get() << item2.get() << item3.get() << item4.get() << item5.get());
|
|
QTest::newRow("two lists 5")
|
|
<< (ItemVector() << item1 << item2 << item4)
|
|
<< (ItemVector() << item1 << item3 << item4)
|
|
<< (QVector<QQuickItem *>() << item1.get() << item2.get() << item3.get() << item4.get());
|
|
}
|
|
|
|
void tst_qquickwindow::mergeTouchPointLists()
|
|
{
|
|
QFETCH(ItemVector, list1);
|
|
QFETCH(ItemVector, list2);
|
|
QFETCH(QVector<QQuickItem *>, expected);
|
|
|
|
QQuickWindow win;
|
|
auto windowPrivate = QQuickWindowPrivate::get(&win);
|
|
|
|
QVector<QQuickItem *> a;
|
|
for (const auto &item : list1)
|
|
a.append(item.get());
|
|
|
|
QVector<QQuickItem *> b;
|
|
for (const auto &item : list2)
|
|
b.append(item.get());
|
|
|
|
auto targetList = windowPrivate->deliveryAgentPrivate()->mergePointerTargets(a, b);
|
|
QCOMPARE(targetList, expected);
|
|
}
|
|
|
|
void tst_qquickwindow::mouseFromTouch_basic()
|
|
{
|
|
TestTouchItem::clearMouseEventCounters();
|
|
QQuickWindow *window = new QQuickWindow;
|
|
QScopedPointer<QQuickWindow> cleanup(window);
|
|
window->resize(250, 250);
|
|
window->setPosition(100, 100);
|
|
window->setTitle(QTest::currentTestFunction());
|
|
window->show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(window));
|
|
QTest::qWait(10);
|
|
|
|
TestTouchItem *item = new TestTouchItem(window->contentItem());
|
|
item->setPosition(QPointF(50, 50));
|
|
item->setSize(QSizeF(150, 150));
|
|
// If it doesn't accept touch, but does accept LeftButton mouse events, then
|
|
// the first point in each touch event should generate a synth-mouse event.
|
|
item->setAcceptTouchEvents(false);
|
|
|
|
QList<QEventPoint> points;
|
|
QEventPoint tp(1, QEventPoint::State::Pressed, {}, {});
|
|
const QPointF localPos = item->mapToScene(QPointF(10, 10));
|
|
const QPointF screenPos = window->mapToGlobal(localPos.toPoint());
|
|
QMutableEventPoint::setPosition(tp, localPos);
|
|
QMutableEventPoint::setGlobalPosition(tp, screenPos);
|
|
QMutableEventPoint::setEllipseDiameters(tp, QSizeF(4, 4));
|
|
points << tp;
|
|
QWindowSystemInterface::handleTouchEvent(window, touchDeviceWithVelocity,
|
|
QWindowSystemInterfacePrivate::toNativeTouchPoints(points, window));
|
|
QGuiApplication::processEvents();
|
|
QQuickTouchUtils::flush(window);
|
|
QMutableEventPoint::setState(points[0], QEventPoint::State::Updated);
|
|
QMutableEventPoint::setPosition(points[0], localPos + QPointF(5, 5));
|
|
QMutableEventPoint::setGlobalPosition(points[0], screenPos + QPointF(5, 5));
|
|
QVector2D velocity(1.5, 2.5);
|
|
QMutableEventPoint::setVelocity(points[0], velocity);
|
|
QWindowSystemInterface::handleTouchEvent(window, touchDeviceWithVelocity,
|
|
QWindowSystemInterfacePrivate::toNativeTouchPoints(points, window));
|
|
QGuiApplication::processEvents();
|
|
QQuickTouchUtils::flush(window);
|
|
QMutableEventPoint::setState(points[0], QEventPoint::State::Released);
|
|
QWindowSystemInterface::handleTouchEvent(window, touchDeviceWithVelocity,
|
|
QWindowSystemInterfacePrivate::toNativeTouchPoints(points, window));
|
|
QGuiApplication::processEvents();
|
|
QQuickTouchUtils::flush(window);
|
|
|
|
// The item should have received a mouse press, move, and release.
|
|
QCOMPARE(item->mousePressNum, 1);
|
|
QCOMPARE(item->mouseMoveNum, 1);
|
|
QCOMPARE(item->mouseReleaseNum, 1);
|
|
QCOMPARE(item->lastMousePos.toPoint(), item->mapFromScene(points[0].position()).toPoint());
|
|
QCOMPARE(item->lastVelocityFromMouseMove, velocity);
|
|
// QVERIFY(item->lastMouseCapabilityFlags.testFlag(QInputDevice::Capability::Velocity)); // TODO
|
|
|
|
QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10); // avoid generating a double-click
|
|
// Now the same with a transformation.
|
|
item->setRotation(90); // clockwise
|
|
QMutableEventPoint::setState(points[0], QEventPoint::State::Pressed);
|
|
QMutableEventPoint::setVelocity(points[0], velocity);
|
|
QMutableEventPoint::setPosition(tp, localPos);
|
|
QMutableEventPoint::setGlobalPosition(tp, screenPos);
|
|
QWindowSystemInterface::handleTouchEvent(window, touchDeviceWithVelocity,
|
|
QWindowSystemInterfacePrivate::toNativeTouchPoints(points, window));
|
|
QGuiApplication::processEvents();
|
|
QQuickTouchUtils::flush(window);
|
|
QMutableEventPoint::setState(points[0], QEventPoint::State::Updated);
|
|
QMutableEventPoint::setPosition(points[0], localPos + QPointF(5, 5));
|
|
QMutableEventPoint::setGlobalPosition(points[0], screenPos + QPointF(5, 5));
|
|
QWindowSystemInterface::handleTouchEvent(window, touchDeviceWithVelocity,
|
|
QWindowSystemInterfacePrivate::toNativeTouchPoints(points, window));
|
|
QGuiApplication::processEvents();
|
|
QQuickTouchUtils::flush(window);
|
|
QCOMPARE(item->lastMousePos.toPoint(), item->mapFromScene(points[0].position()).toPoint());
|
|
QCOMPARE(item->lastVelocityFromMouseMove, velocity); // Velocity is always in scene coords
|
|
|
|
QMutableEventPoint::setState(points[0], QEventPoint::State::Released);
|
|
QWindowSystemInterface::handleTouchEvent(window, touchDeviceWithVelocity,
|
|
QWindowSystemInterfacePrivate::toNativeTouchPoints(points, window));
|
|
QCoreApplication::processEvents();
|
|
QQuickTouchUtils::flush(window);
|
|
delete item;
|
|
}
|
|
|
|
void tst_qquickwindow::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_qquickwindow::synthMouseFromTouch()
|
|
{
|
|
QFETCH(bool, synthMouse);
|
|
QFETCH(bool, acceptTouch);
|
|
|
|
QCoreApplication::setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, synthMouse);
|
|
QScopedPointer<PointerRecordingWindow> window(new PointerRecordingWindow);
|
|
QScopedPointer<MouseRecordingItem> item(new MouseRecordingItem(acceptTouch, nullptr));
|
|
item->setParentItem(window->contentItem());
|
|
window->resize(250, 250);
|
|
window->setPosition(100, 100);
|
|
window->setTitle(QTest::currentTestFunction());
|
|
window->show();
|
|
QVERIFY(QTest::qWaitForWindowActive(window.data()));
|
|
|
|
QPoint p1 = QPoint(20, 20);
|
|
QPoint p2 = QPoint(30, 30);
|
|
QTest::touchEvent(window.data(), touchDevice).press(0, p1, window.data());
|
|
QQuickTouchUtils::flush(window.data());
|
|
QTest::touchEvent(window.data(), touchDevice).move(0, p2, window.data());
|
|
QQuickTouchUtils::flush(window.data());
|
|
QTest::touchEvent(window.data(), touchDevice).release(0, p2, window.data());
|
|
QQuickTouchUtils::flush(window.data());
|
|
|
|
QCOMPARE(item->m_touchEvents.size(), acceptTouch ? 3 : 0);
|
|
QCOMPARE(item->m_mouseEvents.size(), (acceptTouch || !synthMouse) ? 0 : 3);
|
|
QCOMPARE(window->m_mouseEvents.size(), 0);
|
|
for (const auto &ev : item->m_mouseEvents)
|
|
QCOMPARE(ev.source, Qt::MouseEventSynthesizedByQt);
|
|
}
|
|
|
|
void tst_qquickwindow::synthMouseDoubleClickFromTouch_data()
|
|
{
|
|
QTest::addColumn<QPoint>("movement");
|
|
QTest::addColumn<QPoint>("distanceBetweenPresses");
|
|
QTest::addColumn<bool>("expectedSynthesizedDoubleClickEvent");
|
|
|
|
QTest::newRow("normal") << QPoint(0, 0) << QPoint(0, 0) << true;
|
|
QTest::newRow("with 1 pixel wiggle") << QPoint(1, 1) << QPoint(1, 1) << true;
|
|
QTest::newRow("too much distance to second tap") << QPoint(0, 0) << QPoint(50, 0) << false;
|
|
QTest::newRow("too much drag") << QPoint(50, 0) << QPoint(0, 0) << false;
|
|
QTest::newRow("too much drag and too much distance to second tap") << QPoint(50, 0) << QPoint(50, 0) << false;
|
|
}
|
|
|
|
void tst_qquickwindow::synthMouseDoubleClickFromTouch()
|
|
{
|
|
QFETCH(QPoint, movement);
|
|
QFETCH(QPoint, distanceBetweenPresses);
|
|
QFETCH(bool, expectedSynthesizedDoubleClickEvent);
|
|
|
|
QCoreApplication::setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, true);
|
|
QScopedPointer<PointerRecordingWindow> window(new PointerRecordingWindow);
|
|
QScopedPointer<MouseRecordingItem> item(new MouseRecordingItem(false, nullptr));
|
|
item->setParentItem(window->contentItem());
|
|
window->resize(250, 250);
|
|
window->setPosition(100, 100);
|
|
window->setTitle(QTest::currentTestFunction());
|
|
window->show();
|
|
QVERIFY(QTest::qWaitForWindowActive(window.data()));
|
|
QTest::qWait(100);
|
|
|
|
QPoint p1 = item->mapToScene(item->clipRect().center()).toPoint();
|
|
QTest::touchEvent(window.data(), touchDevice).press(0, p1, window.data());
|
|
QTest::touchEvent(window.data(), touchDevice).move(0, p1 + movement, window.data());
|
|
QTest::touchEvent(window.data(), touchDevice).release(0, p1 + movement, window.data());
|
|
|
|
QPoint p2 = p1 + distanceBetweenPresses;
|
|
QTest::touchEvent(window.data(), touchDevice).press(1, p2, window.data());
|
|
QTest::touchEvent(window.data(), touchDevice).move(1, p2 + movement, window.data());
|
|
QTest::touchEvent(window.data(), touchDevice).release(1, p2 + movement, window.data());
|
|
|
|
const int eventCount = item->m_mouseEvents.size();
|
|
QVERIFY(eventCount >= 2);
|
|
|
|
const int nDoubleClicks = std::count_if(item->m_mouseEvents.constBegin(), item->m_mouseEvents.constEnd(),
|
|
[](const MouseRecordingItem::MouseEvent &ev) { return (ev.type == QEvent::MouseButtonDblClick); } );
|
|
const bool foundDoubleClick = (nDoubleClicks == 1);
|
|
QCOMPARE(foundDoubleClick, expectedSynthesizedDoubleClickEvent);
|
|
|
|
}
|
|
|
|
void tst_qquickwindow::clearWindow()
|
|
{
|
|
QQuickWindow *window = new QQuickWindow;
|
|
window->setTitle(QTest::currentTestFunction());
|
|
QQuickItem *item = new QQuickItem;
|
|
item->setParentItem(window->contentItem());
|
|
|
|
QCOMPARE(item->window(), window);
|
|
|
|
delete window;
|
|
|
|
QVERIFY(!item->window());
|
|
|
|
delete item;
|
|
}
|
|
|
|
void tst_qquickwindow::mouseFiltering()
|
|
{
|
|
TestTouchItem::clearMouseEventCounters();
|
|
|
|
QQuickWindow *window = new QQuickWindow;
|
|
QScopedPointer<QQuickWindow> cleanup(window);
|
|
window->resize(250, 250);
|
|
window->setPosition(100, 100);
|
|
window->setTitle(QTest::currentTestFunction());
|
|
window->show();
|
|
QVERIFY(QTest::qWaitForWindowActive(window));
|
|
|
|
TestTouchItem *bottomItem = new TestTouchItem(window->contentItem());
|
|
bottomItem->setObjectName("Bottom Item");
|
|
bottomItem->setSize(QSizeF(150, 150));
|
|
|
|
TestTouchItem *siblingItem = new TestTouchItem(bottomItem);
|
|
siblingItem->setObjectName("Sibling of Middle Item");
|
|
siblingItem->setPosition(QPointF(90, 25));
|
|
siblingItem->setSize(QSizeF(150, 150));
|
|
|
|
TestTouchItem *middleItem = new TestTouchItem(bottomItem);
|
|
middleItem->setObjectName("Middle Item");
|
|
middleItem->setPosition(QPointF(50, 50));
|
|
middleItem->setSize(QSizeF(150, 150));
|
|
|
|
TestTouchItem *topItem = new TestTouchItem(middleItem);
|
|
topItem->setObjectName("Top Item");
|
|
topItem->setPosition(QPointF(50, 50));
|
|
topItem->setSize(QSizeF(150, 150));
|
|
|
|
QPoint pos(100, 100);
|
|
|
|
QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, pos);
|
|
|
|
// Mouse filtering propagates down the stack, so the
|
|
// correct order is
|
|
// 1. middleItem filters event
|
|
// 2. bottomItem filters event
|
|
// 3. topItem receives event
|
|
QTRY_COMPARE(middleItem->mousePressCount, 1);
|
|
QTRY_COMPARE(bottomItem->mousePressCount, 2);
|
|
QTRY_COMPARE(topItem->mousePressCount, 3);
|
|
QCOMPARE(siblingItem->mousePressCount, 0);
|
|
|
|
QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, pos);
|
|
topItem->clearMouseEventCounters();
|
|
middleItem->clearMouseEventCounters();
|
|
bottomItem->clearMouseEventCounters();
|
|
siblingItem->clearMouseEventCounters();
|
|
|
|
// Repeat, but this time have the top item accept the press
|
|
topItem->acceptMouseEvents = true;
|
|
|
|
QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, pos);
|
|
|
|
// Mouse filtering propagates down the stack, so the
|
|
// correct order is
|
|
// 1. middleItem filters event
|
|
// 2. bottomItem filters event
|
|
// 3. topItem receives event
|
|
QTRY_COMPARE(middleItem->mousePressCount, 1);
|
|
QTRY_COMPARE(bottomItem->mousePressCount, 2);
|
|
QTRY_COMPARE(topItem->mousePressCount, 3);
|
|
QCOMPARE(siblingItem->mousePressCount, 0);
|
|
|
|
pos += QPoint(50, 50);
|
|
QTest::mouseMove(window, pos);
|
|
|
|
// The top item has grabbed, so the move goes there, but again
|
|
// all the ancestors can filter, even when the mouse is outside their bounds
|
|
QTRY_COMPARE(middleItem->mouseMoveCount, 1);
|
|
QTRY_COMPARE(bottomItem->mouseMoveCount, 2);
|
|
QTRY_COMPARE(topItem->mouseMoveCount, 3);
|
|
QCOMPARE(siblingItem->mouseMoveCount, 0);
|
|
|
|
// clean up mouse press state for the next tests
|
|
QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, pos);
|
|
}
|
|
|
|
void tst_qquickwindow::qmlCreation()
|
|
{
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.loadUrl(testFileUrl("window.qml"));
|
|
QObject *created = component.create();
|
|
QScopedPointer<QObject> cleanup(created);
|
|
QVERIFY(created);
|
|
|
|
QQuickWindow *window = qobject_cast<QQuickWindow*>(created);
|
|
QVERIFY(window);
|
|
QCOMPARE(window->color(), QColor(Qt::green));
|
|
|
|
QQuickItem *item = window->findChild<QQuickItem*>("item");
|
|
QVERIFY(item);
|
|
QCOMPARE(item->window(), window);
|
|
}
|
|
|
|
void tst_qquickwindow::qmlCreationWithScreen()
|
|
{
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.loadUrl(testFileUrl("windowWithScreen.qml"));
|
|
QObject *created = component.create();
|
|
QScopedPointer<QObject> cleanup(created);
|
|
QVERIFY(created);
|
|
|
|
QQuickWindow *window = qobject_cast<QQuickWindow*>(created);
|
|
QVERIFY(window);
|
|
QCOMPARE(window->color(), QColor(Qt::green));
|
|
|
|
QQuickItem *item = window->findChild<QQuickItem*>("item");
|
|
QVERIFY(item);
|
|
QCOMPARE(item->window(), window);
|
|
}
|
|
|
|
void tst_qquickwindow::clearColor()
|
|
{
|
|
//::grab examines rendering to make sure it works visually
|
|
QQuickWindow *window = new QQuickWindow;
|
|
QScopedPointer<QQuickWindow> cleanup(window);
|
|
window->resize(250, 250);
|
|
window->setPosition(100, 100);
|
|
window->setColor(Qt::blue);
|
|
window->setTitle(QTest::currentTestFunction());
|
|
window->show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(window));
|
|
QCOMPARE(window->color(), QColor(Qt::blue));
|
|
}
|
|
|
|
void tst_qquickwindow::defaultState()
|
|
{
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.setData("import QtQuick 2.0; import QtQuick.Window 2.1; Window { }", QUrl());
|
|
QObject *created = component.create();
|
|
QScopedPointer<QObject> cleanup(created);
|
|
QVERIFY(created);
|
|
|
|
QQuickWindow *qmlWindow = qobject_cast<QQuickWindow*>(created);
|
|
QVERIFY(qmlWindow);
|
|
qmlWindow->setTitle(QTest::currentTestFunction());
|
|
|
|
QQuickWindow cppWindow;
|
|
cppWindow.show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(&cppWindow));
|
|
|
|
QCOMPARE(qmlWindow->windowState(), cppWindow.windowState());
|
|
}
|
|
|
|
void tst_qquickwindow::grab_data()
|
|
{
|
|
QTest::addColumn<bool>("visible");
|
|
QTest::addColumn<bool>("alpha");
|
|
QTest::newRow("visible,opaque") << true << false;
|
|
QTest::newRow("invisible,opaque") << false << false;
|
|
QTest::newRow("visible,transparent") << true << true;
|
|
QTest::newRow("invisible,transparent") << false << true;
|
|
}
|
|
|
|
void tst_qquickwindow::grab()
|
|
{
|
|
if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
|
|
|| (QGuiApplication::platformName() == QLatin1String("minimal")))
|
|
QSKIP("Skipping due to grabWindow not functional on offscreen/minimal platforms");
|
|
|
|
QFETCH(bool, visible);
|
|
QFETCH(bool, alpha);
|
|
|
|
QQuickWindow window;
|
|
window.setTitle(QLatin1String(QTest::currentTestFunction()) + QLatin1Char(' ') + QLatin1String(QTest::currentDataTag()));
|
|
if (alpha) {
|
|
window.setColor(QColor(0, 0, 0, 0));
|
|
} else {
|
|
window.setColor(Qt::red);
|
|
}
|
|
|
|
window.resize(250, 250);
|
|
|
|
if (visible) {
|
|
window.show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(&window));
|
|
} else {
|
|
window.create();
|
|
}
|
|
|
|
QImage content = window.grabWindow();
|
|
QCOMPARE(content.size(), window.size() * window.devicePixelRatio());
|
|
|
|
if (alpha) {
|
|
QCOMPARE((uint) content.convertToFormat(QImage::Format_ARGB32_Premultiplied).pixel(0, 0), (uint) 0x00000000);
|
|
} else {
|
|
QCOMPARE((uint) content.convertToFormat(QImage::Format_RGB32).pixel(0, 0), (uint) 0xffff0000);
|
|
}
|
|
|
|
if (visible) {
|
|
// Now hide() and regrab to exercise the case of a window that is
|
|
// renderable and then becomes non-exposed (non-renderable). This is not
|
|
// the same as the visible==false case which starts with a window that
|
|
// never was renderable before grabbing.
|
|
window.hide();
|
|
QImage content = window.grabWindow();
|
|
QCOMPARE(content.size(), window.size() * window.devicePixelRatio());
|
|
if (alpha) {
|
|
QCOMPARE((uint) content.convertToFormat(QImage::Format_ARGB32_Premultiplied).pixel(0, 0), (uint) 0x00000000);
|
|
} else {
|
|
QCOMPARE((uint) content.convertToFormat(QImage::Format_RGB32).pixel(0, 0), (uint) 0xffff0000);
|
|
}
|
|
|
|
// now make it visible and exercise the main grab path again to see if it still works
|
|
window.show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(&window));
|
|
content = window.grabWindow();
|
|
QCOMPARE(content.size(), window.size() * window.devicePixelRatio());
|
|
if (alpha) {
|
|
QCOMPARE((uint) content.convertToFormat(QImage::Format_ARGB32_Premultiplied).pixel(0, 0), (uint) 0x00000000);
|
|
} else {
|
|
QCOMPARE((uint) content.convertToFormat(QImage::Format_RGB32).pixel(0, 0), (uint) 0xffff0000);
|
|
}
|
|
}
|
|
}
|
|
|
|
class Grabber : public QObject
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
Q_INVOKABLE void grab(QObject *obj) {
|
|
QQuickWindow *window = qobject_cast<QQuickWindow *>(obj);
|
|
images.append(window->grabWindow());
|
|
}
|
|
QVector<QImage> images;
|
|
};
|
|
|
|
void tst_qquickwindow::earlyGrab()
|
|
{
|
|
if (QGuiApplication::platformName() == QLatin1String("minimal"))
|
|
QSKIP("Skipping due to grabWindow not functional on minimal platforms");
|
|
|
|
qmlRegisterType<Grabber>("Test", 1, 0, "Grabber");
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.loadUrl(testFileUrl("earlyGrab.qml"));
|
|
QScopedPointer<QQuickWindow> window(qobject_cast<QQuickWindow *>(component.create()));
|
|
QVERIFY(!window.isNull());
|
|
window->setTitle(QTest::currentTestFunction());
|
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(window.data()));
|
|
|
|
Grabber *grabber = qobject_cast<Grabber *>(window->findChild<QObject *>("grabber"));
|
|
QVERIFY(grabber);
|
|
QCOMPARE(grabber->images.size(), 1);
|
|
QVERIFY(!grabber->images[0].isNull());
|
|
QCOMPARE(grabber->images[0].convertToFormat(QImage::Format_RGBX8888).pixel(10, 20), QColor(Qt::red).rgb());
|
|
}
|
|
|
|
void tst_qquickwindow::multipleWindows()
|
|
{
|
|
QList<QQuickWindow *> windows;
|
|
QScopedPointer<QQuickWindow> cleanup[6];
|
|
|
|
for (int i=0; i<6; ++i) {
|
|
QQuickWindow *c = new QQuickWindow();
|
|
c->setTitle(QLatin1String(QTest::currentTestFunction()) + QString::number(i));
|
|
c->setColor(Qt::GlobalColor(Qt::red + i));
|
|
c->resize(300, 200);
|
|
c->setPosition(100 + i * 30, 100 + i * 20);
|
|
c->show();
|
|
windows << c;
|
|
cleanup[i].reset(c);
|
|
QVERIFY(QTest::qWaitForWindowExposed(c));
|
|
}
|
|
|
|
// move them
|
|
for (int i=0; i<windows.size(); ++i) {
|
|
QQuickWindow *c = windows.at(i);
|
|
c->setPosition(100 + i * 30, 100 + i * 20 + 100);
|
|
}
|
|
|
|
// resize them
|
|
for (int i=0; i<windows.size(); ++i) {
|
|
QQuickWindow *c = windows.at(i);
|
|
c->resize(200, 150);
|
|
}
|
|
}
|
|
|
|
void tst_qquickwindow::animationsWhileHidden()
|
|
{
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.loadUrl(testFileUrl("AnimationsWhileHidden.qml"));
|
|
QObject *created = component.create();
|
|
QScopedPointer<QObject> cleanup(created);
|
|
|
|
QQuickWindow *window = qobject_cast<QQuickWindow*>(created);
|
|
QVERIFY(window);
|
|
window->setTitle(QTest::currentTestFunction());
|
|
QVERIFY(window->isVisible());
|
|
|
|
// Now hide the window and verify that it went off screen
|
|
window->hide();
|
|
QTest::qWait(10);
|
|
QVERIFY(!window->isVisible());
|
|
|
|
// Running animaiton should cause it to become visible again shortly.
|
|
QTRY_VERIFY(window->isVisible());
|
|
}
|
|
|
|
void tst_qquickwindow::headless()
|
|
{
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.loadUrl(testFileUrl("Headless.qml"));
|
|
QObject *created = component.create();
|
|
QScopedPointer<QObject> cleanup(created);
|
|
|
|
QQuickWindow *window = qobject_cast<QQuickWindow*>(created);
|
|
window->setPersistentGraphics(false);
|
|
window->setPersistentSceneGraph(false);
|
|
QVERIFY(window);
|
|
window->setTitle(QTest::currentTestFunction());
|
|
window->show();
|
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(window));
|
|
QVERIFY(window->isVisible());
|
|
const bool threaded = QQuickWindowPrivate::get(window)->context->thread() != QThread::currentThread();
|
|
QSignalSpy initialized(window, SIGNAL(sceneGraphInitialized()));
|
|
QSignalSpy invalidated(window, SIGNAL(sceneGraphInvalidated()));
|
|
|
|
// Verify that the window is alive and kicking
|
|
QVERIFY(window->isSceneGraphInitialized());
|
|
|
|
// Store the visual result
|
|
QImage originalContent = window->grabWindow();
|
|
|
|
// Hide the window and verify signal emittion and GL context deletion
|
|
window->hide();
|
|
window->releaseResources();
|
|
|
|
if (threaded) {
|
|
QTRY_VERIFY(invalidated.size() >= 1);
|
|
}
|
|
// Destroy the native windowing system buffers
|
|
window->destroy();
|
|
QVERIFY(!window->handle());
|
|
|
|
// Show and verify that we are back and running
|
|
window->show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(window));
|
|
|
|
if (threaded)
|
|
QTRY_COMPARE(initialized.size(), 1);
|
|
|
|
QVERIFY(window->isSceneGraphInitialized());
|
|
|
|
// Verify that the visual output is the same
|
|
QImage newContent = window->grabWindow();
|
|
QString errorMessage;
|
|
QVERIFY2(QQuickVisualTestUtils::compareImages(newContent, originalContent, &errorMessage),
|
|
qPrintable(errorMessage));
|
|
}
|
|
|
|
void tst_qquickwindow::destroyShowWithoutHide()
|
|
{
|
|
// this is a variation of the headless case, to test if the more aggressive
|
|
// destroy(); show(); sequence survives.
|
|
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.loadUrl(testFileUrl("Headless.qml"));
|
|
QObject *created = component.create();
|
|
QScopedPointer<QObject> cleanup(created);
|
|
|
|
QQuickWindow *window = qobject_cast<QQuickWindow*>(created);
|
|
QVERIFY(window);
|
|
window->setTitle(QTest::currentTestFunction());
|
|
window->show();
|
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(window));
|
|
QVERIFY(window->isSceneGraphInitialized());
|
|
|
|
QImage originalContent = window->grabWindow();
|
|
|
|
window->destroy();
|
|
QVERIFY(!window->handle());
|
|
|
|
window->show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(window));
|
|
|
|
QVERIFY(window->isSceneGraphInitialized());
|
|
|
|
QImage newContent = window->grabWindow();
|
|
QString errorMessage;
|
|
QVERIFY2(QQuickVisualTestUtils::compareImages(newContent, originalContent, &errorMessage),
|
|
qPrintable(errorMessage));
|
|
}
|
|
|
|
void tst_qquickwindow::focusObject()
|
|
{
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.loadUrl(testFileUrl("focus.qml"));
|
|
QObject *created = component.create();
|
|
QScopedPointer<QObject> cleanup(created);
|
|
|
|
QVERIFY(created);
|
|
|
|
QQuickWindow *window = qobject_cast<QQuickWindow*>(created);
|
|
QVERIFY(window);
|
|
window->setTitle(QTest::currentTestFunction());
|
|
|
|
QSignalSpy focusObjectSpy(window, SIGNAL(focusObjectChanged(QObject*)));
|
|
|
|
window->show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(window));
|
|
window->requestActivate();
|
|
QVERIFY(QTest::qWaitForWindowActive(window));
|
|
|
|
QCOMPARE(window->contentItem(), window->focusObject());
|
|
QCOMPARE(focusObjectSpy.size(), 1);
|
|
|
|
QQuickItem *item1 = window->findChild<QQuickItem*>("item1");
|
|
QVERIFY(item1);
|
|
item1->setFocus(true);
|
|
QCOMPARE(item1, window->focusObject());
|
|
QCOMPARE(focusObjectSpy.size(), 2);
|
|
|
|
QQuickItem *item2 = window->findChild<QQuickItem*>("item2");
|
|
QVERIFY(item2);
|
|
item2->setFocus(true);
|
|
QCOMPARE(item2, window->focusObject());
|
|
QCOMPARE(focusObjectSpy.size(), 3);
|
|
|
|
// set focus for item in non-focused focus scope and
|
|
// ensure focusObject does not change and signal is not emitted
|
|
QQuickItem *item3 = window->findChild<QQuickItem*>("item3");
|
|
QVERIFY(item3);
|
|
item3->setFocus(true);
|
|
QCOMPARE(item2, window->focusObject());
|
|
QCOMPARE(focusObjectSpy.size(), 3);
|
|
}
|
|
|
|
void tst_qquickwindow::focusReason()
|
|
{
|
|
QQuickWindow *window = new QQuickWindow;
|
|
QScopedPointer<QQuickWindow> cleanup(window);
|
|
window->resize(200, 200);
|
|
window->show();
|
|
window->setTitle(QTest::currentTestFunction());
|
|
QVERIFY(QTest::qWaitForWindowExposed(window));
|
|
|
|
QScopedPointer<QQuickItem> firstItem(new QQuickItem);
|
|
firstItem->setSize(QSizeF(100, 100));
|
|
firstItem->setParentItem(window->contentItem());
|
|
|
|
QScopedPointer<QQuickItem> secondItem(new QQuickItem);
|
|
secondItem->setSize(QSizeF(100, 100));
|
|
secondItem->setParentItem(window->contentItem());
|
|
|
|
firstItem->forceActiveFocus(Qt::OtherFocusReason);
|
|
QCOMPARE(QQuickWindowPrivate::get(window)->deliveryAgentPrivate()->lastFocusReason, Qt::OtherFocusReason);
|
|
|
|
secondItem->forceActiveFocus(Qt::TabFocusReason);
|
|
QCOMPARE(QQuickWindowPrivate::get(window)->deliveryAgentPrivate()->lastFocusReason, Qt::TabFocusReason);
|
|
|
|
firstItem->forceActiveFocus(Qt::BacktabFocusReason);
|
|
QCOMPARE(QQuickWindowPrivate::get(window)->deliveryAgentPrivate()->lastFocusReason, Qt::BacktabFocusReason);
|
|
|
|
}
|
|
|
|
void tst_qquickwindow::ignoreUnhandledMouseEvents()
|
|
{
|
|
QQuickWindow *window = new QQuickWindow;
|
|
QScopedPointer<QQuickWindow> cleanup(window);
|
|
window->setTitle(QTest::currentTestFunction());
|
|
window->resize(100, 100);
|
|
window->show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(window));
|
|
|
|
QScopedPointer<QQuickItem> item(new QQuickItem);
|
|
item->setSize(QSizeF(100, 100));
|
|
item->setParentItem(window->contentItem());
|
|
|
|
{
|
|
QMouseEvent me(QEvent::MouseButtonPress, QPointF(50, 50), window->mapToGlobal(QPointF(50, 50)),
|
|
Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
|
|
me.setAccepted(true);
|
|
QVERIFY(QCoreApplication::sendEvent(window, &me));
|
|
QVERIFY(!me.isAccepted());
|
|
}
|
|
|
|
{
|
|
QMouseEvent me(QEvent::MouseMove, QPointF(51, 51), window->mapToGlobal(QPointF(51, 51)),
|
|
Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
|
|
me.setAccepted(true);
|
|
QVERIFY(QCoreApplication::sendEvent(window, &me));
|
|
QVERIFY(!me.isAccepted());
|
|
}
|
|
|
|
{
|
|
QMouseEvent me(QEvent::MouseButtonRelease, QPointF(51, 51), window->mapToGlobal(QPointF(51, 51)),
|
|
Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
|
|
me.setAccepted(true);
|
|
QVERIFY(QCoreApplication::sendEvent(window, &me));
|
|
QVERIFY(!me.isAccepted());
|
|
}
|
|
}
|
|
|
|
|
|
void tst_qquickwindow::ownershipRootItem()
|
|
{
|
|
qmlRegisterType<RootItemAccessor>("Test", 1, 0, "RootItemAccessor");
|
|
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.loadUrl(testFileUrl("ownershipRootItem.qml"));
|
|
QObject *created = component.create();
|
|
QScopedPointer<QObject> cleanup(created);
|
|
|
|
QQuickWindow *window = qobject_cast<QQuickWindow*>(created);
|
|
QVERIFY(window);
|
|
window->setTitle(QTest::currentTestFunction());
|
|
window->show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(window));
|
|
|
|
RootItemAccessor* accessor = window->findChild<RootItemAccessor*>("accessor");
|
|
QVERIFY(accessor);
|
|
engine.collectGarbage();
|
|
|
|
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
|
|
QCoreApplication::processEvents();
|
|
QVERIFY(!accessor->isRootItemDestroyed());
|
|
}
|
|
|
|
#if QT_CONFIG(cursor)
|
|
void tst_qquickwindow::cursor()
|
|
{
|
|
QQuickWindow window;
|
|
window.setTitle(QTest::currentTestFunction());
|
|
window.setFramePosition(QGuiApplication::primaryScreen()->availableGeometry().topLeft() + QPoint(50, 50));
|
|
window.resize(320, 290);
|
|
|
|
QQuickItem parentItem;
|
|
parentItem.setObjectName("parentItem");
|
|
parentItem.setPosition(QPointF(0, 0));
|
|
parentItem.setSize(QSizeF(180, 180));
|
|
parentItem.setParentItem(window.contentItem());
|
|
|
|
QQuickItem childItem;
|
|
childItem.setObjectName("childItem");
|
|
childItem.setPosition(QPointF(60, 90));
|
|
childItem.setSize(QSizeF(120, 120));
|
|
childItem.setParentItem(&parentItem);
|
|
|
|
QQuickItem clippingItem;
|
|
clippingItem.setObjectName("clippingItem");
|
|
clippingItem.setPosition(QPointF(120, 120));
|
|
clippingItem.setSize(QSizeF(180, 180));
|
|
clippingItem.setClip(true);
|
|
clippingItem.setParentItem(window.contentItem());
|
|
|
|
QQuickItem clippedItem;
|
|
clippedItem.setObjectName("clippedItem");
|
|
clippedItem.setPosition(QPointF(-30, -30));
|
|
clippedItem.setSize(QSizeF(120, 120));
|
|
clippedItem.setParentItem(&clippingItem);
|
|
|
|
window.show();
|
|
QVERIFY(QTest::qWaitForWindowActive(&window));
|
|
|
|
// Position the cursor over the parent and child item and the clipped section of clippedItem.
|
|
QTest::mouseMove(&window, QPoint(100, 100));
|
|
|
|
// No items cursors, window cursor is the default arrow.
|
|
QCOMPARE(window.cursor().shape(), Qt::ArrowCursor);
|
|
|
|
// The section of clippedItem under the cursor is clipped, and so doesn't affect the window cursor.
|
|
clippedItem.setCursor(Qt::ForbiddenCursor);
|
|
QCOMPARE(clippedItem.cursor().shape(), Qt::ForbiddenCursor);
|
|
QCOMPARE(window.cursor().shape(), Qt::ArrowCursor);
|
|
|
|
// parentItem is under the cursor, so the window cursor is changed.
|
|
parentItem.setCursor(Qt::IBeamCursor);
|
|
QCOMPARE(parentItem.cursor().shape(), Qt::IBeamCursor);
|
|
QCOMPARE(window.cursor().shape(), Qt::IBeamCursor);
|
|
|
|
// childItem is under the cursor and is in front of its parent, so the window cursor is changed.
|
|
childItem.setCursor(Qt::WaitCursor);
|
|
QCOMPARE(childItem.cursor().shape(), Qt::WaitCursor);
|
|
QCOMPARE(window.cursor().shape(), Qt::WaitCursor);
|
|
|
|
childItem.setCursor(Qt::PointingHandCursor);
|
|
QCOMPARE(childItem.cursor().shape(), Qt::PointingHandCursor);
|
|
QCOMPARE(window.cursor().shape(), Qt::PointingHandCursor);
|
|
|
|
// childItem is the current cursor item, so this has no effect on the window cursor.
|
|
parentItem.unsetCursor();
|
|
QCOMPARE(parentItem.cursor().shape(), Qt::ArrowCursor);
|
|
QCOMPARE(window.cursor().shape(), Qt::PointingHandCursor);
|
|
|
|
parentItem.setCursor(Qt::IBeamCursor);
|
|
QCOMPARE(parentItem.cursor().shape(), Qt::IBeamCursor);
|
|
QCOMPARE(window.cursor().shape(), Qt::PointingHandCursor);
|
|
|
|
// With the childItem cursor cleared, parentItem is now foremost.
|
|
childItem.unsetCursor();
|
|
QCOMPARE(childItem.cursor().shape(), Qt::ArrowCursor);
|
|
QCOMPARE(window.cursor().shape(), Qt::IBeamCursor);
|
|
|
|
// Setting the childItem cursor to the default still takes precedence over parentItem.
|
|
childItem.setCursor(Qt::ArrowCursor);
|
|
QCOMPARE(childItem.cursor().shape(), Qt::ArrowCursor);
|
|
QCOMPARE(window.cursor().shape(), Qt::ArrowCursor);
|
|
|
|
childItem.setCursor(Qt::WaitCursor);
|
|
QCOMPARE(childItem.cursor().shape(), Qt::WaitCursor);
|
|
QCOMPARE(window.cursor().shape(), Qt::WaitCursor);
|
|
|
|
// Move the cursor so it is over just parentItem.
|
|
QTest::mouseMove(&window, QPoint(20, 20));
|
|
QCOMPARE(window.cursor().shape(), Qt::IBeamCursor);
|
|
|
|
// Move the cursor so that is over all items, clippedItem wins because its a child of
|
|
// clippingItem which is in from of parentItem in painting order.
|
|
QTest::mouseMove(&window, QPoint(125, 125));
|
|
QCOMPARE(window.cursor().shape(), Qt::ForbiddenCursor);
|
|
|
|
// Over clippingItem only, so no cursor.
|
|
QTest::mouseMove(&window, QPoint(200, 280));
|
|
QCOMPARE(window.cursor().shape(), Qt::ArrowCursor);
|
|
|
|
// Over no item, so no cursor.
|
|
QTest::mouseMove(&window, QPoint(10, 280));
|
|
QCOMPARE(window.cursor().shape(), Qt::ArrowCursor);
|
|
|
|
// back to the start.
|
|
QTest::mouseMove(&window, QPoint(100, 100));
|
|
QCOMPARE(window.cursor().shape(), Qt::WaitCursor);
|
|
|
|
// Try with the mouse pressed.
|
|
QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, QPoint(100, 100));
|
|
QTest::mouseMove(&window, QPoint(20, 20));
|
|
QCOMPARE(window.cursor().shape(), Qt::IBeamCursor);
|
|
QTest::mouseMove(&window, QPoint(125, 125));
|
|
QCOMPARE(window.cursor().shape(), Qt::ForbiddenCursor);
|
|
QTest::mouseMove(&window, QPoint(200, 280));
|
|
QCOMPARE(window.cursor().shape(), Qt::ArrowCursor);
|
|
QTest::mouseMove(&window, QPoint(10, 280));
|
|
QCOMPARE(window.cursor().shape(), Qt::ArrowCursor);
|
|
QTest::mouseMove(&window, QPoint(100, 100));
|
|
QCOMPARE(window.cursor().shape(), Qt::WaitCursor);
|
|
QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, QPoint(100, 100));
|
|
|
|
// Remove the cursor item from the scene. Theoretically this should make parentItem the
|
|
// cursorItem, but given the situation will correct itself after the next mouse move it
|
|
// simply unsets the window cursor for now.
|
|
childItem.setParentItem(nullptr);
|
|
QCOMPARE(window.cursor().shape(), Qt::ArrowCursor);
|
|
|
|
parentItem.setCursor(Qt::SizeAllCursor);
|
|
QCOMPARE(parentItem.cursor().shape(), Qt::SizeAllCursor);
|
|
QCOMPARE(window.cursor().shape(), Qt::ArrowCursor);
|
|
|
|
// Changing the cursor of an un-parented item doesn't affect the window's cursor.
|
|
childItem.setCursor(Qt::ClosedHandCursor);
|
|
QCOMPARE(childItem.cursor().shape(), Qt::ClosedHandCursor);
|
|
QCOMPARE(window.cursor().shape(), Qt::ArrowCursor);
|
|
|
|
childItem.unsetCursor();
|
|
QCOMPARE(childItem.cursor().shape(), Qt::ArrowCursor);
|
|
QCOMPARE(window.cursor().shape(), Qt::ArrowCursor);
|
|
|
|
QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, QPoint(100, 101));
|
|
QCOMPARE(window.cursor().shape(), Qt::SizeAllCursor);
|
|
}
|
|
#endif
|
|
|
|
void tst_qquickwindow::hideThenDelete_data()
|
|
{
|
|
QTest::addColumn<bool>("persistentSG");
|
|
QTest::addColumn<bool>("persistentGraphics");
|
|
|
|
QTest::newRow("persistent:SG=false,Graphics=false") << false << false;
|
|
QTest::newRow("persistent:SG=true,Graphics=false") << true << false;
|
|
QTest::newRow("persistent:SG=false,Graphics=true") << false << true;
|
|
QTest::newRow("persistent:SG=true,Graphics=true") << true << true;
|
|
}
|
|
|
|
void tst_qquickwindow::hideThenDelete()
|
|
{
|
|
QFETCH(bool, persistentSG);
|
|
QFETCH(bool, persistentGraphics);
|
|
|
|
QScopedPointer<QSignalSpy> sgInvalidated;
|
|
|
|
{
|
|
QQuickWindow window;
|
|
window.setTitle(QLatin1String(QTest::currentTestFunction()) + QLatin1Char(' ')
|
|
+ QLatin1String(QTest::currentDataTag()));
|
|
window.setColor(Qt::red);
|
|
|
|
window.setPersistentSceneGraph(persistentSG);
|
|
window.setPersistentGraphics(persistentGraphics);
|
|
|
|
window.resize(400, 300);
|
|
window.show();
|
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(&window));
|
|
const bool threaded = QQuickWindowPrivate::get(&window)->context->thread() != QGuiApplication::instance()->thread();
|
|
|
|
sgInvalidated.reset(new QSignalSpy(&window, SIGNAL(sceneGraphInvalidated())));
|
|
|
|
window.hide();
|
|
|
|
QTRY_VERIFY(!window.isExposed());
|
|
|
|
// Only the threaded render loop implements the opt-in full
|
|
// scenegraph and graphics teardown on unexpose.
|
|
if (threaded) {
|
|
if (!QSGRendererInterface::isApiRhiBased(window.rendererInterface()->graphicsApi()))
|
|
QSKIP("Skipping persistency verification due to not running with RHI");
|
|
|
|
if (!persistentSG)
|
|
QVERIFY(sgInvalidated->size() > 0);
|
|
else
|
|
QCOMPARE(sgInvalidated->size(), 0);
|
|
}
|
|
}
|
|
|
|
QVERIFY(sgInvalidated->size() > 0);
|
|
}
|
|
|
|
void tst_qquickwindow::showHideAnimate()
|
|
{
|
|
// This test tries to mimick a bug triggered in the qquickanimatedimage test
|
|
// A window is shown, then removed again before it is exposed. This left
|
|
// traces in the render loop which prevent other animations from running
|
|
// later on.
|
|
{
|
|
QQuickWindow window;
|
|
window.resize(400, 300);
|
|
window.show();
|
|
}
|
|
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.loadUrl(testFileUrl("showHideAnimate.qml"));
|
|
QScopedPointer<QQuickItem> created(qobject_cast<QQuickItem *>(component.create()));
|
|
|
|
QVERIFY(created);
|
|
|
|
QTRY_VERIFY(created->opacity() > 0.5);
|
|
QTRY_VERIFY(created->opacity() < 0.5);
|
|
}
|
|
|
|
void tst_qquickwindow::testExpose()
|
|
{
|
|
QQuickWindow window;
|
|
window.setTitle(QTest::currentTestFunction());
|
|
window.setGeometry(100, 100, 300, 200);
|
|
|
|
window.show();
|
|
QTRY_VERIFY(window.isExposed());
|
|
|
|
QSignalSpy swapSpy(&window, SIGNAL(frameSwapped()));
|
|
|
|
// exhaust pending exposes, as some platforms send us plenty
|
|
// while showing the first time
|
|
QTest::qWait(1000);
|
|
while (swapSpy.size() != 0) {
|
|
swapSpy.clear();
|
|
QTest::qWait(100);
|
|
}
|
|
|
|
QWindowSystemInterface::handleExposeEvent(&window, QRegion(10, 10, 20, 20));
|
|
QTRY_COMPARE(swapSpy.size(), 1);
|
|
}
|
|
|
|
void tst_qquickwindow::requestActivate()
|
|
{
|
|
#ifdef Q_OS_ANDROID
|
|
QSKIP("tst_qquickwindow::requestActivate crashes on Android, see QTBUG-103078.");
|
|
#endif
|
|
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.loadUrl(testFileUrl("active.qml"));
|
|
QScopedPointer<QQuickWindow> window1(qobject_cast<QQuickWindow *>(component.create()));
|
|
QVERIFY(!window1.isNull());
|
|
window1->setTitle(QTest::currentTestFunction());
|
|
|
|
QWindowList windows = QGuiApplication::topLevelWindows();
|
|
QCOMPARE(windows.size(), 2);
|
|
|
|
for (int i = 0; i < windows.size(); ++i) {
|
|
if (windows.at(i)->objectName() == window1->objectName()) {
|
|
windows.removeAt(i);
|
|
break;
|
|
}
|
|
}
|
|
QCOMPARE(windows.size(), 1);
|
|
QCOMPARE(windows.at(0)->objectName(), QLatin1String("window2"));
|
|
|
|
window1->show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(windows.at(0))); //We wait till window 2 comes up
|
|
window1->requestActivate(); // and then transfer the focus to window1
|
|
|
|
QTRY_COMPARE(QGuiApplication::focusWindow(), window1.data());
|
|
QVERIFY(window1->isActive());
|
|
|
|
QQuickItem *item = QQuickVisualTestUtils::findItem<QQuickItem>(window1->contentItem(), "item1");
|
|
QVERIFY(item);
|
|
|
|
//copied from src/qmltest/quicktestevent.cpp
|
|
QPoint pos = item->mapToScene(QPointF(item->width()/2, item->height()/2)).toPoint();
|
|
|
|
{
|
|
QMouseEvent me(QEvent::MouseButtonPress, pos, window1->mapToGlobal(pos), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
|
|
QSpontaneKeyEvent::setSpontaneous(&me);
|
|
if (!qApp->notify(window1.data(), &me))
|
|
qWarning("Mouse event MousePress not accepted by receiving window");
|
|
}
|
|
{
|
|
QMouseEvent me = QMouseEvent(QEvent::MouseButtonPress, pos, window1->mapToGlobal(pos), Qt::LeftButton, {}, Qt::NoModifier);
|
|
QSpontaneKeyEvent::setSpontaneous(&me);
|
|
if (!qApp->notify(window1.data(), &me))
|
|
qWarning("Mouse event MouseRelease not accepted by receiving window");
|
|
}
|
|
|
|
QTRY_COMPARE(QGuiApplication::focusWindow(), windows.at(0));
|
|
QVERIFY(windows.at(0)->isActive());
|
|
}
|
|
|
|
void tst_qquickwindow::testWindowVisibilityOrder()
|
|
{
|
|
#ifdef Q_OS_ANDROID
|
|
QSKIP("tst_qquickwindow::testWindowVisibilityOrder crashes on Android, see QTBUG-103078.");
|
|
#endif
|
|
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.loadUrl(testFileUrl("windoworder.qml"));
|
|
QScopedPointer<QQuickWindow> window1(qobject_cast<QQuickWindow *>(component.create()));
|
|
QVERIFY(!window1.isNull());
|
|
window1->setTitle(QTest::currentTestFunction());
|
|
QQuickWindow *window2 = window1->property("win2").value<QQuickWindow*>();
|
|
QQuickWindow *window3 = window1->property("win3").value<QQuickWindow*>();
|
|
QQuickWindow *window4 = window1->property("win4").value<QQuickWindow*>();
|
|
QQuickWindow *window5 = window1->property("win5").value<QQuickWindow*>();
|
|
QVERIFY(window2);
|
|
QVERIFY(window3);
|
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(window3));
|
|
|
|
QWindowList windows = QGuiApplication::topLevelWindows();
|
|
QTRY_COMPARE(windows.size(), 5);
|
|
|
|
QVERIFY(window1->isActive());
|
|
QVERIFY(window2->isActive());
|
|
QVERIFY(window3->isActive());
|
|
|
|
//Test if window4 is shown 2 seconds after the application startup
|
|
//with window4 visible window5 (transient child) should also become visible
|
|
QVERIFY(!window4->isVisible());
|
|
QVERIFY(!window5->isVisible());
|
|
|
|
window4->setVisible(true);
|
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(window5));
|
|
QVERIFY(window4->isVisible());
|
|
QVERIFY(window5->isVisible());
|
|
}
|
|
|
|
void tst_qquickwindow::blockClosing()
|
|
{
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.loadUrl(testFileUrl("ucantclosethis.qml"));
|
|
QScopedPointer<QQuickWindow> window(qobject_cast<QQuickWindow *>(component.create()));
|
|
QVERIFY(!window.isNull());
|
|
window->setTitle(QTest::currentTestFunction());
|
|
window->show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(window.data()));
|
|
QVERIFY(window->isVisible());
|
|
QWindowSystemInterface::handleCloseEvent(window.data());
|
|
QVERIFY(window->isVisible());
|
|
QWindowSystemInterface::handleCloseEvent(window.data());
|
|
QVERIFY(window->isVisible());
|
|
window->setProperty("canCloseThis", true);
|
|
QWindowSystemInterface::handleCloseEvent(window.data());
|
|
QTRY_VERIFY(!window->isVisible());
|
|
}
|
|
|
|
void tst_qquickwindow::blockCloseMethod()
|
|
{
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.loadUrl(testFileUrl("ucantclosethis.qml"));
|
|
QScopedPointer<QQuickWindow> window(qobject_cast<QQuickWindow *>(component.create()));
|
|
QVERIFY(!window.isNull());
|
|
window->setTitle(QTest::currentTestFunction());
|
|
window->show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(window.data()));
|
|
QVERIFY(window->isVisible());
|
|
QVERIFY(QMetaObject::invokeMethod(window.data(), "close", Qt::DirectConnection));
|
|
QVERIFY(window->isVisible());
|
|
QVERIFY(QMetaObject::invokeMethod(window.data(), "close", Qt::DirectConnection));
|
|
QVERIFY(window->isVisible());
|
|
window->setProperty("canCloseThis", true);
|
|
QVERIFY(QMetaObject::invokeMethod(window.data(), "close", Qt::DirectConnection));
|
|
QTRY_VERIFY(!window->isVisible());
|
|
}
|
|
|
|
void tst_qquickwindow::crashWhenHoverItemDeleted()
|
|
{
|
|
// QTBUG-32771
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.loadUrl(testFileUrl("hoverCrash.qml"));
|
|
QScopedPointer<QQuickWindow> window(qobject_cast<QQuickWindow *>(component.create()));
|
|
QVERIFY(!window.isNull());
|
|
window->setTitle(QTest::currentTestFunction());
|
|
window->show();
|
|
QVERIFY(QTest::qWaitForWindowActive(window.data()));
|
|
|
|
// Simulate a move from the first rectangle to the second. Crash will happen in here
|
|
// Moving instantaneously from (0, 99) to (0, 102) does not cause the crash
|
|
for (int i = 99; i < 102; ++i) {
|
|
QTest::mouseMove(window.data(), QPoint(0, i));
|
|
}
|
|
}
|
|
|
|
// QTBUG-33436
|
|
void tst_qquickwindow::unloadSubWindow()
|
|
{
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.loadUrl(testFileUrl("unloadSubWindow.qml"));
|
|
QScopedPointer<QQuickWindow> window(qobject_cast<QQuickWindow *>(component.create()));
|
|
QVERIFY(!window.isNull());
|
|
window->setTitle(QTest::currentTestFunction());
|
|
window->show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(window.data()));
|
|
QPointer<QQuickWindow> transient;
|
|
QTRY_VERIFY(transient = window->property("transientWindow").value<QQuickWindow*>());
|
|
QVERIFY(QTest::qWaitForWindowExposed(transient));
|
|
|
|
// Unload the inner window (in nested Loaders) and make sure it doesn't crash
|
|
QQuickLoader *loader = window->property("loader1").value<QQuickLoader*>();
|
|
loader->setActive(false);
|
|
QTRY_VERIFY(transient.isNull() || !transient->isVisible());
|
|
}
|
|
|
|
// QTBUG-52573
|
|
void tst_qquickwindow::changeVisibilityInCompleted()
|
|
{
|
|
#ifdef Q_OS_ANDROID
|
|
QSKIP("tst_qquickwindow::changeVisibilityInCompleted crashes on Android, see QTBUG-103078.");
|
|
#endif
|
|
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.loadUrl(testFileUrl("changeVisibilityInCompleted.qml"));
|
|
QScopedPointer<QQuickWindow> window(qobject_cast<QQuickWindow *>(component.create()));
|
|
QVERIFY(!window.isNull());
|
|
window->setTitle(QTest::currentTestFunction());
|
|
window->show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(window.data()));
|
|
QPointer<QQuickWindow> winVisible;
|
|
QTRY_VERIFY(winVisible = window->property("winVisible").value<QQuickWindow*>());
|
|
QPointer<QQuickWindow> winVisibility;
|
|
QTRY_VERIFY(winVisibility = window->property("winVisibility").value<QQuickWindow*>());
|
|
QVERIFY(QTest::qWaitForWindowExposed(winVisible));
|
|
QVERIFY(QTest::qWaitForWindowExposed(winVisibility));
|
|
|
|
QVERIFY(winVisible->isVisible());
|
|
QCOMPARE(winVisibility->visibility(), QWindow::Windowed);
|
|
}
|
|
|
|
// QTBUG-32004
|
|
void tst_qquickwindow::qobjectEventFilter_touch()
|
|
{
|
|
QQuickWindow window;
|
|
|
|
window.resize(250, 250);
|
|
window.setPosition(100, 100);
|
|
window.setTitle(QTest::currentTestFunction());
|
|
window.show();
|
|
QVERIFY(QTest::qWaitForWindowActive(&window));
|
|
|
|
TestTouchItem *item = new TestTouchItem(window.contentItem());
|
|
item->setSize(QSizeF(150, 150));
|
|
|
|
EventFilter eventFilter;
|
|
item->installEventFilter(&eventFilter);
|
|
|
|
QPointF pos(10, 10);
|
|
|
|
// press single point
|
|
QTest::touchEvent(&window, touchDevice).press(0, item->mapToScene(pos).toPoint(), &window);
|
|
|
|
QCOMPARE(eventFilter.events.size(), 1);
|
|
QCOMPARE(eventFilter.events.first(), (int)QEvent::TouchBegin);
|
|
}
|
|
|
|
// QTBUG-32004
|
|
void tst_qquickwindow::qobjectEventFilter_key()
|
|
{
|
|
QQuickWindow window;
|
|
|
|
window.resize(250, 250);
|
|
window.setPosition(100, 100);
|
|
window.setTitle(QTest::currentTestFunction());
|
|
window.show();
|
|
QVERIFY(QTest::qWaitForWindowActive(&window));
|
|
|
|
TestTouchItem *item = new TestTouchItem(window.contentItem());
|
|
item->setSize(QSizeF(150, 150));
|
|
item->setFocus(true);
|
|
|
|
EventFilter eventFilter;
|
|
item->installEventFilter(&eventFilter);
|
|
|
|
QTest::keyPress(&window, Qt::Key_A);
|
|
|
|
// NB: It may also receive some QKeyEvent(ShortcutOverride) which we're not interested in
|
|
QVERIFY(eventFilter.events.contains((int)QEvent::KeyPress));
|
|
eventFilter.events.clear();
|
|
|
|
QTest::keyRelease(&window, Qt::Key_A);
|
|
|
|
QVERIFY(eventFilter.events.contains((int)QEvent::KeyRelease));
|
|
}
|
|
|
|
// QTBUG-32004
|
|
void tst_qquickwindow::qobjectEventFilter_mouse()
|
|
{
|
|
QQuickWindow window;
|
|
|
|
window.resize(250, 250);
|
|
window.setPosition(100, 100);
|
|
window.setTitle(QTest::currentTestFunction());
|
|
window.show();
|
|
QVERIFY(QTest::qWaitForWindowActive(&window));
|
|
|
|
TestTouchItem *item = new TestTouchItem(window.contentItem());
|
|
item->setSize(QSizeF(150, 150));
|
|
|
|
EventFilter eventFilter;
|
|
item->installEventFilter(&eventFilter);
|
|
|
|
QPoint point = item->mapToScene(QPointF(10, 10)).toPoint();
|
|
QTest::mouseMove(&window, point);
|
|
QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, point);
|
|
|
|
QVERIFY(eventFilter.events.contains((int)QEvent::MouseButtonPress));
|
|
|
|
// clean up mouse press state for the next tests
|
|
QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, point);
|
|
}
|
|
|
|
void tst_qquickwindow::animatingSignal()
|
|
{
|
|
QQuickWindow window;
|
|
window.setTitle(QTest::currentTestFunction());
|
|
window.setGeometry(100, 100, 300, 200);
|
|
|
|
QSignalSpy spy(&window, SIGNAL(afterAnimating()));
|
|
|
|
window.show();
|
|
QTRY_VERIFY(window.isExposed());
|
|
|
|
QTRY_VERIFY(spy.size() > 1);
|
|
}
|
|
|
|
void tst_qquickwindow::frameSignals()
|
|
{
|
|
QQuickWindow window;
|
|
window.setTitle(QTest::currentTestFunction());
|
|
window.setGeometry(100, 100, 300, 200);
|
|
|
|
QSignalSpy beforeSpy(&window, SIGNAL(beforeFrameBegin()));
|
|
QSignalSpy afterSpy(&window, SIGNAL(afterFrameEnd()));
|
|
|
|
window.show();
|
|
QTRY_VERIFY(window.isExposed());
|
|
QSGRendererInterface *rif = window.rendererInterface();
|
|
QVERIFY(rif);
|
|
|
|
QTRY_VERIFY(beforeSpy.size() > 1);
|
|
QTRY_VERIFY(afterSpy.size() > 1);
|
|
QTRY_COMPARE(beforeSpy.size(), afterSpy.size());
|
|
}
|
|
|
|
// QTBUG-36938
|
|
void tst_qquickwindow::contentItemSize()
|
|
{
|
|
QQuickWindow window;
|
|
window.setTitle(QTest::currentTestFunction());
|
|
QQuickItem *contentItem = window.contentItem();
|
|
QVERIFY(contentItem);
|
|
QCOMPARE(QSize(contentItem->width(), contentItem->height()), window.size());
|
|
|
|
QSizeF size(300, 200);
|
|
window.resize(size.toSize());
|
|
window.show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(&window));
|
|
|
|
QCOMPARE(window.size(), size.toSize());
|
|
QCOMPARE(QSizeF(contentItem->width(), contentItem->height()), size);
|
|
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.setData(QByteArray("import QtQuick 2.1\n Rectangle { anchors.fill: parent }"), QUrl());
|
|
QScopedPointer<QQuickItem> rect(qobject_cast<QQuickItem *>(component.create()));
|
|
QVERIFY(rect);
|
|
rect->setParentItem(window.contentItem());
|
|
QCOMPARE(QSizeF(rect->width(), rect->height()), size);
|
|
|
|
size.transpose();
|
|
window.resize(size.toSize());
|
|
QCOMPARE(window.size(), size.toSize());
|
|
// wait for resize event
|
|
QTRY_COMPARE(QSizeF(contentItem->width(), contentItem->height()), size);
|
|
QCOMPARE(QSizeF(rect->width(), rect->height()), size);
|
|
}
|
|
|
|
void tst_qquickwindow::defaultSurfaceFormat()
|
|
{
|
|
// It is quite difficult to verify anything for real since the resulting format after
|
|
// surface/context creation can be anything, depending on the platform and drivers,
|
|
// and many options and settings may fail in various configurations, but test at
|
|
// least using some harmless settings to check that the global, static format is
|
|
// taken into account in the requested format.
|
|
|
|
QSurfaceFormat savedDefaultFormat = QSurfaceFormat::defaultFormat();
|
|
|
|
// Verify that depth and stencil are set, as they should be, unless they are disabled
|
|
// via environment variables.
|
|
|
|
QSurfaceFormat format = savedDefaultFormat;
|
|
format.setSwapInterval(0);
|
|
format.setRedBufferSize(8);
|
|
format.setGreenBufferSize(8);
|
|
format.setBlueBufferSize(8);
|
|
format.setProfile(QSurfaceFormat::CompatibilityProfile);
|
|
format.setOption(QSurfaceFormat::DebugContext);
|
|
// Will not set depth and stencil. That should be added automatically,
|
|
// unless the are disabled (but they aren't).
|
|
QSurfaceFormat::setDefaultFormat(format);
|
|
|
|
QQuickWindow window;
|
|
window.setTitle(QTest::currentTestFunction());
|
|
window.show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(&window));
|
|
|
|
if (window.rendererInterface()->graphicsApi() != QSGRendererInterface::OpenGLRhi)
|
|
QSKIP("Skipping OpenGL context test due to not running with OpenGL");
|
|
|
|
const QSurfaceFormat reqFmt = window.requestedFormat();
|
|
QCOMPARE(format.swapInterval(), reqFmt.swapInterval());
|
|
QCOMPARE(format.redBufferSize(), reqFmt.redBufferSize());
|
|
QCOMPARE(format.greenBufferSize(), reqFmt.greenBufferSize());
|
|
QCOMPARE(format.blueBufferSize(), reqFmt.blueBufferSize());
|
|
QCOMPARE(format.profile(), reqFmt.profile());
|
|
QCOMPARE(int(format.options()), int(reqFmt.options()));
|
|
|
|
#if QT_CONFIG(opengl)
|
|
// Depth and stencil should be >= what has been requested. For real. But use
|
|
// the context since the window's surface format is only partially updated
|
|
// on most platforms.
|
|
const QOpenGLContext *ctx = nullptr;
|
|
QTRY_VERIFY((ctx = static_cast<QOpenGLContext *>(window.rendererInterface()->getResource(
|
|
&window, QSGRendererInterface::OpenGLContextResource))) != nullptr);
|
|
QVERIFY(ctx->format().depthBufferSize() >= 16);
|
|
QVERIFY(ctx->format().stencilBufferSize() >= 8);
|
|
#endif
|
|
QSurfaceFormat::setDefaultFormat(savedDefaultFormat);
|
|
}
|
|
|
|
void tst_qquickwindow::attachedProperty()
|
|
{
|
|
QQuickView view(testFileUrl("windowattached.qml"));
|
|
view.setTitle(QTest::currentTestFunction());
|
|
view.show();
|
|
view.requestActivate();
|
|
QVERIFY(QTest::qWaitForWindowActive(&view));
|
|
QVERIFY(view.rootObject()->property("windowActive").toBool());
|
|
QCOMPARE(view.rootObject()->property("contentItem").value<QQuickItem*>(), view.contentItem());
|
|
QCOMPARE(view.rootObject()->property("windowWidth").toInt(), view.width());
|
|
QCOMPARE(view.rootObject()->property("windowHeight").toInt(), view.height());
|
|
QCOMPARE(view.rootObject()->property("window").value<QQuickView*>(), &view);
|
|
|
|
QQuickWindow *innerWindow = view.rootObject()->findChild<QQuickWindow*>("extraWindow");
|
|
QVERIFY(innerWindow);
|
|
innerWindow->show();
|
|
innerWindow->requestActivate();
|
|
QVERIFY(QTest::qWaitForWindowActive(innerWindow));
|
|
|
|
QQuickText *text = view.rootObject()->findChild<QQuickText*>("extraWindowText");
|
|
QVERIFY(text);
|
|
QCOMPARE(text->text(), QLatin1String("active\nvisibility: 2"));
|
|
QCOMPARE(text->property("contentItem").value<QQuickItem*>(), innerWindow->contentItem());
|
|
QCOMPARE(text->property("windowWidth").toInt(), innerWindow->width());
|
|
QCOMPARE(text->property("windowHeight").toInt(), innerWindow->height());
|
|
QCOMPARE(text->property("window").value<QQuickWindow*>(), innerWindow);
|
|
|
|
text->setParentItem(nullptr);
|
|
QVERIFY(!text->property("contentItem").value<QQuickItem*>());
|
|
QCOMPARE(text->property("windowWidth").toInt(), 0);
|
|
QCOMPARE(text->property("windowHeight").toInt(), 0);
|
|
QVERIFY(!text->property("window").value<QQuickWindow*>());
|
|
}
|
|
|
|
class RenderJob : public QRunnable
|
|
{
|
|
public:
|
|
RenderJob(QQuickWindow::RenderStage s, QList<QQuickWindow::RenderStage> *l) : stage(s), list(l) { }
|
|
~RenderJob() { ++deleted; }
|
|
QQuickWindow::RenderStage stage;
|
|
QList<QQuickWindow::RenderStage> *list;
|
|
void run() override
|
|
{
|
|
list->append(stage);
|
|
}
|
|
static int deleted;
|
|
};
|
|
int RenderJob::deleted = 0;
|
|
|
|
void tst_qquickwindow::testRenderJob()
|
|
{
|
|
QList<QQuickWindow::RenderStage> completedJobs;
|
|
|
|
QQuickWindow::RenderStage stages[] = {
|
|
QQuickWindow::BeforeSynchronizingStage,
|
|
QQuickWindow::AfterSynchronizingStage,
|
|
QQuickWindow::BeforeRenderingStage,
|
|
QQuickWindow::AfterRenderingStage,
|
|
QQuickWindow::AfterSwapStage,
|
|
QQuickWindow::NoStage
|
|
};
|
|
|
|
const int numJobs = 6;
|
|
|
|
{
|
|
QQuickWindow window;
|
|
window.setTitle(QTest::currentTestFunction());
|
|
RenderJob::deleted = 0;
|
|
|
|
// Schedule the jobs
|
|
for (int i = 0; i < numJobs; ++i)
|
|
window.scheduleRenderJob(new RenderJob(stages[i], &completedJobs), stages[i]);
|
|
window.show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(&window));
|
|
|
|
// All jobs should be deleted
|
|
QTRY_COMPARE(RenderJob::deleted, numJobs);
|
|
|
|
// The NoStage job is not completed, if it is issued when there is no context,
|
|
// but the rest will be queued and completed once relevant render stage is hit.
|
|
QCOMPARE(completedJobs.size(), numJobs - 1);
|
|
|
|
// Verify jobs were completed in correct order
|
|
for (int i = 0; i < numJobs - 1; ++i)
|
|
QCOMPARE(completedJobs.at(i), stages[i]);
|
|
|
|
|
|
// Check that NoStage job gets executed if it is scheduled when window is exposed
|
|
completedJobs.clear();
|
|
RenderJob::deleted = 0;
|
|
window.scheduleRenderJob(new RenderJob(QQuickWindow::NoStage, &completedJobs),
|
|
QQuickWindow::NoStage);
|
|
QTRY_COMPARE(RenderJob::deleted, 1);
|
|
QCOMPARE(completedJobs.size(), 1);
|
|
}
|
|
|
|
// Verify that jobs are deleted when window is not rendered at all
|
|
completedJobs.clear();
|
|
RenderJob::deleted = 0;
|
|
{
|
|
QQuickWindow window2;
|
|
for (int i = 0; i < numJobs; ++i) {
|
|
window2.scheduleRenderJob(new RenderJob(stages[i], &completedJobs), stages[i]);
|
|
}
|
|
}
|
|
QTRY_COMPARE(RenderJob::deleted, numJobs);
|
|
QCOMPARE(completedJobs.size(), 0);
|
|
}
|
|
|
|
class EventCounter : public QQuickRectangle
|
|
{
|
|
public:
|
|
EventCounter(QQuickItem *parent = nullptr)
|
|
: QQuickRectangle(parent)
|
|
{ }
|
|
|
|
void addFilterEvent(QEvent::Type type)
|
|
{
|
|
m_returnTrueForType.append(type);
|
|
}
|
|
|
|
int childMouseEventFilterEventCount(QEvent::Type type)
|
|
{
|
|
return m_childMouseEventFilterEventCount.value(type, 0);
|
|
}
|
|
|
|
int eventCount(QEvent::Type type)
|
|
{
|
|
return m_eventCount.value(type, 0);
|
|
}
|
|
|
|
void reset()
|
|
{
|
|
m_eventCount.clear();
|
|
m_childMouseEventFilterEventCount.clear();
|
|
}
|
|
protected:
|
|
bool childMouseEventFilter(QQuickItem *, QEvent *event) override
|
|
{
|
|
m_childMouseEventFilterEventCount[event->type()]++;
|
|
return m_returnTrueForType.contains(event->type());
|
|
}
|
|
|
|
bool event(QEvent *event) override
|
|
{
|
|
m_eventCount[event->type()]++;
|
|
return QQuickRectangle::event(event);
|
|
}
|
|
|
|
|
|
private:
|
|
QList<QEvent::Type> m_returnTrueForType;
|
|
QMap<QEvent::Type, int> m_childMouseEventFilterEventCount;
|
|
QMap<QEvent::Type, int> m_eventCount;
|
|
};
|
|
|
|
void tst_qquickwindow::testHoverChildMouseEventFilter()
|
|
{
|
|
QQuickWindow window;
|
|
|
|
window.resize(250, 250);
|
|
window.setPosition(100, 100);
|
|
window.setTitle(QTest::currentTestFunction());
|
|
window.show();
|
|
QVERIFY(QTest::qWaitForWindowActive(&window));
|
|
|
|
EventCounter *bottomItem = new EventCounter(window.contentItem());
|
|
bottomItem->setObjectName("Bottom Item");
|
|
bottomItem->setSize(QSizeF(150, 150));
|
|
bottomItem->setAcceptHoverEvents(true);
|
|
|
|
EventCounter *middleItem = new EventCounter(bottomItem);
|
|
middleItem->setObjectName("Middle Item");
|
|
middleItem->setPosition(QPointF(50, 50));
|
|
middleItem->setSize(QSizeF(150, 150));
|
|
middleItem->setAcceptHoverEvents(true);
|
|
|
|
EventCounter *topItem = new EventCounter(middleItem);
|
|
topItem->setObjectName("Top Item");
|
|
topItem->setPosition(QPointF(50, 50));
|
|
topItem->setSize(QSizeF(150, 150));
|
|
topItem->setAcceptHoverEvents(true);
|
|
|
|
QPoint pos(10, 10);
|
|
|
|
QTest::mouseMove(&window, pos);
|
|
|
|
QTRY_VERIFY(bottomItem->eventCount(QEvent::HoverEnter) > 0);
|
|
QCOMPARE(bottomItem->childMouseEventFilterEventCount(QEvent::HoverEnter), 0);
|
|
QCOMPARE(middleItem->eventCount(QEvent::HoverEnter), 0);
|
|
QCOMPARE(topItem->eventCount(QEvent::HoverEnter), 0);
|
|
bottomItem->reset();
|
|
|
|
pos = QPoint(60, 60);
|
|
QTest::mouseMove(&window, pos);
|
|
QTRY_VERIFY(middleItem->eventCount(QEvent::HoverEnter) > 0);
|
|
QCOMPARE(bottomItem->childMouseEventFilterEventCount(QEvent::HoverEnter), 0);
|
|
middleItem->reset();
|
|
|
|
pos = QPoint(70,70);
|
|
bottomItem->setFiltersChildMouseEvents(true);
|
|
QTest::mouseMove(&window, pos);
|
|
QTRY_VERIFY(middleItem->eventCount(QEvent::HoverMove) > 0);
|
|
QVERIFY(bottomItem->childMouseEventFilterEventCount(QEvent::HoverMove) > 0);
|
|
QCOMPARE(topItem->eventCount(QEvent::HoverEnter), 0);
|
|
bottomItem->reset();
|
|
middleItem->reset();
|
|
|
|
pos = QPoint(110,110);
|
|
bottomItem->addFilterEvent(QEvent::HoverEnter);
|
|
QTest::mouseMove(&window, pos);
|
|
QTRY_VERIFY(bottomItem->childMouseEventFilterEventCount(QEvent::HoverEnter) > 0);
|
|
QCOMPARE(topItem->eventCount(QEvent::HoverEnter), 0);
|
|
QCOMPARE(middleItem->eventCount(QEvent::HoverEnter), 0);
|
|
}
|
|
|
|
class HoverTimestampConsumer : public QQuickItem
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
HoverTimestampConsumer(QQuickItem *parent = nullptr)
|
|
: QQuickItem(parent)
|
|
{
|
|
setAcceptHoverEvents(true);
|
|
}
|
|
|
|
void hoverEnterEvent(QHoverEvent *event) override
|
|
{ hoverTimestamps << event->timestamp(); }
|
|
void hoverLeaveEvent(QHoverEvent *event) override
|
|
{ hoverTimestamps << event->timestamp(); }
|
|
void hoverMoveEvent(QHoverEvent *event) override
|
|
{ hoverTimestamps << event->timestamp(); }
|
|
|
|
QList<ulong> hoverTimestamps;
|
|
};
|
|
|
|
// Checks that a QHoverEvent carries the timestamp of the QMouseEvent that caused it.
|
|
// QTBUG-54600
|
|
void tst_qquickwindow::testHoverTimestamp()
|
|
{
|
|
QQuickWindow window;
|
|
|
|
window.resize(200, 200);
|
|
window.setPosition(100, 100);
|
|
window.setTitle(QTest::currentTestFunction());
|
|
window.show();
|
|
QVERIFY(QTest::qWaitForWindowActive(&window));
|
|
|
|
HoverTimestampConsumer *hoverConsumer = new HoverTimestampConsumer(window.contentItem());
|
|
hoverConsumer->setWidth(100);
|
|
hoverConsumer->setHeight(100);
|
|
hoverConsumer->setX(50);
|
|
hoverConsumer->setY(50);
|
|
|
|
// First position, outside
|
|
{
|
|
QMouseEvent mouseEvent(QEvent::MouseMove, QPointF(40, 40), QPointF(40, 40), QPointF(140, 140),
|
|
Qt::NoButton, Qt::NoButton, Qt::NoModifier, Qt::MouseEventNotSynthesized);
|
|
mouseEvent.setTimestamp(10);
|
|
QVERIFY(QCoreApplication::sendEvent(&window, &mouseEvent));
|
|
}
|
|
|
|
// Enter
|
|
{
|
|
QMouseEvent mouseEvent(QEvent::MouseMove, QPointF(50, 50), QPointF(50, 50), QPointF(150, 150),
|
|
Qt::NoButton, Qt::NoButton, Qt::NoModifier, Qt::MouseEventNotSynthesized);
|
|
mouseEvent.setTimestamp(20);
|
|
QVERIFY(QCoreApplication::sendEvent(&window, &mouseEvent));
|
|
}
|
|
QCOMPARE(hoverConsumer->hoverTimestamps.size(), 1);
|
|
QCOMPARE(hoverConsumer->hoverTimestamps.last(), 20UL);
|
|
|
|
// Move
|
|
{
|
|
QMouseEvent mouseEvent(QEvent::MouseMove, QPointF(60, 60), QPointF(60, 60), QPointF(160, 160),
|
|
Qt::NoButton, Qt::NoButton, Qt::NoModifier, Qt::MouseEventNotSynthesized);
|
|
mouseEvent.setTimestamp(30);
|
|
QVERIFY(QCoreApplication::sendEvent(&window, &mouseEvent));
|
|
}
|
|
QCOMPARE(hoverConsumer->hoverTimestamps.size(), 2);
|
|
QCOMPARE(hoverConsumer->hoverTimestamps.last(), 30UL);
|
|
|
|
// Move
|
|
{
|
|
QMouseEvent mouseEvent(QEvent::MouseMove, QPointF(100, 100), QPointF(100, 100), QPointF(200, 200),
|
|
Qt::NoButton, Qt::NoButton, Qt::NoModifier, Qt::MouseEventNotSynthesized);
|
|
mouseEvent.setTimestamp(40);
|
|
QVERIFY(QCoreApplication::sendEvent(&window, &mouseEvent));
|
|
}
|
|
QCOMPARE(hoverConsumer->hoverTimestamps.size(), 3);
|
|
QCOMPARE(hoverConsumer->hoverTimestamps.last(), 40UL);
|
|
|
|
// Leave
|
|
{
|
|
QMouseEvent mouseEvent(QEvent::MouseMove, QPointF(160, 160), QPointF(160, 160), QPointF(260, 260),
|
|
Qt::NoButton, Qt::NoButton, Qt::NoModifier, Qt::MouseEventNotSynthesized);
|
|
mouseEvent.setTimestamp(5);
|
|
QVERIFY(QCoreApplication::sendEvent(&window, &mouseEvent));
|
|
}
|
|
QCOMPARE(hoverConsumer->hoverTimestamps.size(), 4);
|
|
QCOMPARE(hoverConsumer->hoverTimestamps.last(), 5UL);
|
|
}
|
|
|
|
class CircleItem : public QQuickRectangle
|
|
{
|
|
public:
|
|
CircleItem(QQuickItem *parent = nullptr) : QQuickRectangle(parent) { }
|
|
|
|
void setRadius(qreal radius) {
|
|
const qreal diameter = radius*2;
|
|
setWidth(diameter);
|
|
setHeight(diameter);
|
|
}
|
|
|
|
bool childMouseEventFilter(QQuickItem *item, QEvent *event) override
|
|
{
|
|
Q_UNUSED(item);
|
|
if (event->type() == QEvent::MouseButtonPress && !contains(static_cast<QMouseEvent*>(event)->position().toPoint())) {
|
|
// This is an evil hack: in case of items that are not rectangles, we never accept the event.
|
|
// Instead the events are now delivered to QDeclarativeGeoMapItemBase which doesn't to anything with them.
|
|
// The map below it still works since it filters events and steals the events at some point.
|
|
event->setAccepted(false);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool contains(const QPointF &pos) const override {
|
|
// returns true if the point is inside the the embedded circle inside the (square) rect
|
|
const float radius = (float)width()/2;
|
|
const QVector2D center(radius, radius);
|
|
const QVector2D dx = QVector2D(pos) - center;
|
|
const bool ret = dx.lengthSquared() < radius*radius;
|
|
return ret;
|
|
}
|
|
};
|
|
|
|
void tst_qquickwindow::test_circleMapItem()
|
|
{
|
|
QQuickWindow window;
|
|
|
|
window.resize(250, 250);
|
|
window.setPosition(100, 100);
|
|
window.setTitle(QTest::currentTestFunction());
|
|
|
|
QQuickItem *root = window.contentItem();
|
|
QQuickMouseArea *mab = new QQuickMouseArea(root);
|
|
mab->setObjectName("Bottom MouseArea");
|
|
mab->setSize(QSizeF(100, 100));
|
|
|
|
CircleItem *topItem = new CircleItem(root);
|
|
topItem->setFiltersChildMouseEvents(true);
|
|
topItem->setColor(Qt::green);
|
|
topItem->setObjectName("Top Item");
|
|
topItem->setPosition(QPointF(30, 30));
|
|
topItem->setRadius(20);
|
|
QQuickMouseArea *mat = new QQuickMouseArea(topItem);
|
|
mat->setObjectName("Top Item/MouseArea");
|
|
mat->setSize(QSizeF(40, 40));
|
|
|
|
QSignalSpy bottomSpy(mab, SIGNAL(clicked(QQuickMouseEvent*)));
|
|
QSignalSpy topSpy(mat, SIGNAL(clicked(QQuickMouseEvent*)));
|
|
|
|
window.show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(&window));
|
|
QTest::qWait(1000);
|
|
|
|
QPoint pos(50, 50);
|
|
QTest::mouseClick(&window, Qt::LeftButton, Qt::KeyboardModifiers(), pos);
|
|
|
|
QCOMPARE(topSpy.size(), 1);
|
|
QCOMPARE(bottomSpy.size(), 0);
|
|
|
|
// Outside the "Circles" "input area", but on top of the bottomItem rectangle
|
|
pos = QPoint(66, 66);
|
|
QTest::mouseClick(&window, Qt::LeftButton, Qt::KeyboardModifiers(), pos);
|
|
|
|
QCOMPARE(bottomSpy.size(), 1);
|
|
QCOMPARE(topSpy.size(), 1);
|
|
}
|
|
|
|
void tst_qquickwindow::grabContentItemToImage()
|
|
{
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.loadUrl(testFileUrl("grabContentItemToImage.qml"));
|
|
|
|
QObject *created = component.create();
|
|
QScopedPointer<QObject> cleanup(created);
|
|
QVERIFY(created);
|
|
|
|
QQuickWindow *window = qobject_cast<QQuickWindow *>(created);
|
|
QVERIFY(QTest::qWaitForWindowActive(window));
|
|
|
|
QMetaObject::invokeMethod(window, "grabContentItemToImage");
|
|
QTRY_COMPARE(created->property("success").toInt(), 1);
|
|
}
|
|
|
|
class TestDropTarget : public QQuickItem
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
TestDropTarget(QQuickItem *parent = nullptr)
|
|
: QQuickItem(parent)
|
|
, enterDropAction(Qt::CopyAction)
|
|
, moveDropAction(Qt::CopyAction)
|
|
, dropDropAction(Qt::CopyAction)
|
|
, enterAccept(true)
|
|
, moveAccept(true)
|
|
, dropAccept(true)
|
|
{
|
|
setFlags(ItemAcceptsDrops);
|
|
}
|
|
|
|
void reset()
|
|
{
|
|
enterDropAction = Qt::CopyAction;
|
|
moveDropAction = Qt::CopyAction;
|
|
dropDropAction = Qt::CopyAction;
|
|
enterAccept = true;
|
|
moveAccept = true;
|
|
dropAccept = true;
|
|
}
|
|
|
|
void dragEnterEvent(QDragEnterEvent *event) override
|
|
{
|
|
event->setAccepted(enterAccept);
|
|
event->setDropAction(enterDropAction);
|
|
}
|
|
|
|
void dragMoveEvent(QDragMoveEvent *event) override
|
|
{
|
|
event->setAccepted(moveAccept);
|
|
event->setDropAction(moveDropAction);
|
|
}
|
|
|
|
void dropEvent(QDropEvent *event) override
|
|
{
|
|
event->setAccepted(dropAccept);
|
|
event->setDropAction(dropDropAction);
|
|
}
|
|
|
|
Qt::DropAction enterDropAction;
|
|
Qt::DropAction moveDropAction;
|
|
Qt::DropAction dropDropAction;
|
|
bool enterAccept;
|
|
bool moveAccept;
|
|
bool dropAccept;
|
|
};
|
|
|
|
class DragEventTester {
|
|
public:
|
|
DragEventTester()
|
|
: pos(60, 60)
|
|
, actions(Qt::CopyAction | Qt::MoveAction | Qt::LinkAction)
|
|
, buttons(Qt::LeftButton)
|
|
, modifiers(Qt::NoModifier)
|
|
{
|
|
}
|
|
|
|
~DragEventTester() {
|
|
qDeleteAll(events);
|
|
events.clear();
|
|
enterEvent = nullptr;
|
|
moveEvent = nullptr;
|
|
dropEvent = nullptr;
|
|
leaveEvent = nullptr;
|
|
}
|
|
|
|
void addEnterEvent()
|
|
{
|
|
enterEvent = new QDragEnterEvent(pos, actions, &data, buttons, modifiers);
|
|
events.append(enterEvent);
|
|
}
|
|
|
|
void addMoveEvent()
|
|
{
|
|
moveEvent = new QDragMoveEvent(pos, actions, &data, buttons, modifiers, QEvent::DragMove);
|
|
events.append(moveEvent);
|
|
}
|
|
|
|
void addDropEvent()
|
|
{
|
|
dropEvent = new QDropEvent(pos, actions, &data, buttons, modifiers, QEvent::Drop);
|
|
events.append(dropEvent);
|
|
}
|
|
|
|
void addLeaveEvent()
|
|
{
|
|
leaveEvent = new QDragLeaveEvent();
|
|
events.append(leaveEvent);
|
|
}
|
|
|
|
void sendDragEventSequence(QQuickWindow *window) const {
|
|
for (int i = 0; i < events.size(); ++i) {
|
|
QCoreApplication::sendEvent(window, events[i]);
|
|
}
|
|
}
|
|
|
|
// Used for building events.
|
|
QMimeData data;
|
|
QPoint pos;
|
|
Qt::DropActions actions;
|
|
Qt::MouseButtons buttons;
|
|
Qt::KeyboardModifiers modifiers;
|
|
|
|
// Owns events.
|
|
QList<QEvent *> events;
|
|
|
|
// Non-owner pointers for easy acccess.
|
|
QDragEnterEvent *enterEvent;
|
|
QDragMoveEvent *moveEvent;
|
|
QDropEvent *dropEvent;
|
|
QDragLeaveEvent *leaveEvent;
|
|
};
|
|
|
|
void tst_qquickwindow::testDragEventPropertyPropagation()
|
|
{
|
|
QQuickWindow window;
|
|
TestDropTarget dropTarget(window.contentItem());
|
|
|
|
// Setting the size is important because the QQuickWindow checks if the drag happened inside
|
|
// the drop target.
|
|
dropTarget.setSize(QSizeF(100, 100));
|
|
|
|
// Test enter events property propagation.
|
|
// For enter events, only isAccepted gets propagated.
|
|
{
|
|
DragEventTester builder;
|
|
dropTarget.enterAccept = false;
|
|
dropTarget.enterDropAction = Qt::IgnoreAction;
|
|
builder.addEnterEvent(); builder.addMoveEvent(); builder.addLeaveEvent();
|
|
builder.sendDragEventSequence(&window);
|
|
QDragEnterEvent* enterEvent = builder.enterEvent;
|
|
QCOMPARE(enterEvent->isAccepted(), dropTarget.enterAccept);
|
|
}
|
|
{
|
|
DragEventTester builder;
|
|
dropTarget.enterAccept = false;
|
|
dropTarget.enterDropAction = Qt::CopyAction;
|
|
builder.addEnterEvent(); builder.addMoveEvent(); builder.addLeaveEvent();
|
|
builder.sendDragEventSequence(&window);
|
|
QDragEnterEvent* enterEvent = builder.enterEvent;
|
|
QCOMPARE(enterEvent->isAccepted(), dropTarget.enterAccept);
|
|
}
|
|
{
|
|
DragEventTester builder;
|
|
dropTarget.enterAccept = true;
|
|
dropTarget.enterDropAction = Qt::IgnoreAction;
|
|
builder.addEnterEvent(); builder.addMoveEvent(); builder.addLeaveEvent();
|
|
builder.sendDragEventSequence(&window);
|
|
QDragEnterEvent* enterEvent = builder.enterEvent;
|
|
QCOMPARE(enterEvent->isAccepted(), dropTarget.enterAccept);
|
|
}
|
|
{
|
|
DragEventTester builder;
|
|
dropTarget.enterAccept = true;
|
|
dropTarget.enterDropAction = Qt::CopyAction;
|
|
builder.addEnterEvent(); builder.addMoveEvent(); builder.addLeaveEvent();
|
|
builder.sendDragEventSequence(&window);
|
|
QDragEnterEvent* enterEvent = builder.enterEvent;
|
|
QCOMPARE(enterEvent->isAccepted(), dropTarget.enterAccept);
|
|
}
|
|
|
|
// Test move events property propagation.
|
|
// For move events, both isAccepted and dropAction get propagated.
|
|
dropTarget.reset();
|
|
{
|
|
DragEventTester builder;
|
|
dropTarget.moveAccept = false;
|
|
dropTarget.moveDropAction = Qt::IgnoreAction;
|
|
builder.addEnterEvent(); builder.addMoveEvent(); builder.addLeaveEvent();
|
|
builder.sendDragEventSequence(&window);
|
|
QDragMoveEvent* moveEvent = builder.moveEvent;
|
|
QCOMPARE(moveEvent->isAccepted(), dropTarget.moveAccept);
|
|
QCOMPARE(moveEvent->dropAction(), dropTarget.moveDropAction);
|
|
}
|
|
{
|
|
DragEventTester builder;
|
|
dropTarget.moveAccept = false;
|
|
dropTarget.moveDropAction = Qt::CopyAction;
|
|
builder.addEnterEvent(); builder.addMoveEvent(); builder.addLeaveEvent();
|
|
builder.sendDragEventSequence(&window);
|
|
QDragMoveEvent* moveEvent = builder.moveEvent;
|
|
QCOMPARE(moveEvent->isAccepted(), dropTarget.moveAccept);
|
|
QCOMPARE(moveEvent->dropAction(), dropTarget.moveDropAction);
|
|
}
|
|
{
|
|
DragEventTester builder;
|
|
dropTarget.moveAccept = true;
|
|
dropTarget.moveDropAction = Qt::IgnoreAction;
|
|
builder.addEnterEvent(); builder.addMoveEvent(); builder.addLeaveEvent();
|
|
builder.sendDragEventSequence(&window);
|
|
QDragMoveEvent* moveEvent = builder.moveEvent;
|
|
QCOMPARE(moveEvent->isAccepted(), dropTarget.moveAccept);
|
|
QCOMPARE(moveEvent->dropAction(), dropTarget.moveDropAction);
|
|
}
|
|
{
|
|
DragEventTester builder;
|
|
dropTarget.moveAccept = true;
|
|
dropTarget.moveDropAction = Qt::CopyAction;
|
|
builder.addEnterEvent(); builder.addMoveEvent(); builder.addLeaveEvent();
|
|
builder.sendDragEventSequence(&window);
|
|
QDragMoveEvent* moveEvent = builder.moveEvent;
|
|
QCOMPARE(moveEvent->isAccepted(), dropTarget.moveAccept);
|
|
QCOMPARE(moveEvent->dropAction(), dropTarget.moveDropAction);
|
|
}
|
|
|
|
// Test drop events property propagation.
|
|
// For drop events, both isAccepted and dropAction get propagated.
|
|
dropTarget.reset();
|
|
{
|
|
DragEventTester builder;
|
|
dropTarget.dropAccept = false;
|
|
dropTarget.dropDropAction = Qt::IgnoreAction;
|
|
builder.addEnterEvent(); builder.addMoveEvent(); builder.addDropEvent();
|
|
builder.sendDragEventSequence(&window);
|
|
QDropEvent* dropEvent = builder.dropEvent;
|
|
QCOMPARE(dropEvent->isAccepted(), dropTarget.dropAccept);
|
|
QCOMPARE(dropEvent->dropAction(), dropTarget.dropDropAction);
|
|
}
|
|
{
|
|
DragEventTester builder;
|
|
dropTarget.dropAccept = false;
|
|
dropTarget.dropDropAction = Qt::CopyAction;
|
|
builder.addEnterEvent(); builder.addMoveEvent(); builder.addDropEvent();
|
|
builder.sendDragEventSequence(&window);
|
|
QDropEvent* dropEvent = builder.dropEvent;
|
|
QCOMPARE(dropEvent->isAccepted(), dropTarget.dropAccept);
|
|
QCOMPARE(dropEvent->dropAction(), dropTarget.dropDropAction);
|
|
}
|
|
{
|
|
DragEventTester builder;
|
|
dropTarget.dropAccept = true;
|
|
dropTarget.dropDropAction = Qt::IgnoreAction;
|
|
builder.addEnterEvent(); builder.addMoveEvent(); builder.addDropEvent();
|
|
builder.sendDragEventSequence(&window);
|
|
QDropEvent* dropEvent = builder.dropEvent;
|
|
QCOMPARE(dropEvent->isAccepted(), dropTarget.dropAccept);
|
|
QCOMPARE(dropEvent->dropAction(), dropTarget.dropDropAction);
|
|
}
|
|
{
|
|
DragEventTester builder;
|
|
dropTarget.dropAccept = true;
|
|
dropTarget.dropDropAction = Qt::CopyAction;
|
|
builder.addEnterEvent(); builder.addMoveEvent(); builder.addDropEvent();
|
|
builder.sendDragEventSequence(&window);
|
|
QDropEvent* dropEvent = builder.dropEvent;
|
|
QCOMPARE(dropEvent->isAccepted(), dropTarget.dropAccept);
|
|
QCOMPARE(dropEvent->dropAction(), dropTarget.dropDropAction);
|
|
}
|
|
}
|
|
|
|
void tst_qquickwindow::findChild()
|
|
{
|
|
QQuickWindow window;
|
|
|
|
// QQuickWindow
|
|
// |_ QQuickWindow::contentItem
|
|
// | |_ QObject("contentItemChild")
|
|
// |_ QObject("viewChild")
|
|
|
|
QObject *windowChild = new QObject(&window);
|
|
windowChild->setObjectName("windowChild");
|
|
|
|
QObject *contentItemChild = new QObject(window.contentItem());
|
|
contentItemChild->setObjectName("contentItemChild");
|
|
|
|
QCOMPARE(window.findChild<QObject *>("windowChild"), windowChild);
|
|
QCOMPARE(window.findChild<QObject *>("contentItemChild"), contentItemChild);
|
|
|
|
QVERIFY(!window.contentItem()->findChild<QObject *>("viewChild")); // sibling
|
|
QCOMPARE(window.contentItem()->findChild<QObject *>("contentItemChild"), contentItemChild);
|
|
}
|
|
|
|
class DeliveryRecord : public QPair<QString, QString>
|
|
{
|
|
public:
|
|
DeliveryRecord(const QString &filter, const QString &receiver) : QPair<QString, QString>(filter, receiver) { }
|
|
DeliveryRecord(const QString &receiver) : QPair<QString, QString>(QString(), receiver) { }
|
|
DeliveryRecord() : QPair<QString, QString>() { }
|
|
QString toString() const {
|
|
if (second.isEmpty())
|
|
return QLatin1String("Delivery(no receiver)");
|
|
else if (first.isEmpty())
|
|
return QString(QLatin1String("Delivery(to '%1')")).arg(second);
|
|
else
|
|
return QString(QLatin1String("Delivery('%1' filtering for '%2')")).arg(first).arg(second);
|
|
}
|
|
};
|
|
|
|
Q_DECLARE_METATYPE(DeliveryRecord)
|
|
|
|
QDebug operator<<(QDebug dbg, const DeliveryRecord &pair)
|
|
{
|
|
dbg << pair.toString();
|
|
return dbg;
|
|
}
|
|
|
|
typedef QVector<DeliveryRecord> DeliveryRecordVector;
|
|
|
|
class EventItem : public QQuickRectangle
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
EventItem(QQuickItem *parent)
|
|
: QQuickRectangle(parent)
|
|
, m_eventAccepts(true)
|
|
, m_filterReturns(true)
|
|
, m_filterAccepts(true)
|
|
, m_filterNotPreAccepted(false)
|
|
{
|
|
QSizeF psize(parent->width(), parent->height());
|
|
psize -= QSizeF(20, 20);
|
|
setWidth(psize.width());
|
|
setHeight(psize.height());
|
|
setPosition(QPointF(10, 10));
|
|
}
|
|
|
|
void setFilterReturns(bool filterReturns) { m_filterReturns = filterReturns; }
|
|
void setFilterAccepts(bool accepts) { m_filterAccepts = accepts; }
|
|
void setEventAccepts(bool accepts) { m_eventAccepts = accepts; }
|
|
|
|
/*!
|
|
* \internal
|
|
*
|
|
* returns false if any of the calls to childMouseEventFilter had the wrong
|
|
* preconditions. If all calls had the expected precondition, returns true.
|
|
*/
|
|
bool testFilterPreConditions() const { return !m_filterNotPreAccepted; }
|
|
static QVector<DeliveryRecord> &deliveryList() { return m_deliveryList; }
|
|
static void setExpectedDeliveryList(const QVector<DeliveryRecord> &v) { m_expectedDeliveryList = v; }
|
|
|
|
bool isRelevant(QQuickItem *receiver, QEvent *e)
|
|
{
|
|
if (receiver->acceptTouchEvents())
|
|
return e->type() == QEvent::TouchBegin;
|
|
return e->type() == QEvent::MouseButtonPress;
|
|
}
|
|
|
|
protected:
|
|
bool childMouseEventFilter(QQuickItem *i, QEvent *e) override
|
|
{
|
|
if (!isRelevant(i, e))
|
|
return QQuickRectangle::childMouseEventFilter(i, e);
|
|
|
|
appendEvent(this, i, e);
|
|
if (!e->isAccepted())
|
|
m_filterNotPreAccepted = true;
|
|
e->setAccepted(m_filterAccepts);
|
|
return m_filterReturns;
|
|
}
|
|
|
|
bool event(QEvent *e) override
|
|
{
|
|
if (!isRelevant(this, e))
|
|
QQuickRectangle::event(e);
|
|
|
|
appendEvent(nullptr, this, e);
|
|
e->setAccepted(m_eventAccepts);
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
static void appendEvent(QQuickItem *filter, QQuickItem *receiver, QEvent *event) {
|
|
auto record = DeliveryRecord(filter ? filter->objectName() : QString(), receiver ? receiver->objectName() : QString());
|
|
int i = m_deliveryList.size();
|
|
if (m_expectedDeliveryList.size() > i && m_expectedDeliveryList[i] == record)
|
|
qCDebug(lcTests).noquote().nospace() << i << ": " << record;
|
|
else
|
|
qCDebug(lcTests).noquote().nospace() << i << ": " << record
|
|
<< ", expected " << (m_expectedDeliveryList.size() > i ? m_expectedDeliveryList[i].toString() : QLatin1String("nothing")) << " <---";
|
|
m_deliveryList << record;
|
|
}
|
|
bool m_eventAccepts;
|
|
bool m_filterReturns;
|
|
bool m_filterAccepts;
|
|
bool m_filterNotPreAccepted;
|
|
|
|
// list of (filtering-parent . receiver) pairs
|
|
static DeliveryRecordVector m_expectedDeliveryList;
|
|
static DeliveryRecordVector m_deliveryList;
|
|
};
|
|
|
|
DeliveryRecordVector EventItem::m_expectedDeliveryList;
|
|
DeliveryRecordVector EventItem::m_deliveryList;
|
|
|
|
typedef QVector<const char*> CharStarVector;
|
|
|
|
Q_DECLARE_METATYPE(CharStarVector)
|
|
|
|
struct InputState {
|
|
struct {
|
|
// event() behavior
|
|
bool eventAccepts;
|
|
// filterChildMouse behavior
|
|
bool returns;
|
|
bool accepts;
|
|
bool filtersChildMouseEvent;
|
|
} r[4];
|
|
};
|
|
|
|
Q_DECLARE_METATYPE(InputState)
|
|
|
|
void tst_qquickwindow::testChildMouseEventFilter_data()
|
|
{
|
|
// HIERARCHY:
|
|
// r0->r1->r2->r3
|
|
//
|
|
QTest::addColumn<QPoint>("mousePos");
|
|
QTest::addColumn<QString>("eventMode");
|
|
QTest::addColumn<InputState>("inputState");
|
|
QTest::addColumn<DeliveryRecordVector>("expectedDeliveryOrder");
|
|
|
|
for (const QString &eventMode : {"mouse", "touch", "touchToMouse"}) {
|
|
|
|
#define desc(txt) qPrintable(QString("%1 events, ").arg(eventMode) + txt)
|
|
|
|
QTest::newRow(desc("if filtered and rejected, do not deliver it to the item that filtered it"))
|
|
<< QPoint(100, 100)
|
|
<< eventMode
|
|
<< InputState({
|
|
// | event() | child mouse filter
|
|
// +---------+---------+---------+---------
|
|
{ // | accepts | returns | accepts | filtersChildMouseEvent
|
|
{ false, false, false, false},
|
|
{ true, false, false, false},
|
|
{ false, true, false, true},
|
|
{ false, false, false, false}
|
|
}
|
|
})
|
|
<< (DeliveryRecordVector()
|
|
<< DeliveryRecord("r2", "r3")
|
|
//<< DeliveryRecord("r3") // it got filtered -> do not deliver
|
|
// DeliveryRecord("r2") // r2 filtered it -> do not deliver
|
|
<< DeliveryRecord("r1")
|
|
);
|
|
|
|
QTest::newRow(desc("no filtering, no accepting"))
|
|
<< QPoint(100, 100)
|
|
<< eventMode
|
|
<< InputState({
|
|
// | event() | child mouse filter
|
|
// +---------+---------+---------+---------
|
|
{ // | accepts | returns | accepts | filtersChildMouseEvent
|
|
{ false, false, false, false},
|
|
{ false , false, false, false},
|
|
{ false, false, false, false},
|
|
{ false, false, false, false}
|
|
}
|
|
})
|
|
<< (DeliveryRecordVector()
|
|
<< DeliveryRecord("r3")
|
|
<< DeliveryRecord("r2")
|
|
<< DeliveryRecord("r1")
|
|
<< DeliveryRecord("r0")
|
|
<< DeliveryRecord("root")
|
|
);
|
|
|
|
QTest::newRow(desc("all filtering, no accepting"))
|
|
<< QPoint(100, 100)
|
|
<< eventMode
|
|
<< InputState({
|
|
// | event() | child mouse filter
|
|
// +---------+---------+---------+---------
|
|
{ // | accepts | returns | accepts | filtersChildMouseEvent
|
|
{ false, false, false, true},
|
|
{ false, false, false, true},
|
|
{ false, false, false, true},
|
|
{ false, false, false, true}
|
|
}
|
|
})
|
|
<< (DeliveryRecordVector()
|
|
<< DeliveryRecord("r2", "r3")
|
|
<< DeliveryRecord("r1", "r3")
|
|
<< DeliveryRecord("r0", "r3")
|
|
<< DeliveryRecord("r3")
|
|
<< DeliveryRecord("r1", "r2")
|
|
<< DeliveryRecord("r0", "r2")
|
|
<< DeliveryRecord("r2")
|
|
<< DeliveryRecord("r0", "r1")
|
|
<< DeliveryRecord("r1")
|
|
<< DeliveryRecord("r0")
|
|
<< DeliveryRecord("root")
|
|
);
|
|
|
|
|
|
QTest::newRow(desc("some filtering, no accepting"))
|
|
<< QPoint(100, 100)
|
|
<< eventMode
|
|
<< InputState({
|
|
// | event() | child mouse filter
|
|
// +---------+---------+---------+---------
|
|
{ // | accepts | returns | accepts | filtersChildMouseEvent
|
|
{ false, false, false, true},
|
|
{ false, false, false, true},
|
|
{ false, false, false, false},
|
|
{ false, false, false, false}
|
|
}
|
|
})
|
|
<< (DeliveryRecordVector()
|
|
<< DeliveryRecord("r1", "r3")
|
|
<< DeliveryRecord("r0", "r3")
|
|
<< DeliveryRecord("r3")
|
|
<< DeliveryRecord("r1", "r2")
|
|
<< DeliveryRecord("r0", "r2")
|
|
<< DeliveryRecord("r2")
|
|
<< DeliveryRecord("r0", "r1")
|
|
<< DeliveryRecord("r1")
|
|
<< DeliveryRecord("r0")
|
|
<< DeliveryRecord("root")
|
|
);
|
|
|
|
QTest::newRow(desc("r1 accepts"))
|
|
<< QPoint(100, 100)
|
|
<< eventMode
|
|
<< InputState({
|
|
// | event() | child mouse filter
|
|
// +---------+---------+---------+---------
|
|
{ // | accepts | returns | accepts | filtersChildMouseEvent
|
|
{ false, false, false, true},
|
|
{ true , false, false, true},
|
|
{ false, false, false, false},
|
|
{ false, false, false, false}
|
|
}
|
|
})
|
|
<< (DeliveryRecordVector()
|
|
<< DeliveryRecord("r1", "r3")
|
|
<< DeliveryRecord("r0", "r3")
|
|
<< DeliveryRecord("r3")
|
|
<< DeliveryRecord("r1", "r2")
|
|
<< DeliveryRecord("r0", "r2")
|
|
<< DeliveryRecord("r2")
|
|
<< DeliveryRecord("r0", "r1")
|
|
<< DeliveryRecord("r1")
|
|
);
|
|
|
|
QTest::newRow(desc("r1 rejects and filters"))
|
|
<< QPoint(100, 100)
|
|
<< eventMode
|
|
<< InputState({
|
|
// | event() | child mouse filter
|
|
// +---------+---------+---------+---------
|
|
{ // | accepts | returns | accepts | filtersChildMouseEvent
|
|
{ false, false, false, true},
|
|
{ false , true, false, true},
|
|
{ false, false, false, false},
|
|
{ false, false, false, false}
|
|
}
|
|
})
|
|
<< (DeliveryRecordVector()
|
|
<< DeliveryRecord("r1", "r3")
|
|
<< DeliveryRecord("r0", "r3")
|
|
// << DeliveryRecord("r3") // since it got filtered we don't deliver to r3
|
|
<< DeliveryRecord("r1", "r2")
|
|
<< DeliveryRecord("r0", "r2")
|
|
// << DeliveryRecord("r2" // since it got filtered we don't deliver to r2
|
|
<< DeliveryRecord("r0", "r1")
|
|
// << DeliveryRecord("r1") // since it acted as a filter and returned true, we don't deliver to r1
|
|
<< DeliveryRecord("r0")
|
|
<< DeliveryRecord("root")
|
|
);
|
|
}
|
|
}
|
|
|
|
void tst_qquickwindow::testChildMouseEventFilter()
|
|
{
|
|
QFETCH(QPoint, mousePos);
|
|
QFETCH(QString, eventMode);
|
|
QFETCH(InputState, inputState);
|
|
QFETCH(DeliveryRecordVector, expectedDeliveryOrder);
|
|
|
|
EventItem::setExpectedDeliveryList(expectedDeliveryOrder);
|
|
|
|
QQuickWindow window;
|
|
window.resize(500, 809);
|
|
QQuickItem *root = window.contentItem();
|
|
root->setAcceptedMouseButtons(Qt::LeftButton);
|
|
|
|
root->setObjectName("root");
|
|
QScopedPointer<EventFilter> rootFilter(new EventFilter);
|
|
root->installEventFilter(rootFilter.data());
|
|
|
|
const bool useMouseEvents = eventMode == "mouse";
|
|
const bool acceptTouchEvents = eventMode == "touch";
|
|
|
|
// Create 4 items; each item a child of the previous item.
|
|
EventItem *r[4];
|
|
r[0] = new EventItem(root);
|
|
r[0]->setColor(QColor(0x404040));
|
|
r[0]->setWidth(200);
|
|
r[0]->setHeight(200);
|
|
r[0]->setAcceptTouchEvents(acceptTouchEvents);
|
|
|
|
r[1] = new EventItem(r[0]);
|
|
r[1]->setColor(QColor(0x606060));
|
|
r[1]->setAcceptTouchEvents(acceptTouchEvents);
|
|
|
|
r[2] = new EventItem(r[1]);
|
|
r[2]->setColor(Qt::red);
|
|
r[2]->setAcceptTouchEvents(acceptTouchEvents);
|
|
|
|
r[3] = new EventItem(r[2]);
|
|
r[3]->setColor(Qt::green);
|
|
r[3]->setAcceptTouchEvents(acceptTouchEvents);
|
|
|
|
for (uint i = 0; i < sizeof(r)/sizeof(EventItem*); ++i) {
|
|
r[i]->setEventAccepts(inputState.r[i].eventAccepts);
|
|
r[i]->setFilterReturns(inputState.r[i].returns);
|
|
r[i]->setFilterAccepts(inputState.r[i].accepts);
|
|
r[i]->setFiltersChildMouseEvents(inputState.r[i].filtersChildMouseEvent);
|
|
r[i]->setObjectName(QString::fromLatin1("r%1").arg(i));
|
|
r[i]->setAcceptedMouseButtons(Qt::LeftButton);
|
|
}
|
|
|
|
window.show();
|
|
window.requestActivate();
|
|
QVERIFY(QTest::qWaitForWindowActive(&window));
|
|
|
|
DeliveryRecordVector &actualDeliveryOrder = EventItem::deliveryList();
|
|
actualDeliveryOrder.clear();
|
|
|
|
if (useMouseEvents)
|
|
QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, mousePos);
|
|
else
|
|
QTest::touchEvent(&window, touchDevice).press(0, mousePos, &window);
|
|
|
|
// Check if event got delivered to the root item. If so, append it to the list of items the event got delivered to
|
|
if (rootFilter->events.contains(QEvent::MouseButtonPress))
|
|
actualDeliveryOrder.append(DeliveryRecord("root"));
|
|
|
|
for (int i = 0; i < qMax(actualDeliveryOrder.size(), expectedDeliveryOrder.size()); ++i) {
|
|
const DeliveryRecord expectedNames = expectedDeliveryOrder.value(i);
|
|
const DeliveryRecord actualNames = actualDeliveryOrder.value(i);
|
|
QCOMPARE(actualNames.toString(), expectedNames.toString());
|
|
}
|
|
|
|
for (EventItem *item : r) {
|
|
QVERIFY(item->testFilterPreConditions());
|
|
}
|
|
|
|
// "restore" mouse/touch state
|
|
if (useMouseEvents)
|
|
QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, mousePos);
|
|
else
|
|
QTest::touchEvent(&window, touchDevice).release(0, mousePos, &window);
|
|
}
|
|
|
|
void tst_qquickwindow::cleanupGrabsOnRelease()
|
|
{
|
|
TestTouchItem::clearMouseEventCounters();
|
|
|
|
QQuickWindow *window = new QQuickWindow;
|
|
QScopedPointer<QQuickWindow> cleanup(window);
|
|
window->resize(250, 250);
|
|
window->setPosition(100, 100);
|
|
window->setTitle(QTest::currentTestFunction());
|
|
window->show();
|
|
QVERIFY(QTest::qWaitForWindowActive(window));
|
|
|
|
TestTouchItem *parent = new TestTouchItem(window->contentItem());
|
|
parent->setObjectName("parent");
|
|
parent->setSize(QSizeF(150, 150));
|
|
parent->acceptMouseEvents = true;
|
|
parent->grabOnRelease = true;
|
|
|
|
TestTouchItem *child = new TestTouchItem(parent);
|
|
child->setObjectName("child");
|
|
child->setSize(QSizeF(100, 100));
|
|
child->acceptMouseEvents = true;
|
|
|
|
QPoint pos(80, 80);
|
|
|
|
QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, pos);
|
|
QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, pos);
|
|
// There is an explicit parent->grabMouse on release(!). This means grab changes from child
|
|
// to parent:
|
|
// This will emit two ungrab events:
|
|
// 1. One for the child (due to the explicit call to parent->grabMouse())
|
|
// 2. One for the parent (since the mouse button was finally released)
|
|
QCOMPARE(child->mouseUngrabEventCount, 1);
|
|
QCOMPARE(parent->mouseUngrabEventCount, 1);
|
|
}
|
|
|
|
void tst_qquickwindow::subclassWithPointerEventVirtualOverrides_data()
|
|
{
|
|
QTest::addColumn<const QPointingDevice *>("device");
|
|
|
|
QTest::newRow("mouse click") << QPointingDevice::primaryPointingDevice();
|
|
QTest::newRow("touch tap") << static_cast<const QPointingDevice*>(touchDevice); // TODO QTBUG-107864
|
|
QTest::newRow("stylus tap") << tabletStylusDevice;
|
|
}
|
|
|
|
void tst_qquickwindow::subclassWithPointerEventVirtualOverrides() // QTBUG-97859
|
|
{
|
|
QFETCH(const QPointingDevice *, device);
|
|
|
|
PointerRecordingWindow window;
|
|
window.resize(250, 250);
|
|
window.setPosition(100, 100);
|
|
window.setTitle(QTest::currentTestFunction());
|
|
window.show();
|
|
QVERIFY(QTest::qWaitForWindowActive(&window));
|
|
const QPoint pos(120, 120);
|
|
|
|
QQuickTest::pointerPress(device, &window, 0, pos);
|
|
QQuickTest::pointerRelease(device, &window, 0, pos);
|
|
|
|
switch (device->type()) {
|
|
case QPointingDevice::DeviceType::Mouse:
|
|
QTRY_COMPARE(window.m_mouseEvents.size(), 3); // separate move before press
|
|
QCOMPARE(window.m_events.size(), 3);
|
|
break;
|
|
case QPointingDevice::DeviceType::TouchScreen:
|
|
QTRY_COMPARE(window.m_touchEvents.size(), 2);
|
|
QCOMPARE(window.m_events.size(), 2);
|
|
break;
|
|
case QPointingDevice::DeviceType::Stylus:
|
|
QTRY_COMPARE(window.m_tabletEvents.size(), 2);
|
|
QVERIFY(window.m_events.size() >= window.m_tabletEvents.size()); // tablet + synth-mouse events
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if QT_CONFIG(shortcut)
|
|
void tst_qquickwindow::testShortCut()
|
|
{
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.loadUrl(testFileUrl("shortcut.qml"));
|
|
|
|
QObject *created = component.create();
|
|
QScopedPointer<QObject> cleanup(created);
|
|
QVERIFY(created);
|
|
|
|
QQuickWindow *window = qobject_cast<QQuickWindow *>(created);
|
|
QVERIFY(QTest::qWaitForWindowActive(window));
|
|
|
|
EventFilter eventFilter;
|
|
window->activeFocusItem()->installEventFilter(&eventFilter);
|
|
//Send non-spontaneous key press event
|
|
QKeyEvent keyEvent(QEvent::KeyPress, Qt::Key_B, Qt::NoModifier);
|
|
QCoreApplication::sendEvent(window, &keyEvent);
|
|
QVERIFY(eventFilter.events.contains(int(QEvent::ShortcutOverride)));
|
|
QVERIFY(window->property("received").value<bool>());
|
|
}
|
|
|
|
void tst_qquickwindow::shortcutOverride_data()
|
|
{
|
|
QTest::addColumn<Qt::Key>("key");
|
|
QTest::addColumn<bool>("overridden");
|
|
QTest::addColumn<bool>("receivedA");
|
|
QTest::addColumn<bool>("receivedB");
|
|
|
|
QTest::addRow("Space") << Qt::Key_Space << false << false << false;
|
|
QTest::addRow("A") << Qt::Key_A << true << false << false;
|
|
QTest::addRow("B") << Qt::Key_B << false << false << true;
|
|
}
|
|
|
|
void tst_qquickwindow::shortcutOverride()
|
|
{
|
|
QFETCH(Qt::Key, key);
|
|
QFETCH(bool, overridden);
|
|
QFETCH(bool, receivedA);
|
|
QFETCH(bool, receivedB);
|
|
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.loadUrl(testFileUrl("shortcutOverride.qml"));
|
|
|
|
QScopedPointer<QWindow> window(qobject_cast<QQuickWindow *>(component.create()));
|
|
QVERIFY(window);
|
|
QVERIFY(QTest::qWaitForWindowActive(window.get()));
|
|
|
|
QTest::keyPress(window.get(), key);
|
|
QCOMPARE(window->property("overridden").value<bool>(), overridden);
|
|
QCOMPARE(window->property("receivedA").value<bool>(), receivedA);
|
|
QCOMPARE(window->property("receivedB").value<bool>(), receivedB);
|
|
}
|
|
#endif
|
|
|
|
void tst_qquickwindow::rendererInterface()
|
|
{
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.loadUrl(testFileUrl("Headless.qml"));
|
|
QObject *created = component.create();
|
|
QScopedPointer<QObject> cleanup(created);
|
|
|
|
QQuickWindow *window = qobject_cast<QQuickWindow*>(created);
|
|
QVERIFY(window);
|
|
window->setTitle(QTest::currentTestFunction());
|
|
window->show();
|
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(window));
|
|
QVERIFY(window->isSceneGraphInitialized());
|
|
QVERIFY(window->rendererInterface());
|
|
|
|
QSGRendererInterface *rif = window->rendererInterface();
|
|
QVERIFY(rif->graphicsApi() != QSGRendererInterface::Unknown);
|
|
|
|
// Verify the essential integration points used by Quick3D.
|
|
if (QSGRendererInterface::isApiRhiBased(rif->graphicsApi())) {
|
|
QVERIFY(rif->getResource(window, QSGRendererInterface::RhiResource));
|
|
QVERIFY(rif->getResource(window, QSGRendererInterface::RhiSwapchainResource));
|
|
// the rendercontrol specific objects should not be present for an on-screen window
|
|
QVERIFY(!rif->getResource(window, QSGRendererInterface::RhiRedirectCommandBuffer));
|
|
QVERIFY(!rif->getResource(window, QSGRendererInterface::RhiRedirectRenderTarget));
|
|
}
|
|
|
|
// Now, depending on the graphics API, verify the native objects that are
|
|
// most common in applications that integrate native rendering code. Check
|
|
// only the globally available ones (that are available whenever the
|
|
// scenegraph is initialized, not just during recording a frame)
|
|
switch (rif->graphicsApi()) {
|
|
case QSGRendererInterface::OpenGLRhi:
|
|
QVERIFY(rif->getResource(window, QSGRendererInterface::OpenGLContextResource));
|
|
#if QT_CONFIG(opengl)
|
|
{
|
|
QOpenGLContext *ctx = static_cast<QOpenGLContext *>(rif->getResource(window, QSGRendererInterface::OpenGLContextResource));
|
|
QVERIFY(ctx->isValid());
|
|
}
|
|
#endif
|
|
break;
|
|
case QSGRendererInterface::Direct3D11Rhi:
|
|
QVERIFY(rif->getResource(window, QSGRendererInterface::DeviceResource));
|
|
QVERIFY(rif->getResource(window, QSGRendererInterface::DeviceContextResource));
|
|
break;
|
|
case QSGRendererInterface::VulkanRhi:
|
|
QVERIFY(rif->getResource(window, QSGRendererInterface::DeviceResource));
|
|
QVERIFY(rif->getResource(window, QSGRendererInterface::PhysicalDeviceResource));
|
|
QVERIFY(rif->getResource(window, QSGRendererInterface::VulkanInstanceResource));
|
|
#if QT_CONFIG(vulkan)
|
|
QCOMPARE(rif->getResource(window, QSGRendererInterface::VulkanInstanceResource), window->vulkanInstance());
|
|
#endif
|
|
QVERIFY(rif->getResource(window, QSGRendererInterface::CommandQueueResource));
|
|
break;
|
|
case QSGRendererInterface::MetalRhi:
|
|
QVERIFY(rif->getResource(window, QSGRendererInterface::DeviceResource));
|
|
QVERIFY(rif->getResource(window, QSGRendererInterface::CommandQueueResource));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Now the objects that are available only when preparing a frame.
|
|
if (QSGRendererInterface::isApiRhiBased(rif->graphicsApi())) {
|
|
bool ok[4] = { false, false, false, false };
|
|
auto f = [&ok, window](int idx) {
|
|
QSGRendererInterface *rif = window->rendererInterface();
|
|
if (rif) {
|
|
ok[idx] = true;
|
|
switch (rif->graphicsApi()) {
|
|
case QSGRendererInterface::VulkanRhi:
|
|
if (!rif->getResource(window, QSGRendererInterface::CommandListResource))
|
|
ok[idx] = false;
|
|
if (!rif->getResource(window, QSGRendererInterface::RenderPassResource))
|
|
ok[idx] = false;
|
|
break;
|
|
case QSGRendererInterface::MetalRhi:
|
|
if (!rif->getResource(window, QSGRendererInterface::CommandListResource))
|
|
ok[idx] = false;
|
|
if (idx == 1 || idx == 2) { // must be recording a render pass to query the command encoder
|
|
if (!rif->getResource(window, QSGRendererInterface::CommandEncoderResource))
|
|
ok[idx] = false;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
// Also tests if all 4 signals are emitted as expected.
|
|
QObject::connect(window, &QQuickWindow::beforeRendering, window, std::bind(f, 0), Qt::DirectConnection);
|
|
QObject::connect(window, &QQuickWindow::beforeRenderPassRecording, window, std::bind(f, 1), Qt::DirectConnection);
|
|
QObject::connect(window, &QQuickWindow::afterRenderPassRecording, window, std::bind(f, 2), Qt::DirectConnection);
|
|
QObject::connect(window, &QQuickWindow::afterRendering, window, std::bind(f, 3), Qt::DirectConnection);
|
|
window->grabWindow();
|
|
QVERIFY(ok[0]);
|
|
QVERIFY(ok[1]);
|
|
QVERIFY(ok[2]);
|
|
QVERIFY(ok[3]);
|
|
}
|
|
}
|
|
|
|
void tst_qquickwindow::rendererInterfaceWithRenderControl_data()
|
|
{
|
|
QTest::addColumn<QSGRendererInterface::GraphicsApi>("api");
|
|
|
|
#if QT_CONFIG(opengl)
|
|
QTest::newRow("OpenGL") << QSGRendererInterface::OpenGLRhi;
|
|
#endif
|
|
#if QT_CONFIG(vulkan)
|
|
QTest::newRow("Vulkan") << QSGRendererInterface::VulkanRhi;
|
|
#endif
|
|
#ifdef Q_OS_WIN
|
|
QTest::newRow("D3D11") << QSGRendererInterface::Direct3D11Rhi;
|
|
#endif
|
|
#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
|
|
QTest::newRow("Metal") << QSGRendererInterface::MetalRhi;
|
|
#endif
|
|
}
|
|
|
|
void tst_qquickwindow::rendererInterfaceWithRenderControl()
|
|
{
|
|
QFETCH(QSGRendererInterface::GraphicsApi, api);
|
|
|
|
// no automatic QVulkanInstance when used with rendercontrol, must create our own
|
|
#if QT_CONFIG(vulkan)
|
|
QVulkanInstance inst;
|
|
if (api == QSGRendererInterface::VulkanRhi) {
|
|
if (!inst.create())
|
|
QSKIP("Skipping Vulkan-based test due to failing to create Vulkan instance");
|
|
}
|
|
#endif
|
|
|
|
{
|
|
// Changing the graphics api is not possible once a QQuickWindow et al is
|
|
// created, however we do support changing it once all QQuickWindow,
|
|
// QQuickRenderControl, etc. instances are destroyed, before creating new
|
|
// ones. That's why it is possible to have this test run with multiple QRhi
|
|
// backends.
|
|
QQuickWindow::setGraphicsApi(api);
|
|
|
|
QScopedPointer<QQuickRenderControl> renderControl(new QQuickRenderControl);
|
|
QScopedPointer<QQuickWindow> quickWindow(new QQuickWindow(renderControl.data()));
|
|
#if QT_CONFIG(vulkan)
|
|
if (api == QSGRendererInterface::VulkanRhi)
|
|
quickWindow->setVulkanInstance(&inst);
|
|
#endif
|
|
|
|
QScopedPointer<QQmlEngine> qmlEngine(new QQmlEngine);
|
|
// Pick a scene that does not have a Window, as having a Window in there is
|
|
// incompatible with QQuickRenderControl-based redirection by definition.
|
|
QScopedPointer<QQmlComponent> qmlComponent(new QQmlComponent(qmlEngine.data(),
|
|
testFileUrl(QLatin1String("showHideAnimate.qml"))));
|
|
QVERIFY(!qmlComponent->isLoading());
|
|
if (qmlComponent->isError()) {
|
|
for (const QQmlError &error : qmlComponent->errors())
|
|
qWarning() << error.url() << error.line() << error;
|
|
}
|
|
QVERIFY(!qmlComponent->isError());
|
|
|
|
QObject *rootObject = qmlComponent->create();
|
|
if (qmlComponent->isError()) {
|
|
for (const QQmlError &error : qmlComponent->errors())
|
|
qWarning() << error.url() << error.line() << error;
|
|
}
|
|
QVERIFY(!qmlComponent->isError());
|
|
|
|
QQuickItem *rootItem = qobject_cast<QQuickItem *>(rootObject);
|
|
QVERIFY(rootItem);
|
|
rootItem->setSize(QSize(200, 200));
|
|
|
|
quickWindow->contentItem()->setSize(rootItem->size());
|
|
quickWindow->setGeometry(0, 0, rootItem->width(), rootItem->height());
|
|
|
|
rootItem->setParentItem(quickWindow->contentItem());
|
|
|
|
const bool initSuccess = renderControl->initialize();
|
|
|
|
// We cannot just test for initSuccess; it is highly likely that a
|
|
// number of configurations will simply fail in a CI environment
|
|
// (Vulkan, Metal, ...) So the only reasonable choice is to skip if
|
|
// initialize() failed. The exception for now is OpenGL - that should
|
|
// usually work (except software backend etc.).
|
|
if (!initSuccess) {
|
|
#if QT_CONFIG(opengl)
|
|
if (api != QSGRendererInterface::OpenGLRhi
|
|
|| !QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::OpenGL))
|
|
#endif
|
|
{
|
|
QSKIP("Could not initialize graphics, perhaps unsupported graphics API, skipping");
|
|
}
|
|
}
|
|
|
|
// Not strictly required for the CI (because platforms like offscreen
|
|
// skip already above), but play nice with running with
|
|
// QT_QUICK_BACKEND=software on a normal desktop platform.
|
|
if (QQuickWindow::sceneGraphBackend() == QLatin1String("software"))
|
|
QSKIP("Software backend was forced via env.var, skipping this test");
|
|
|
|
QVERIFY(initSuccess);
|
|
|
|
QQuickWindow *window = quickWindow.data();
|
|
QSGRendererInterface *rif = window->rendererInterface();
|
|
QVERIFY(rif);
|
|
QCOMPARE(rif->graphicsApi(), api);
|
|
|
|
QRhi *rhi = static_cast<QRhi *>(rif->getResource(window, QSGRendererInterface::RhiResource));
|
|
QVERIFY(rhi);
|
|
|
|
// not an on-screen window so no swapchain
|
|
QVERIFY(!rif->getResource(window, QSGRendererInterface::RhiSwapchainResource));
|
|
|
|
switch (rif->graphicsApi()) {
|
|
case QSGRendererInterface::OpenGLRhi:
|
|
QVERIFY(rif->getResource(window, QSGRendererInterface::OpenGLContextResource));
|
|
#if QT_CONFIG(opengl)
|
|
{
|
|
QOpenGLContext *ctx = static_cast<QOpenGLContext *>(rif->getResource(window, QSGRendererInterface::OpenGLContextResource));
|
|
QVERIFY(ctx->isValid());
|
|
}
|
|
#endif
|
|
break;
|
|
case QSGRendererInterface::Direct3D11Rhi:
|
|
QVERIFY(rif->getResource(window, QSGRendererInterface::DeviceResource));
|
|
QVERIFY(rif->getResource(window, QSGRendererInterface::DeviceContextResource));
|
|
break;
|
|
case QSGRendererInterface::VulkanRhi:
|
|
QVERIFY(rif->getResource(window, QSGRendererInterface::DeviceResource));
|
|
QVERIFY(rif->getResource(window, QSGRendererInterface::PhysicalDeviceResource));
|
|
QVERIFY(rif->getResource(window, QSGRendererInterface::VulkanInstanceResource));
|
|
#if QT_CONFIG(vulkan)
|
|
QCOMPARE(rif->getResource(window, QSGRendererInterface::VulkanInstanceResource), window->vulkanInstance());
|
|
QCOMPARE(rif->getResource(window, QSGRendererInterface::VulkanInstanceResource), &inst);
|
|
#endif
|
|
QVERIFY(rif->getResource(window, QSGRendererInterface::CommandQueueResource));
|
|
break;
|
|
case QSGRendererInterface::MetalRhi:
|
|
QVERIFY(rif->getResource(window, QSGRendererInterface::DeviceResource));
|
|
QVERIFY(rif->getResource(window, QSGRendererInterface::CommandQueueResource));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
const QSize size(1280, 720);
|
|
QScopedPointer<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8, size, 1,
|
|
QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
|
|
QVERIFY(tex->create());
|
|
QScopedPointer<QRhiRenderBuffer> ds(rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, size, 1));
|
|
QVERIFY(ds->create());
|
|
QRhiTextureRenderTargetDescription rtDesc(QRhiColorAttachment(tex.data()));
|
|
rtDesc.setDepthStencilBuffer(ds.data());
|
|
QScopedPointer<QRhiTextureRenderTarget> texRt(rhi->newTextureRenderTarget(rtDesc));
|
|
QScopedPointer<QRhiRenderPassDescriptor> rp(texRt->newCompatibleRenderPassDescriptor());
|
|
texRt->setRenderPassDescriptor(rp.data());
|
|
QVERIFY(texRt->create());
|
|
|
|
// redirect Qt Quick rendering into our texture
|
|
quickWindow->setRenderTarget(QQuickRenderTarget::fromRhiRenderTarget(texRt.data()));
|
|
|
|
bool ok[4] = { false, false, false, false };
|
|
auto f = [&ok, window](int idx) {
|
|
QSGRendererInterface *rif = window->rendererInterface();
|
|
if (rif) {
|
|
ok[idx] = true;
|
|
switch (rif->graphicsApi()) {
|
|
case QSGRendererInterface::VulkanRhi:
|
|
if (!rif->getResource(window, QSGRendererInterface::CommandListResource))
|
|
ok[idx] = false;
|
|
if (!rif->getResource(window, QSGRendererInterface::RenderPassResource))
|
|
ok[idx] = false;
|
|
break;
|
|
case QSGRendererInterface::MetalRhi:
|
|
if (!rif->getResource(window, QSGRendererInterface::CommandListResource))
|
|
ok[idx] = false;
|
|
if (idx == 1 || idx == 2) { // must be recording a render pass to query the command encoder
|
|
if (!rif->getResource(window, QSGRendererInterface::CommandEncoderResource))
|
|
ok[idx] = false;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
// we could just check this below like the RhiRedirect* ones, but this way we test the signals as well
|
|
QObject::connect(window, &QQuickWindow::beforeRendering, window, std::bind(f, 0), Qt::DirectConnection);
|
|
QObject::connect(window, &QQuickWindow::beforeRenderPassRecording, window, std::bind(f, 1), Qt::DirectConnection);
|
|
QObject::connect(window, &QQuickWindow::afterRenderPassRecording, window, std::bind(f, 2), Qt::DirectConnection);
|
|
QObject::connect(window, &QQuickWindow::afterRendering, window, std::bind(f, 3), Qt::DirectConnection);
|
|
|
|
renderControl->polishItems();
|
|
renderControl->beginFrame();
|
|
|
|
renderControl->sync(); // this is when the custom rendertarget request gets processed
|
|
// now check for the queries that are used by Quick3D f.ex.
|
|
QVERIFY(rif->getResource(window, QSGRendererInterface::RhiRedirectCommandBuffer));
|
|
QVERIFY(rif->getResource(window, QSGRendererInterface::RhiRedirectRenderTarget));
|
|
QCOMPARE(rif->getResource(window, QSGRendererInterface::RhiRedirectRenderTarget), texRt.data());
|
|
|
|
renderControl->render();
|
|
renderControl->endFrame();
|
|
|
|
QVERIFY(ok[0]);
|
|
QVERIFY(ok[1]);
|
|
QVERIFY(ok[2]);
|
|
QVERIFY(ok[3]);
|
|
}
|
|
|
|
// Now that everything is torn down, go back to the default unspecified-api
|
|
// state, to prevent conflicting with tests that come afterwards.
|
|
QQuickWindow::setGraphicsApi(QSGRendererInterface::Unknown);
|
|
}
|
|
|
|
void tst_qquickwindow::graphicsConfiguration()
|
|
{
|
|
QQuickGraphicsConfiguration config;
|
|
qDebug() << config;
|
|
QVERIFY(config.isDepthBufferEnabledFor2D());
|
|
QVERIFY(!config.isDebugLayerEnabled());
|
|
QVERIFY(!config.isDebugMarkersEnabled());
|
|
QVERIFY(!config.prefersSoftwareDevice());
|
|
QVERIFY(config.isAutomaticPipelineCacheEnabled());
|
|
QVERIFY(config.pipelineCacheSaveFile().isEmpty());
|
|
QVERIFY(config.pipelineCacheLoadFile().isEmpty());
|
|
|
|
QQuickGraphicsConfiguration config2 = config;
|
|
config.setDebugLayer(true);
|
|
config.setDepthBufferFor2D(false);
|
|
QVERIFY(config.isDebugLayerEnabled());
|
|
QVERIFY(!config2.isDebugLayerEnabled());
|
|
|
|
config2 = config;
|
|
QVERIFY(config2.isDebugLayerEnabled());
|
|
QVERIFY(!config2.isDepthBufferEnabledFor2D());
|
|
|
|
config.setAutomaticPipelineCache(false);
|
|
config.setPipelineCacheSaveFile(QLatin1String("save"));
|
|
config.setPipelineCacheLoadFile(QLatin1String("load"));
|
|
config2 = config;
|
|
QVERIFY(!config2.isAutomaticPipelineCacheEnabled());
|
|
QCOMPARE(config2.pipelineCacheSaveFile(), QLatin1String("save"));
|
|
QCOMPARE(config2.pipelineCacheLoadFile(), QLatin1String("load"));
|
|
|
|
#if QT_CONFIG(vulkan)
|
|
QCOMPARE(QQuickGraphicsConfiguration::preferredInstanceExtensions(), QRhiVulkanInitParams::preferredInstanceExtensions());
|
|
#endif
|
|
}
|
|
|
|
void tst_qquickwindow::visibleVsVisibility_data()
|
|
{
|
|
QTest::addColumn<QUrl>("qmlfile");
|
|
QTest::addColumn<bool>("expectVisible");
|
|
QTest::addColumn<bool>("expectConflictingPropertyWarning");
|
|
|
|
QTest::newRow("default invisible") << testFileUrl("window.qml") << false << false;
|
|
QTest::newRow("just visibility") << testFileUrl("maximized.qml") << true << false;
|
|
// In these conflicting cases, the 'visibility' property "wins" (see QQuickWindowQmlImpl::setWindowVisibility())
|
|
QTest::newRow("conflicting invisible") << testFileUrl("conflictingVisibleFalse.qml") << true << true;
|
|
QTest::newRow("conflicting visible") << testFileUrl("conflictingVisibleTrue.qml") << false << true;
|
|
}
|
|
|
|
void tst_qquickwindow::visibleVsVisibility()
|
|
{
|
|
QFETCH(QUrl, qmlfile);
|
|
QFETCH(bool, expectVisible);
|
|
QFETCH(bool, expectConflictingPropertyWarning);
|
|
|
|
const QString warningMsg = qmlfile.toString() + ":3:1: QML Window: Conflicting properties 'visible' and 'visibility'";
|
|
|
|
QTest::failOnWarning(QRegularExpression(".*"));
|
|
if (expectConflictingPropertyWarning)
|
|
QTest::ignoreMessage(QtWarningMsg, warningMsg.toUtf8().data());
|
|
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.loadUrl(qmlfile);
|
|
QObject *created = component.create();
|
|
QScopedPointer<QObject> cleanup(created);
|
|
QVERIFY(created);
|
|
|
|
QQuickWindow *window = qobject_cast<QQuickWindow*>(created);
|
|
QVERIFY(window);
|
|
QCOMPARE(window->isVisible(), expectVisible);
|
|
}
|
|
|
|
void tst_qquickwindow::eventTypes()
|
|
{
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.loadUrl(testFileUrl("eventTypes.qml"));
|
|
QObject *created = component.create();
|
|
QScopedPointer<QObject> cleanup(created);
|
|
QVERIFY(created);
|
|
}
|
|
|
|
QTEST_MAIN(tst_qquickwindow)
|
|
|
|
#include "tst_qquickwindow.moc"
|