81QQuickItem *QQuickTumblerPrivate::determineViewType(QQuickItem *contentItem)
88 if (contentItem->inherits(
"QQuickPathView")) {
90 viewContentItem = contentItem;
91 viewContentItemType = PathViewContentItem;
95 }
else if (contentItem->inherits(
"QQuickListView")) {
97 viewContentItem = qobject_cast<QQuickFlickable*>(contentItem)->contentItem();
98 viewContentItemType = ListViewContentItem;
103 const auto childItems = contentItem->childItems();
104 for (QQuickItem *childItem : childItems) {
105 QQuickItem *item = determineViewType(childItem);
112 viewContentItemType = UnsupportedContentItemType;
140void QQuickTumblerPrivate::_q_updateItemHeights()
147 Q_Q(
const QQuickTumbler);
148 const qreal itemHeight = delegateHeight(q);
149 const auto items = viewContentItemChildItems();
150 for (QQuickItem *childItem : items)
151 childItem->setHeight(itemHeight);
154void QQuickTumblerPrivate::_q_updateItemWidths()
159 Q_Q(
const QQuickTumbler);
160 const qreal availableWidth = q->availableWidth();
161 const auto items = viewContentItemChildItems();
162 for (QQuickItem *childItem : items)
163 childItem->setWidth(availableWidth);
166void QQuickTumblerPrivate::_q_onViewCurrentIndexChanged()
169 if (!view || ignoreCurrentIndexChanges || currentIndexSetDuringModelChange) {
173 qCDebug(lcTumbler).nospace() <<
"view currentIndex changed to "
174 << (view ? view->property(
"currentIndex").toString() : QStringLiteral(
"unknown index (no view)"))
175 <<
", but we're ignoring it because one or more of the following conditions are true:"
176 <<
"\n- !view: " << !view
177 <<
"\n- ignoreCurrentIndexChanges: " << ignoreCurrentIndexChanges
178 <<
"\n- currentIndexSetDuringModelChange: " << currentIndexSetDuringModelChange;
182 const int oldCurrentIndex = currentIndex;
183 currentIndex = view->property(
"currentIndex").toInt();
185 qCDebug(lcTumbler).nospace() <<
"view currentIndex changed to "
186 << (view ? view->property(
"currentIndex").toString() : QStringLiteral(
"unknown index (no view)"))
187 <<
", our old currentIndex was " << oldCurrentIndex;
189 if (oldCurrentIndex != currentIndex)
190 emit q->currentIndexChanged();
193void QQuickTumblerPrivate::_q_onViewCountChanged()
196 qCDebug(lcTumbler) <<
"view count changed - ignoring signals?" << ignoreSignals;
200 setCount(view->property(
"count").toInt());
203 if (pendingCurrentIndex != -1) {
206 setCurrentIndex(pendingCurrentIndex);
209 if (currentIndex == pendingCurrentIndex)
210 setPendingCurrentIndex(-1);
213 }
else if (currentIndex == -1) {
235void QQuickTumblerPrivate::calculateDisplacements()
237 const auto items = viewContentItemChildItems();
238 for (QQuickItem *childItem : items) {
239 QQuickTumblerAttached *attached = qobject_cast<QQuickTumblerAttached *>(qmlAttachedPropertiesObject<QQuickTumbler>(childItem,
false));
241 QQuickTumblerAttachedPrivate::get(attached)->calculateDisplacement();
257void QQuickTumblerPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change,
const QRectF &diff)
259 QQuickControlPrivate::itemGeometryChanged(item, change, diff);
260 if (change.sizeChange())
261 calculateDisplacements();
269QQuickTumbler::QQuickTumbler(QQuickItem *parent)
270 : QQuickControl(*(
new QQuickTumblerPrivate), parent)
273 d->setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Preferred);
275 setActiveFocusOnTab(
true);
277 connect(
this, SIGNAL(leftPaddingChanged()),
this, SLOT(_q_updateItemWidths()));
278 connect(
this, SIGNAL(rightPaddingChanged()),
this, SLOT(_q_updateItemWidths()));
279 connect(
this, SIGNAL(topPaddingChanged()),
this, SLOT(_q_updateItemHeights()));
280 connect(
this, SIGNAL(bottomPaddingChanged()),
this, SLOT(_q_updateItemHeights()));
301void QQuickTumbler::setModel(
const QVariant &model)
304 if (model == d->model)
314 if (d->view && d->currentIndexSetDuringModelChange) {
315 const int viewCurrentIndex = d->view->property(
"currentIndex").toInt();
316 if (viewCurrentIndex != d->currentIndex)
317 d->view->setProperty(
"currentIndex", d->currentIndex);
320 d->currentIndexSetDuringModelChange =
false;
324 if (isComponentComplete() && d->view && count() == 0)
325 d->setCurrentIndex(-1);
413void QQuickTumbler::setVisibleItemCount(
int visibleItemCount)
416 if (visibleItemCount == d->visibleItemCount)
419 d->visibleItemCount = visibleItemCount;
420 d->_q_updateItemHeights();
421 emit visibleItemCountChanged();
498void QQuickTumbler::positionViewAtIndex(
int index, QQuickTumbler::PositionMode mode)
530void QQuickTumbler::setFlickDeceleration(qreal flickDeceleration)
533 const qreal oldFlickDeceleration = d->effectiveFlickDeceleration();
534 flickDeceleration = qMax(0.001, flickDeceleration);
535 d->flickDeceleration = flickDeceleration;
536 if (!qFuzzyCompare(oldFlickDeceleration, flickDeceleration))
537 emit flickDecelerationChanged();
540void QQuickTumbler::resetFlickDeceleration()
543 const qreal oldFlickDeceleration = d->effectiveFlickDeceleration();
544 d->flickDeceleration = 0.0;
545 if (!qFuzzyCompare(oldFlickDeceleration, d->effectiveFlickDeceleration()))
546 emit flickDecelerationChanged();
549void QQuickTumbler::geometryChange(
const QRectF &newGeometry,
const QRectF &oldGeometry)
553 QQuickControl::geometryChange(newGeometry, oldGeometry);
555 d->_q_updateItemHeights();
557 if (newGeometry.width() != oldGeometry.width())
558 d->_q_updateItemWidths();
561void QQuickTumbler::componentComplete()
564 qCDebug(lcTumbler) <<
"componentComplete()";
565 QQuickControl::componentComplete();
569 qCDebug(lcTumbler) <<
"emitting wrapChanged() to force view to be created";
572 d->setupViewData(d->contentItem);
582 d->_q_updateItemHeights();
583 d->_q_updateItemWidths();
584 d->_q_onViewCountChanged();
586 qCDebug(lcTumbler) <<
"componentComplete() is done";
589void QQuickTumbler::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem)
593 QQuickControl::contentItemChange(newItem, oldItem);
596 d->disconnectFromView();
601 if (isComponentComplete()) {
604 d->setupViewData(newItem);
609void QQuickTumblerPrivate::disconnectFromView()
620 QObject::disconnect(view, SIGNAL(currentIndexChanged()), q, SLOT(_q_onViewCurrentIndexChanged()));
621 QObject::disconnect(view, SIGNAL(currentItemChanged()), q, SIGNAL(currentItemChanged()));
622 QObject::disconnect(view, SIGNAL(countChanged()), q, SLOT(_q_onViewCountChanged()));
623 QObject::disconnect(view, SIGNAL(movingChanged()), q, SIGNAL(movingChanged()));
625 if (viewContentItemType == PathViewContentItem)
626 QObject::disconnect(view, SIGNAL(offsetChanged()), q, SLOT(_q_onViewOffsetChanged()));
628 QObject::disconnect(view, SIGNAL(contentYChanged()), q, SLOT(_q_onViewContentYChanged()));
630 QQuickItemPrivate *oldViewContentItemPrivate = QQuickItemPrivate::get(viewContentItem);
631 oldViewContentItemPrivate->removeItemChangeListener(
this, QQuickItemPrivate::Children | QQuickItemPrivate::Geometry);
636void QQuickTumblerPrivate::setupViewData(QQuickItem *newControlContentItem)
642 determineViewType(newControlContentItem);
644 if (viewContentItemType == QQuickTumblerPrivate::NoContentItem)
647 if (viewContentItemType == QQuickTumblerPrivate::UnsupportedContentItemType) {
648 warnAboutIncorrectContentItem();
653 QObject::connect(view, SIGNAL(currentIndexChanged()), q, SLOT(_q_onViewCurrentIndexChanged()));
654 QObject::connect(view, SIGNAL(currentItemChanged()), q, SIGNAL(currentItemChanged()));
655 QObject::connect(view, SIGNAL(countChanged()), q, SLOT(_q_onViewCountChanged()));
656 QObject::connect(view, SIGNAL(movingChanged()), q, SIGNAL(movingChanged()));
658 if (viewContentItemType == PathViewContentItem) {
659 QObject::connect(view, SIGNAL(offsetChanged()), q, SLOT(_q_onViewOffsetChanged()));
660 _q_onViewOffsetChanged();
662 QObject::connect(view, SIGNAL(contentYChanged()), q, SLOT(_q_onViewContentYChanged()));
663 _q_onViewContentYChanged();
666 QQuickItemPrivate *viewContentItemPrivate = QQuickItemPrivate::get(viewContentItem);
667 viewContentItemPrivate->addItemChangeListener(
this, QQuickItemPrivate::Children | QQuickItemPrivate::Geometry);
672 calculateDisplacements();
674 if (q->isComponentComplete()) {
675 _q_updateItemWidths();
676 _q_updateItemHeights();
686void QQuickTumblerPrivate::syncCurrentIndex()
688 const int actualViewIndex = view->property(
"currentIndex").toInt();
691 const bool isPendingCurrentIndex = pendingCurrentIndex != -1;
692 const int indexToSet = isPendingCurrentIndex ? pendingCurrentIndex : currentIndex;
695 if (actualViewIndex == indexToSet) {
696 setPendingCurrentIndex(-1);
702 if (q->count() == 0 && actualViewIndex <= 0)
705 ignoreCurrentIndexChanges =
true;
706 view->setProperty(
"currentIndex", QVariant(indexToSet));
707 ignoreCurrentIndexChanges =
false;
709 if (view->property(
"currentIndex").toInt() == indexToSet)
710 setPendingCurrentIndex(-1);
711 else if (isPendingCurrentIndex)
727void QQuickTumblerPrivate::setCurrentIndex(
int newCurrentIndex,
728 QQuickTumblerPrivate::PropertyChangeReason changeReason)
731 qCDebug(lcTumbler).nospace() <<
"setting currentIndex to " << newCurrentIndex
732 <<
", old currentIndex was " << currentIndex
733 <<
", changeReason is " << propertyChangeReasonToString(changeReason);
734 if (newCurrentIndex == currentIndex || newCurrentIndex < -1)
737 if (!q->isComponentComplete()) {
739 qCDebug(lcTumbler) <<
"we're not complete; setting pendingCurrentIndex instead";
740 setPendingCurrentIndex(newCurrentIndex);
744 if (modelBeingSet && changeReason == UserChange) {
749 qCDebug(lcTumbler) <<
"a model is being set; setting pendingCurrentIndex instead";
750 setPendingCurrentIndex(newCurrentIndex);
758 if ((count > 0 && newCurrentIndex == -1) || (newCurrentIndex >= count)) {
767 bool couldSet =
false;
768 if (count == 0 && newCurrentIndex == -1) {
772 ignoreCurrentIndexChanges =
true;
773 ignoreSignals =
true;
774 view->setProperty(
"currentIndex", newCurrentIndex);
775 ignoreSignals =
false;
776 ignoreCurrentIndexChanges =
false;
778 couldSet = view->property(
"currentIndex").toInt() == newCurrentIndex;
784 currentIndex = newCurrentIndex;
785 emit q->currentIndexChanged();
788 qCDebug(lcTumbler) <<
"view's currentIndex is now" << view->property(
"currentIndex").toInt()
789 <<
"and ours is" << currentIndex;
816void QQuickTumblerPrivate::setWrap(
bool shouldWrap, QQml::PropertyUtils::State propertyState)
818 if (isExplicitlySet(propertyState))
820 qCDebug(lcTumbler) <<
"setting wrap to" << shouldWrap <<
"- explicit?" << explicitWrap;
823 if (q->isComponentComplete() && shouldWrap == wrap)
829 const int oldCurrentIndex = currentIndex;
832 const qreal oldFlickDeceleration = effectiveFlickDeceleration();
834 disconnectFromView();
840 ignoreCurrentIndexChanges =
true;
843 emit q->wrapChanged();
845 ignoreCurrentIndexChanges =
false;
856 if (q->isComponentComplete() || contentItem)
857 setupViewData(contentItem);
859 setCurrentIndex(oldCurrentIndex);
861 if (effectiveFlickDeceleration() != oldFlickDeceleration)
862 emit q->flickDecelerationChanged();
883void QQuickTumbler::keyPressEvent(QKeyEvent *event)
885 QQuickControl::keyPressEvent(event);
888 if (event->isAutoRepeat() || !d->view)
891 if (event->key() == Qt::Key_Up) {
892 QMetaObject::invokeMethod(d->view,
"decrementCurrentIndex");
893 }
else if (event->key() == Qt::Key_Down) {
894 QMetaObject::invokeMethod(d->view,
"incrementCurrentIndex");
898void QQuickTumbler::updatePolish()
901 if (d->pendingCurrentIndex != -1) {
904 d->setCount(d->view->property(
"count").toInt());
908 d->setPendingCurrentIndex(-1);
915 d->setCurrentIndex(d->pendingCurrentIndex);
917 if (d->currentIndex != d->pendingCurrentIndex && d->currentIndex == -1) {
920 d->setCurrentIndex(0);
923 d->setPendingCurrentIndex(-1);
932void QQuickTumblerAttachedPrivate::init(QQuickItem *delegateItem)
934 Q_Q(QQuickTumblerAttached);
935 if (!delegateItem->parentItem()) {
936 qmlWarning(q) <<
"Tumbler: attached properties must be accessed through a delegate item that has a parent";
940 QVariant indexContextProperty = qmlContext(delegateItem)->contextProperty(QStringLiteral(
"index"));
941 if (!indexContextProperty.isValid()) {
942 qmlWarning(q) <<
"Tumbler: attempting to access attached property on item without an \"index\" property";
946 index = indexContextProperty.toInt();
948 QQuickItem *parentItem = delegateItem;
949 while ((parentItem = parentItem->parentItem())) {
950 if ((tumbler = qobject_cast<QQuickTumbler*>(parentItem)))
955void QQuickTumblerAttachedPrivate::calculateDisplacement()
957 const qreal previousDisplacement = displacement;
967 QQuickTumblerPrivate *tumblerPrivate = QQuickTumblerPrivate::get(tumbler);
968 if (!tumblerPrivate->viewContentItem) {
969 emitIfDisplacementChanged(previousDisplacement, displacement);
975 const int count = tumblerPrivate->view->property(
"count").toInt();
978 emitIfDisplacementChanged(previousDisplacement, displacement);
982 if (tumblerPrivate->viewContentItemType == QQuickTumblerPrivate::PathViewContentItem) {
983 const qreal offset = tumblerPrivate->viewOffset;
985 displacement = count > 1 ? count - index - offset : 0;
987 const int visibleItems = tumbler->visibleItemCount();
988 const int halfVisibleItems = visibleItems / 2 + (visibleItems < count ? 1 : 0);
989 if (displacement > halfVisibleItems)
990 displacement -= count;
991 else if (displacement < -halfVisibleItems)
992 displacement += count;
994 const qreal contentY = tumblerPrivate->viewContentY;
995 const qreal delegateH = delegateHeight(tumbler);
996 const qreal preferredHighlightBegin = tumblerPrivate->view->property(
"preferredHighlightBegin").toReal();
997 const qreal itemY = qobject_cast<QQuickItem*>(parent)->y();
998 qreal currentItemY = 0;
999 auto currentItem = tumblerPrivate->view->property(
"currentItem").value<QQuickItem*>();
1001 currentItemY = currentItem->y();
1003 const qreal topOfCurrentItemInViewport = currentItemY - contentY;
1005 const qreal relativePositionToPreferredHighlightBegin = topOfCurrentItemInViewport - preferredHighlightBegin;
1007 const qreal distanceFromCurrentItem = currentItemY - itemY;
1008 const qreal displacementInPixels = distanceFromCurrentItem - relativePositionToPreferredHighlightBegin;
1010 displacement = displacementInPixels / delegateH;
1013 emitIfDisplacementChanged(previousDisplacement, displacement);
1016void QQuickTumblerAttachedPrivate::emitIfDisplacementChanged(qreal oldDisplacement, qreal newDisplacement)
1018 Q_Q(QQuickTumblerAttached);
1019 if (newDisplacement != oldDisplacement)
1020 emit q->displacementChanged();
1023QQuickTumblerAttached::QQuickTumblerAttached(QObject *parent)
1024 : QObject(*(
new QQuickTumblerAttachedPrivate), parent)
1026 Q_D(QQuickTumblerAttached);
1027 QQuickItem *delegateItem = qobject_cast<QQuickItem *>(parent);
1029 d->init(delegateItem);
1031 qmlWarning(parent) <<
"Tumbler: attached properties of Tumbler must be accessed through a delegate item";
1039 QQuickTumblerPrivate *tumblerPrivate = QQuickTumblerPrivate::get(d->tumbler);
1040 tumblerPrivate->setupViewData(tumblerPrivate->contentItem);
1042 if (delegateItem && delegateItem->parentItem() == tumblerPrivate->viewContentItem) {
1046 d->calculateDisplacement();