diff options
Diffstat (limited to 'src/quicktemplates2/qquicktumbler.cpp')
-rw-r--r-- | src/quicktemplates2/qquicktumbler.cpp | 1045 |
1 files changed, 0 insertions, 1045 deletions
diff --git a/src/quicktemplates2/qquicktumbler.cpp b/src/quicktemplates2/qquicktumbler.cpp deleted file mode 100644 index e8fbcbd5..00000000 --- a/src/quicktemplates2/qquicktumbler.cpp +++ /dev/null @@ -1,1045 +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; - } - - // PathView likes to use 0 as currentIndex for empty models, but we 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->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" |