1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "viewtestutils_p.h"
5
6#include <QtCore/QRandomGenerator>
7#include <QtCore/QTimer>
8#include <QtQuick/QQuickView>
9#include <QtQuick/QQuickView>
10#include <QtGui/QScreen>
11#include <QtGui/qpa/qwindowsysteminterface.h>
12#include <QtGui/private/qhighdpiscaling_p.h>
13
14#include <QtTest/QTest>
15
16#include <QtQuick/private/qquickdeliveryagent_p_p.h>
17#if QT_CONFIG(quick_itemview)
18#include <QtQuick/private/qquickitemview_p_p.h>
19#endif
20#include <QtQuick/private/qquickwindow_p.h>
21
22#include <QtQuickTestUtils/private/visualtestutils_p.h>
23
24QT_BEGIN_NAMESPACE
25
26QQuickView *QQuickViewTestUtils::createView()
27{
28 QQuickView *window = new QQuickView(0);
29 const QSize size(240, 320);
30 window->resize(newSize: size);
31 QQuickViewTestUtils::centerOnScreen(window, size);
32 return window;
33}
34
35void QQuickViewTestUtils::centerOnScreen(QQuickWindow *window, const QSize &size)
36{
37 const QRect screenGeometry = window->screen()->availableGeometry();
38 const QPoint offset = QPoint(size.width() / 2, size.height() / 2);
39 window->setFramePosition(screenGeometry.center() - offset);
40}
41
42void QQuickViewTestUtils::centerOnScreen(QQuickWindow *window)
43{
44 QQuickViewTestUtils::centerOnScreen(window, size: window->size());
45}
46
47void QQuickViewTestUtils::moveMouseAway(QQuickView *window)
48{
49#if QT_CONFIG(cursor) // Get the cursor out of the way.
50 QCursor::setPos(window->geometry().topRight() + QPoint(100, 100));
51#else
52 Q_UNUSED(window);
53#endif
54}
55
56QList<int> QQuickViewTestUtils::adjustIndexesForAddDisplaced(const QList<int> &indexes, int index, int count)
57{
58 QList<int> result;
59 for (int i=0; i<indexes.size(); i++) {
60 int num = indexes[i];
61 if (num >= index) {
62 num += count;
63 }
64 result << num;
65 }
66 return result;
67}
68
69QList<int> QQuickViewTestUtils::adjustIndexesForMove(const QList<int> &indexes, int from, int to, int count)
70{
71 QList<int> result;
72 for (int i=0; i<indexes.size(); i++) {
73 int num = indexes[i];
74 if (from < to) {
75 if (num >= from && num < from + count)
76 num += (to - from); // target
77 else if (num >= from && num < to + count)
78 num -= count; // displaced
79 } else if (from > to) {
80 if (num >= from && num < from + count)
81 num -= (from - to); // target
82 else if (num >= to && num < from + count)
83 num += count; // displaced
84 }
85 result << num;
86 }
87 return result;
88}
89
90QList<int> QQuickViewTestUtils::adjustIndexesForRemoveDisplaced(const QList<int> &indexes, int index, int count)
91{
92 QList<int> result;
93 for (int i=0; i<indexes.size(); i++) {
94 int num = indexes[i];
95 if (num >= index)
96 num -= count;
97 result << num;
98 }
99 return result;
100}
101
102QQuickViewTestUtils::QaimModel::QaimModel(QObject *parent)
103 : QAbstractListModel(parent)
104{
105}
106
107int QQuickViewTestUtils::QaimModel::rowCount(const QModelIndex &parent) const
108{
109 Q_UNUSED(parent);
110 return list.size();
111}
112
113int QQuickViewTestUtils::QaimModel::columnCount(const QModelIndex &parent) const
114{
115 Q_UNUSED(parent);
116 return columns;
117}
118
119QHash<int,QByteArray> QQuickViewTestUtils::QaimModel::roleNames() const
120{
121 QHash<int,QByteArray> roles = QAbstractListModel::roleNames();
122 roles.insert(key: Name, value: "name");
123 roles.insert(key: Number, value: "number");
124 return roles;
125}
126
127QVariant QQuickViewTestUtils::QaimModel::data(const QModelIndex &index, int role) const
128{
129 QVariant rv;
130 if (role == Name)
131 rv = list.at(i: index.row()).first;
132 else if (role == Number)
133 rv = list.at(i: index.row()).second;
134
135 return rv;
136}
137
138int QQuickViewTestUtils::QaimModel::count() const
139{
140 return rowCount() * columnCount();
141}
142
143QString QQuickViewTestUtils::QaimModel::name(int index) const
144{
145 return list.at(i: index).first;
146}
147
148QString QQuickViewTestUtils::QaimModel::number(int index) const
149{
150 return list.at(i: index).second;
151}
152
153void QQuickViewTestUtils::QaimModel::addItem(const QString &name, const QString &number)
154{
155 emit beginInsertRows(parent: QModelIndex(), first: list.size(), last: list.size());
156 list.append(t: QPair<QString,QString>(name, number));
157 emit endInsertRows();
158}
159
160void QQuickViewTestUtils::QaimModel::addItems(const QList<QPair<QString, QString> > &items)
161{
162 emit beginInsertRows(parent: QModelIndex(), first: list.size(), last: list.size()+items.size()-1);
163 for (int i=0; i<items.size(); i++)
164 list.append(t: QPair<QString,QString>(items[i].first, items[i].second));
165 emit endInsertRows();
166}
167
168void QQuickViewTestUtils::QaimModel::insertItem(int index, const QString &name, const QString &number)
169{
170 emit beginInsertRows(parent: QModelIndex(), first: index, last: index);
171 list.insert(i: index, t: QPair<QString,QString>(name, number));
172 emit endInsertRows();
173}
174
175void QQuickViewTestUtils::QaimModel::insertItems(int index, const QList<QPair<QString, QString> > &items)
176{
177 emit beginInsertRows(parent: QModelIndex(), first: index, last: index+items.size()-1);
178 for (int i=0; i<items.size(); i++)
179 list.insert(i: index + i, t: QPair<QString,QString>(items[i].first, items[i].second));
180 emit endInsertRows();
181}
182
183void QQuickViewTestUtils::QaimModel::removeItem(int index)
184{
185 emit beginRemoveRows(parent: QModelIndex(), first: index, last: index);
186 list.removeAt(i: index);
187 emit endRemoveRows();
188}
189
190void QQuickViewTestUtils::QaimModel::removeItems(int index, int count)
191{
192 emit beginRemoveRows(parent: QModelIndex(), first: index, last: index+count-1);
193 while (count--)
194 list.removeAt(i: index);
195 emit endRemoveRows();
196}
197
198void QQuickViewTestUtils::QaimModel::moveItem(int from, int to)
199{
200 emit beginMoveRows(sourceParent: QModelIndex(), sourceFirst: from, sourceLast: from, destinationParent: QModelIndex(), destinationRow: to);
201 list.move(from, to);
202 emit endMoveRows();
203}
204
205void QQuickViewTestUtils::QaimModel::moveItems(int from, int to, int count)
206{
207 emit beginMoveRows(sourceParent: QModelIndex(), sourceFirst: from, sourceLast: from+count-1, destinationParent: QModelIndex(), destinationRow: to > from ? to+count : to);
208 qquickmodelviewstestutil_move(from, to, n: count, items: &list);
209 emit endMoveRows();
210}
211
212void QQuickViewTestUtils::QaimModel::modifyItem(int idx, const QString &name, const QString &number)
213{
214 list[idx] = QPair<QString,QString>(name, number);
215 emit dataChanged(topLeft: index(row: idx,column: 0), bottomRight: index(row: idx,column: 0));
216}
217
218void QQuickViewTestUtils::QaimModel::clear()
219{
220 int count = list.size();
221 if (count > 0) {
222 beginRemoveRows(parent: QModelIndex(), first: 0, last: count-1);
223 list.clear();
224 endRemoveRows();
225 }
226}
227
228void QQuickViewTestUtils::QaimModel::reset()
229{
230 emit beginResetModel();
231 emit endResetModel();
232}
233
234void QQuickViewTestUtils::QaimModel::resetItems(const QList<QPair<QString, QString> > &items)
235{
236 beginResetModel();
237 list = items;
238 endResetModel();
239}
240
241class ScopedPrintable
242{
243 Q_DISABLE_COPY_MOVE(ScopedPrintable)
244
245public:
246 ScopedPrintable(const QString &string) : data(QTest::toString(str: string)) {}
247 ~ScopedPrintable() { delete[] data; }
248
249 operator const char*() const { return data; }
250
251private:
252 const char *data;
253};
254
255void QQuickViewTestUtils::QaimModel::matchAgainst(const QList<QPair<QString, QString> > &other, const QString &error1, const QString &error2) {
256 for (int i=0; i<other.size(); i++) {
257 QVERIFY2(list.contains(other[i]),
258 ScopedPrintable(other[i].first + QLatin1Char(' ') + other[i].second + QLatin1Char(' ') + error1));
259 }
260 for (int i=0; i<list.size(); i++) {
261 QVERIFY2(other.contains(list[i]),
262 ScopedPrintable(list[i].first + QLatin1Char(' ') + list[i].second + QLatin1Char(' ') + error2));
263 }
264}
265
266
267
268QQuickViewTestUtils::ListRange::ListRange()
269 : valid(false)
270{
271}
272
273QQuickViewTestUtils::ListRange::ListRange(const ListRange &other)
274 : valid(other.valid)
275{
276 indexes = other.indexes;
277}
278
279QQuickViewTestUtils::ListRange::ListRange(int start, int end)
280 : valid(true)
281{
282 for (int i=start; i<=end; i++)
283 indexes << i;
284}
285
286QQuickViewTestUtils::ListRange::~ListRange()
287{
288}
289
290QQuickViewTestUtils::ListRange QQuickViewTestUtils::ListRange::operator+(const ListRange &other) const
291{
292 if (other == *this)
293 return *this;
294 ListRange a(*this);
295 a.indexes.append(l: other.indexes);
296 return a;
297}
298
299bool QQuickViewTestUtils::ListRange::operator==(const ListRange &other) const
300{
301 return QSet<int>(indexes.cbegin(), indexes.cend())
302 == QSet<int>(other.indexes.cbegin(), other.indexes.cend());
303}
304
305bool QQuickViewTestUtils::ListRange::operator!=(const ListRange &other) const
306{
307 return !(*this == other);
308}
309
310bool QQuickViewTestUtils::ListRange::isValid() const
311{
312 return valid;
313}
314
315int QQuickViewTestUtils::ListRange::count() const
316{
317 return indexes.size();
318}
319
320QList<QPair<QString,QString> > QQuickViewTestUtils::ListRange::getModelDataValues(const QaimModel &model)
321{
322 QList<QPair<QString,QString> > data;
323 if (!valid)
324 return data;
325 for (int i=0; i<indexes.size(); i++)
326 data.append(t: qMakePair(value1: model.name(index: indexes[i]), value2: model.number(index: indexes[i])));
327 return data;
328}
329
330QQuickViewTestUtils::StressTestModel::StressTestModel()
331 : QAbstractListModel()
332 , m_rowCount(20)
333{
334 QTimer *t = new QTimer(this);
335 t->setInterval(500);
336 t->start();
337
338 connect(sender: t, signal: &QTimer::timeout, context: this, slot: &StressTestModel::updateModel);
339}
340
341int QQuickViewTestUtils::StressTestModel::rowCount(const QModelIndex &) const
342{
343 return m_rowCount;
344}
345
346QVariant QQuickViewTestUtils::StressTestModel::data(const QModelIndex &, int) const
347{
348 return QVariant();
349}
350
351void QQuickViewTestUtils::StressTestModel::updateModel()
352{
353 if (m_rowCount > 10) {
354 for (int i = 0; i < 10; ++i) {
355 int rnum = QRandomGenerator::global()->bounded(highest: m_rowCount);
356 beginRemoveRows(parent: QModelIndex(), first: rnum, last: rnum);
357 m_rowCount--;
358 endRemoveRows();
359 }
360 }
361 if (m_rowCount < 20) {
362 for (int i = 0; i < 10; ++i) {
363 int rnum = QRandomGenerator::global()->bounded(highest: m_rowCount);
364 beginInsertRows(parent: QModelIndex(), first: rnum, last: rnum);
365 m_rowCount++;
366 endInsertRows();
367 }
368 }
369}
370
371#if QT_CONFIG(quick_itemview) && defined(QT_BUILD_INTERNAL)
372bool QQuickViewTestUtils::testVisibleItems(const QQuickItemViewPrivate *priv, bool *nonUnique, FxViewItem **failItem, int *expectedIdx)
373{
374 QHash<QQuickItem*, int> uniqueItems;
375
376 int skip = 0;
377 for (int i = 0; i < priv->visibleItems.size(); ++i) {
378 FxViewItem *item = priv->visibleItems.at(i);
379 if (!item) {
380 *failItem = nullptr;
381 return false;
382 }
383#if 0
384 qDebug() << "\t" << item->index
385 << item->item
386 << item->position()
387 << (!item->item || QQuickItemPrivate::get(item->item)->culled ? "hidden" : "visible");
388#endif
389 if (item->index == -1) {
390 ++skip;
391 } else if (item->index != priv->visibleIndex + i - skip) {
392 *nonUnique = false;
393 *failItem = item;
394 *expectedIdx = priv->visibleIndex + i - skip;
395 return false;
396 } else if (uniqueItems.contains(key: item->item)) {
397 *nonUnique = true;
398 *failItem = item;
399 *expectedIdx = uniqueItems.find(key: item->item).value();
400 return false;
401 }
402
403 uniqueItems.insert(key: item->item, value: item->index);
404 }
405
406 return true;
407}
408#endif
409
410namespace QQuickTouchUtils {
411
412 /* QQuickWindow does event compression and only delivers events just
413 * before it is about to render the next frame. Since some tests
414 * rely on events being delivered immediately AND that no other
415 * event processing has occurred in the meanwhile, we flush the
416 * event manually and immediately.
417 */
418 void flush(QQuickWindow *window) {
419 if (!window)
420 return;
421 QQuickDeliveryAgentPrivate *da = QQuickWindowPrivate::get(c: window)->deliveryAgentPrivate();
422 if (!da || !da->delayedTouch)
423 return;
424 da->deliverDelayedTouchEvent();
425 }
426
427}
428
429namespace QTest {
430 int Q_TESTLIB_EXPORT defaultMouseDelay();
431}
432
433namespace QQuickTest {
434
435 /*! \internal
436 Initialize \a view, set \a url, center in available geometry, move mouse away if desired.
437 If \a errorMessage is given, QQuickView::errors() will be concatenated into it;
438 otherwise, the QWARN messages are generally enough to debug the test.
439
440 Returns \c false if the view fails to load the QML. That should be fatal in most tests,
441 so normally the return value should be checked with QVERIFY.
442 */
443 bool initView(QQuickView &view, const QUrl &url, bool moveMouseOut, QByteArray *errorMessage)
444 {
445 const bool platformIsWayland = !QGuiApplication::platformName().compare(other: QLatin1String("wayland"), cs: Qt::CaseInsensitive);
446 view.setSource(url);
447 while (view.status() == QQuickView::Loading)
448 QTest::qWait(ms: 10);
449 if (view.status() != QQuickView::Ready) {
450 if (errorMessage) {
451 for (const QQmlError &e : view.errors())
452 errorMessage->append(a: e.toString().toLocal8Bit() + '\n');
453 }
454 return false;
455 }
456 if (view.width() == 0)
457 view.setWidth(100);
458 if (view.height() == 0)
459 view.setHeight(100);
460 if (!platformIsWayland) {
461 const QSize size = view.size();
462 const QRect screenGeometry = view.screen()->availableGeometry();
463 const QPoint offset = QPoint(size.width() / 2, size.height() / 2);
464 view.setFramePosition(screenGeometry.center() - offset);
465#if QT_CONFIG(cursor) // Get the cursor out of the way. But it's not possible on Wayland.
466 if (moveMouseOut)
467 QCursor::setPos(view.geometry().topRight() + QPoint(100, 100));
468#else
469 Q_UNUSED(moveMouseOut);
470#endif
471 }
472 return true;
473 }
474
475 /*! \internal
476 Initialize \a view, set \a url, center in available geometry, move mouse away,
477 show the \a view, wait for it to be exposed, and verify that its rootObject is not null.
478
479 Returns \c false if anything fails, which should be fatal in most tests.
480 The usual way to call this function is
481 \code
482 QQuickView window;
483 QVERIFY(QQuickTest::showView(window, testFileUrl("myitems.qml")));
484 \endcode
485 */
486 bool showView(QQuickView &view, const QUrl &url)
487 {
488 if (!initView(view, url))
489 return false;
490 const QPoint framePos(view.framePosition());
491 view.show();
492 if (!QTest::qWaitForWindowExposed(window: &view))
493 return false;
494 if (!view.rootObject())
495 return false;
496 if (view.flags().testFlag(flag: Qt::FramelessWindowHint))
497 return true;
498 const bool positionOk = QTest::qWaitFor(predicate: [&]{ return framePos != view.position(); });
499 if (!positionOk) {
500 qCritical() << "Position failed to update";
501 return false;
502 }
503 return true;
504 }
505
506 // TODO maybe move the generic pointerPress/Move/Release functions to QTestLib later on
507
508 static Qt::MouseButton pressedTabletButton = Qt::NoButton;
509 static Qt::KeyboardModifiers pressedTabletModifiers = Qt::NoModifier;
510
511 void pointerPress(const QPointingDevice *dev, QQuickWindow *window, int pointId, const QPoint &p,
512 Qt::MouseButton button, Qt::KeyboardModifiers modifiers, int delay)
513 {
514 const auto defaultDelay = QTest::defaultMouseDelay();
515 switch (dev->type()) {
516 case QPointingDevice::DeviceType::Mouse:
517 case QPointingDevice::DeviceType::TouchPad:
518 QTest::mousePress(window, button, stateKey: modifiers, pos: p, delay: delay >= 0 ? delay : defaultDelay ? defaultDelay : 1);
519 break;
520 case QPointingDevice::DeviceType::TouchScreen:
521 // TODO apply delay when QTBUG-95421 is fixed
522 QTest::touchEvent(window, device: const_cast<QPointingDevice *>(dev)).press(touchId: pointId, pt: p, window);
523 QQuickTouchUtils::flush(window);
524 break;
525 case QPointingDevice::DeviceType::Puck:
526 case QPointingDevice::DeviceType::Stylus:
527 case QPointingDevice::DeviceType::Airbrush:{
528 const QPointF nativeLocal = QHighDpi::toNativeLocalPosition(value: p, context: window);
529 const QPointF nativeGlobal = QHighDpi::toNativeGlobalPosition(value: window->mapToGlobal(pos: p), context: window);
530 QTest::lastMouseTimestamp += delay >= 0 ? delay : defaultDelay ? defaultDelay : 1;
531 pressedTabletButton = button;
532 pressedTabletModifiers = modifiers;
533 QWindowSystemInterface::handleTabletEvent(window, timestamp: QTest::lastMouseTimestamp, device: dev, local: nativeLocal, global: nativeGlobal,
534 buttons: button, pressure: 0.8, xTilt: 0, yTilt: 0, tangentialPressure: 0, rotation: 0, z: 0, modifiers);
535 break;
536 }
537 default:
538 qWarning() << "can't send a press event from" << dev;
539 break;
540 }
541 }
542
543 void pointerMove(const QPointingDevice *dev, QQuickWindow *window, int pointId, const QPoint &p, int delay)
544 {
545 const auto defaultDelay = QTest::defaultMouseDelay();
546 switch (dev->type()) {
547 case QPointingDevice::DeviceType::Mouse:
548 case QPointingDevice::DeviceType::TouchPad:
549 QTest::mouseMove(window, pos: p, delay: delay >= 0 ? delay : defaultDelay ? defaultDelay : 1);
550 break;
551 case QPointingDevice::DeviceType::TouchScreen:
552 // TODO apply delay when QTBUG-95421 is fixed
553 QTest::touchEvent(window, device: const_cast<QPointingDevice *>(dev)).move(touchId: pointId, pt: p, window);
554 QQuickTouchUtils::flush(window);
555 break;
556 case QPointingDevice::DeviceType::Puck:
557 case QPointingDevice::DeviceType::Stylus:
558 case QPointingDevice::DeviceType::Airbrush: {
559 const QPointF nativeLocal = QHighDpi::toNativeLocalPosition(value: p, context: window);
560 const QPointF nativeGlobal = QHighDpi::toNativeGlobalPosition(value: window->mapToGlobal(pos: p), context: window);
561 const auto delay = QTest::defaultMouseDelay();
562 // often QTest::defaultMouseDelay() == 0; but avoid infinite velocity
563 QTest::lastMouseTimestamp += delay >= 0 ? delay : defaultDelay ? defaultDelay : 1;
564 QWindowSystemInterface::handleTabletEvent(window, timestamp: QTest::lastMouseTimestamp, device: dev, local: nativeLocal, global: nativeGlobal,
565 buttons: pressedTabletButton, pressure: pressedTabletButton == Qt::NoButton ? 0 : 0.75,
566 xTilt: 0, yTilt: 0, tangentialPressure: 0, rotation: 0, z: 0, modifiers: pressedTabletModifiers);
567 break;
568 }
569 default:
570 qWarning() << "can't send a move event from" << dev;
571 break;
572 }
573 }
574
575 void pointerRelease(const QPointingDevice *dev, QQuickWindow *window, int pointId, const QPoint &p,
576 Qt::MouseButton button, Qt::KeyboardModifiers modifiers, int delay)
577 {
578 const auto defaultDelay = QTest::defaultMouseDelay();
579 switch (dev->type()) {
580 case QPointingDevice::DeviceType::Mouse:
581 case QPointingDevice::DeviceType::TouchPad:
582 QTest::mouseRelease(window, button, stateKey: modifiers, pos: p, delay: delay >= 0 ? delay : defaultDelay ? defaultDelay : 1);
583 break;
584 case QPointingDevice::DeviceType::TouchScreen:
585 // TODO apply delay when QTBUG-95421 is fixed
586 QTest::touchEvent(window, device: const_cast<QPointingDevice *>(dev)).release(touchId: pointId, pt: p, window);
587 QQuickTouchUtils::flush(window);
588 break;
589 case QPointingDevice::DeviceType::Puck:
590 case QPointingDevice::DeviceType::Stylus:
591 case QPointingDevice::DeviceType::Airbrush: {
592 const QPointF nativeLocal = QHighDpi::toNativeLocalPosition(value: p, context: window);
593 const QPointF nativeGlobal = QHighDpi::toNativeGlobalPosition(value: window->mapToGlobal(pos: p), context: window);
594 QTest::lastMouseTimestamp += delay >= 0 ? delay : defaultDelay ? defaultDelay : 1;
595 QWindowSystemInterface::handleTabletEvent(window, timestamp: QTest::lastMouseTimestamp, device: dev, local: nativeLocal, global: nativeGlobal,
596 buttons: Qt::NoButton, pressure: 0, xTilt: 0, yTilt: 0, tangentialPressure: 0, rotation: 0, z: 0, modifiers);
597 break;
598 }
599 default:
600 qWarning() << "can't send a press event from" << dev;
601 break;
602 }
603 }
604
605 void pointerMoveAndPress(const QPointingDevice *dev, QQuickWindow *window,
606 int pointId, const QPoint &p, Qt::MouseButton button,
607 Qt::KeyboardModifiers modifiers, int delay)
608 {
609 pointerMove(dev, window, pointId, p, delay);
610 pointerPress(dev, window, pointId, p, button, modifiers);
611 }
612
613 void pointerMoveAndRelease(const QPointingDevice *dev, QQuickWindow *window,
614 int pointId, const QPoint &p, Qt::MouseButton button,
615 Qt::KeyboardModifiers modifiers, int delay)
616 {
617 pointerMove(dev, window, pointId, p, delay);
618 pointerRelease(dev, window, pointId, p, button, modifiers);
619 }
620
621 void pointerFlick(const QPointingDevice *dev, QQuickWindow *window,
622 int pointId, const QPoint &from, const QPoint &to, int duration,
623 Qt::MouseButton button, Qt::KeyboardModifiers modifiers, int delay)
624 {
625 const int pointCount = 5;
626 const QPoint diff = to - from;
627
628 // send press, five equally spaced moves, and release.
629 pointerMoveAndPress(dev, window, pointId, p: from, button, modifiers, delay);
630
631 for (int i = 0; i < pointCount; ++i)
632 pointerMove(dev, window, pointId, p: from + (i + 1) * diff / pointCount, delay: duration / pointCount);
633
634 pointerMoveAndRelease(dev, window, pointId, p: to, button, modifiers);
635 }
636}
637
638QT_END_NAMESPACE
639
640#include "moc_viewtestutils_p.cpp"
641

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

source code of qtdeclarative/src/quicktestutils/quick/viewtestutils.cpp