Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qquickpathview.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qquickpathview_p.h"
6#include "qquickflickablebehavior_p.h" //Contains flicking behavior defines
7#include "qquicktext_p.h"
8
9#include <QtQuick/private/qquickstate_p.h>
10#include <private/qqmlglobal_p.h>
11#include <private/qqmlopenmetaobject_p.h>
12#include <private/qqmlchangeset_p.h>
13#include <qpa/qplatformtheme.h>
14
15#include <QtQml/qqmlinfo.h>
16
17#include <QtGui/private/qeventpoint_p.h>
18#include <QtGui/qevent.h>
19#include <QtGui/qguiapplication.h>
20#include <QtGui/private/qguiapplication_p.h>
21#include <QtGui/qstylehints.h>
22#include <QtCore/qmath.h>
23
24#include <cmath>
25
27
28Q_DECLARE_LOGGING_CATEGORY(lcItemViewDelegateLifecycle)
29#if !QT_CONFIG(quick_itemview)
30Q_LOGGING_CATEGORY(lcItemViewDelegateLifecycle, "qt.quick.itemview.lifecycle")
31#endif
32Q_LOGGING_CATEGORY(lcPathView, "qt.quick.pathview")
33
35
37: QObject(parent), m_percent(-1), m_view(nullptr), m_onPath(false), m_isCurrent(false)
38{
40 m_metaobject = new QQmlOpenMetaObject(this, qPathViewAttachedType);
41 m_metaobject->setCached(true);
42 } else {
43 m_metaobject = new QQmlOpenMetaObject(this);
44 }
45}
46
50
52{
53 return m_metaobject->value(name);
54}
56{
57 m_metaobject->setValue(name, val);
58}
59
61 : path(nullptr), currentIndex(0), currentItemOffset(0), startPc(0)
62 , offset(0), offsetAdj(0), mappedRange(1), mappedCache(0)
63 , stealMouse(false), ownModel(false), interactive(true), haveHighlightRange(true)
64 , autoHighlight(true), highlightUp(false), layoutScheduled(false)
65 , moving(false), flicking(false), dragging(false), inRequest(false), delegateValidated(false)
66 , inRefill(false)
67 , dragMargin(0), deceleration(100)
68 , maximumFlickVelocity(QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::FlickMaximumVelocity).toReal())
69 , moveOffset(this, &QQuickPathViewPrivate::setAdjustedOffset), flickDuration(0)
70 , pathItems(-1), requestedIndex(-1), cacheSize(0), requestedCacheSize(0), requestedZ(0)
71 , moveReason(Other), movementDirection(QQuickPathView::Shortest), moveDirection(QQuickPathView::Shortest)
72 , attType(nullptr), highlightComponent(nullptr), highlightItem(nullptr)
73 , moveHighlight(this, &QQuickPathViewPrivate::setHighlightPosition)
74 , highlightPosition(0)
75 , highlightRangeStart(0), highlightRangeEnd(0)
76 , highlightRangeMode(QQuickPathView::StrictlyEnforceRange)
77 , highlightMoveDuration(300), modelCount(0), snapMode(QQuickPathView::NoSnap)
78{
80}
81
83{
84 Q_Q(QQuickPathView);
85 offset = 0;
86 q->setAcceptedMouseButtons(Qt::LeftButton);
88 q->setFiltersChildMouseEvents(true);
90 q, QQuickPathView, SLOT(ticked()));
93 q, QQuickPathView, SLOT(movementEnding()));
94}
95
96QQuickItem *QQuickPathViewPrivate::getItem(int modelIndex, qreal z, bool async)
97{
98 Q_Q(QQuickPathView);
99 requestedIndex = modelIndex;
100 requestedZ = z;
101 inRequest = true;
104 if (!item) {
105 if (object) {
106 model->release(object);
107 if (!delegateValidated) {
108 delegateValidated = true;
109 QObject* delegate = q->delegate();
110 qmlWarning(delegate ? delegate : q) << QQuickPathView::tr("Delegate must be of Item type");
111 }
112 }
113 } else {
115 requestedIndex = -1;
117 itemPrivate->addItemChangeListener(
119 }
120 inRequest = false;
121 return item;
122}
123
124void QQuickPathView::createdItem(int index, QObject *object)
125{
126 Q_D(QQuickPathView);
128 if (d->requestedIndex != index) {
129 qPathViewAttachedType = d->attachedType();
130 QQuickPathViewAttached *att = static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item));
131 qPathViewAttachedType = nullptr;
132 if (att) {
133 att->m_view = this;
134 att->setOnPath(false);
135 }
136 item->setParentItem(this);
137 d->updateItem(item, 1);
138 } else {
139 d->requestedIndex = -1;
140 if (!d->inRequest)
141 refill();
142 }
143}
144
145void QQuickPathView::initItem(int index, QObject *object)
146{
147 Q_D(QQuickPathView);
149 if (item && d->requestedIndex == index) {
150 QQuickItemPrivate::get(item)->setCulled(true);
151 item->setParentItem(this);
152 qPathViewAttachedType = d->attachedType();
153 QQuickPathViewAttached *att = static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item));
154 qPathViewAttachedType = nullptr;
155 if (att) {
156 att->m_view = this;
157 qreal percent = d->positionOfIndex(index);
158 if (percent < 1 && d->path) {
159 const auto attributes = d->path->attributes();
160 for (const QString &attr : attributes)
161 att->setValue(attr.toUtf8(), d->path->attributeAt(attr, percent));
162 item->setZ(d->requestedZ);
163 }
164 att->setOnPath(percent < 1);
165 }
166 }
167}
168
170{
171 if (!item)
172 return;
173 qCDebug(lcItemViewDelegateLifecycle) << "release" << item;
175 itemPrivate->removeItemChangeListener(
177 if (!model)
178 return;
179 QQmlInstanceModel::ReleaseFlags flags = model->release(item);
180 if (!flags) {
181 // item was not destroyed, and we no longer reference it.
183 att->setOnPath(false);
184 } else if (flags & QQmlInstanceModel::Destroyed) {
185 // but we still reference it
186 item->setParentItem(nullptr);
187 }
188}
189
191{
192 return static_cast<QQuickPathViewAttached *>(qmlAttachedPropertiesObject<QQuickPathView>(item, false));
193}
194
196{
197 if (!attType) {
198 // pre-create one metatype to share with all attached objects
199 attType = new QQmlOpenMetaObjectType(&QQuickPathViewAttached::staticMetaObject);
200 if (path) {
201 const auto attributes = path->attributes();
202 for (const QString &attr : attributes)
203 attType->createProperty(attr.toUtf8());
204 }
205 }
206
207 return attType;
208}
209
211{
212 if (currentItem) {
214 currentItem = nullptr;
215 }
216
217 for (QQuickItem *p : std::as_const(items))
218 releaseItem(p);
219
220 for (QQuickItem *p : std::as_const(itemCache))
221 releaseItem(p);
222
223 if (requestedIndex >= 0) {
224 if (model)
226 requestedIndex = -1;
227 }
228
229 items.clear();
231 tl.clear();
232}
233
235{
236 // Update the actual cache size to be at max
237 // the available non-visible items.
239
240 if (model && pathItems != -1 && pathItems < modelCount) {
242 mappedCache = qreal(cacheSize)/pathItems/2; // Half of cache at each end
243 } else {
244 mappedRange = 1;
245 mappedCache = 0;
246 }
247}
248
250{
251 qreal pos = -1;
252
253 if (model && index >= 0 && index < modelCount) {
254 qreal start = 0;
258 qreal globalPos = index + offset;
259 globalPos = std::fmod(globalPos, qreal(modelCount)) / modelCount;
260 if (pathItems != -1 && pathItems < modelCount) {
261 globalPos += start / mappedRange;
262 globalPos = std::fmod(globalPos, qreal(1));
263 pos = globalPos * mappedRange;
264 } else {
265 pos = std::fmod(globalPos + start, qreal(1));
266 }
267 }
268
269 return pos;
270}
271
272// returns true if position is between lower and upper, taking into
273// account the circular space.
275 qreal upper, bool emptyRangeCheck) const
276{
277 if (emptyRangeCheck && qFuzzyCompare(lower, upper))
278 return true;
279 if (lower > upper) {
280 if (position > upper && position > lower)
282 lower -= mappedRange;
283 }
284 return position >= lower && position < upper;
285}
286
288{
289 Q_Q(QQuickPathView);
290 if (!q->isComponentComplete())
291 return;
292
293 bool changed = false;
294 if (highlightItem) {
297 highlightItem = nullptr;
298 changed = true;
299 }
300
301 QQuickItem *item = nullptr;
302 if (highlightComponent) {
303 QQmlContext *creationContext = highlightComponent->creationContext();
304 QQmlContext *highlightContext = new QQmlContext(
305 creationContext ? creationContext : qmlContext(q));
306 QObject *nobj = highlightComponent->create(highlightContext);
307 if (nobj) {
308 QQml_setParent_noEvent(highlightContext, nobj);
310 if (!item)
311 delete nobj;
312 } else {
313 delete highlightContext;
314 }
315 } else {
316 item = new QQuickItem;
317 }
318 if (item) {
322 changed = true;
323 }
324 if (changed)
325 emit q->highlightItemChanged();
326}
327
329{
330 Q_Q(QQuickPathView);
331 if (!q->isComponentComplete() || !isValid())
332 return;
333 if (highlightItem) {
336 } else {
338
339 offsetAdj = 0;
342
343 const int duration = highlightMoveDuration;
344
346 highlightUp = false;
351 } else if (target - highlightPosition <= -modelCount/2) {
352 highlightUp = true;
355 tl.set(moveHighlight, 0);
357 } else {
360 }
361 }
362 }
363}
364
366{
368 qreal start = 0;
369 qreal end = 1;
373 }
374
376 // calc normalized position of highlight relative to offset
377 qreal relativeHighlight = std::fmod(pos + offset, range) / range;
378
379 if (!highlightUp && relativeHighlight > end / mappedRange) {
380 qreal diff = 1 - relativeHighlight;
381 setOffset(offset + diff * range);
382 } else if (highlightUp && relativeHighlight >= (end - start) / mappedRange) {
383 qreal diff = relativeHighlight - (end - start) / mappedRange;
384 setOffset(offset - diff * range - 0.00001);
385 }
386
388 qreal pathPos = positionOfIndex(pos);
389 updateItem(highlightItem, pathPos);
391 att->setOnPath(pathPos < 1);
392 }
393}
394
395void QQuickPathView::pathUpdated()
396{
397 Q_D(QQuickPathView);
398 for (QQuickItem *item : std::as_const(d->items)) {
399 if (QQuickPathViewAttached *att = d->attached(item))
400 att->m_percent = -1;
401 }
402 refill();
403}
404
406{
407 if (!path)
408 return;
410 if (qFuzzyCompare(att->m_percent, percent))
411 return;
412 att->m_percent = percent;
413 const auto attributes = path->attributes();
414 for (const QString &attr : attributes)
415 att->setValue(attr.toUtf8(), path->attributeAt(attr, percent));
416 att->setOnPath(percent < 1);
417 }
418 QQuickItemPrivate::get(item)->setCulled(percent >= 1);
419 QPointF pf = path->pointAtPercent(qMin(percent, qreal(1)));
420 item->setX(pf.x() - item->width()/2);
421 item->setY(pf.y() - item->height()/2);
422}
423
425{
426 Q_Q(QQuickPathView);
427 if (!q->isComponentComplete())
428 return;
429
430 clear();
431
432 if (!isValid())
433 return;
434
436 q->refill();
437}
438
440{
441 Q_Q(QQuickPathView);
442 if (dragging == d)
443 return;
444
445 dragging = d;
446 if (dragging)
447 emit q->dragStarted();
448 else
449 emit q->dragEnded();
450
451 emit q->draggingChanged();
452}
453
522 : QQuickItem(*(new QQuickPathViewPrivate), parent)
523{
524 Q_D(QQuickPathView);
525 d->init();
526}
527
529{
530 Q_D(QQuickPathView);
531 d->clear();
532 if (d->attType)
533 d->attType->release();
534 if (d->ownModel)
535 delete d->model;
536}
537
595{
596 Q_D(const QQuickPathView);
597 return d->modelVariant;
598}
599
601{
602 Q_D(QQuickPathView);
603 QVariant model = m;
604 if (model.userType() == qMetaTypeId<QJSValue>())
606
607 if (d->modelVariant == model)
608 return;
609
610 if (d->model) {
611 qmlobject_disconnect(d->model, QQmlInstanceModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)),
612 this, QQuickPathView, SLOT(modelUpdated(QQmlChangeSet,bool)));
613 qmlobject_disconnect(d->model, QQmlInstanceModel, SIGNAL(createdItem(int,QObject*)),
614 this, QQuickPathView, SLOT(createdItem(int,QObject*)));
615 qmlobject_disconnect(d->model, QQmlInstanceModel, SIGNAL(initItem(int,QObject*)),
616 this, QQuickPathView, SLOT(initItem(int,QObject*)));
617 d->clear();
618 }
619
620 d->modelVariant = model;
621 QObject *object = qvariant_cast<QObject*>(model);
622 QQmlInstanceModel *vim = nullptr;
623 if (object && (vim = qobject_cast<QQmlInstanceModel *>(object))) {
624 if (d->ownModel) {
625 delete d->model;
626 d->ownModel = false;
627 }
628 d->model = vim;
629 } else {
630 if (!d->ownModel) {
631 d->model = new QQmlDelegateModel(qmlContext(this));
632 d->ownModel = true;
634 static_cast<QQmlDelegateModel *>(d->model.data())->componentComplete();
635 }
636 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(d->model))
637 dataModel->setModel(model);
638 }
639 int oldModelCount = d->modelCount;
640 d->modelCount = 0;
641 if (d->model) {
642 qmlobject_connect(d->model, QQmlInstanceModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)),
643 this, QQuickPathView, SLOT(modelUpdated(QQmlChangeSet,bool)));
644 qmlobject_connect(d->model, QQmlInstanceModel, SIGNAL(createdItem(int,QObject*)),
645 this, QQuickPathView, SLOT(createdItem(int,QObject*)));
646 qmlobject_connect(d->model, QQmlInstanceModel, SIGNAL(initItem(int,QObject*)),
647 this, QQuickPathView, SLOT(initItem(int,QObject*)));
648 d->modelCount = d->model->count();
649 }
650 if (isComponentComplete()) {
651 if (d->currentIndex != 0) {
652 d->currentIndex = 0;
654 }
655 if (!(qFuzzyIsNull(d->offset))) {
656 d->offset = 0;
658 }
659 }
660 d->regenerate();
661 if (d->modelCount != oldModelCount)
664}
665
671{
672 Q_D(const QQuickPathView);
673 return d->model ? d->modelCount : 0;
674}
675
682{
683 Q_D(const QQuickPathView);
684 return d->path;
685}
686
688{
689 Q_D(QQuickPathView);
690 if (d->path == path)
691 return;
692 if (d->path)
693 qmlobject_disconnect(d->path, QQuickPath, SIGNAL(changed()),
694 this, QQuickPathView, SLOT(pathUpdated()));
695 d->path = path;
696
697 if (path) {
698 qmlobject_connect(d->path, QQuickPath, SIGNAL(changed()),
699 this, QQuickPathView, SLOT(pathUpdated()));
700 }
701
702 if (isComponentComplete()) {
703 d->clear();
704 if (d->isValid()) {
705 if (d->attType) {
706 d->attType->release();
707 d->attType = nullptr;
708 }
709 d->regenerate();
710 }
711 }
712
714}
715
721{
722 Q_D(const QQuickPathView);
723 return d->currentIndex;
724}
725
727{
728 Q_D(QQuickPathView);
729 if (!isComponentComplete()) {
730 if (idx != d->currentIndex) {
731 d->currentIndex = idx;
733 }
734 return;
735 }
736
737 idx = d->modelCount
738 ? ((idx % d->modelCount) + d->modelCount) % d->modelCount
739 : 0;
740 if (d->model && (idx != d->currentIndex || !d->currentItem)) {
741 if (d->currentItem) {
742 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
743 att->setIsCurrentItem(false);
744 d->releaseItem(d->currentItem);
745 }
746 int oldCurrentIdx = d->currentIndex;
747 QQuickItem *oldCurrentItem = d->currentItem;
748 d->currentItem = nullptr;
750 d->currentIndex = idx;
751 if (d->modelCount) {
752 d->createCurrentItem();
753 if (d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange)
754 d->snapToIndex(d->currentIndex, QQuickPathViewPrivate::SetIndex);
755 d->currentItemOffset = d->positionOfIndex(d->currentIndex);
756 d->updateHighlight();
757 }
758 if (oldCurrentIdx != d->currentIndex)
760 if (oldCurrentItem != d->currentItem)
762 }
763}
764
770{
771 Q_D(const QQuickPathView);
772 return d->currentItem;
773}
774
788
802
810{
811 Q_D(const QQuickPathView);
812 return d->offset;
813}
814
816{
817 Q_D(QQuickPathView);
818 d->moveReason = QQuickPathViewPrivate::Other;
819 d->setOffset(offset);
820 d->updateCurrent();
821}
822
824{
825 Q_Q(QQuickPathView);
826 if (!qFuzzyCompare(offset, o)) {
827 if (isValid() && q->isComponentComplete()) {
828 qreal oldOffset = offset;
829 offset = std::fmod(o, qreal(modelCount));
830 if (offset < 0)
832 qCDebug(lcItemViewDelegateLifecycle) << o << "was" << oldOffset << "now" << offset;
833 q->refill();
834 } else {
835 offset = o;
836 }
837 emit q->offsetChanged();
838 }
839}
840
845
870{
871 Q_D(const QQuickPathView);
872 return d->highlightComponent;
873}
874
876{
877 Q_D(QQuickPathView);
878 if (highlight != d->highlightComponent) {
879 d->highlightComponent = highlight;
880 d->createHighlight();
881 d->updateHighlight();
883 }
884}
885
895{
896 Q_D(const QQuickPathView);
897 return d->highlightItem;
898}
899
937{
938 Q_D(const QQuickPathView);
939 return d->highlightRangeStart;
940}
941
943{
944 Q_D(QQuickPathView);
945 if (qFuzzyCompare(d->highlightRangeStart, start) || start < 0 || start > 1)
946 return;
947 d->highlightRangeStart = start;
948 d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
949 refill();
951}
952
954{
955 Q_D(const QQuickPathView);
956 return d->highlightRangeEnd;
957}
958
960{
961 Q_D(QQuickPathView);
962 if (qFuzzyCompare(d->highlightRangeEnd, end) || end < 0 || end > 1)
963 return;
964 d->highlightRangeEnd = end;
965 d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
966 refill();
968}
969
971{
972 Q_D(const QQuickPathView);
973 return d->highlightRangeMode;
974}
975
977{
978 Q_D(QQuickPathView);
979 if (d->highlightRangeMode == mode)
980 return;
981 d->highlightRangeMode = mode;
982 d->haveHighlightRange = d->highlightRangeStart <= d->highlightRangeEnd;
983 if (d->haveHighlightRange) {
984 d->regenerate();
985 int index = d->highlightRangeMode != NoHighlightRange ? d->currentIndex : d->calcCurrentIndex();
986 if (index >= 0)
987 d->snapToIndex(index, QQuickPathViewPrivate::Other);
988 }
990}
991
1002{
1003 Q_D(const QQuickPathView);
1004 return d->highlightMoveDuration;
1005}
1006
1008{
1009 Q_D(QQuickPathView);
1010 if (d->highlightMoveDuration == duration)
1011 return;
1012 d->highlightMoveDuration = duration;
1014}
1015
1025{
1026 Q_D(const QQuickPathView);
1027 return d->dragMargin;
1028}
1029
1031{
1032 Q_D(QQuickPathView);
1033 if (qFuzzyCompare(d->dragMargin, dragMargin))
1034 return;
1035 d->dragMargin = dragMargin;
1037}
1038
1046{
1047 Q_D(const QQuickPathView);
1048 return d->deceleration;
1049}
1050
1052{
1053 Q_D(QQuickPathView);
1054 if (qFuzzyCompare(d->deceleration, dec))
1055 return;
1056 d->deceleration = dec;
1058}
1059
1067{
1068 Q_D(const QQuickPathView);
1069 return d->maximumFlickVelocity;
1070}
1071
1073{
1074 Q_D(QQuickPathView);
1075 if (qFuzzyCompare(vel, d->maximumFlickVelocity))
1076 return;
1077 d->maximumFlickVelocity = vel;
1079}
1080
1081
1091{
1092 Q_D(const QQuickPathView);
1093 return d->interactive;
1094}
1095
1097{
1098 Q_D(QQuickPathView);
1099 if (interactive != d->interactive) {
1100 d->interactive = interactive;
1101 if (!interactive)
1102 d->tl.clear();
1104 }
1105}
1106
1114{
1115 Q_D(const QQuickPathView);
1116 return d->moving;
1117}
1118
1126{
1127 Q_D(const QQuickPathView);
1128 return d->flicking;
1129}
1130
1138{
1139 Q_D(const QQuickPathView);
1140 return d->dragging;
1141}
1142
1209{
1210 Q_D(const QQuickPathView);
1211 if (d->model) {
1212 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(d->model))
1213 return dataModel->delegate();
1214 }
1215
1216 return nullptr;
1217}
1218
1220{
1221 Q_D(QQuickPathView);
1222 if (delegate == this->delegate())
1223 return;
1224 if (!d->ownModel) {
1225 d->model = new QQmlDelegateModel(qmlContext(this));
1226 d->ownModel = true;
1227 if (isComponentComplete())
1228 static_cast<QQmlDelegateModel *>(d->model.data())->componentComplete();
1229 }
1230 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(d->model)) {
1231 int oldCount = dataModel->count();
1232 dataModel->setDelegate(delegate);
1233 d->modelCount = dataModel->count();
1234 d->regenerate();
1235 if (oldCount != dataModel->count())
1238 d->delegateValidated = false;
1239 }
1240}
1241
1249{
1250 Q_D(const QQuickPathView);
1251 return d->pathItems;
1252}
1253
1255{
1256 Q_D(QQuickPathView);
1257 if (i == d->pathItems)
1258 return;
1259 if (i < 1)
1260 i = 1;
1261 d->pathItems = i;
1262 d->updateMappedRange();
1263 if (d->isValid() && isComponentComplete()) {
1264 d->regenerate();
1265 }
1267}
1268
1270{
1271 Q_D(QQuickPathView);
1272 if (-1 == d->pathItems)
1273 return;
1274 d->pathItems = -1;
1275 d->updateMappedRange();
1276 if (d->isValid() && isComponentComplete())
1277 d->regenerate();
1279}
1280
1303{
1304 Q_D(const QQuickPathView);
1305 return d->requestedCacheSize;
1306}
1307
1309{
1310 Q_D(QQuickPathView);
1311 if (i == d->requestedCacheSize || i < 0)
1312 return;
1313
1314 d->requestedCacheSize = i;
1315 d->updateMappedRange();
1316 refill();
1318}
1319
1339{
1340 Q_D(const QQuickPathView);
1341 return d->snapMode;
1342}
1343
1345{
1346 Q_D(QQuickPathView);
1347 if (mode == d->snapMode)
1348 return;
1349 d->snapMode = mode;
1351}
1352
1377{
1378 Q_D(const QQuickPathView);
1379 return d->movementDirection;
1380}
1381
1383{
1384 Q_D(QQuickPathView);
1385 if (dir == d->movementDirection)
1386 return;
1387 d->movementDirection = dir;
1388 if (!d->tl.isActive())
1389 d->moveDirection = d->movementDirection;
1390 emit movementDirectionChanged();
1391}
1392
1416{
1417 Q_D(QQuickPathView);
1418 if (!d->isValid())
1419 return;
1420 if (mode < QQuickPathView::Beginning || mode > QQuickPathView::SnapPosition || mode == 3) // 3 is unused in PathView
1421 return;
1422
1423 if (mode == QQuickPathView::Contain && (d->pathItems < 0 || d->modelCount <= d->pathItems))
1424 return;
1425
1426 int count = d->pathItems == -1 ? d->modelCount : qMin(d->pathItems, d->modelCount);
1427 int idx = (index+d->modelCount) % d->modelCount;
1428 bool snap = d->haveHighlightRange && (d->highlightRangeMode != QQuickPathView::NoHighlightRange
1429 || d->snapMode != QQuickPathView::NoSnap);
1430
1431 qreal beginOffset;
1432 qreal endOffset;
1433 if (snap) {
1434 beginOffset = d->modelCount - idx - qFloor(count * d->highlightRangeStart);
1435 endOffset = beginOffset + count - 1;
1436 } else {
1437 beginOffset = d->modelCount - idx;
1438 // Small offset since the last point coincides with the first and
1439 // this the only "end" position that gives the expected visual result.
1440 qreal adj = sizeof(qreal) == sizeof(float) ? 0.00001f : 0.000000000001;
1441 endOffset = std::fmod(beginOffset + count, qreal(d->modelCount)) - adj;
1442 }
1443 qreal offset = d->offset;
1444 switch (mode) {
1445 case Beginning:
1446 offset = beginOffset;
1447 break;
1448 case End:
1449 offset = endOffset;
1450 break;
1451 case Center:
1452 if (beginOffset < endOffset)
1453 offset = (beginOffset + endOffset)/2;
1454 else
1455 offset = (beginOffset + (endOffset + d->modelCount))/2;
1456 if (snap)
1457 offset = qRound(offset);
1458 break;
1459 case Contain:
1460 if ((beginOffset < endOffset && (d->offset < beginOffset || d->offset > endOffset))
1461 || (d->offset < beginOffset && d->offset > endOffset)) {
1462 qreal diff1 = std::fmod(beginOffset - d->offset + d->modelCount, qreal(d->modelCount));
1463 qreal diff2 = std::fmod(d->offset - endOffset + d->modelCount, qreal(d->modelCount));
1464 if (diff1 < diff2)
1465 offset = beginOffset;
1466 else
1467 offset = endOffset;
1468 }
1469 break;
1470 case SnapPosition:
1471 offset = d->modelCount - idx;
1472 break;
1473 }
1474
1475 d->tl.clear();
1477}
1478
1488{
1489 Q_D(const QQuickPathView);
1490 QQuickItem *item = itemAt(x, y);
1491 return item ? d->model->indexOf(item, nullptr) : -1;
1492}
1493
1503{
1504 Q_D(const QQuickPathView);
1505 if (!d->isValid())
1506 return nullptr;
1507
1508 for (QQuickItem *item : d->items) {
1509 QPointF p = item->mapFromItem(this, QPointF(x, y));
1510 if (item->contains(p))
1511 return item;
1512 }
1513
1514 return nullptr;
1515}
1516
1530QQuickItem *QQuickPathView::itemAtIndex(int index) const
1531{
1532 Q_D(const QQuickPathView);
1533 if (!d->isValid())
1534 return nullptr;
1535
1536 for (QQuickItem *item : d->items) {
1537 if (index == d->model->indexOf(item, nullptr))
1538 return item;
1539 }
1540
1541 return nullptr;
1542}
1543
1553{
1554 const auto pathLength = path->path().length();
1555 qreal samples = qMin(pathLength / 5, qreal(500));
1556 qreal res = pathLength / samples;
1557
1558 qreal mindist = 1e10; // big number
1559 QPointF nearPoint = path->pointAtPercent(0);
1560 qreal nearPc = 0;
1561
1562 // get rough pos
1563 for (qreal i=1; i < samples; i++) {
1564 QPointF pt = path->pointAtPercent(i/samples);
1565 QPointF diff = pt - point;
1566 qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
1567 if (dist < mindist) {
1568 nearPoint = pt;
1569 nearPc = i;
1570 mindist = dist;
1571 }
1572 }
1573
1574 // now refine
1575 qreal approxPc = nearPc;
1576 for (qreal i = approxPc-1; i < approxPc+1; i += 1/(2*res)) {
1577 QPointF pt = path->pointAtPercent(i/samples);
1578 QPointF diff = pt - point;
1579 qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
1580 if (dist < mindist) {
1581 nearPoint = pt;
1582 nearPc = i;
1583 mindist = dist;
1584 }
1585 }
1586
1587 if (nearPercent)
1588 *nearPercent = nearPc / samples;
1589
1590 return nearPoint;
1591}
1592
1594{
1598 qCDebug(lcPathView) << "instantaneous velocity" << v;
1599}
1600
1602{
1603 qreal velocity = 0;
1606 for (int i = 0; i < count; ++i) {
1608 velocity += v;
1609 }
1610 velocity /= count;
1611 qCDebug(lcPathView) << "average velocity" << velocity << "based on" << count << "samples";
1612 }
1613 return velocity;
1614}
1615
1617{
1618 if (0 != event->timestamp())
1619 return qint64(event->timestamp());
1620 return timer.elapsed();
1621}
1622
1624{
1625 Q_D(QQuickPathView);
1626 if (d->interactive) {
1627 d->handleMousePressEvent(event);
1628 event->accept();
1629 } else {
1631 }
1632}
1633
1635{
1636 Q_Q(QQuickPathView);
1637 if (!interactive || !items.size() || !model || !modelCount)
1638 return;
1640 int idx = 0;
1641 for (; idx < items.size(); ++idx) {
1642 QQuickItem *item = items.at(idx);
1643 if (item->contains(item->mapFromScene(event->scenePosition())))
1644 break;
1645 }
1646 if (idx == items.size() && qFuzzyIsNull(dragMargin)) // didn't click on an item
1647 return;
1648
1649 startPoint = pointNear(event->position(), &startPc);
1650 startPos = event->position();
1651 if (idx == items.size()) {
1652 qreal distance = qAbs(event->position().x() - startPoint.x()) + qAbs(event->position().y() - startPoint.y());
1653 if (distance > dragMargin)
1654 return;
1655 }
1656
1657 if (tl.isActive() && flicking && flickDuration && qreal(tl.time()) / flickDuration < 0.8) {
1658 stealMouse = true; // If we've been flicked then steal the click.
1659 q->grabMouse(); // grab it right now too, just to be sure (QTBUG-77173)
1660 } else {
1661 stealMouse = false;
1662 }
1663 q->setKeepMouseGrab(stealMouse);
1664
1665 timer.start();
1667 tl.clear();
1668}
1669
1671{
1672 Q_D(QQuickPathView);
1673 if (d->interactive) {
1674 d->handleMouseMoveEvent(event);
1675 event->accept();
1676 } else {
1678 }
1679}
1680
1682{
1683 Q_Q(QQuickPathView);
1684 if (!interactive || !timer.isValid() || !model || !modelCount)
1685 return;
1686
1687 qint64 currentTimestamp = computeCurrentTime(event);
1688 qreal newPc;
1689 QPointF pathPoint = pointNear(event->position(), &newPc);
1690 if (!stealMouse) {
1691 QPointF posDelta = event->position() - startPos;
1694 // The touch has exceeded the threshold. If the movement along the path is close to the drag threshold
1695 // then we'll assume that this gesture targets the PathView. This ensures PathView gesture grabbing
1696 // is in sync with other items.
1697 QPointF pathDelta = pathPoint - startPoint;
1698 const int startDragDistance = QGuiApplication::styleHints()->startDragDistance();
1699 if (qAbs(pathDelta.x()) > startDragDistance * 0.8
1700 || qAbs(pathDelta.y()) > startDragDistance * 0.8) {
1701 stealMouse = true;
1702 q->setKeepMouseGrab(true);
1703 }
1704 }
1705 } else {
1708 qreal diff = (newPc - startPc)*count;
1709 if (!qFuzzyIsNull(diff)) {
1710 q->setOffset(offset + diff);
1711
1712 if (diff > modelCount/2)
1713 diff -= modelCount;
1714 else if (diff < -modelCount/2)
1715 diff += modelCount;
1716
1717 qint64 elapsed = currentTimestamp - lastPosTime;
1718 if (elapsed > 0)
1719 addVelocitySample(diff / (qreal(elapsed) / 1000));
1720 }
1721 if (!moving) {
1722 moving = true;
1723 emit q->movingChanged();
1724 emit q->movementStarted();
1725 }
1726 setDragging(true);
1727 }
1728 startPc = newPc;
1729 lastPosTime = currentTimestamp;
1730}
1731
1733{
1734 Q_D(QQuickPathView);
1735 if (d->interactive) {
1736 d->handleMouseReleaseEvent(event);
1737 event->accept();
1738 ungrabMouse();
1739 } else {
1741 }
1742}
1743
1745{
1746 Q_Q(QQuickPathView);
1747 stealMouse = false;
1748 q->setKeepMouseGrab(false);
1749 setDragging(false);
1750 if (!interactive || !timer.isValid() || !model || !modelCount) {
1751 timer.invalidate();
1752 if (!tl.isActive())
1753 q->movementEnding();
1754 return;
1755 }
1756
1757 qreal velocity = calcVelocity();
1759 // Let the velocity linearly decay such that it becomes 0 if elapsed time > QML_FLICK_VELOCITY_DECAY_TIME
1760 // The intention is that if you are flicking at some speed, then stop in one place for some time before releasing,
1761 // the previous velocity is lost. (QTBUG-77173, QTBUG-59052)
1763 qCDebug(lcPathView) << "after elapsed time" << elapsed << "velocity decayed to" << velocity;
1765 const auto averageItemLength = path->path().length() / count;
1766 qreal pixelVelocity = averageItemLength * velocity;
1767 if (qAbs(pixelVelocity) > _q_MinimumFlickVelocity) {
1769 // limit velocity
1770 qreal maxVel = velocity < 0 ? -maximumFlickVelocity : maximumFlickVelocity;
1771 velocity = maxVel / averageItemLength;
1772 }
1773 // Calculate the distance to be travelled
1774 qreal v2 = velocity*velocity;
1775 qreal accel = deceleration/10;
1776 qreal dist = 0;
1780 // encourage snapping one item in direction of motion
1781 if (velocity > 0)
1782 dist = qRound(0.5 + offset) - offset;
1783 else
1784 dist = qRound(0.5 - offset) + offset;
1785 } else {
1786 // + 0.25 to encourage moving at least one item in the flick direction
1787 dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2) + 0.25));
1788
1789 // round to nearest item.
1790 if (velocity > 0)
1791 dist = qRound(dist + offset) - offset;
1792 else
1793 dist = qRound(dist - offset) + offset;
1794 }
1795 // Calculate accel required to stop on item boundary
1796 if (dist <= 0) {
1797 dist = 0;
1798 accel = 0;
1799 } else {
1800 accel = v2 / (2 * qAbs(dist));
1801 }
1802 } else {
1803 dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2)));
1804 }
1805 flickDuration = int(1000 * qAbs(velocity) / accel);
1806 offsetAdj = 0;
1808 tl.accel(moveOffset, velocity, accel, dist);
1810 if (!flicking) {
1811 flicking = true;
1812 emit q->flickingChanged();
1813 emit q->flickStarted();
1814 }
1815 } else {
1816 fixOffset();
1817 }
1818
1819 timer.invalidate();
1820 if (!tl.isActive())
1821 q->movementEnding();
1822}
1823
1825{
1826 Q_D(QQuickPathView);
1827 if (!isVisible() || !d->interactive || !e->isPointerEvent())
1829
1830 QPointerEvent *pe = static_cast<QPointerEvent *>(e);
1832 // The event is localized for the intended receiver (in the delegate, probably),
1833 // but we need to look at position relative to the PathView itself.
1834 const auto &point = pe->points().first();
1835 QPointF localPos = mapFromScene(point.scenePosition());
1836 QQuickItem *grabber = qmlobject_cast<QQuickItem *>(pe->exclusiveGrabber(point));
1837 if (grabber == this && d->stealMouse) {
1838 // we are already the grabber and we do want the mouse event to ourselves.
1839 return true;
1840 }
1841
1842 bool grabberDisabled = grabber && !grabber->isEnabled();
1843 bool stealThisEvent = d->stealMouse;
1844 if ((stealThisEvent || contains(localPos)) && (!grabber || !grabber->keepMouseGrab() || grabberDisabled)) {
1845 // Make a localized copy of the QMouseEvent.
1846 QMutableSinglePointEvent localizedEvent(*static_cast<QMouseEvent *>(pe));
1847 QMutableEventPoint::setPosition(localizedEvent.point(0), localPos);
1848 localizedEvent.setAccepted(false);
1849
1850 switch (localizedEvent.type()) {
1851 case QEvent::MouseMove:
1852 d->handleMouseMoveEvent(static_cast<QMouseEvent *>(static_cast<QSinglePointEvent *>(&localizedEvent)));
1853 break;
1855 d->handleMousePressEvent(static_cast<QMouseEvent *>(static_cast<QSinglePointEvent *>(&localizedEvent)));
1856 stealThisEvent = d->stealMouse; // Update stealThisEvent in case changed by function call above
1857 break;
1859 d->handleMouseReleaseEvent(static_cast<QMouseEvent *>(static_cast<QSinglePointEvent *>(&localizedEvent)));
1860 break;
1861 default:
1862 break;
1863 }
1864
1865 grabber = qmlobject_cast<QQuickItem *>(localizedEvent.exclusiveGrabber(localizedEvent.points().first()));
1866 if ((grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this) || grabberDisabled)
1867 pe->setExclusiveGrabber(point, this);
1868
1869 const bool filtered = stealThisEvent || grabberDisabled;
1870 if (filtered)
1871 pe->setAccepted(stealThisEvent && grabber == this && grabber->isEnabled());
1872
1873 return filtered;
1874 } else if (d->timer.isValid()) {
1875 d->timer.invalidate();
1876 d->fixOffset();
1877 }
1878 if (pe->type() == QEvent::MouseButtonRelease || (grabber && grabber->keepMouseGrab() && !grabberDisabled))
1879 d->stealMouse = false;
1880 return false;
1881 }
1882
1884}
1885
1887{
1888 Q_D(QQuickPathView);
1889 if (d->stealMouse ||
1890 (!d->flicking && d->snapMode != NoSnap && !qFuzzyCompare(qRound(d->offset), d->offset))) {
1891 // if our mouse grab has been removed (probably by a Flickable),
1892 // or if we should snap but haven't done it, fix our state
1893 d->stealMouse = false;
1894 setKeepMouseGrab(false);
1895 d->timer.invalidate();
1896 d->fixOffset();
1897 d->setDragging(false);
1898 if (!d->tl.isActive())
1899 movementEnding();
1900 }
1901}
1902
1904{
1906 refill();
1907}
1908
1909static inline int currentIndexRemainder(int currentIndex, int modelCount) noexcept
1910{
1911 if (currentIndex < 0)
1912 return modelCount + currentIndex % modelCount;
1913 else
1914 return currentIndex % modelCount;
1915}
1916
1918{
1919 Q_D(QQuickPathView);
1920 if (d->model && d->ownModel)
1921 static_cast<QQmlDelegateModel *>(d->model.data())->componentComplete();
1922
1924
1925 if (d->model) {
1926 d->modelCount = d->model->count();
1927 if (d->modelCount && d->currentIndex != 0) // an initial value has been provided for currentIndex
1928 d->offset = std::fmod(qreal(d->modelCount - currentIndexRemainder(d->currentIndex, d->modelCount)), qreal(d->modelCount));
1929 }
1930
1931 d->createHighlight();
1932 d->regenerate();
1933 d->updateHighlight();
1934 d->updateCurrent();
1935
1936 if (d->modelCount)
1938}
1939
1940void QQuickPathView::refill()
1941{
1942 Q_D(QQuickPathView);
1943
1944 if (d->inRefill) {
1945 d->scheduleLayout();
1946 return;
1947 }
1948
1949 d->layoutScheduled = false;
1950
1951 if (!d->isValid() || !isComponentComplete())
1952 return;
1953
1954 d->inRefill = true;
1955
1956 bool currentVisible = false;
1957 int count = d->pathItems == -1 ? d->modelCount : qMin(d->pathItems, d->modelCount);
1958
1959 // first move existing items and remove items off path
1960 qCDebug(lcItemViewDelegateLifecycle) << "currentIndex" << d->currentIndex << "offset" << d->offset;
1961 QList<QQuickItem*>::iterator it = d->items.begin();
1962 while (it != d->items.end()) {
1963 QQuickItem *item = *it;
1964 int idx = d->model->indexOf(item, nullptr);
1965 qreal pos = d->positionOfIndex(idx);
1966 if (lcItemViewDelegateLifecycle().isDebugEnabled()) {
1967 QQuickText *text = qmlobject_cast<QQuickText*>(item);
1968 if (text)
1969 qCDebug(lcItemViewDelegateLifecycle) << "idx" << idx << "@" << pos << ": QQuickText" << text->objectName() << QStringView{text->text()}.left(40);
1970 else
1971 qCDebug(lcItemViewDelegateLifecycle) << "idx" << idx << "@" << pos << ":" << item;
1972 }
1973 if (pos < 1) {
1974 d->updateItem(item, pos);
1975 if (idx == d->currentIndex) {
1976 currentVisible = true;
1977 d->currentItemOffset = pos;
1978 }
1979 ++it;
1980 } else {
1981 d->updateItem(item, pos);
1982 if (QQuickPathViewAttached *att = d->attached(item))
1983 att->setOnPath(pos < 1);
1984 if (!d->isInBound(pos, d->mappedRange - d->mappedCache, 1 + d->mappedCache)) {
1985 qCDebug(lcItemViewDelegateLifecycle) << "release" << idx << "@" << pos << ", !isInBound: lower" << (d->mappedRange - d->mappedCache) << "upper" << (1 + d->mappedCache);
1986 d->releaseItem(item);
1987 it = d->items.erase(it);
1988 } else {
1989 ++it;
1990 }
1991 }
1992 }
1993
1994 bool waiting = false;
1995 if (d->modelCount) {
1996 // add items as needed
1997 if (d->items.size() < count+d->cacheSize) {
1998 int endIdx = 0;
1999 qreal endPos;
2000 int startIdx = 0;
2001 qreal startPos = 0;
2002 const bool wasEmpty = d->items.isEmpty();
2003 if (!wasEmpty) {
2004 //Find the beginning and end, items may not be in sorted order
2005 endPos = -1;
2006 startPos = 2;
2007
2008 for (QQuickItem * item : std::as_const(d->items)) {
2009 int idx = d->model->indexOf(item, nullptr);
2010 qreal curPos = d->positionOfIndex(idx);
2011 if (curPos > endPos) {
2012 endPos = curPos;
2013 endIdx = idx;
2014 }
2015
2016 if (curPos < startPos) {
2017 startPos = curPos;
2018 startIdx = idx;
2019 }
2020 }
2021 } else {
2022 if (d->haveHighlightRange
2023 && (d->highlightRangeMode != QQuickPathView::NoHighlightRange
2024 || d->snapMode != QQuickPathView::NoSnap))
2025 startPos = d->highlightRangeStart;
2026 // With no items, then "end" is just off the top so we populate via append
2027 endIdx = (qRound(d->modelCount - d->offset) - 1) % d->modelCount;
2028 endIdx = qMax(-1, endIdx); // endIdx shouldn't be smaller than -1
2029 endPos = d->positionOfIndex(endIdx);
2030 }
2031 //Append
2032 int idx = endIdx + 1;
2033 if (idx >= d->modelCount)
2034 idx = 0;
2035 qreal nextPos = d->positionOfIndex(idx);
2036 while ((d->isInBound(nextPos, endPos, 1 + d->mappedCache, false) || !d->items.size())
2037 && d->items.size() < count + d->cacheSize) {
2038 qCDebug(lcItemViewDelegateLifecycle) << "append" << idx << "@" << nextPos << (d->currentIndex == idx ? "current" : "") << "items count was" << d->items.size();
2039 QQuickItem *item = d->getItem(idx, idx+1, nextPos >= 1);
2040 if (!item) {
2041 waiting = true;
2042 break;
2043 }
2044 if (d->items.contains(item)) {
2045 d->releaseItem(item);
2046 break; //Otherwise we'd "re-add" it, and get confused
2047 }
2048 if (d->currentIndex == idx) {
2049 currentVisible = true;
2050 d->currentItemOffset = nextPos;
2051 }
2052 d->items.append(item);
2053 d->updateItem(item, nextPos);
2054 endIdx = idx;
2055 endPos = nextPos;
2056 ++idx;
2057 if (idx >= d->modelCount)
2058 idx = 0;
2059 nextPos = d->positionOfIndex(idx);
2060 }
2061
2062 //Prepend
2063 idx = (wasEmpty ? d->calcCurrentIndex() : startIdx) - 1;
2064
2065 if (idx < 0)
2066 idx = d->modelCount - 1;
2067 nextPos = d->positionOfIndex(idx);
2068 while (!waiting && d->isInBound(nextPos, d->mappedRange - d->mappedCache, startPos)
2069 && d->items.size() < count+d->cacheSize) {
2070 qCDebug(lcItemViewDelegateLifecycle) << "prepend" << idx << "@" << nextPos << (d->currentIndex == idx ? "current" : "") << "items count was" << d->items.size();
2071 QQuickItem *item = d->getItem(idx, idx+1, nextPos >= 1);
2072 if (!item) {
2073 waiting = true;
2074 break;
2075 }
2076 if (d->items.contains(item)) {
2077 d->releaseItem(item);
2078 break; //Otherwise we'd "re-add" it, and get confused
2079 }
2080 if (d->currentIndex == idx) {
2081 currentVisible = true;
2082 d->currentItemOffset = nextPos;
2083 }
2084 d->items.prepend(item);
2085 d->updateItem(item, nextPos);
2086 startIdx = idx;
2087 startPos = nextPos;
2088 --idx;
2089 if (idx < 0)
2090 idx = d->modelCount - 1;
2091 nextPos = d->positionOfIndex(idx);
2092 }
2093
2094 // In rare cases, when jumping around with pathCount close to modelCount,
2095 // new items appear in the middle. This more generic addition iteration handles this
2096 // Since this is the rare case, we try append/prepend first and only do this if
2097 // there are gaps still left to fill.
2098 if (!waiting && d->items.size() < count+d->cacheSize) {
2099 qCDebug(lcItemViewDelegateLifecycle) << "Checking for pathview middle inserts, items count was" << d->items.size();
2100 idx = startIdx;
2101 QQuickItem *lastItem = d->items.at(0);
2102 while (idx != endIdx) {
2103 nextPos = d->positionOfIndex(idx);
2104 if (d->isInBound(nextPos, d->mappedRange - d->mappedCache, 1 + d->mappedCache)) {
2105 //This gets the reference from the delegate model, and will not re-create
2106 QQuickItem *item = d->getItem(idx, idx+1, nextPos >= 1);
2107 if (!item) {
2108 waiting = true;
2109 break;
2110 }
2111
2112 if (!d->items.contains(item)) { //We found a hole
2113 qCDebug(lcItemViewDelegateLifecycle) << "middle insert" << idx << "@" << nextPos
2114 << (d->currentIndex == idx ? "current" : "")
2115 << "items count was" << d->items.size();
2116 if (d->currentIndex == idx) {
2117 currentVisible = true;
2118 d->currentItemOffset = nextPos;
2119 }
2120 int lastListIdx = d->items.indexOf(lastItem);
2121 d->items.insert(lastListIdx + 1, item);
2122 d->updateItem(item, nextPos);
2123 } else {
2124 d->releaseItem(item);
2125 }
2126
2127 lastItem = item;
2128 }
2129
2130 ++idx;
2131 if (idx >= d->modelCount)
2132 idx = 0;
2133 }
2134 }
2135 }
2136 }
2137
2138 bool currentChanged = false;
2139 if (!currentVisible) {
2140 d->currentItemOffset = 1;
2141 if (d->currentItem) {
2142 d->updateItem(d->currentItem, 1);
2143 } else if (!waiting && d->currentIndex >= 0 && d->currentIndex < d->modelCount) {
2144 if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex))) {
2145 currentChanged = true;
2146 d->updateItem(d->currentItem, 1);
2147 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
2148 att->setIsCurrentItem(true);
2149 }
2150 }
2151 } else if (!waiting && !d->currentItem) {
2152 if ((d->currentItem = d->getItem(d->currentIndex, d->currentIndex))) {
2153 currentChanged = true;
2154 d->currentItem->setFocus(true);
2155 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
2156 att->setIsCurrentItem(true);
2157 }
2158 }
2159
2160 if (d->highlightItem && d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
2161 d->updateItem(d->highlightItem, d->highlightRangeStart);
2162 if (QQuickPathViewAttached *att = d->attached(d->highlightItem))
2163 att->setOnPath(true);
2164 } else if (d->highlightItem && d->moveReason != QQuickPathViewPrivate::SetIndex) {
2165 d->updateItem(d->highlightItem, d->currentItemOffset);
2166 if (QQuickPathViewAttached *att = d->attached(d->highlightItem))
2167 att->setOnPath(currentVisible);
2168 }
2169 for (QQuickItem *item : std::as_const(d->itemCache))
2170 d->releaseItem(item);
2171 d->itemCache.clear();
2172
2173 d->inRefill = false;
2174 if (currentChanged)
2176}
2177
2178void QQuickPathView::modelUpdated(const QQmlChangeSet &changeSet, bool reset)
2179{
2180 Q_D(QQuickPathView);
2181 if (!d->model || !d->model->isValid() || !d->path || !isComponentComplete())
2182 return;
2183
2184 if (reset) {
2185 d->modelCount = d->model->count();
2186 d->regenerate();
2188 return;
2189 }
2190
2191 if (changeSet.removes().isEmpty() && changeSet.inserts().isEmpty())
2192 return;
2193
2194 const int modelCount = d->modelCount;
2195 int moveId = -1;
2196 int moveOffset = 0;
2197 bool currentChanged = false;
2198 bool changedOffset = false;
2199 for (const QQmlChangeSet::Change &r : changeSet.removes()) {
2200 if (moveId == -1 && d->currentIndex >= r.index + r.count) {
2201 d->currentIndex -= r.count;
2202 currentChanged = true;
2203 } else if (moveId == -1 && d->currentIndex >= r.index && d->currentIndex < r.index + r.count) {
2204 // current item has been removed.
2205 if (r.isMove()) {
2206 moveId = r.moveId;
2207 moveOffset = d->currentIndex - r.index;
2208 } else if (d->currentItem) {
2209 if (QQuickPathViewAttached *att = d->attached(d->currentItem))
2210 att->setIsCurrentItem(true);
2211 d->releaseItem(d->currentItem);
2212 d->currentItem = nullptr;
2213 }
2214 d->currentIndex = qMin(r.index, d->modelCount - r.count - 1);
2215 currentChanged = true;
2216 }
2217
2218 if (r.index > d->currentIndex) {
2219 changedOffset = true;
2220 d->offset -= r.count;
2221 d->offsetAdj -= r.count;
2222 }
2223 d->modelCount -= r.count;
2224 }
2225 for (const QQmlChangeSet::Change &i : changeSet.inserts()) {
2226 if (d->modelCount) {
2227 if (moveId == -1 && i.index <= d->currentIndex) {
2228 d->currentIndex += i.count;
2229 currentChanged = true;
2230 } else {
2231 if (moveId != -1 && moveId == i.moveId) {
2232 d->currentIndex = i.index + moveOffset;
2233 currentChanged = true;
2234 }
2235 if (i.index > d->currentIndex) {
2236 d->offset += i.count;
2237 d->offsetAdj += i.count;
2238 changedOffset = true;
2239 }
2240 }
2241 }
2242 d->modelCount += i.count;
2243 }
2244
2245 d->offset = std::fmod(d->offset, qreal(d->modelCount));
2246 if (d->offset < 0)
2247 d->offset += d->modelCount;
2248 if (d->currentIndex == -1)
2249 d->currentIndex = d->calcCurrentIndex();
2250
2251 d->itemCache += d->items;
2252 d->items.clear();
2253
2254 if (!d->modelCount) {
2255 for (QQuickItem * item : std::as_const(d->itemCache))
2256 d->releaseItem(item);
2257 d->itemCache.clear();
2258 d->offset = 0;
2259 changedOffset = true;
2260 d->tl.reset(d->moveOffset);
2261 } else {
2262 if (!d->flicking && !d->moving && d->haveHighlightRange && d->highlightRangeMode == QQuickPathView::StrictlyEnforceRange) {
2263 d->offset = std::fmod(qreal(d->modelCount - d->currentIndex), qreal(d->modelCount));
2264 changedOffset = true;
2265 }
2266 d->updateMappedRange();
2267 d->scheduleLayout();
2268 }
2269 if (changedOffset)
2271 if (currentChanged)
2273 if (d->modelCount != modelCount)
2275}
2276
2277void QQuickPathView::destroyingItem(QObject *item)
2278{
2279 Q_UNUSED(item);
2280}
2281
2282void QQuickPathView::ticked()
2283{
2284 Q_D(QQuickPathView);
2285 d->updateCurrent();
2286}
2287
2288void QQuickPathView::movementEnding()
2289{
2290 Q_D(QQuickPathView);
2291 if (d->flicking) {
2292 d->flicking = false;
2294 emit flickEnded();
2295 }
2296 if (d->moving && !d->stealMouse) {
2297 d->moving = false;
2300 }
2301 d->moveDirection = d->movementDirection;
2302}
2303
2304// find the item closest to the snap position
2306{
2307 int current = 0;
2308 if (modelCount && model && items.size()) {
2309 offset = std::fmod(offset, qreal(modelCount));
2310 if (offset < 0)
2311 offset += modelCount;
2312 current = qRound(qAbs(std::fmod(modelCount - offset, qreal(modelCount))));
2313 current = current % modelCount;
2314 }
2315
2316 return current;
2317}
2318
2320{
2321 if (requestedIndex != -1)
2322 return;
2323
2324 bool inItems = false;
2325 for (QQuickItem *item : std::as_const(items)) {
2326 if (model->indexOf(item, nullptr) == currentIndex) {
2327 inItems = true;
2328 break;
2329 }
2330 }
2331
2332 if (inItems) {
2334 currentItem->setFocus(true);
2336 att->setIsCurrentItem(true);
2337 }
2338 } else if (currentIndex >= 0 && currentIndex < modelCount) {
2342 att->setIsCurrentItem(true);
2343 }
2344 }
2345}
2346
2348{
2349 Q_Q(QQuickPathView);
2350 if (moveReason == SetIndex)
2351 return;
2353 return;
2354
2355 int idx = calcCurrentIndex();
2356 if (model && (idx != currentIndex || !currentItem)) {
2357 if (currentItem) {
2359 att->setIsCurrentItem(false);
2361 }
2362 int oldCurrentIndex = currentIndex;
2363 currentIndex = idx;
2364 currentItem = nullptr;
2366 if (oldCurrentIndex != currentIndex)
2367 emit q->currentIndexChanged();
2368 emit q->currentItemChanged();
2369 }
2370}
2371
2373{
2374 static_cast<QQuickPathViewPrivate *>(d)->fixOffset();
2375}
2376
2378{
2379 Q_Q(QQuickPathView);
2380 if (model && items.size()) {
2383 int curr = calcCurrentIndex();
2385 q->setCurrentIndex(curr);
2386 else
2387 snapToIndex(curr, Other);
2388 }
2389 }
2390}
2391
2393{
2394 if (!model || modelCount <= 0)
2395 return;
2396
2397 qreal targetOffset = std::fmod(qreal(modelCount - index), qreal(modelCount));
2398 moveReason = reason;
2399 offsetAdj = 0;
2402
2403 const int duration = highlightMoveDuration;
2404
2406 const qreal averageItemLength = path->path().length() / count;
2407 const qreal threshold = 0.5 / averageItemLength; // if we are within .5 px, we want to immediately assign rather than animate
2408
2409 if (!duration || qAbs(offset - targetOffset) < threshold || (qFuzzyIsNull(targetOffset) && qAbs(modelCount - offset) < threshold)) {
2410 tl.set(moveOffset, targetOffset);
2412 qreal distance = modelCount - targetOffset + offset;
2413 if (targetOffset > moveOffset) {
2416 tl.move(moveOffset, targetOffset, QEasingCurve(qFuzzyIsNull(offset) ? QEasingCurve::InOutQuad : QEasingCurve::OutQuad), int(duration * (modelCount-targetOffset) / distance));
2417 } else {
2418 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
2419 }
2420 } else if (moveDirection == QQuickPathView::Negative || targetOffset - offset <= -modelCount/2) {
2421 qreal distance = modelCount - offset + targetOffset;
2422 if (targetOffset < moveOffset) {
2424 tl.set(moveOffset, 0);
2425 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::OutQuad), int(duration * targetOffset / distance));
2426 } else {
2427 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
2428 }
2429 } else {
2430 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
2431 }
2432}
2433
2438
2440
2441#include "moc_qquickpathview_p.cpp"
\inmodule QtCore
Definition qbytearray.h:57
\inmodule QtCore
void invalidate() noexcept
Marks this QElapsedTimer object as invalid.
qint64 elapsed() const noexcept
Returns the number of milliseconds since this QElapsedTimer was last started.
void start() noexcept
\typealias QElapsedTimer::Duration Synonym for std::chrono::nanoseconds.
bool isValid() const noexcept
Returns false if the timer has never been started or invalidated by a call to invalidate().
\inmodule QtCore
Definition qcoreevent.h:45
@ MouseMove
Definition qcoreevent.h:63
@ MouseButtonPress
Definition qcoreevent.h:60
@ MouseButtonRelease
Definition qcoreevent.h:61
bool isPointerEvent() const noexcept
Definition qcoreevent.h:314
virtual bool contains(const QPointF &point) const
Returns true if this item contains point, which is in local coordinates; otherwise,...
void setX(qreal x)
QPointF mapFromItem(const QGraphicsItem *item, const QPointF &point) const
Maps the point point, which is in item's coordinate system, to this item's coordinate system,...
void setParentItem(QGraphicsItem *parent)
Sets this item's parent item to newParent.
void setY(qreal y)
QPointF mapFromScene(const QPointF &point) const
Maps the point point, which is in this item's scene's coordinate system, to this item's coordinate sy...
static QStyleHints * styleHints()
Returns the application's style hints.
\inmodule QtGui
Definition qevent.h:49
The QJSValue class acts as a container for Qt/JavaScript data types.
Definition qjsvalue.h:31
static constexpr Policy Preferred
qsizetype size() const noexcept
Definition qlist.h:397
T & first()
Definition qlist.h:645
const_reference at(qsizetype i) const noexcept
Definition qlist.h:446
void clear()
Definition qlist.h:434
\inmodule QtGui
Definition qevent.h:196
\inmodule QtCore
Definition qobject.h:103
void deleteLater()
\threadsafe
Definition qobject.cpp:2435
void clear()
void remove(int idx, int count=1)
void append(const T &v)
const T & at(int idx) const
int count() const
The QPlatformTheme class allows customizing the UI based on themes.
\inmodule QtCore\reentrant
Definition qpoint.h:217
constexpr qreal x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:343
constexpr qreal y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:348
A base class for pointer events.
Definition qevent.h:73
const QList< QEventPoint > & points() const
Returns a list of points in this pointer event.
Definition qevent.h:87
The QQmlChangeSet class stores an ordered list of notifications about changes to a linear data set.
const QVector< Change > & removes() const
const QVector< Change > & inserts() const
The QQmlComponent class encapsulates a QML component definition.
QQmlContext * creationContext() const
Returns the QQmlContext the component was created in.
virtual QObject * create(QQmlContext *context=nullptr)
Create an object instance from this component, within the specified context.
The QQmlContext class defines a context within a QML engine.
Definition qqmlcontext.h:25
void setModel(const QVariant &)
int count() const override
\qmlproperty int QtQml.Models::DelegateModel::count
virtual void cancel(int)
virtual int indexOf(QObject *object, QObject *objectContext) const =0
virtual ReleaseFlags release(QObject *object, ReusableFlag reusableFlag=NotReusable)=0
int createProperty(const QByteArray &name)
bool setValue(const QByteArray &, const QVariant &, bool force=false)
QVariant value(const QByteArray &) const
static bool isMouseEvent(const QPointerEvent *ev)
static bool dragOverThreshold(qreal d, Qt::Axis axis, QMouseEvent *event, int startDragThreshold=-1)
void removeItemChangeListener(QQuickItemChangeListener *, ChangeTypes types)
void setSizePolicy(const QLayoutPolicy::Policy &horizontalPolicy, const QLayoutPolicy::Policy &verticalPolicy)
qreal z() const
void addItemChangeListener(QQuickItemChangeListener *listener, ChangeTypes types)
static QQuickItemPrivate * get(QQuickItem *item)
The QQuickItem class provides the most basic of all visual items in \l {Qt Quick}.
Definition qquickitem.h:63
virtual void mouseReleaseEvent(QMouseEvent *event)
This event handler can be reimplemented in a subclass to receive mouse release events for an item.
void setFocus(bool)
void setParentItem(QQuickItem *parent)
void componentComplete() override
\reimp Derived classes should call the base class method before adding their own actions to perform a...
QPointF mapFromScene(const QPointF &point) const
Maps the given point in the scene's coordinate system to the equivalent point within this item's coor...
bool isVisible() const
virtual Q_INVOKABLE bool contains(const QPointF &point) const
\qmlmethod bool QtQuick::Item::contains(point point)
void ungrabMouse()
virtual void mousePressEvent(QMouseEvent *event)
This event handler can be reimplemented in a subclass to receive mouse press events for an item.
bool isComponentComplete() const
Returns true if construction of the QML component is complete; otherwise returns false.
void setKeepMouseGrab(bool)
Sets whether the mouse input should remain exclusively with this item.
virtual bool childMouseEventFilter(QQuickItem *, QEvent *)
Reimplement this method to filter the pointer events that are received by this item's children.
virtual void updatePolish()
This function should perform any layout as required for this item.
virtual void mouseMoveEvent(QMouseEvent *event)
This event handler can be reimplemented in a subclass to receive mouse move events for an item.
void setValue(const QByteArray &name, const QVariant &val)
QVariant value(const QByteArray &name) const
QPointer< QQmlInstanceModel > model
void releaseItem(QQuickItem *item)
qint64 computeCurrentTime(QInputEvent *event) const
void updateItem(QQuickItem *, qreal)
void snapToIndex(int index, MovementReason reason)
QPODVector< qreal, 10 > velocityBuffer
void handleMousePressEvent(QMouseEvent *event)
QQmlOpenMetaObjectType * attType
void handleMouseReleaseEvent(QMouseEvent *)
QPointer< QQuickItem > currentItem
QQmlComponent * highlightComponent
void setAdjustedOffset(qreal offset)
QQuickTimeLineValueProxy< QQuickPathViewPrivate > moveHighlight
void setHighlightPosition(qreal pos)
QQuickTimeLineValueProxy< QQuickPathViewPrivate > moveOffset
void handleMouseMoveEvent(QMouseEvent *event)
QPointF pointNear(const QPointF &point, qreal *nearPercent=0) const
void setOffset(qreal offset)
QQmlOpenMetaObjectType * attachedType()
qreal positionOfIndex(qreal index) const
QQuickItem * getItem(int modelIndex, qreal z=0, bool async=false)
QList< QQuickItem * > itemCache
QQuickPathView::SnapMode snapMode
QQuickPathView::HighlightRangeMode highlightRangeMode
void addVelocitySample(qreal v)
QQuickPathView::MovementDirection moveDirection
QQuickPathViewAttached * attached(QQuickItem *item)
QList< QQuickItem * > items
bool isInBound(qreal position, qreal lower, qreal upper, bool emptyRangeCheck=true) const
static void fixOffsetCallback(void *)
QQuickPath * path
void dragMarginChanged()
void setPreferredHighlightBegin(qreal)
void setInteractive(bool)
void countChanged()
MovementDirection movementDirection
void decrementCurrentIndex()
\qmlmethod QtQuick::PathView::decrementCurrentIndex()
void delegateChanged()
void pathChanged()
virtual ~QQuickPathView()
void setCacheItemCount(int)
void interactiveChanged()
void setModel(const QVariant &)
void updatePolish() override
This function should perform any layout as required for this item.
void setHighlightRangeMode(HighlightRangeMode mode)
QQuickPathView(QQuickItem *parent=nullptr)
void cacheItemCountChanged()
void setFlickDeceleration(qreal dec)
void setSnapMode(SnapMode mode)
QQuickItem * highlightItem
void highlightMoveDurationChanged()
void setCurrentIndex(int idx)
bool isMoving() const
\qmlproperty bool QtQuick::PathView::moving
void pathItemCountChanged()
void flickDecelerationChanged()
void setOffset(qreal offset)
void currentItemChanged()
friend class QQuickPathViewAttached
bool childMouseEventFilter(QQuickItem *, QEvent *) override
Reimplement this method to filter the pointer events that are received by this item's children.
QQmlComponent * highlight
bool isInteractive() const
\qmlproperty bool QtQuick::PathView::interactive
void incrementCurrentIndex()
\qmlmethod QtQuick::PathView::incrementCurrentIndex()
void movementEnded()
void preferredHighlightEndChanged()
void currentIndexChanged()
void setMovementDirection(MovementDirection dir)
void setMaximumFlickVelocity(qreal)
void modelChanged()
bool isFlicking() const
\qmlproperty bool QtQuick::PathView::flicking
void setHighlight(QQmlComponent *highlight)
void snapModeChanged()
Q_INVOKABLE int indexAt(qreal x, qreal y) const
\qmlmethod int QtQuick::PathView::indexAt(real x, real y)
void flickingChanged()
void setDelegate(QQmlComponent *)
void maximumFlickVelocityChanged()
void setHighlightMoveDuration(int)
void movingChanged()
void highlightChanged()
void mouseMoveEvent(QMouseEvent *event) override
This event handler can be reimplemented in a subclass to receive mouse move events for an item.
void setPath(QQuickPath *)
void mousePressEvent(QMouseEvent *event) override
This event handler can be reimplemented in a subclass to receive mouse press events for an item.
Q_INVOKABLE QQuickItem * itemAt(qreal x, qreal y) const
\qmlmethod Item QtQuick::PathView::itemAt(real x, real y)
qreal preferredHighlightBegin
void preferredHighlightBeginChanged()
void setDragMargin(qreal margin)
void offsetChanged()
bool isDragging() const
\qmlproperty bool QtQuick::PathView::dragging
void mouseReleaseEvent(QMouseEvent *) override
This event handler can be reimplemented in a subclass to receive mouse release events for an item.
void componentComplete() override
\reimp Derived classes should call the base class method before adding their own actions to perform a...
QQuickItem * currentItem
void setPathItemCount(int)
void mouseUngrabEvent() override
This event handler can be reimplemented in a subclass to be notified when a mouse ungrab event has oc...
HighlightRangeMode highlightRangeMode
Q_INVOKABLE void positionViewAtIndex(int index, int mode)
\qmlmethod QtQuick::PathView::positionViewAtIndex(int index, PositionMode mode)
static QQuickPathViewAttached * qmlAttachedProperties(QObject *)
void highlightRangeModeChanged()
void setPreferredHighlightEnd(qreal)
QQmlComponent * delegate
void setValue(qreal v) override
Set the current value.
The QQuickTimeLine class provides a timeline for controlling animations.
void reset(QQuickTimeLineValue &)
Cancel (but don't complete) all scheduled actions for timeLineValue.
int accel(QQuickTimeLineValue &, qreal velocity, qreal accel)
Decelerate timeLineValue from the starting velocity to zero at the given acceleration rate.
void callback(const QQuickTimeLineCallback &)
Execute the event.
bool isActive() const
Returns true if the timeline is active.
void clear()
Resets the timeline.
void set(QQuickTimeLineValue &, qreal)
Set the value of timeLineValue.
void move(QQuickTimeLineValue &, qreal destination, int time=500)
Linearly change the timeLineValue from its current value to the given destination value over time mil...
A base class for pointer events containing a single point, such as mouse events.
Definition qevent.h:109
\inmodule QtCore
Definition qstringview.h:78
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
QString left(qsizetype n) const &
Definition qstring.h:363
\inmodule QtCore
Definition qvariant.h:65
T value() const &
Definition qvariant.h:516
int userType() const
Definition qvariant.h:339
#define this
Definition dialogs.cpp:9
QString text
QSet< QString >::iterator it
Combined button and popup list for selecting options.
int toUtf8(char16_t u, OutputPtr &dst, InputPtr &src, InputPtr end)
@ LeftButton
Definition qnamespace.h:58
@ XAxis
@ YAxis
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:333
bool qFuzzyIsNull(qfloat16 f) noexcept
Definition qfloat16.h:349
int qRound(qfloat16 d) noexcept
Definition qfloat16.h:327
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
#define Q_DECLARE_LOGGING_CATEGORY(name)
int qFloor(T v)
Definition qmath.h:42
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
constexpr T qAbs(const T &t)
Definition qnumeric.h:328
#define SLOT(a)
Definition qobjectdefs.h:52
#define SIGNAL(a)
Definition qobjectdefs.h:53
GLint GLfloat GLfloat GLfloat v2
GLsizei const GLfloat * v
[13]
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat z
GLint GLint GLint GLint GLint x
[0]
GLsizei samples
GLenum mode
const GLfloat * m
GLuint index
[2]
GLboolean r
[2]
GLuint GLuint end
GLenum GLenum GLsizei count
GLsizei range
GLsizei GLsizei GLfloat distance
GLenum target
GLbitfield flags
GLuint start
GLenum GLuint GLintptr offset
GLuint name
GLint y
struct _cl_event * event
GLhandleARB obj
[2]
GLboolean reset
GLuint res
GLuint GLfloat * val
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLsizei const GLchar *const * path
GLfloat GLfloat p
[1]
static quint64 cacheSize()
QQmlContext * qmlContext(const QObject *obj)
Definition qqml.cpp:75
#define qmlobject_disconnect(Sender, SenderType, Signal, Receiver, ReceiverType, Method)
Disconnect Signal of Sender from Method of Receiver.
#define qmlobject_connect(Sender, SenderType, Signal, Receiver, ReceiverType, Method)
Connect Signal of Sender to Method of Receiver.
void QQml_setParent_noEvent(QObject *object, QObject *parent)
Makes the object a child of parent.
QQuickItem * qmlobject_cast< QQuickItem * >(QObject *object)
Q_QML_EXPORT QQmlInfo qmlWarning(const QObject *me)
static qreal position(const QQuickItem *item, QQuickAnchors::Anchor anchorLine)
#define QML_FLICK_SAMPLEBUFFER
const qreal _q_MinimumFlickVelocity
#define QML_FLICK_DISCARDSAMPLES
#define QML_FLICK_VELOCITY_DECAY_TIME
QQuickItem * qobject_cast< QQuickItem * >(QObject *o)
Definition qquickitem.h:492
static QT_BEGIN_NAMESPACE QQmlOpenMetaObjectType * qPathViewAttachedType
static int currentIndexRemainder(int currentIndex, int modelCount) noexcept
static double elapsed(qint64 after, qint64 before)
#define emit
#define Q_UNUSED(x)
long long qint64
Definition qtypes.h:60
double qreal
Definition qtypes.h:187
static QVariant toVariant(const QV4::Value &value, QMetaType typeHint, JSToQVariantConversionBehavior conversionBehavior, V4ObjectSet *visitedObjects)
std::uniform_real_distribution dist(1, 2.5)
[2]
settings setValue("DataPump/bgcolor", color)
QObject::connect nullptr
QString dir
[11]
QGraphicsItem * item
QList< QTreeWidgetItem * > items