aboutsummaryrefslogtreecommitdiffstats
path: root/src/quicktemplates2/qquicktumbler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quicktemplates2/qquicktumbler.cpp')
-rw-r--r--src/quicktemplates2/qquicktumbler.cpp1046
1 files changed, 0 insertions, 1046 deletions
diff --git a/src/quicktemplates2/qquicktumbler.cpp b/src/quicktemplates2/qquicktumbler.cpp
deleted file mode 100644
index 4f7d3487..00000000
--- a/src/quicktemplates2/qquicktumbler.cpp
+++ /dev/null
@@ -1,1046 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2017 The Qt Company Ltd.
-** Contact: http://www.qt.io/licensing/
-**
-** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL3$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see http://www.qt.io/terms-conditions. For further
-** information use the contact form at http://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPLv3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or later as published by the Free
-** Software Foundation and appearing in the file LICENSE.GPL included in
-** the packaging of this file. Please review the following information to
-** ensure the GNU General Public License version 2.0 requirements will be
-** met: http://www.gnu.org/licenses/gpl-2.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include "qquicktumbler_p.h"
-
-#include <QtCore/qloggingcategory.h>
-#include <QtGui/qpa/qplatformtheme.h>
-#include <QtQml/qqmlinfo.h>
-#include <QtQuick/private/qquickflickable_p.h>
-#include <QtQuickTemplates2/private/qquickcontrol_p_p.h>
-#include <QtQuickTemplates2/private/qquicktumbler_p_p.h>
-
-QT_BEGIN_NAMESPACE
-
-Q_LOGGING_CATEGORY(lcTumbler, "qt.quick.controls.tumbler")
-
-/*!
- \qmltype Tumbler
- \inherits Control
-//! \instantiates QQuickTumbler
- \inqmlmodule QtQuick.Controls
- \since 5.7
- \ingroup qtquickcontrols2-input
- \brief Spinnable wheel of items that can be selected.
-
- \image qtquickcontrols2-tumbler-wrap.gif
-
- \code
- Tumbler {
- model: 5
- // ...
- }
- \endcode
-
- Tumbler allows the user to select an option from a spinnable \e "wheel" of
- items. It is useful for when there are too many options to use, for
- example, a RadioButton, and too few options to require the use of an
- editable SpinBox. It is convenient in that it requires no keyboard usage
- and wraps around at each end when there are a large number of items.
-
- The API is similar to that of views like \l ListView and \l PathView; a
- \l model and \l delegate can be set, and the \l count and \l currentItem
- properties provide read-only access to information about the view. To
- position the view at a certain index, use \l positionViewAtIndex().
-
- Unlike views like \l PathView and \l ListView, however, there is always a
- current item (when the model isn't empty). This means that when \l count is
- equal to \c 0, \l currentIndex will be \c -1. In all other cases, it will
- be greater than or equal to \c 0.
-
- By default, Tumbler \l {wrap}{wraps} when it reaches the top and bottom, as
- long as there are more items in the model than there are visible items;
- that is, when \l count is greater than \l visibleItemCount:
-
- \snippet qtquickcontrols2-tumbler-timePicker.qml tumbler
-
- \sa {Customizing Tumbler}, {Input Controls}
-*/
-
-namespace {
- static inline qreal delegateHeight(const QQuickTumbler *tumbler)
- {
- return tumbler->availableHeight() / tumbler->visibleItemCount();
- }
-}
-
-/*
- Finds the contentItem of the view that is a child of the control's \a contentItem.
- The type is stored in \a type.
-*/
-QQuickItem *QQuickTumblerPrivate::determineViewType(QQuickItem *contentItem)
-{
- if (!contentItem) {
- resetViewData();
- return nullptr;
- }
-
- if (contentItem->inherits("QQuickPathView")) {
- view = contentItem;
- viewContentItem = contentItem;
- viewContentItemType = PathViewContentItem;
- viewOffset = 0;
-
- return contentItem;
- } else if (contentItem->inherits("QQuickListView")) {
- view = contentItem;
- viewContentItem = qobject_cast<QQuickFlickable*>(contentItem)->contentItem();
- viewContentItemType = ListViewContentItem;
- viewContentY = 0;
-
- return contentItem;
- } else {
- const auto childItems = contentItem->childItems();
- for (QQuickItem *childItem : childItems) {
- QQuickItem *item = determineViewType(childItem);
- if (item)
- return item;
- }
- }
-
- resetViewData();
- viewContentItemType = UnsupportedContentItemType;
- return nullptr;
-}
-
-void QQuickTumblerPrivate::resetViewData()
-{
- view = nullptr;
- viewContentItem = nullptr;
- if (viewContentItemType == PathViewContentItem)
- viewOffset = 0;
- else if (viewContentItemType == ListViewContentItem)
- viewContentY = 0;
- viewContentItemType = NoContentItem;
-}
-
-QList<QQuickItem *> QQuickTumblerPrivate::viewContentItemChildItems() const
-{
- if (!viewContentItem)
- return QList<QQuickItem *>();
-
- return viewContentItem->childItems();
-}
-
-QQuickTumblerPrivate *QQuickTumblerPrivate::get(QQuickTumbler *tumbler)
-{
- return tumbler->d_func();
-}
-
-void QQuickTumblerPrivate::_q_updateItemHeights()
-{
- if (ignoreSignals)
- return;
-
- // Can't use our own private padding members here, as the padding property might be set,
- // which doesn't affect them, only their getters.
- Q_Q(const QQuickTumbler);
- const qreal itemHeight = delegateHeight(q);
- const auto items = viewContentItemChildItems();
- for (QQuickItem *childItem : items)
- childItem->setHeight(itemHeight);
-}
-
-void QQuickTumblerPrivate::_q_updateItemWidths()
-{
- if (ignoreSignals)
- return;
-
- Q_Q(const QQuickTumbler);
- const qreal availableWidth = q->availableWidth();
- const auto items = viewContentItemChildItems();
- for (QQuickItem *childItem : items)
- childItem->setWidth(availableWidth);
-}
-
-void QQuickTumblerPrivate::_q_onViewCurrentIndexChanged()
-{
- Q_Q(QQuickTumbler);
- if (!view || ignoreCurrentIndexChanges || currentIndexSetDuringModelChange) {
- // If the user set currentIndex in the onModelChanged handler,
- // we have to respect that currentIndex by ignoring changes in the view
- // until the model has finished being set.
- qCDebug(lcTumbler).nospace() << "view currentIndex changed to "
- << (view ? view->property("currentIndex").toString() : QStringLiteral("unknown index (no view)"))
- << ", but we're ignoring it because one or more of the following conditions are true:"
- << "\n- !view: " << !view
- << "\n- ignoreCurrentIndexChanges: " << ignoreCurrentIndexChanges
- << "\n- currentIndexSetDuringModelChange: " << currentIndexSetDuringModelChange;
- return;
- }
-
- const int oldCurrentIndex = currentIndex;
- currentIndex = view->property("currentIndex").toInt();
-
- qCDebug(lcTumbler).nospace() << "view currentIndex changed to "
- << (view ? view->property("currentIndex").toString() : QStringLiteral("unknown index (no view)"))
- << ", our old currentIndex was " << oldCurrentIndex;
-
- if (oldCurrentIndex != currentIndex)
- emit q->currentIndexChanged();
-}
-
-void QQuickTumblerPrivate::_q_onViewCountChanged()
-{
- Q_Q(QQuickTumbler);
- qCDebug(lcTumbler) << "view count changed - ignoring signals?" << ignoreSignals;
- if (ignoreSignals)
- return;
-
- setCount(view->property("count").toInt());
-
- if (count > 0) {
- if (pendingCurrentIndex != -1) {
- // If there was an attempt to set currentIndex at creation, try to finish that attempt now.
- // componentComplete() is too early, because the count might only be known sometime after completion.
- setCurrentIndex(pendingCurrentIndex);
- // If we could successfully set the currentIndex, consider it done.
- // Otherwise, we'll try again later in updatePolish().
- if (currentIndex == pendingCurrentIndex)
- setPendingCurrentIndex(-1);
- else
- q->polish();
- } else if (currentIndex == -1) {
- // If new items were added and our currentIndex was -1, we must
- // enforce our rule of a non-negative currentIndex when count > 0.
- setCurrentIndex(0);
- }
- } else {
- setCurrentIndex(-1);
- }
-}
-
-void QQuickTumblerPrivate::_q_onViewOffsetChanged()
-{
- viewOffset = view->property("offset").toReal();
- calculateDisplacements();
-}
-
-void QQuickTumblerPrivate::_q_onViewContentYChanged()
-{
- viewContentY = view->property("contentY").toReal();
- calculateDisplacements();
-}
-
-void QQuickTumblerPrivate::calculateDisplacements()
-{
- const auto items = viewContentItemChildItems();
- for (QQuickItem *childItem : items) {
- QQuickTumblerAttached *attached = qobject_cast<QQuickTumblerAttached *>(qmlAttachedPropertiesObject<QQuickTumbler>(childItem, false));
- if (attached)
- QQuickTumblerAttachedPrivate::get(attached)->calculateDisplacement();
- }
-}
-
-void QQuickTumblerPrivate::itemChildAdded(QQuickItem *, QQuickItem *)
-{
- _q_updateItemWidths();
- _q_updateItemHeights();
-}
-
-void QQuickTumblerPrivate::itemChildRemoved(QQuickItem *, QQuickItem *)
-{
- _q_updateItemWidths();
- _q_updateItemHeights();
-}
-
-void QQuickTumblerPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff)
-{
- QQuickControlPrivate::itemGeometryChanged(item, change, diff);
- if (change.sizeChange())
- calculateDisplacements();
-}
-
-QPalette QQuickTumblerPrivate::defaultPalette() const
-{
- return QQuickTheme::palette(QQuickTheme::Tumbler);
-}
-
-QQuickTumbler::QQuickTumbler(QQuickItem *parent)
- : QQuickControl(*(new QQuickTumblerPrivate), parent)
-{
- setActiveFocusOnTab(true);
-
- connect(this, SIGNAL(leftPaddingChanged()), this, SLOT(_q_updateItemWidths()));
- connect(this, SIGNAL(rightPaddingChanged()), this, SLOT(_q_updateItemWidths()));
- connect(this, SIGNAL(topPaddingChanged()), this, SLOT(_q_updateItemHeights()));
- connect(this, SIGNAL(bottomPaddingChanged()), this, SLOT(_q_updateItemHeights()));
-}
-
-QQuickTumbler::~QQuickTumbler()
-{
- Q_D(QQuickTumbler);
- // Ensure that the item change listener is removed.
- d->disconnectFromView();
-}
-
-/*!
- \qmlproperty variant QtQuick.Controls::Tumbler::model
-
- This property holds the model that provides data for this tumbler.
-*/
-QVariant QQuickTumbler::model() const
-{
- Q_D(const QQuickTumbler);
- return d->model;
-}
-
-void QQuickTumbler::setModel(const QVariant &model)
-{
- Q_D(QQuickTumbler);
- if (model == d->model)
- return;
-
- d->beginSetModel();
-
- d->model = model;
- emit modelChanged();
-
- d->endSetModel();
-
- d->currentIndexSetDuringModelChange = false;
-
- // Don't try to correct the currentIndex if count() isn't known yet.
- // We can check in setupViewData() instead.
- if (isComponentComplete() && d->view && count() == 0)
- d->setCurrentIndex(-1);
-}
-
-/*!
- \qmlproperty int QtQuick.Controls::Tumbler::count
- \readonly
-
- This property holds the number of items in the model.
-*/
-int QQuickTumbler::count() const
-{
- Q_D(const QQuickTumbler);
- return d->count;
-}
-
-/*!
- \qmlproperty int QtQuick.Controls::Tumbler::currentIndex
-
- This property holds the index of the current item.
-
- The value of this property is \c -1 when \l count is equal to \c 0. In all
- other cases, it will be greater than or equal to \c 0.
-
- \sa currentItem, positionViewAtIndex()
-*/
-int QQuickTumbler::currentIndex() const
-{
- Q_D(const QQuickTumbler);
- return d->currentIndex;
-}
-
-void QQuickTumbler::setCurrentIndex(int currentIndex)
-{
- Q_D(QQuickTumbler);
- if (d->modelBeingSet)
- d->currentIndexSetDuringModelChange = true;
- d->setCurrentIndex(currentIndex, QQuickTumblerPrivate::UserChange);
-}
-
-/*!
- \qmlproperty Item QtQuick.Controls::Tumbler::currentItem
- \readonly
-
- This property holds the item at the current index.
-
- \sa currentIndex, positionViewAtIndex()
-*/
-QQuickItem *QQuickTumbler::currentItem() const
-{
- Q_D(const QQuickTumbler);
- return d->view ? d->view->property("currentItem").value<QQuickItem*>() : nullptr;
-}
-
-/*!
- \qmlproperty Component QtQuick.Controls::Tumbler::delegate
-
- This property holds the delegate used to display each item.
-*/
-QQmlComponent *QQuickTumbler::delegate() const
-{
- Q_D(const QQuickTumbler);
- return d->delegate;
-}
-
-void QQuickTumbler::setDelegate(QQmlComponent *delegate)
-{
- Q_D(QQuickTumbler);
- if (delegate == d->delegate)
- return;
-
- d->delegate = delegate;
- emit delegateChanged();
-}
-
-/*!
- \qmlproperty int QtQuick.Controls::Tumbler::visibleItemCount
-
- This property holds the number of items visible in the tumbler. It must be
- an odd number, as the current item is always vertically centered.
-*/
-int QQuickTumbler::visibleItemCount() const
-{
- Q_D(const QQuickTumbler);
- return d->visibleItemCount;
-}
-
-void QQuickTumbler::setVisibleItemCount(int visibleItemCount)
-{
- Q_D(QQuickTumbler);
- if (visibleItemCount == d->visibleItemCount)
- return;
-
- d->visibleItemCount = visibleItemCount;
- d->_q_updateItemHeights();
- emit visibleItemCountChanged();
-}
-
-QQuickTumblerAttached *QQuickTumbler::qmlAttachedProperties(QObject *object)
-{
- return new QQuickTumblerAttached(object);
-}
-
-/*!
- \qmlproperty bool QtQuick.Controls::Tumbler::wrap
- \since QtQuick.Controls 2.1 (Qt 5.8)
-
- This property determines whether or not the tumbler wraps around when it
- reaches the top or bottom.
-
- The default value is \c false when \l count is less than
- \l visibleItemCount, as it is simpler to interact with a non-wrapping Tumbler
- when there are only a few items. To override this behavior, explicitly set
- the value of this property. To return to the default behavior, set this
- property to \c undefined.
-*/
-bool QQuickTumbler::wrap() const
-{
- Q_D(const QQuickTumbler);
- return d->wrap;
-}
-
-void QQuickTumbler::setWrap(bool wrap)
-{
- Q_D(QQuickTumbler);
- d->setWrap(wrap, true);
-}
-
-void QQuickTumbler::resetWrap()
-{
- Q_D(QQuickTumbler);
- d->explicitWrap = false;
- d->setWrapBasedOnCount();
-}
-
-/*!
- \qmlproperty bool QtQuick.Controls::Tumbler::moving
- \since QtQuick.Controls 2.2 (Qt 5.9)
-
- This property describes whether the tumbler is currently moving, due to
- the user either dragging or flicking it.
-*/
-bool QQuickTumbler::isMoving() const
-{
- Q_D(const QQuickTumbler);
- return d->view && d->view->property("moving").toBool();
-}
-
-/*!
- \qmlmethod void QtQuick.Controls::Tumbler::positionViewAtIndex(int index, PositionMode mode)
- \since QtQuick.Controls 2.5 (Qt 5.12)
-
- Positions the view so that the \a index is at the position specified by \a mode.
-
- For example:
-
- \code
- positionViewAtIndex(10, Tumbler.Center)
- \endcode
-
- If \l wrap is true (the default), the modes available to \l {PathView}'s
- \l {PathView::}{positionViewAtIndex()} function
- are available, otherwise the modes available to \l {ListView}'s
- \l {ListView::}{positionViewAtIndex()} function
- are available.
-
- \note There is a known limitation that using \c Tumbler.Beginning when \l
- wrap is \c true will result in the wrong item being positioned at the top
- of view. As a workaround, pass \c {index - 1}.
-
- \sa currentIndex
-*/
-void QQuickTumbler::positionViewAtIndex(int index, QQuickTumbler::PositionMode mode)
-{
- Q_D(QQuickTumbler);
- if (!d->view) {
- d->warnAboutIncorrectContentItem();
- return;
- }
-
- QMetaObject::invokeMethod(d->view, "positionViewAtIndex", Q_ARG(int, index), Q_ARG(int, mode));
-}
-
-void QQuickTumbler::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
-{
- Q_D(QQuickTumbler);
-
- QQuickControl::geometryChange(newGeometry, oldGeometry);
-
- d->_q_updateItemHeights();
-
- if (newGeometry.width() != oldGeometry.width())
- d->_q_updateItemWidths();
-}
-
-void QQuickTumbler::componentComplete()
-{
- Q_D(QQuickTumbler);
- qCDebug(lcTumbler) << "componentComplete()";
- QQuickControl::componentComplete();
-
- if (!d->view) {
- // Force the view to be created.
- qCDebug(lcTumbler) << "emitting wrapChanged() to force view to be created";
- emit wrapChanged();
- // Determine the type of view for attached properties, etc.
- d->setupViewData(d->contentItem);
- }
-
- // If there was no contentItem or it was of an unsupported type,
- // we don't have anything else to do.
- if (!d->view)
- return;
-
- // Update item heights after we've populated the model,
- // otherwise ignoreSignals will cause these functions to return early.
- d->_q_updateItemHeights();
- d->_q_updateItemWidths();
- d->_q_onViewCountChanged();
-
- qCDebug(lcTumbler) << "componentComplete() is done";
-}
-
-void QQuickTumbler::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem)
-{
- Q_D(QQuickTumbler);
-
- QQuickControl::contentItemChange(newItem, oldItem);
-
- if (oldItem)
- d->disconnectFromView();
-
- if (newItem) {
- // We wait until wrap is set to that we know which type of view to create.
- // If we try to set up the view too early, we'll issue warnings about it not existing.
- if (isComponentComplete()) {
- // Make sure we use the new content item and not the current one, as that won't
- // be changed until after contentItemChange() has finished.
- d->setupViewData(newItem);
-
- d->_q_updateItemHeights();
- d->_q_updateItemWidths();
- }
- }
-}
-
-void QQuickTumblerPrivate::disconnectFromView()
-{
- Q_Q(QQuickTumbler);
- if (!view) {
- // If a custom content item is declared, it can happen that
- // the original contentItem exists without the view etc. having been
- // determined yet, and then this is called when the custom content item
- // is eventually set.
- return;
- }
-
- QObject::disconnect(view, SIGNAL(currentIndexChanged()), q, SLOT(_q_onViewCurrentIndexChanged()));
- QObject::disconnect(view, SIGNAL(currentItemChanged()), q, SIGNAL(currentItemChanged()));
- QObject::disconnect(view, SIGNAL(countChanged()), q, SLOT(_q_onViewCountChanged()));
- QObject::disconnect(view, SIGNAL(movingChanged()), q, SIGNAL(movingChanged()));
-
- if (viewContentItemType == PathViewContentItem)
- QObject::disconnect(view, SIGNAL(offsetChanged()), q, SLOT(_q_onViewOffsetChanged()));
- else
- QObject::disconnect(view, SIGNAL(contentYChanged()), q, SLOT(_q_onViewContentYChanged()));
-
- QQuickItemPrivate *oldViewContentItemPrivate = QQuickItemPrivate::get(viewContentItem);
- oldViewContentItemPrivate->removeItemChangeListener(this, QQuickItemPrivate::Children | QQuickItemPrivate::Geometry);
-
- resetViewData();
-}
-
-void QQuickTumblerPrivate::setupViewData(QQuickItem *newControlContentItem)
-{
- // Don't do anything if we've already set up.
- if (view)
- return;
-
- determineViewType(newControlContentItem);
-
- if (viewContentItemType == QQuickTumblerPrivate::NoContentItem)
- return;
-
- if (viewContentItemType == QQuickTumblerPrivate::UnsupportedContentItemType) {
- warnAboutIncorrectContentItem();
- return;
- }
-
- Q_Q(QQuickTumbler);
- QObject::connect(view, SIGNAL(currentIndexChanged()), q, SLOT(_q_onViewCurrentIndexChanged()));
- QObject::connect(view, SIGNAL(currentItemChanged()), q, SIGNAL(currentItemChanged()));
- QObject::connect(view, SIGNAL(countChanged()), q, SLOT(_q_onViewCountChanged()));
- QObject::connect(view, SIGNAL(movingChanged()), q, SIGNAL(movingChanged()));
-
- if (viewContentItemType == PathViewContentItem) {
- QObject::connect(view, SIGNAL(offsetChanged()), q, SLOT(_q_onViewOffsetChanged()));
- _q_onViewOffsetChanged();
- } else {
- QObject::connect(view, SIGNAL(contentYChanged()), q, SLOT(_q_onViewContentYChanged()));
- _q_onViewContentYChanged();
- }
-
- QQuickItemPrivate *viewContentItemPrivate = QQuickItemPrivate::get(viewContentItem);
- viewContentItemPrivate->addItemChangeListener(this, QQuickItemPrivate::Children | QQuickItemPrivate::Geometry);
-
- // Sync the view's currentIndex with ours.
- syncCurrentIndex();
-
- calculateDisplacements();
-}
-
-void QQuickTumblerPrivate::warnAboutIncorrectContentItem()
-{
- Q_Q(QQuickTumbler);
- qmlWarning(q) << "Tumbler: contentItem must contain either a PathView or a ListView";
-}
-
-void QQuickTumblerPrivate::syncCurrentIndex()
-{
- const int actualViewIndex = view->property("currentIndex").toInt();
- Q_Q(QQuickTumbler);
-
- const bool isPendingCurrentIndex = pendingCurrentIndex != -1;
- const int indexToSet = isPendingCurrentIndex ? pendingCurrentIndex : currentIndex;
-
- // Nothing to do.
- if (actualViewIndex == indexToSet) {
- setPendingCurrentIndex(-1);
- return;
- }
-
- // actualViewIndex might be 0 or -1 for PathView and ListView respectively,
- // but we always use -1 for that.
- if (q->count() == 0 && actualViewIndex <= 0)
- return;
-
- ignoreCurrentIndexChanges = true;
- view->setProperty("currentIndex", QVariant(indexToSet));
- ignoreCurrentIndexChanges = false;
-
- if (view->property("currentIndex").toInt() == indexToSet)
- setPendingCurrentIndex(-1);
- else if (isPendingCurrentIndex)
- q->polish();
-}
-
-void QQuickTumblerPrivate::setPendingCurrentIndex(int index)
-{
- qCDebug(lcTumbler) << "setting pendingCurrentIndex to" << index;
- pendingCurrentIndex = index;
-}
-
-QString QQuickTumblerPrivate::propertyChangeReasonToString(
- QQuickTumblerPrivate::PropertyChangeReason changeReason)
-{
- return changeReason == UserChange ? QStringLiteral("UserChange") : QStringLiteral("InternalChange");
-}
-
-void QQuickTumblerPrivate::setCurrentIndex(int newCurrentIndex,
- QQuickTumblerPrivate::PropertyChangeReason changeReason)
-{
- Q_Q(QQuickTumbler);
- qCDebug(lcTumbler).nospace() << "setting currentIndex to " << newCurrentIndex
- << ", old currentIndex was " << currentIndex
- << ", changeReason is " << propertyChangeReasonToString(changeReason);
- if (newCurrentIndex == currentIndex || newCurrentIndex < -1)
- return;
-
- if (!q->isComponentComplete()) {
- // Views can't set currentIndex until they're ready.
- qCDebug(lcTumbler) << "we're not complete; setting pendingCurrentIndex instead";
- setPendingCurrentIndex(newCurrentIndex);
- return;
- }
-
- if (modelBeingSet && changeReason == UserChange) {
- // If modelBeingSet is true and the user set the currentIndex,
- // the model is in the process of being set and the user has set
- // the currentIndex in onModelChanged. We have to queue the currentIndex
- // change until we're ready.
- qCDebug(lcTumbler) << "a model is being set; setting pendingCurrentIndex instead";
- setPendingCurrentIndex(newCurrentIndex);
- return;
- }
-
- // -1 doesn't make sense for a non-empty Tumbler, because unlike
- // e.g. ListView, there's always one item selected.
- // Wait until the component has finished before enforcing this rule, though,
- // because the count might not be known yet.
- if ((count > 0 && newCurrentIndex == -1) || (newCurrentIndex >= count)) {
- return;
- }
-
- // The view might not have been created yet, as is the case
- // if you create a Tumbler component and pass e.g. { currentIndex: 2 }
- // to createObject().
- if (view) {
- // Only actually set our currentIndex if the view was able to set theirs.
- bool couldSet = false;
- if (count == 0 && newCurrentIndex == -1) {
- // PathView insists on using 0 as the currentIndex when there are no items.
- couldSet = true;
- } else {
- ignoreCurrentIndexChanges = true;
- ignoreSignals = true;
- view->setProperty("currentIndex", newCurrentIndex);
- ignoreSignals = false;
- ignoreCurrentIndexChanges = false;
-
- couldSet = view->property("currentIndex").toInt() == newCurrentIndex;
- }
-
- if (couldSet) {
- // The view's currentIndex might not have actually changed, but ours has,
- // and that's what user code sees.
- currentIndex = newCurrentIndex;
- emit q->currentIndexChanged();
- }
-
- qCDebug(lcTumbler) << "view's currentIndex is now" << view->property("currentIndex").toInt()
- << "and ours is" << currentIndex;
- }
-}
-
-void QQuickTumblerPrivate::setCount(int newCount)
-{
- qCDebug(lcTumbler).nospace() << "setting count to " << newCount
- << ", old count was " << count;
- if (newCount == count)
- return;
-
- count = newCount;
-
- Q_Q(QQuickTumbler);
- setWrapBasedOnCount();
-
- emit q->countChanged();
-}
-
-void QQuickTumblerPrivate::setWrapBasedOnCount()
-{
- if (count == 0 || explicitWrap || modelBeingSet)
- return;
-
- setWrap(count >= visibleItemCount, false);
-}
-
-void QQuickTumblerPrivate::setWrap(bool shouldWrap, bool isExplicit)
-{
- qCDebug(lcTumbler) << "setting wrap to" << shouldWrap << "- exlicit?" << isExplicit;
- if (isExplicit)
- explicitWrap = true;
-
- Q_Q(QQuickTumbler);
- if (q->isComponentComplete() && shouldWrap == wrap)
- return;
-
- // Since we use the currentIndex of the contentItem directly, we must
- // ensure that we keep track of the currentIndex so it doesn't get lost
- // between view changes.
- const int oldCurrentIndex = currentIndex;
-
- disconnectFromView();
-
- wrap = shouldWrap;
-
- // New views will set their currentIndex upon creation, which we'd otherwise
- // take as the correct one, so we must ignore them.
- ignoreCurrentIndexChanges = true;
-
- // This will cause the view to be created if our contentItem is a TumblerView.
- emit q->wrapChanged();
-
- ignoreCurrentIndexChanges = false;
-
- // If isComponentComplete() is true, we require a contentItem. If it's not
- // true, it might not have been created yet, so we wait until
- // componentComplete() is called.
- //
- // When the contentItem (usually QQuickTumblerView) has been created, we
- // can start determining its type, etc. If the delegates use attached
- // properties, this will have already been called, in which case it will
- // return early. If the delegate doesn't use attached properties, we need
- // to call it here.
- if (q->isComponentComplete() || contentItem)
- setupViewData(contentItem);
-
- setCurrentIndex(oldCurrentIndex);
-}
-
-void QQuickTumblerPrivate::beginSetModel()
-{
- modelBeingSet = true;
-}
-
-void QQuickTumblerPrivate::endSetModel()
-{
- modelBeingSet = false;
- setWrapBasedOnCount();
-}
-
-void QQuickTumbler::keyPressEvent(QKeyEvent *event)
-{
- QQuickControl::keyPressEvent(event);
-
- Q_D(QQuickTumbler);
- if (event->isAutoRepeat() || !d->view)
- return;
-
- if (event->key() == Qt::Key_Up) {
- QMetaObject::invokeMethod(d->view, "decrementCurrentIndex");
- } else if (event->key() == Qt::Key_Down) {
- QMetaObject::invokeMethod(d->view, "incrementCurrentIndex");
- }
-}
-
-void QQuickTumbler::updatePolish()
-{
- Q_D(QQuickTumbler);
- if (d->pendingCurrentIndex != -1) {
- // Update our count, as ignoreSignals might have been true
- // when _q_onViewCountChanged() was last called.
- d->setCount(d->view->property("count").toInt());
-
- // If the count is still 0, it's not going to happen.
- if (d->count == 0) {
- d->setPendingCurrentIndex(-1);
- return;
- }
-
- // If there is a pending currentIndex at this stage, it means that
- // the view wouldn't set our currentIndex in _q_onViewCountChanged
- // because it wasn't ready. Try one last time here.
- d->setCurrentIndex(d->pendingCurrentIndex);
-
- if (d->currentIndex != d->pendingCurrentIndex && d->currentIndex == -1) {
- // If we *still* couldn't set it, it's probably invalid.
- // See if we can at least enforce our rule of "non-negative currentIndex when count > 0" instead.
- d->setCurrentIndex(0);
- }
-
- d->setPendingCurrentIndex(-1);
- }
-}
-
-QFont QQuickTumbler::defaultFont() const
-{
- return QQuickTheme::font(QQuickTheme::Tumbler);
-}
-
-void QQuickTumblerAttachedPrivate::init(QQuickItem *delegateItem)
-{
- if (!delegateItem->parentItem()) {
- qWarning() << "Tumbler: attached properties must be accessed through a delegate item that has a parent";
- return;
- }
-
- QVariant indexContextProperty = qmlContext(delegateItem)->contextProperty(QStringLiteral("index"));
- if (!indexContextProperty.isValid()) {
- qWarning() << "Tumbler: attempting to access attached property on item without an \"index\" property";
- return;
- }
-
- index = indexContextProperty.toInt();
-
- QQuickItem *parentItem = delegateItem;
- while ((parentItem = parentItem->parentItem())) {
- if ((tumbler = qobject_cast<QQuickTumbler*>(parentItem)))
- break;
- }
-}
-
-void QQuickTumblerAttachedPrivate::calculateDisplacement()
-{
- const qreal previousDisplacement = displacement;
- displacement = 0;
-
- if (!tumbler) {
- // Can happen if the attached properties are accessed on the wrong type of item or the tumbler was destroyed.
- // We don't want to emit the change signal though, as this could cause warnings about Tumbler.tumbler being null.
- return;
- }
-
- // Can happen if there is no ListView or PathView within the contentItem.
- QQuickTumblerPrivate *tumblerPrivate = QQuickTumblerPrivate::get(tumbler);
- if (!tumblerPrivate->viewContentItem) {
- emitIfDisplacementChanged(previousDisplacement, displacement);
- return;
- }
-
- // The attached property gets created before our count is updated, so just cheat here
- // to avoid having to listen to count changes.
- const int count = tumblerPrivate->view->property("count").toInt();
- // This can happen in tests, so it may happen in normal usage too.
- if (count == 0) {
- emitIfDisplacementChanged(previousDisplacement, displacement);
- return;
- }
-
- if (tumblerPrivate->viewContentItemType == QQuickTumblerPrivate::PathViewContentItem) {
- const qreal offset = tumblerPrivate->viewOffset;
-
- displacement = count > 1 ? count - index - offset : 0;
- // Don't add 1 if count <= visibleItemCount
- const int visibleItems = tumbler->visibleItemCount();
- const int halfVisibleItems = visibleItems / 2 + (visibleItems < count ? 1 : 0);
- if (displacement > halfVisibleItems)
- displacement -= count;
- else if (displacement < -halfVisibleItems)
- displacement += count;
- } else {
- const qreal contentY = tumblerPrivate->viewContentY;
- const qreal delegateH = delegateHeight(tumbler);
- const qreal preferredHighlightBegin = tumblerPrivate->view->property("preferredHighlightBegin").toReal();
- const qreal itemY = qobject_cast<QQuickItem*>(parent)->y();
- qreal currentItemY = 0;
- auto currentItem = tumblerPrivate->view->property("currentItem").value<QQuickItem*>();
- if (currentItem)
- currentItemY = currentItem->y();
- // Start from the y position of the current item.
- const qreal topOfCurrentItemInViewport = currentItemY - contentY;
- // Then, calculate the distance between it and the preferredHighlightBegin.
- const qreal relativePositionToPreferredHighlightBegin = topOfCurrentItemInViewport - preferredHighlightBegin;
- // Next, calculate the distance between us and the current item.
- const qreal distanceFromCurrentItem = currentItemY - itemY;
- const qreal displacementInPixels = distanceFromCurrentItem - relativePositionToPreferredHighlightBegin;
- // Convert it from pixels to a floating point index.
- displacement = displacementInPixels / delegateH;
- }
-
- emitIfDisplacementChanged(previousDisplacement, displacement);
-}
-
-void QQuickTumblerAttachedPrivate::emitIfDisplacementChanged(qreal oldDisplacement, qreal newDisplacement)
-{
- Q_Q(QQuickTumblerAttached);
- if (newDisplacement != oldDisplacement)
- emit q->displacementChanged();
-}
-
-QQuickTumblerAttached::QQuickTumblerAttached(QObject *parent)
- : QObject(*(new QQuickTumblerAttachedPrivate), parent)
-{
- Q_D(QQuickTumblerAttached);
- QQuickItem *delegateItem = qobject_cast<QQuickItem *>(parent);
- if (delegateItem)
- d->init(delegateItem);
- else if (parent)
- qmlWarning(parent) << "Tumbler: attached properties of Tumbler must be accessed through a delegate item";
-
- if (d->tumbler) {
- // When the Tumbler is completed, wrapChanged() is emitted to let QQuickTumblerView
- // know that it can create the view. The view itself might instantiate delegates
- // that use attached properties. At this point, setupViewData() hasn't been called yet
- // (it's called on the next line in componentComplete()), so we call it here so that
- // we have access to the view.
- QQuickTumblerPrivate *tumblerPrivate = QQuickTumblerPrivate::get(d->tumbler);
- tumblerPrivate->setupViewData(tumblerPrivate->contentItem);
-
- if (delegateItem && delegateItem->parentItem() == tumblerPrivate->viewContentItem) {
- // This item belongs to the "new" view, meaning that the tumbler's contentItem
- // was probably assigned declaratively. If they're not equal, calling
- // calculateDisplacement() would use the old contentItem data, which is bad.
- d->calculateDisplacement();
- }
- }
-}
-
-/*!
- \qmlattachedproperty Tumbler QtQuick.Controls::Tumbler::tumbler
- \readonly
-
- This attached property holds the tumbler. The property can be attached to
- a tumbler delegate. The value is \c null if the item is not a tumbler delegate.
-*/
-QQuickTumbler *QQuickTumblerAttached::tumbler() const
-{
- Q_D(const QQuickTumblerAttached);
- return d->tumbler;
-}
-
-/*!
- \qmlattachedproperty real QtQuick.Controls::Tumbler::displacement
- \readonly
-
- This attached property holds a value from \c {-visibleItemCount / 2} to
- \c {visibleItemCount / 2}, which represents how far away this item is from
- being the current item, with \c 0 being completely current.
-
- For example, the item below will be 40% opaque when it is not the current item,
- and transition to 100% opacity when it becomes the current item:
-
- \code
- delegate: Text {
- text: modelData
- opacity: 0.4 + Math.max(0, 1 - Math.abs(Tumbler.displacement)) * 0.6
- }
- \endcode
-*/
-qreal QQuickTumblerAttached::displacement() const
-{
- Q_D(const QQuickTumblerAttached);
- return d->displacement;
-}
-
-QT_END_NAMESPACE
-
-#include "moc_qquicktumbler_p.cpp"