80QQuickItem *QQuickTumblerPrivate::determineViewType(QQuickItem *contentItem)
87 if (contentItem->inherits(
"QQuickPathView")) {
89 viewContentItem = contentItem;
90 viewContentItemType = PathViewContentItem;
94 }
else if (contentItem->inherits(
"QQuickListView")) {
96 viewContentItem = qobject_cast<QQuickFlickable*>(contentItem)->contentItem();
97 viewContentItemType = ListViewContentItem;
102 const auto childItems = contentItem->childItems();
103 for (QQuickItem *childItem : childItems) {
104 QQuickItem *item = determineViewType(childItem);
111 viewContentItemType = UnsupportedContentItemType;
139void QQuickTumblerPrivate::_q_updateItemHeights()
146 Q_Q(
const QQuickTumbler);
147 const qreal itemHeight = delegateHeight(q);
148 const auto items = viewContentItemChildItems();
149 for (QQuickItem *childItem : items)
150 childItem->setHeight(itemHeight);
153void QQuickTumblerPrivate::_q_updateItemWidths()
158 Q_Q(
const QQuickTumbler);
159 const qreal availableWidth = q->availableWidth();
160 const auto items = viewContentItemChildItems();
161 for (QQuickItem *childItem : items)
162 childItem->setWidth(availableWidth);
165void QQuickTumblerPrivate::_q_onViewCurrentIndexChanged()
168 if (!view || ignoreCurrentIndexChanges || currentIndexSetDuringModelChange) {
172 qCDebug(lcTumbler).nospace() <<
"view currentIndex changed to "
173 << (view ? view->property(
"currentIndex").toString() : QStringLiteral(
"unknown index (no view)"))
174 <<
", but we're ignoring it because one or more of the following conditions are true:"
175 <<
"\n- !view: " << !view
176 <<
"\n- ignoreCurrentIndexChanges: " << ignoreCurrentIndexChanges
177 <<
"\n- currentIndexSetDuringModelChange: " << currentIndexSetDuringModelChange;
181 const int oldCurrentIndex = currentIndex;
182 currentIndex = view->property(
"currentIndex").toInt();
184 qCDebug(lcTumbler).nospace() <<
"view currentIndex changed to "
185 << (view ? view->property(
"currentIndex").toString() : QStringLiteral(
"unknown index (no view)"))
186 <<
", our old currentIndex was " << oldCurrentIndex;
188 if (oldCurrentIndex != currentIndex)
189 emit q->currentIndexChanged();
192void QQuickTumblerPrivate::_q_onViewCountChanged()
195 qCDebug(lcTumbler) <<
"view count changed - ignoring signals?" << ignoreSignals;
199 setCount(view->property(
"count").toInt());
202 if (pendingCurrentIndex != -1) {
205 setCurrentIndex(pendingCurrentIndex);
208 if (currentIndex == pendingCurrentIndex)
209 setPendingCurrentIndex(-1);
212 }
else if (currentIndex == -1) {
234void QQuickTumblerPrivate::calculateDisplacements()
236 const auto items = viewContentItemChildItems();
237 for (QQuickItem *childItem : items) {
238 QQuickTumblerAttached *attached = qobject_cast<QQuickTumblerAttached *>(qmlAttachedPropertiesObject<QQuickTumbler>(childItem,
false));
240 QQuickTumblerAttachedPrivate::get(attached)->calculateDisplacement();
256void QQuickTumblerPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change,
const QRectF &diff)
258 QQuickControlPrivate::itemGeometryChanged(item, change, diff);
259 if (change.sizeChange())
260 calculateDisplacements();
268QQuickTumbler::QQuickTumbler(QQuickItem *parent)
269 : QQuickControl(*(
new QQuickTumblerPrivate), parent)
272 d->setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Preferred);
274 setActiveFocusOnTab(
true);
276 connect(
this, SIGNAL(leftPaddingChanged()),
this, SLOT(_q_updateItemWidths()));
277 connect(
this, SIGNAL(rightPaddingChanged()),
this, SLOT(_q_updateItemWidths()));
278 connect(
this, SIGNAL(topPaddingChanged()),
this, SLOT(_q_updateItemHeights()));
279 connect(
this, SIGNAL(bottomPaddingChanged()),
this, SLOT(_q_updateItemHeights()));
300void QQuickTumbler::setModel(
const QVariant &model)
303 if (model == d->model)
313 if (d->view && d->currentIndexSetDuringModelChange) {
314 const int viewCurrentIndex = d->view->property(
"currentIndex").toInt();
315 if (viewCurrentIndex != d->currentIndex)
316 d->view->setProperty(
"currentIndex", d->currentIndex);
319 d->currentIndexSetDuringModelChange =
false;
323 if (isComponentComplete() && d->view && count() == 0)
324 d->setCurrentIndex(-1);
412void QQuickTumbler::setVisibleItemCount(
int visibleItemCount)
415 if (visibleItemCount == d->visibleItemCount)
418 d->visibleItemCount = visibleItemCount;
419 d->_q_updateItemHeights();
420 emit visibleItemCountChanged();
497void QQuickTumbler::positionViewAtIndex(
int index, QQuickTumbler::PositionMode mode)
529void QQuickTumbler::setFlickDeceleration(qreal flickDeceleration)
532 const qreal oldFlickDeceleration = d->effectiveFlickDeceleration();
533 flickDeceleration = qMax(0.001, flickDeceleration);
534 d->flickDeceleration = flickDeceleration;
535 if (!qFuzzyCompare(oldFlickDeceleration, flickDeceleration))
536 emit flickDecelerationChanged();
539void QQuickTumbler::resetFlickDeceleration()
542 const qreal oldFlickDeceleration = d->effectiveFlickDeceleration();
543 d->flickDeceleration = 0.0;
544 if (!qFuzzyCompare(oldFlickDeceleration, d->effectiveFlickDeceleration()))
545 emit flickDecelerationChanged();
548void QQuickTumbler::geometryChange(
const QRectF &newGeometry,
const QRectF &oldGeometry)
552 QQuickControl::geometryChange(newGeometry, oldGeometry);
554 d->_q_updateItemHeights();
556 if (newGeometry.width() != oldGeometry.width())
557 d->_q_updateItemWidths();
560void QQuickTumbler::componentComplete()
563 qCDebug(lcTumbler) <<
"componentComplete()";
564 QQuickControl::componentComplete();
568 qCDebug(lcTumbler) <<
"emitting wrapChanged() to force view to be created";
571 d->setupViewData(d->contentItem);
581 d->_q_updateItemHeights();
582 d->_q_updateItemWidths();
583 d->_q_onViewCountChanged();
585 qCDebug(lcTumbler) <<
"componentComplete() is done";
588void QQuickTumbler::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem)
592 QQuickControl::contentItemChange(newItem, oldItem);
595 d->disconnectFromView();
600 if (isComponentComplete()) {
603 d->setupViewData(newItem);
608void QQuickTumblerPrivate::disconnectFromView()
619 QObject::disconnect(view, SIGNAL(currentIndexChanged()), q, SLOT(_q_onViewCurrentIndexChanged()));
620 QObject::disconnect(view, SIGNAL(currentItemChanged()), q, SIGNAL(currentItemChanged()));
621 QObject::disconnect(view, SIGNAL(countChanged()), q, SLOT(_q_onViewCountChanged()));
622 QObject::disconnect(view, SIGNAL(movingChanged()), q, SIGNAL(movingChanged()));
624 if (viewContentItemType == PathViewContentItem)
625 QObject::disconnect(view, SIGNAL(offsetChanged()), q, SLOT(_q_onViewOffsetChanged()));
627 QObject::disconnect(view, SIGNAL(contentYChanged()), q, SLOT(_q_onViewContentYChanged()));
629 QQuickItemPrivate *oldViewContentItemPrivate = QQuickItemPrivate::get(viewContentItem);
630 oldViewContentItemPrivate->removeItemChangeListener(
this, QQuickItemPrivate::Children | QQuickItemPrivate::Geometry);
635void QQuickTumblerPrivate::setupViewData(QQuickItem *newControlContentItem)
641 determineViewType(newControlContentItem);
643 if (viewContentItemType == QQuickTumblerPrivate::NoContentItem)
646 if (viewContentItemType == QQuickTumblerPrivate::UnsupportedContentItemType) {
647 warnAboutIncorrectContentItem();
652 QObject::connect(view, SIGNAL(currentIndexChanged()), q, SLOT(_q_onViewCurrentIndexChanged()));
653 QObject::connect(view, SIGNAL(currentItemChanged()), q, SIGNAL(currentItemChanged()));
654 QObject::connect(view, SIGNAL(countChanged()), q, SLOT(_q_onViewCountChanged()));
655 QObject::connect(view, SIGNAL(movingChanged()), q, SIGNAL(movingChanged()));
657 if (viewContentItemType == PathViewContentItem) {
658 QObject::connect(view, SIGNAL(offsetChanged()), q, SLOT(_q_onViewOffsetChanged()));
659 _q_onViewOffsetChanged();
661 QObject::connect(view, SIGNAL(contentYChanged()), q, SLOT(_q_onViewContentYChanged()));
662 _q_onViewContentYChanged();
665 QQuickItemPrivate *viewContentItemPrivate = QQuickItemPrivate::get(viewContentItem);
666 viewContentItemPrivate->addItemChangeListener(
this, QQuickItemPrivate::Children | QQuickItemPrivate::Geometry);
671 calculateDisplacements();
673 if (q->isComponentComplete()) {
674 _q_updateItemWidths();
675 _q_updateItemHeights();
685void QQuickTumblerPrivate::syncCurrentIndex()
687 const int actualViewIndex = view->property(
"currentIndex").toInt();
690 const bool isPendingCurrentIndex = pendingCurrentIndex != -1;
691 const int indexToSet = isPendingCurrentIndex ? pendingCurrentIndex : currentIndex;
694 if (actualViewIndex == indexToSet) {
695 setPendingCurrentIndex(-1);
701 if (q->count() == 0 && actualViewIndex <= 0)
704 ignoreCurrentIndexChanges =
true;
705 view->setProperty(
"currentIndex", QVariant(indexToSet));
706 ignoreCurrentIndexChanges =
false;
708 if (view->property(
"currentIndex").toInt() == indexToSet)
709 setPendingCurrentIndex(-1);
710 else if (isPendingCurrentIndex)
726void QQuickTumblerPrivate::setCurrentIndex(
int newCurrentIndex,
727 QQuickTumblerPrivate::PropertyChangeReason changeReason)
730 qCDebug(lcTumbler).nospace() <<
"setting currentIndex to " << newCurrentIndex
731 <<
", old currentIndex was " << currentIndex
732 <<
", changeReason is " << propertyChangeReasonToString(changeReason);
733 if (newCurrentIndex == currentIndex || newCurrentIndex < -1)
736 if (!q->isComponentComplete()) {
738 qCDebug(lcTumbler) <<
"we're not complete; setting pendingCurrentIndex instead";
739 setPendingCurrentIndex(newCurrentIndex);
743 if (modelBeingSet && changeReason == UserChange) {
748 qCDebug(lcTumbler) <<
"a model is being set; setting pendingCurrentIndex instead";
749 setPendingCurrentIndex(newCurrentIndex);
757 if ((count > 0 && newCurrentIndex == -1) || (newCurrentIndex >= count)) {
766 bool couldSet =
false;
767 if (count == 0 && newCurrentIndex == -1) {
771 ignoreCurrentIndexChanges =
true;
772 ignoreSignals =
true;
773 view->setProperty(
"currentIndex", newCurrentIndex);
774 ignoreSignals =
false;
775 ignoreCurrentIndexChanges =
false;
777 couldSet = view->property(
"currentIndex").toInt() == newCurrentIndex;
783 currentIndex = newCurrentIndex;
784 emit q->currentIndexChanged();
787 qCDebug(lcTumbler) <<
"view's currentIndex is now" << view->property(
"currentIndex").toInt()
788 <<
"and ours is" << currentIndex;
815void QQuickTumblerPrivate::setWrap(
bool shouldWrap, QQml::PropertyUtils::State propertyState)
817 if (isExplicitlySet(propertyState))
819 qCDebug(lcTumbler) <<
"setting wrap to" << shouldWrap <<
"- explicit?" << explicitWrap;
822 if (q->isComponentComplete() && shouldWrap == wrap)
828 const int oldCurrentIndex = currentIndex;
831 const qreal oldFlickDeceleration = effectiveFlickDeceleration();
833 disconnectFromView();
839 ignoreCurrentIndexChanges =
true;
842 emit q->wrapChanged();
844 ignoreCurrentIndexChanges =
false;
855 if (q->isComponentComplete() || contentItem)
856 setupViewData(contentItem);
858 setCurrentIndex(oldCurrentIndex);
860 if (effectiveFlickDeceleration() != oldFlickDeceleration)
861 emit q->flickDecelerationChanged();
882void QQuickTumbler::keyPressEvent(QKeyEvent *event)
884 QQuickControl::keyPressEvent(event);
887 if (event->isAutoRepeat() || !d->view)
890 if (event->key() == Qt::Key_Up) {
891 QMetaObject::invokeMethod(d->view,
"decrementCurrentIndex");
892 }
else if (event->key() == Qt::Key_Down) {
893 QMetaObject::invokeMethod(d->view,
"incrementCurrentIndex");
897void QQuickTumbler::updatePolish()
900 if (d->pendingCurrentIndex != -1) {
903 d->setCount(d->view->property(
"count").toInt());
907 d->setPendingCurrentIndex(-1);
914 d->setCurrentIndex(d->pendingCurrentIndex);
916 if (d->currentIndex != d->pendingCurrentIndex && d->currentIndex == -1) {
919 d->setCurrentIndex(0);
922 d->setPendingCurrentIndex(-1);
931void QQuickTumblerAttachedPrivate::init(QQuickItem *delegateItem)
933 Q_Q(QQuickTumblerAttached);
934 if (!delegateItem->parentItem()) {
935 qmlWarning(q) <<
"Tumbler: attached properties must be accessed through a delegate item that has a parent";
939 QVariant indexContextProperty = qmlContext(delegateItem)->contextProperty(QStringLiteral(
"index"));
940 if (!indexContextProperty.isValid()) {
941 qmlWarning(q) <<
"Tumbler: attempting to access attached property on item without an \"index\" property";
945 index = indexContextProperty.toInt();
947 QQuickItem *parentItem = delegateItem;
948 while ((parentItem = parentItem->parentItem())) {
949 if ((tumbler = qobject_cast<QQuickTumbler*>(parentItem)))
954void QQuickTumblerAttachedPrivate::calculateDisplacement()
956 const qreal previousDisplacement = displacement;
966 QQuickTumblerPrivate *tumblerPrivate = QQuickTumblerPrivate::get(tumbler);
967 if (!tumblerPrivate->viewContentItem) {
968 emitIfDisplacementChanged(previousDisplacement, displacement);
974 const int count = tumblerPrivate->view->property(
"count").toInt();
977 emitIfDisplacementChanged(previousDisplacement, displacement);
981 if (tumblerPrivate->viewContentItemType == QQuickTumblerPrivate::PathViewContentItem) {
982 const qreal offset = tumblerPrivate->viewOffset;
984 displacement = count > 1 ? count - index - offset : 0;
986 const int visibleItems = tumbler->visibleItemCount();
987 const int halfVisibleItems = visibleItems / 2 + (visibleItems < count ? 1 : 0);
988 if (displacement > halfVisibleItems)
989 displacement -= count;
990 else if (displacement < -halfVisibleItems)
991 displacement += count;
993 const qreal contentY = tumblerPrivate->viewContentY;
994 const qreal delegateH = delegateHeight(tumbler);
995 const qreal preferredHighlightBegin = tumblerPrivate->view->property(
"preferredHighlightBegin").toReal();
996 const qreal itemY = qobject_cast<QQuickItem*>(parent)->y();
997 qreal currentItemY = 0;
998 auto currentItem = tumblerPrivate->view->property(
"currentItem").value<QQuickItem*>();
1000 currentItemY = currentItem->y();
1002 const qreal topOfCurrentItemInViewport = currentItemY - contentY;
1004 const qreal relativePositionToPreferredHighlightBegin = topOfCurrentItemInViewport - preferredHighlightBegin;
1006 const qreal distanceFromCurrentItem = currentItemY - itemY;
1007 const qreal displacementInPixels = distanceFromCurrentItem - relativePositionToPreferredHighlightBegin;
1009 displacement = displacementInPixels / delegateH;
1012 emitIfDisplacementChanged(previousDisplacement, displacement);
1015void QQuickTumblerAttachedPrivate::emitIfDisplacementChanged(qreal oldDisplacement, qreal newDisplacement)
1017 Q_Q(QQuickTumblerAttached);
1018 if (newDisplacement != oldDisplacement)
1019 emit q->displacementChanged();
1022QQuickTumblerAttached::QQuickTumblerAttached(QObject *parent)
1023 : QObject(*(
new QQuickTumblerAttachedPrivate), parent)
1025 Q_D(QQuickTumblerAttached);
1026 QQuickItem *delegateItem = qobject_cast<QQuickItem *>(parent);
1028 d->init(delegateItem);
1030 qmlWarning(parent) <<
"Tumbler: attached properties of Tumbler must be accessed through a delegate item";
1038 QQuickTumblerPrivate *tumblerPrivate = QQuickTumblerPrivate::get(d->tumbler);
1039 tumblerPrivate->setupViewData(tumblerPrivate->contentItem);
1041 if (delegateItem && delegateItem->parentItem() == tumblerPrivate->viewContentItem) {
1045 d->calculateDisplacement();