| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtQuick module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** 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 https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://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.LGPL3 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-3.0.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 (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qquicklistview_p.h" |
| #include "qquickitemview_p_p.h" |
| |
| #include <private/qqmlobjectmodel_p.h> |
| #include <QtQml/qqmlexpression.h> |
| #include <QtQml/qqmlengine.h> |
| #include <QtQml/qqmlinfo.h> |
| #include <QtGui/qevent.h> |
| #include <QtCore/qcoreapplication.h> |
| #include <QtCore/qmath.h> |
| |
| #include <private/qquicksmoothedanimation_p_p.h> |
| #include "qplatformdefs.h" |
| |
| QT_BEGIN_NAMESPACE |
| |
| #ifndef QML_FLICK_SNAPONETHRESHOLD |
| #define QML_FLICK_SNAPONETHRESHOLD 30 |
| #endif |
| |
| class FxListItemSG; |
| |
| class QQuickListViewPrivate : public QQuickItemViewPrivate |
| { |
| Q_DECLARE_PUBLIC(QQuickListView) |
| public: |
| static QQuickListViewPrivate* get(QQuickListView *item) { return item->d_func(); } |
| |
| Qt::Orientation layoutOrientation() const override; |
| bool isContentFlowReversed() const override; |
| bool isRightToLeft() const; |
| bool isBottomToTop() const; |
| |
| qreal positionAt(int index) const override; |
| qreal endPositionAt(int index) const override; |
| qreal originPosition() const override; |
| qreal lastPosition() const override; |
| |
| FxViewItem *itemBefore(int modelIndex) const; |
| QString sectionAt(int modelIndex); |
| qreal snapPosAt(qreal pos); |
| FxViewItem *snapItemAt(qreal pos); |
| |
| void init() override; |
| void clear(bool onDestruction) override; |
| |
| bool addVisibleItems(qreal fillFrom, qreal fillTo, qreal bufferFrom, qreal bufferTo, bool doBuffer) override; |
| bool removeNonVisibleItems(qreal bufferFrom, qreal bufferTo) override; |
| void visibleItemsChanged() override; |
| |
| void removeItem(FxViewItem *item); |
| |
| FxViewItem *newViewItem(int index, QQuickItem *item) override; |
| void initializeViewItem(FxViewItem *item) override; |
| bool releaseItem(FxViewItem *item) override; |
| void repositionItemAt(FxViewItem *item, int index, qreal sizeBuffer) override; |
| void repositionPackageItemAt(QQuickItem *item, int index) override; |
| void resetFirstItemPosition(qreal pos = 0.0) override; |
| void adjustFirstItem(qreal forwards, qreal backwards, int) override; |
| void updateSizeChangesBeforeVisiblePos(FxViewItem *item, ChangeResult *removeResult) override; |
| |
| void createHighlight(bool onDestruction = false) override; |
| void updateHighlight() override; |
| void resetHighlightPosition() override; |
| bool movingFromHighlight() override; |
| |
| void setPosition(qreal pos) override; |
| void layoutVisibleItems(int fromModelIndex = 0) override; |
| |
| bool applyInsertionChange(const QQmlChangeSet::Change &insert, ChangeResult *changeResult, QList<FxViewItem *> *addedItems, QList<MovedItem> *movingIntoView) override; |
| void translateAndTransitionItemsAfter(int afterIndex, const ChangeResult &insertionResult, const ChangeResult &removalResult) override; |
| |
| void updateSectionCriteria() override; |
| void updateSections() override; |
| QQuickItem *getSectionItem(const QString §ion); |
| void releaseSectionItem(QQuickItem *item); |
| void releaseSectionItems(); |
| void updateInlineSection(FxListItemSG *); |
| void updateCurrentSection(); |
| void updateStickySections(); |
| |
| qreal headerSize() const override; |
| qreal footerSize() const override; |
| bool showHeaderForIndex(int index) const override; |
| bool showFooterForIndex(int index) const override; |
| void updateHeader() override; |
| void updateFooter() override; |
| bool hasStickyHeader() const override; |
| bool hasStickyFooter() const override; |
| |
| void changedVisibleIndex(int newIndex) override; |
| void initializeCurrentItem() override; |
| |
| void updateAverage(); |
| |
| void itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &oldGeometry) override; |
| void fixupPosition() override; |
| void fixup(AxisData &data, qreal minExtent, qreal maxExtent) override; |
| bool flick(QQuickItemViewPrivate::AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize, |
| QQuickTimeLineCallback::Callback fixupCallback, qreal velocity) override; |
| |
| QQuickListView::Orientation orient; |
| qreal visiblePos; |
| qreal averageSize; |
| qreal spacing; |
| QQuickListView::SnapMode snapMode; |
| |
| QQuickListView::HeaderPositioning headerPositioning; |
| QQuickListView::FooterPositioning footerPositioning; |
| |
| QSmoothedAnimation *highlightPosAnimator; |
| QSmoothedAnimation *highlightWidthAnimator; |
| QSmoothedAnimation *highlightHeightAnimator; |
| qreal highlightMoveVelocity; |
| qreal highlightResizeVelocity; |
| int highlightResizeDuration; |
| |
| QQuickViewSection *sectionCriteria; |
| QString currentSection; |
| static const int sectionCacheSize = 5; |
| QQuickItem *sectionCache[sectionCacheSize]; |
| QQuickItem *currentSectionItem; |
| QString currentStickySection; |
| QQuickItem *nextSectionItem; |
| QString nextStickySection; |
| QString lastVisibleSection; |
| QString nextSection; |
| |
| qreal overshootDist; |
| bool correctFlick : 1; |
| bool inFlickCorrection : 1; |
| |
| QQuickListViewPrivate() |
| : orient(QQuickListView::Vertical) |
| , visiblePos(0) |
| , averageSize(100.0), spacing(0.0) |
| , snapMode(QQuickListView::NoSnap) |
| , headerPositioning(QQuickListView::InlineHeader) |
| , footerPositioning(QQuickListView::InlineFooter) |
| , highlightPosAnimator(nullptr), highlightWidthAnimator(nullptr), highlightHeightAnimator(nullptr) |
| , highlightMoveVelocity(400), highlightResizeVelocity(400), highlightResizeDuration(-1) |
| , sectionCriteria(nullptr), currentSectionItem(nullptr), nextSectionItem(nullptr) |
| , overshootDist(0.0), correctFlick(false), inFlickCorrection(false) |
| { |
| highlightMoveDuration = -1; //override default value set in base class |
| } |
| ~QQuickListViewPrivate() { |
| delete highlightPosAnimator; |
| delete highlightWidthAnimator; |
| delete highlightHeightAnimator; |
| } |
| |
| friend class QQuickViewSection; |
| }; |
| |
| //---------------------------------------------------------------------------- |
| |
| QQuickViewSection::QQuickViewSection(QQuickListView *parent) |
| : QObject(parent), m_criteria(FullString), m_delegate(nullptr), m_labelPositioning(InlineLabels) |
| , m_view(parent ? QQuickListViewPrivate::get(parent) : nullptr) |
| { |
| } |
| |
| void QQuickViewSection::setProperty(const QString &property) |
| { |
| if (property != m_property) { |
| m_property = property; |
| emit propertyChanged(); |
| // notify view that the contents of the sections must be recalculated |
| m_view->updateSectionCriteria(); |
| } |
| } |
| |
| void QQuickViewSection::setCriteria(QQuickViewSection::SectionCriteria criteria) |
| { |
| if (criteria != m_criteria) { |
| m_criteria = criteria; |
| emit criteriaChanged(); |
| // notify view that the contents of the sections must be recalculated |
| m_view->updateSectionCriteria(); |
| } |
| } |
| |
| void QQuickViewSection::setDelegate(QQmlComponent *delegate) |
| { |
| if (delegate != m_delegate) { |
| if (m_delegate) |
| m_view->releaseSectionItems(); |
| m_delegate = delegate; |
| emit delegateChanged(); |
| m_view->forceLayoutPolish(); |
| } |
| } |
| |
| QString QQuickViewSection::sectionString(const QString &value) |
| { |
| if (m_criteria == FirstCharacter) |
| return value.isEmpty() ? QString() : value.at(0); |
| else |
| return value; |
| } |
| |
| void QQuickViewSection::setLabelPositioning(int l) |
| { |
| if (m_labelPositioning != l) { |
| m_labelPositioning = l; |
| emit labelPositioningChanged(); |
| m_view->forceLayoutPolish(); |
| } |
| } |
| |
| //---------------------------------------------------------------------------- |
| |
| class FxListItemSG : public FxViewItem |
| { |
| public: |
| FxListItemSG(QQuickItem *i, QQuickListView *v, bool own) : FxViewItem(i, v, own, static_cast<QQuickItemViewAttached*>(qmlAttachedPropertiesObject<QQuickListView>(i))), view(v) |
| { |
| } |
| |
| inline QQuickItem *section() const { |
| return item && attached ? static_cast<QQuickListViewAttached*>(attached)->m_sectionItem : nullptr; |
| } |
| void setSection(QQuickItem *s) { |
| static_cast<QQuickListViewAttached*>(attached)->m_sectionItem = s; |
| } |
| |
| qreal position() const override { |
| if (section()) { |
| if (view->orientation() == QQuickListView::Vertical) |
| return (view->verticalLayoutDirection() == QQuickItemView::BottomToTop ? -section()->height()-section()->y() : section()->y()); |
| else |
| return (view->effectiveLayoutDirection() == Qt::RightToLeft ? -section()->width()-section()->x() : section()->x()); |
| } else { |
| return itemPosition(); |
| } |
| } |
| qreal itemPosition() const { |
| if (view->orientation() == QQuickListView::Vertical) |
| return (view->verticalLayoutDirection() == QQuickItemView::BottomToTop ? -itemHeight()-itemY() : itemY()); |
| else |
| return (view->effectiveLayoutDirection() == Qt::RightToLeft ? -itemWidth()-itemX() : itemX()); |
| } |
| qreal size() const override { |
| if (section()) |
| return (view->orientation() == QQuickListView::Vertical ? itemHeight()+section()->height() : itemWidth()+section()->width()); |
| else |
| return (view->orientation() == QQuickListView::Vertical ? itemHeight() : itemWidth()); |
| } |
| qreal itemSize() const { |
| return (view->orientation() == QQuickListView::Vertical ? itemHeight() : itemWidth()); |
| } |
| qreal sectionSize() const override { |
| if (section()) |
| return (view->orientation() == QQuickListView::Vertical ? section()->height() : section()->width()); |
| return 0.0; |
| } |
| qreal endPosition() const override { |
| if (view->orientation() == QQuickListView::Vertical) { |
| return (view->verticalLayoutDirection() == QQuickItemView::BottomToTop |
| ? -itemY() |
| : itemY() + itemHeight()); |
| } else { |
| return (view->effectiveLayoutDirection() == Qt::RightToLeft |
| ? -itemX() |
| : itemX() + itemWidth()); |
| } |
| } |
| void setPosition(qreal pos, bool immediate = false) { |
| // position the section immediately even if there is a transition |
| if (section()) { |
| if (view->orientation() == QQuickListView::Vertical) { |
| if (view->verticalLayoutDirection() == QQuickItemView::BottomToTop) |
| section()->setY(-section()->height()-pos); |
| else |
| section()->setY(pos); |
| } else { |
| if (view->effectiveLayoutDirection() == Qt::RightToLeft) |
| section()->setX(-section()->width()-pos); |
| else |
| section()->setX(pos); |
| } |
| } |
| moveTo(pointForPosition(pos), immediate); |
| } |
| void setSize(qreal size) { |
| if (view->orientation() == QQuickListView::Vertical) |
| item->setHeight(size); |
| else |
| item->setWidth(size); |
| } |
| bool contains(qreal x, qreal y) const override { |
| return (x >= itemX() && x < itemX() + itemWidth() && |
| y >= itemY() && y < itemY() + itemHeight()); |
| } |
| |
| QQuickListView *view; |
| |
| private: |
| QPointF pointForPosition(qreal pos) const { |
| if (view->orientation() == QQuickListView::Vertical) { |
| if (view->verticalLayoutDirection() == QQuickItemView::BottomToTop) { |
| if (section()) |
| pos += section()->height(); |
| return QPointF(itemX(), -itemHeight() - pos); |
| } else { |
| if (section()) |
| pos += section()->height(); |
| return QPointF(itemX(), pos); |
| } |
| } else { |
| if (view->effectiveLayoutDirection() == Qt::RightToLeft) { |
| if (section()) |
| pos += section()->width(); |
| return QPointF(-itemWidth() - pos, itemY()); |
| } else { |
| if (section()) |
| pos += section()->width(); |
| return QPointF(pos, itemY()); |
| } |
| } |
| } |
| }; |
| |
| //---------------------------------------------------------------------------- |
| |
| bool QQuickListViewPrivate::isContentFlowReversed() const |
| { |
| return isRightToLeft() || isBottomToTop(); |
| } |
| |
| Qt::Orientation QQuickListViewPrivate::layoutOrientation() const |
| { |
| return static_cast<Qt::Orientation>(orient); |
| } |
| |
| bool QQuickListViewPrivate::isRightToLeft() const |
| { |
| Q_Q(const QQuickListView); |
| return orient == QQuickListView::Horizontal && q->effectiveLayoutDirection() == Qt::RightToLeft; |
| } |
| |
| bool QQuickListViewPrivate::isBottomToTop() const |
| { |
| return orient == QQuickListView::Vertical && verticalLayoutDirection == QQuickItemView::BottomToTop; |
| } |
| |
| // Returns the item before modelIndex, if created. |
| // May return an item marked for removal. |
| FxViewItem *QQuickListViewPrivate::itemBefore(int modelIndex) const |
| { |
| if (modelIndex < visibleIndex) |
| return nullptr; |
| int idx = 1; |
| int lastIndex = -1; |
| while (idx < visibleItems.count()) { |
| FxViewItem *item = visibleItems.at(idx); |
| if (item->index != -1) |
| lastIndex = item->index; |
| if (item->index == modelIndex) |
| return visibleItems.at(idx-1); |
| ++idx; |
| } |
| if (lastIndex == modelIndex-1) |
| return visibleItems.constLast(); |
| return nullptr; |
| } |
| |
| void QQuickListViewPrivate::setPosition(qreal pos) |
| { |
| Q_Q(QQuickListView); |
| if (orient == QQuickListView::Vertical) { |
| if (isBottomToTop()) |
| q->QQuickFlickable::setContentY(-pos-size()); |
| else |
| q->QQuickFlickable::setContentY(pos); |
| } else { |
| if (isRightToLeft()) |
| q->QQuickFlickable::setContentX(-pos-size()); |
| else |
| q->QQuickFlickable::setContentX(pos); |
| } |
| } |
| |
| qreal QQuickListViewPrivate::originPosition() const |
| { |
| qreal pos = 0; |
| if (!visibleItems.isEmpty()) { |
| pos = (*visibleItems.constBegin())->position(); |
| if (visibleIndex > 0) |
| pos -= visibleIndex * (averageSize + spacing); |
| } |
| return pos; |
| } |
| |
| qreal QQuickListViewPrivate::lastPosition() const |
| { |
| qreal pos = 0; |
| if (!visibleItems.isEmpty()) { |
| int invisibleCount = INT_MIN; |
| int delayRemovedCount = 0; |
| for (int i = visibleItems.count()-1; i >= 0; --i) { |
| FxViewItem *item = visibleItems.at(i); |
| if (item->index != -1) { |
| // Find the invisible count after the last visible item with known index |
| invisibleCount = model->count() - (item->index + 1 + delayRemovedCount); |
| break; |
| } else if (item->attached->delayRemove()) { |
| ++delayRemovedCount; |
| } |
| } |
| if (invisibleCount == INT_MIN) { |
| // All visible items are in delayRemove state |
| invisibleCount = model->count(); |
| } |
| pos = (*(--visibleItems.constEnd()))->endPosition(); |
| if (invisibleCount > 0) |
| pos += invisibleCount * (averageSize + spacing); |
| } else if (model && model->count()) { |
| pos = (model->count() * averageSize + (model->count()-1) * spacing); |
| } |
| return pos; |
| } |
| |
| qreal QQuickListViewPrivate::positionAt(int modelIndex) const |
| { |
| if (FxViewItem *item = visibleItem(modelIndex)) { |
| return item->position(); |
| } |
| if (!visibleItems.isEmpty()) { |
| if (modelIndex < visibleIndex) { |
| int count = visibleIndex - modelIndex; |
| qreal cs = 0; |
| if (modelIndex == currentIndex && currentItem) { |
| cs = currentItem->size() + spacing; |
| --count; |
| } |
| return (*visibleItems.constBegin())->position() - count * (averageSize + spacing) - cs; |
| } else { |
| int count = modelIndex - findLastVisibleIndex(visibleIndex) - 1; |
| return (*(--visibleItems.constEnd()))->endPosition() + spacing + count * (averageSize + spacing); |
| } |
| } |
| return 0; |
| } |
| |
| qreal QQuickListViewPrivate::endPositionAt(int modelIndex) const |
| { |
| if (FxViewItem *item = visibleItem(modelIndex)) |
| return item->endPosition(); |
| if (!visibleItems.isEmpty()) { |
| if (modelIndex < visibleIndex) { |
| int count = visibleIndex - modelIndex; |
| return (*visibleItems.constBegin())->position() - (count - 1) * (averageSize + spacing) - spacing; |
| } else { |
| int count = modelIndex - findLastVisibleIndex(visibleIndex) - 1; |
| return (*(--visibleItems.constEnd()))->endPosition() + count * (averageSize + spacing); |
| } |
| } |
| return 0; |
| } |
| |
| QString QQuickListViewPrivate::sectionAt(int modelIndex) |
| { |
| if (FxViewItem *item = visibleItem(modelIndex)) |
| return item->attached->section(); |
| |
| QString section; |
| if (sectionCriteria && modelIndex >= 0 && modelIndex < itemCount) { |
| QString propValue = model->stringValue(modelIndex, sectionCriteria->property()); |
| section = sectionCriteria->sectionString(propValue); |
| } |
| |
| return section; |
| } |
| |
| qreal QQuickListViewPrivate::snapPosAt(qreal pos) |
| { |
| if (FxListItemSG *snapItem = static_cast<FxListItemSG*>(snapItemAt(pos))) |
| return snapItem->itemPosition(); |
| if (visibleItems.count()) { |
| qreal firstPos = (*visibleItems.constBegin())->position(); |
| qreal endPos = (*(--visibleItems.constEnd()))->position(); |
| if (pos < firstPos) { |
| return firstPos - qRound((firstPos - pos) / averageSize) * averageSize; |
| } else if (pos > endPos) |
| return endPos + qRound((pos - endPos) / averageSize) * averageSize; |
| } |
| return qRound((pos - originPosition()) / averageSize) * averageSize + originPosition(); |
| } |
| |
| FxViewItem *QQuickListViewPrivate::snapItemAt(qreal pos) |
| { |
| const qreal velocity = orient == QQuickListView::Vertical ? vData.velocity : hData.velocity; |
| FxViewItem *snapItem = nullptr; |
| FxViewItem *prevItem = nullptr; |
| qreal prevItemSize = 0; |
| for (FxViewItem *item : qAsConst(visibleItems)) { |
| if (item->index == -1) |
| continue; |
| |
| const FxListItemSG *listItem = static_cast<FxListItemSG *>(item); |
| qreal itemTop = listItem->position(); |
| qreal itemSize = listItem->size(); |
| if (highlight && itemTop >= pos && listItem->endPosition() <= pos + highlight->size()) |
| return item; |
| |
| if (listItem->section() && velocity > 0) { |
| if (itemTop + listItem->sectionSize() / 2 >= pos && itemTop - prevItemSize / 2 < pos) |
| snapItem = prevItem; |
| itemTop = listItem->itemPosition(); |
| itemSize = listItem->itemSize(); |
| } |
| |
| // Middle of item and spacing (i.e. the middle of the distance between this item and the next |
| qreal halfwayToNextItem = itemTop + (itemSize+spacing) / 2; |
| qreal halfwayToPrevItem = itemTop - (prevItemSize+spacing) / 2; |
| if (halfwayToNextItem >= pos && halfwayToPrevItem < pos) |
| snapItem = item; |
| |
| prevItemSize = listItem->itemSize(); |
| prevItem = item; |
| } |
| return snapItem; |
| } |
| |
| void QQuickListViewPrivate::changedVisibleIndex(int newIndex) |
| { |
| visiblePos = positionAt(newIndex); |
| visibleIndex = newIndex; |
| } |
| |
| void QQuickListViewPrivate::init() |
| { |
| QQuickItemViewPrivate::init(); |
| ::memset(sectionCache, 0, sizeof(QQuickItem*) * sectionCacheSize); |
| } |
| |
| void QQuickListViewPrivate::clear(bool onDestruction) |
| { |
| for (int i = 0; i < sectionCacheSize; ++i) { |
| delete sectionCache[i]; |
| sectionCache[i] = nullptr; |
| } |
| visiblePos = 0; |
| releaseSectionItem(currentSectionItem); |
| currentSectionItem = nullptr; |
| releaseSectionItem(nextSectionItem); |
| nextSectionItem = nullptr; |
| lastVisibleSection = QString(); |
| QQuickItemViewPrivate::clear(onDestruction); |
| } |
| |
| FxViewItem *QQuickListViewPrivate::newViewItem(int modelIndex, QQuickItem *item) |
| { |
| Q_Q(QQuickListView); |
| |
| FxListItemSG *listItem = new FxListItemSG(item, q, false); |
| listItem->index = modelIndex; |
| |
| // initialise attached properties |
| if (sectionCriteria) { |
| QString propValue = model->stringValue(modelIndex, sectionCriteria->property()); |
| QString section = sectionCriteria->sectionString(propValue); |
| QString prevSection; |
| QString nextSection; |
| if (modelIndex > 0) { |
| if (FxViewItem *item = itemBefore(modelIndex)) |
| prevSection = item->attached->section(); |
| else |
| prevSection = sectionAt(modelIndex-1); |
| } |
| if (modelIndex < model->count()-1) { |
| nextSection = sectionAt(modelIndex+1); |
| } |
| listItem->attached->setSections(prevSection, section, nextSection); |
| } |
| |
| return listItem; |
| } |
| |
| void QQuickListViewPrivate::initializeViewItem(FxViewItem *item) |
| { |
| QQuickItemViewPrivate::initializeViewItem(item); |
| |
| // need to track current items that are animating |
| item->trackGeometry(true); |
| |
| if (sectionCriteria && sectionCriteria->delegate()) { |
| if (QString::compare(item->attached->m_prevSection, item->attached->m_section, Qt::CaseInsensitive)) |
| updateInlineSection(static_cast<FxListItemSG*>(item)); |
| } |
| } |
| |
| bool QQuickListViewPrivate::releaseItem(FxViewItem *item) |
| { |
| if (!item || !model) |
| return QQuickItemViewPrivate::releaseItem(item); |
| |
| QPointer<QQuickItem> it = item->item; |
| QQuickListViewAttached *att = static_cast<QQuickListViewAttached*>(item->attached); |
| |
| bool released = QQuickItemViewPrivate::releaseItem(item); |
| if (released && it && att && att->m_sectionItem) { |
| // We hold no more references to this item |
| int i = 0; |
| do { |
| if (!sectionCache[i]) { |
| sectionCache[i] = att->m_sectionItem; |
| sectionCache[i]->setVisible(false); |
| att->m_sectionItem = nullptr; |
| break; |
| } |
| ++i; |
| } while (i < sectionCacheSize); |
| delete att->m_sectionItem; |
| att->m_sectionItem = nullptr; |
| } |
| |
| return released; |
| } |
| |
| bool QQuickListViewPrivate::addVisibleItems(qreal fillFrom, qreal fillTo, qreal bufferFrom, qreal bufferTo, bool doBuffer) |
| { |
| qreal itemEnd = visiblePos; |
| if (visibleItems.count()) { |
| visiblePos = (*visibleItems.constBegin())->position(); |
| itemEnd = (*(--visibleItems.constEnd()))->endPosition() + spacing; |
| } |
| |
| int modelIndex = findLastVisibleIndex(); |
| bool haveValidItems = modelIndex >= 0; |
| modelIndex = modelIndex < 0 ? visibleIndex : modelIndex + 1; |
| |
| if (haveValidItems && (bufferFrom > itemEnd+averageSize+spacing |
| || bufferTo < visiblePos - averageSize - spacing)) { |
| // We've jumped more than a page. Estimate which items are now |
| // visible and fill from there. |
| int count = (fillFrom - itemEnd) / (averageSize + spacing); |
| int newModelIdx = qBound(0, modelIndex + count, model->count()); |
| count = newModelIdx - modelIndex; |
| if (count) { |
| releaseVisibleItems(); |
| modelIndex = newModelIdx; |
| visibleIndex = modelIndex; |
| visiblePos = itemEnd + count * (averageSize + spacing); |
| itemEnd = visiblePos; |
| } |
| } |
| |
| QQmlIncubator::IncubationMode incubationMode = doBuffer ? QQmlIncubator::Asynchronous : QQmlIncubator::AsynchronousIfNested; |
| |
| bool changed = false; |
| FxListItemSG *item = nullptr; |
| qreal pos = itemEnd; |
| while (modelIndex < model->count() && pos <= fillTo) { |
| if (!(item = static_cast<FxListItemSG*>(createItem(modelIndex, incubationMode)))) |
| break; |
| qCDebug(lcItemViewDelegateLifecycle) << "refill: append item" << modelIndex << "pos" << pos << "buffer" << doBuffer << "item" << (QObject *)(item->item); |
| if (!transitioner || !transitioner->canTransition(QQuickItemViewTransitioner::PopulateTransition, true)) // pos will be set by layoutVisibleItems() |
| item->setPosition(pos, true); |
| if (item->item) |
| QQuickItemPrivate::get(item->item)->setCulled(doBuffer); |
| pos += item->size() + spacing; |
| visibleItems.append(item); |
| ++modelIndex; |
| changed = true; |
| } |
| |
| if (doBuffer && requestedIndex != -1) // already waiting for an item |
| return changed; |
| |
| while (visibleIndex > 0 && visibleIndex <= model->count() && visiblePos > fillFrom) { |
| if (!(item = static_cast<FxListItemSG*>(createItem(visibleIndex-1, incubationMode)))) |
| break; |
| qCDebug(lcItemViewDelegateLifecycle) << "refill: prepend item" << visibleIndex-1 << "current top pos" << visiblePos << "buffer" << doBuffer << "item" << (QObject *)(item->item); |
| --visibleIndex; |
| visiblePos -= item->size() + spacing; |
| if (!transitioner || !transitioner->canTransition(QQuickItemViewTransitioner::PopulateTransition, true)) // pos will be set by layoutVisibleItems() |
| item->setPosition(visiblePos, true); |
| if (item->item) |
| QQuickItemPrivate::get(item->item)->setCulled(doBuffer); |
| visibleItems.prepend(item); |
| changed = true; |
| } |
| |
| return changed; |
| } |
| |
| void QQuickListViewPrivate::removeItem(FxViewItem *item) |
| { |
| if (item->transitionScheduledOrRunning()) { |
| qCDebug(lcItemViewDelegateLifecycle) << "\tnot releasing animating item" << item->index << (QObject *)(item->item); |
| item->releaseAfterTransition = true; |
| releasePendingTransition.append(item); |
| } else { |
| qCDebug(lcItemViewDelegateLifecycle) << "\treleasing stationary item" << item->index << (QObject *)(item->item); |
| releaseItem(item); |
| } |
| } |
| |
| bool QQuickListViewPrivate::removeNonVisibleItems(qreal bufferFrom, qreal bufferTo) |
| { |
| FxViewItem *item = nullptr; |
| bool changed = false; |
| |
| // Remove items from the start of the view. |
| // Zero-sized items shouldn't be removed unless a non-zero-sized item is also being |
| // removed, otherwise a zero-sized item is infinitely added and removed over and |
| // over by refill(). |
| int index = 0; |
| while (visibleItems.count() > 1 && index < visibleItems.count() |
| && (item = visibleItems.at(index)) && item->endPosition() < bufferFrom) { |
| if (item->attached->delayRemove()) |
| break; |
| |
| if (item->size() > 0) { |
| qCDebug(lcItemViewDelegateLifecycle) << "refill: remove first" << visibleIndex << "top end pos" << item->endPosition(); |
| // remove this item and all zero-sized items before it |
| while (item) { |
| if (item->index != -1) |
| visibleIndex++; |
| visibleItems.removeAt(index); |
| removeItem(item); |
| if (index == 0) |
| break; |
| item = visibleItems.at(--index); |
| } |
| changed = true; |
| } else { |
| index++; |
| } |
| } |
| |
| while (visibleItems.count() > 1 && (item = visibleItems.constLast()) && item->position() > bufferTo) { |
| if (item->attached->delayRemove()) |
| break; |
| qCDebug(lcItemViewDelegateLifecycle) << "refill: remove last" << visibleIndex+visibleItems.count()-1 << item->position() << (QObject *)(item->item); |
| visibleItems.removeLast(); |
| removeItem(item); |
| changed = true; |
| } |
| |
| return changed; |
| } |
| |
| void QQuickListViewPrivate::visibleItemsChanged() |
| { |
| if (visibleItems.count()) |
| visiblePos = (*visibleItems.constBegin())->position(); |
| updateAverage(); |
| if (currentIndex >= 0 && currentItem && !visibleItem(currentIndex)) { |
| static_cast<FxListItemSG*>(currentItem)->setPosition(positionAt(currentIndex)); |
| updateHighlight(); |
| } |
| if (sectionCriteria) |
| updateCurrentSection(); |
| updateUnrequestedPositions(); |
| } |
| |
| void QQuickListViewPrivate::layoutVisibleItems(int fromModelIndex) |
| { |
| if (!visibleItems.isEmpty()) { |
| const qreal from = isContentFlowReversed() ? -position()-displayMarginBeginning-size() : position()-displayMarginBeginning; |
| const qreal to = isContentFlowReversed() ? -position()+displayMarginEnd : position()+size()+displayMarginEnd; |
| |
| FxViewItem *firstItem = *visibleItems.constBegin(); |
| bool fixedCurrent = currentItem && firstItem->item == currentItem->item; |
| firstVisibleItemPosition = firstItem->position(); |
| qreal sum = firstItem->size(); |
| qreal pos = firstItem->position() + firstItem->size() + spacing; |
| firstItem->setVisible(firstItem->endPosition() >= from && firstItem->position() <= to); |
| |
| for (int i=1; i < visibleItems.count(); ++i) { |
| FxListItemSG *item = static_cast<FxListItemSG*>(visibleItems.at(i)); |
| if (item->index >= fromModelIndex) { |
| item->setPosition(pos); |
| item->setVisible(item->endPosition() >= from && item->position() <= to); |
| } |
| pos += item->size() + spacing; |
| sum += item->size(); |
| fixedCurrent = fixedCurrent || (currentItem && item->item == currentItem->item); |
| } |
| averageSize = qRound(sum / visibleItems.count()); |
| |
| // move current item if it is not a visible item. |
| if (currentIndex >= 0 && currentItem && !fixedCurrent) |
| static_cast<FxListItemSG*>(currentItem)->setPosition(positionAt(currentIndex)); |
| |
| updateCurrentSection(); |
| updateStickySections(); |
| } |
| } |
| |
| void QQuickListViewPrivate::repositionItemAt(FxViewItem *item, int index, qreal sizeBuffer) |
| { |
| static_cast<FxListItemSG *>(item)->setPosition(positionAt(index) + sizeBuffer); |
| } |
| |
| void QQuickListViewPrivate::repositionPackageItemAt(QQuickItem *item, int index) |
| { |
| Q_Q(QQuickListView); |
| qreal pos = position(); |
| if (orient == QQuickListView::Vertical) { |
| if (item->y() + item->height() > pos && item->y() < pos + q->height()) { |
| if (isBottomToTop()) |
| item->setY(-positionAt(index)-item->height()); |
| else |
| item->setY(positionAt(index)); |
| } |
| } else { |
| if (item->x() + item->width() > pos && item->x() < pos + q->width()) { |
| if (isRightToLeft()) |
| item->setX(-positionAt(index)-item->width()); |
| else |
| item->setX(positionAt(index)); |
| } |
| } |
| } |
| |
| void QQuickListViewPrivate::resetFirstItemPosition(qreal pos) |
| { |
| FxListItemSG *item = static_cast<FxListItemSG*>(visibleItems.constFirst()); |
| item->setPosition(pos); |
| } |
| |
| void QQuickListViewPrivate::adjustFirstItem(qreal forwards, qreal backwards, int) |
| { |
| if (!visibleItems.count()) |
| return; |
| qreal diff = forwards - backwards; |
| static_cast<FxListItemSG*>(visibleItems.constFirst())->setPosition(visibleItems.constFirst()->position() + diff); |
| } |
| |
| void QQuickListViewPrivate::updateSizeChangesBeforeVisiblePos(FxViewItem *item, ChangeResult *removeResult) |
| { |
| if (item != visibleItems.constFirst()) |
| QQuickItemViewPrivate::updateSizeChangesBeforeVisiblePos(item, removeResult); |
| } |
| |
| void QQuickListViewPrivate::createHighlight(bool onDestruction) |
| { |
| bool changed = false; |
| if (highlight) { |
| if (trackedItem == highlight) |
| trackedItem = nullptr; |
| delete highlight; |
| highlight = nullptr; |
| |
| delete highlightPosAnimator; |
| delete highlightWidthAnimator; |
| delete highlightHeightAnimator; |
| highlightPosAnimator = nullptr; |
| highlightWidthAnimator = nullptr; |
| highlightHeightAnimator = nullptr; |
| |
| changed = true; |
| } |
| |
| if (onDestruction) |
| return; |
| |
| Q_Q(QQuickListView); |
| if (currentItem) { |
| QQuickItem *item = createHighlightItem(); |
| if (item) { |
| FxListItemSG *newHighlight = new FxListItemSG(item, q, true); |
| newHighlight->trackGeometry(true); |
| |
| if (autoHighlight) { |
| newHighlight->setSize(static_cast<FxListItemSG*>(currentItem)->itemSize()); |
| newHighlight->setPosition(static_cast<FxListItemSG*>(currentItem)->itemPosition()); |
| } |
| const QLatin1String posProp(orient == QQuickListView::Vertical ? "y" : "x"); |
| highlightPosAnimator = new QSmoothedAnimation; |
| highlightPosAnimator->target = QQmlProperty(item, posProp); |
| highlightPosAnimator->velocity = highlightMoveVelocity; |
| highlightPosAnimator->userDuration = highlightMoveDuration; |
| |
| highlightWidthAnimator = new QSmoothedAnimation; |
| highlightWidthAnimator->velocity = highlightResizeVelocity; |
| highlightWidthAnimator->userDuration = highlightResizeDuration; |
| highlightWidthAnimator->target = QQmlProperty(item, QStringLiteral("width")); |
| |
| highlightHeightAnimator = new QSmoothedAnimation; |
| highlightHeightAnimator->velocity = highlightResizeVelocity; |
| highlightHeightAnimator->userDuration = highlightResizeDuration; |
| highlightHeightAnimator->target = QQmlProperty(item, QStringLiteral("height")); |
| |
| highlight = newHighlight; |
| changed = true; |
| } |
| } |
| if (changed) |
| emit q->highlightItemChanged(); |
| } |
| |
| void QQuickListViewPrivate::updateHighlight() |
| { |
| applyPendingChanges(); |
| |
| if ((!currentItem && highlight) || (currentItem && !highlight)) |
| createHighlight(); |
| bool strictHighlight = haveHighlightRange && highlightRange == QQuickListView::StrictlyEnforceRange; |
| if (currentItem && autoHighlight && highlight && (!strictHighlight || !pressed)) { |
| // auto-update highlight |
| FxListItemSG *listItem = static_cast<FxListItemSG*>(currentItem); |
| highlightPosAnimator->to = isContentFlowReversed() |
| ? -listItem->itemPosition()-listItem->itemSize() |
| : listItem->itemPosition(); |
| highlightWidthAnimator->to = listItem->item->width(); |
| highlightHeightAnimator->to = listItem->item->height(); |
| if (orient == QQuickListView::Vertical) { |
| if (highlight->item->width() == 0) |
| highlight->item->setWidth(currentItem->item->width()); |
| } else { |
| if (highlight->item->height() == 0) |
| highlight->item->setHeight(currentItem->item->height()); |
| } |
| |
| highlightPosAnimator->restart(); |
| highlightWidthAnimator->restart(); |
| highlightHeightAnimator->restart(); |
| } |
| updateTrackedItem(); |
| } |
| |
| void QQuickListViewPrivate::resetHighlightPosition() |
| { |
| if (highlight && currentItem) |
| static_cast<FxListItemSG*>(highlight)->setPosition(static_cast<FxListItemSG*>(currentItem)->itemPosition()); |
| } |
| |
| bool QQuickListViewPrivate::movingFromHighlight() |
| { |
| if (!haveHighlightRange || highlightRange != QQuickListView::StrictlyEnforceRange) |
| return false; |
| |
| return (highlightPosAnimator && highlightPosAnimator->isRunning()) || |
| (highlightHeightAnimator && highlightHeightAnimator->isRunning()) || |
| (highlightWidthAnimator && highlightWidthAnimator->isRunning()); |
| } |
| |
| |
| QQuickItem * QQuickListViewPrivate::getSectionItem(const QString §ion) |
| { |
| Q_Q(QQuickListView); |
| QQuickItem *sectionItem = nullptr; |
| int i = sectionCacheSize-1; |
| while (i >= 0 && !sectionCache[i]) |
| --i; |
| if (i >= 0) { |
| sectionItem = sectionCache[i]; |
| sectionCache[i] = nullptr; |
| sectionItem->setVisible(true); |
| QQmlContext *context = QQmlEngine::contextForObject(sectionItem)->parentContext(); |
| context->setContextProperty(QLatin1String("section"), section); |
| } else { |
| QQmlContext *creationContext = sectionCriteria->delegate()->creationContext(); |
| QQmlContext *context = new QQmlContext( |
| creationContext ? creationContext : qmlContext(q)); |
| context->setContextProperty(QLatin1String("section"), section); |
| QObject *nobj = sectionCriteria->delegate()->beginCreate(context); |
| if (nobj) { |
| QQml_setParent_noEvent(context, nobj); |
| sectionItem = qobject_cast<QQuickItem *>(nobj); |
| if (!sectionItem) { |
| delete nobj; |
| } else { |
| if (qFuzzyIsNull(sectionItem->z())) |
| sectionItem->setZ(2); |
| QQml_setParent_noEvent(sectionItem, contentItem); |
| sectionItem->setParentItem(contentItem); |
| } |
| // sections are not controlled by FxListItemSG, so apply attached properties here |
| QQuickItemViewAttached *attached = static_cast<QQuickItemViewAttached*>(qmlAttachedPropertiesObject<QQuickListView>(sectionItem)); |
| attached->setView(q); |
| } else { |
| delete context; |
| } |
| sectionCriteria->delegate()->completeCreate(); |
| } |
| |
| return sectionItem; |
| } |
| |
| void QQuickListViewPrivate::releaseSectionItem(QQuickItem *item) |
| { |
| if (!item) |
| return; |
| int i = 0; |
| do { |
| if (!sectionCache[i]) { |
| sectionCache[i] = item; |
| sectionCache[i]->setVisible(false); |
| return; |
| } |
| ++i; |
| } while (i < sectionCacheSize); |
| delete item; |
| } |
| |
| |
| void QQuickListViewPrivate::releaseSectionItems() |
| { |
| for (FxViewItem *item : qAsConst(visibleItems)) { |
| FxListItemSG *listItem = static_cast<FxListItemSG *>(item); |
| if (listItem->section()) { |
| qreal pos = listItem->position(); |
| releaseSectionItem(listItem->section()); |
| listItem->setSection(nullptr); |
| listItem->setPosition(pos); |
| } |
| } |
| for (int i = 0; i < sectionCacheSize; ++i) { |
| delete sectionCache[i]; |
| sectionCache[i] = nullptr; |
| } |
| } |
| |
| void QQuickListViewPrivate::updateInlineSection(FxListItemSG *listItem) |
| { |
| if (!sectionCriteria || !sectionCriteria->delegate()) |
| return; |
| if (QString::compare(listItem->attached->m_prevSection, listItem->attached->m_section, Qt::CaseInsensitive) |
| && (sectionCriteria->labelPositioning() & QQuickViewSection::InlineLabels |
| || (listItem->index == 0 && sectionCriteria->labelPositioning() & QQuickViewSection::CurrentLabelAtStart))) { |
| if (!listItem->section()) { |
| qreal pos = listItem->position(); |
| listItem->setSection(getSectionItem(listItem->attached->m_section)); |
| listItem->setPosition(pos); |
| } else { |
| QQmlContext *context = QQmlEngine::contextForObject(listItem->section())->parentContext(); |
| context->setContextProperty(QLatin1String("section"), listItem->attached->m_section); |
| } |
| } else if (listItem->section()) { |
| qreal pos = listItem->position(); |
| releaseSectionItem(listItem->section()); |
| listItem->setSection(nullptr); |
| listItem->setPosition(pos); |
| } |
| } |
| |
| void QQuickListViewPrivate::updateStickySections() |
| { |
| if (!sectionCriteria || !sectionCriteria->delegate() |
| || (!sectionCriteria->labelPositioning() && !currentSectionItem && !nextSectionItem)) |
| return; |
| |
| bool isFlowReversed = isContentFlowReversed(); |
| qreal viewPos = isFlowReversed ? -position()-size() : position(); |
| qreal startPos = hasStickyHeader() ? header->endPosition() : viewPos; |
| qreal endPos = hasStickyFooter() ? footer->position() : viewPos + size(); |
| |
| QQuickItem *sectionItem = nullptr; |
| QQuickItem *lastSectionItem = nullptr; |
| int index = 0; |
| while (index < visibleItems.count()) { |
| if (QQuickItem *section = static_cast<FxListItemSG *>(visibleItems.at(index))->section()) { |
| // Find the current section header and last visible section header |
| // and hide them if they will overlap a static section header. |
| qreal sectionPos = orient == QQuickListView::Vertical ? section->y() : section->x(); |
| qreal sectionSize = orient == QQuickListView::Vertical ? section->height() : section->width(); |
| bool visTop = true; |
| if (sectionCriteria->labelPositioning() & QQuickViewSection::CurrentLabelAtStart) |
| visTop = isFlowReversed ? -sectionPos-sectionSize >= startPos : sectionPos >= startPos; |
| bool visBot = true; |
| if (sectionCriteria->labelPositioning() & QQuickViewSection::NextLabelAtEnd) |
| visBot = isFlowReversed ? -sectionPos <= endPos : sectionPos + sectionSize < endPos; |
| section->setVisible(visBot && visTop); |
| if (visTop && !sectionItem) |
| sectionItem = section; |
| if (isFlowReversed) { |
| if (-sectionPos <= endPos) |
| lastSectionItem = section; |
| } else { |
| if (sectionPos + sectionSize < endPos) |
| lastSectionItem = section; |
| } |
| } |
| ++index; |
| } |
| |
| // Current section header |
| if (sectionCriteria->labelPositioning() & QQuickViewSection::CurrentLabelAtStart && isValid() && visibleItems.count()) { |
| if (!currentSectionItem) { |
| currentSectionItem = getSectionItem(currentSection); |
| } else if (QString::compare(currentStickySection, currentSection, Qt::CaseInsensitive)) { |
| QQmlContext *context = QQmlEngine::contextForObject(currentSectionItem)->parentContext(); |
| context->setContextProperty(QLatin1String("section"), currentSection); |
| } |
| currentStickySection = currentSection; |
| if (!currentSectionItem) |
| return; |
| |
| qreal sectionSize = orient == QQuickListView::Vertical ? currentSectionItem->height() : currentSectionItem->width(); |
| bool atBeginning = orient == QQuickListView::Vertical ? (isBottomToTop() ? vData.atEnd : vData.atBeginning) : (isRightToLeft() ? hData.atEnd : hData.atBeginning); |
| |
| currentSectionItem->setVisible(!atBeginning && (!header || hasStickyHeader() || header->endPosition() < viewPos)); |
| qreal pos = isFlowReversed ? position() + size() - sectionSize : startPos; |
| if (header) |
| pos = isFlowReversed ? qMin(-header->endPosition() - sectionSize, pos) : qMax(header->endPosition(), pos); |
| if (sectionItem) { |
| qreal sectionPos = orient == QQuickListView::Vertical ? sectionItem->y() : sectionItem->x(); |
| pos = isFlowReversed ? qMax(pos, sectionPos + sectionSize) : qMin(pos, sectionPos - sectionSize); |
| } |
| if (footer) |
| pos = isFlowReversed ? qMax(-footer->position(), pos) : qMin(footer->position() - sectionSize, pos); |
| if (orient == QQuickListView::Vertical) |
| currentSectionItem->setY(pos); |
| else |
| currentSectionItem->setX(pos); |
| } else if (currentSectionItem) { |
| releaseSectionItem(currentSectionItem); |
| currentSectionItem = nullptr; |
| } |
| |
| // Next section footer |
| if (sectionCriteria->labelPositioning() & QQuickViewSection::NextLabelAtEnd && isValid() && visibleItems.count()) { |
| if (!nextSectionItem) { |
| nextSectionItem = getSectionItem(nextSection); |
| } else if (QString::compare(nextStickySection, nextSection, Qt::CaseInsensitive)) { |
| QQmlContext *context = QQmlEngine::contextForObject(nextSectionItem)->parentContext(); |
| context->setContextProperty(QLatin1String("section"), nextSection); |
| } |
| nextStickySection = nextSection; |
| if (!nextSectionItem) |
| return; |
| |
| qreal sectionSize = orient == QQuickListView::Vertical ? nextSectionItem->height() : nextSectionItem->width(); |
| nextSectionItem->setVisible(!nextSection.isEmpty()); |
| qreal pos = isFlowReversed ? position() : endPos - sectionSize; |
| if (footer) |
| pos = isFlowReversed ? qMax(-footer->position(), pos) : qMin(footer->position() - sectionSize, pos); |
| if (lastSectionItem) { |
| qreal sectionPos = orient == QQuickListView::Vertical ? lastSectionItem->y() : lastSectionItem->x(); |
| pos = isFlowReversed ? qMin(pos, sectionPos - sectionSize) : qMax(pos, sectionPos + sectionSize); |
| } |
| if (header) |
| pos = isFlowReversed ? qMin(-header->endPosition() - sectionSize, pos) : qMax(header->endPosition(), pos); |
| if (orient == QQuickListView::Vertical) |
| nextSectionItem->setY(pos); |
| else |
| nextSectionItem->setX(pos); |
| } else if (nextSectionItem) { |
| releaseSectionItem(nextSectionItem); |
| nextSectionItem = nullptr; |
| } |
| } |
| |
| void QQuickListViewPrivate::updateSections() |
| { |
| Q_Q(QQuickListView); |
| if (!q->isComponentComplete()) |
| return; |
| |
| QQuickItemViewPrivate::updateSections(); |
| |
| if (sectionCriteria && !visibleItems.isEmpty() && isValid()) { |
| QString prevSection; |
| if (visibleIndex > 0) |
| prevSection = sectionAt(visibleIndex-1); |
| QQuickListViewAttached *prevAtt = nullptr; |
| int prevIdx = -1; |
| int idx = -1; |
| for (FxViewItem *item : qAsConst(visibleItems)) { |
| QQuickListViewAttached *attached = static_cast<QQuickListViewAttached*>(item->attached); |
| attached->setPrevSection(prevSection); |
| if (item->index != -1) { |
| QString propValue = model->stringValue(item->index, sectionCriteria->property()); |
| attached->setSection(sectionCriteria->sectionString(propValue)); |
| idx = item->index; |
| } |
| updateInlineSection(static_cast<FxListItemSG*>(item)); |
| if (prevAtt) |
| prevAtt->setNextSection(sectionAt(prevIdx+1)); |
| prevSection = attached->section(); |
| prevAtt = attached; |
| prevIdx = item->index; |
| } |
| if (prevAtt) { |
| if (idx > 0 && idx < model->count()-1) |
| prevAtt->setNextSection(sectionAt(idx+1)); |
| else |
| prevAtt->setNextSection(QString()); |
| } |
| } |
| |
| lastVisibleSection = QString(); |
| } |
| |
| void QQuickListViewPrivate::updateCurrentSection() |
| { |
| Q_Q(QQuickListView); |
| if (!sectionCriteria || visibleItems.isEmpty()) { |
| if (!currentSection.isEmpty()) { |
| currentSection.clear(); |
| emit q->currentSectionChanged(); |
| } |
| return; |
| } |
| bool inlineSections = sectionCriteria->labelPositioning() & QQuickViewSection::InlineLabels; |
| qreal viewPos = isContentFlowReversed() ? -position()-size() : position(); |
| qreal startPos = hasStickyHeader() ? header->endPosition() : viewPos; |
| int index = 0; |
| int modelIndex = visibleIndex; |
| while (index < visibleItems.count()) { |
| FxViewItem *item = visibleItems.at(index); |
| if (item->endPosition() > startPos) |
| break; |
| if (item->index != -1) |
| modelIndex = item->index; |
| ++index; |
| } |
| |
| QString newSection = currentSection; |
| if (index < visibleItems.count()) |
| newSection = visibleItems.at(index)->attached->section(); |
| else |
| newSection = (*visibleItems.constBegin())->attached->section(); |
| if (newSection != currentSection) { |
| currentSection = newSection; |
| updateStickySections(); |
| emit q->currentSectionChanged(); |
| } |
| |
| if (sectionCriteria->labelPositioning() & QQuickViewSection::NextLabelAtEnd) { |
| // Don't want to scan for next section on every movement, so remember |
| // the last section in the visible area and only scan for the next |
| // section when that changes. Clearing lastVisibleSection will also |
| // force searching. |
| QString lastSection = currentSection; |
| qreal endPos = hasStickyFooter() ? footer->position() : viewPos + size(); |
| if (nextSectionItem && !inlineSections) |
| endPos -= orient == QQuickListView::Vertical ? nextSectionItem->height() : nextSectionItem->width(); |
| while (index < visibleItems.count()) { |
| FxListItemSG *listItem = static_cast<FxListItemSG *>(visibleItems.at(index)); |
| if (listItem->itemPosition() >= endPos) |
| break; |
| if (listItem->index != -1) |
| modelIndex = listItem->index; |
| lastSection = listItem->attached->section(); |
| ++index; |
| } |
| |
| if (lastVisibleSection != lastSection) { |
| nextSection = QString(); |
| lastVisibleSection = lastSection; |
| for (int i = modelIndex; i < itemCount; ++i) { |
| QString section = sectionAt(i); |
| if (section != lastSection) { |
| nextSection = section; |
| updateStickySections(); |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| void QQuickListViewPrivate::initializeCurrentItem() |
| { |
| QQuickItemViewPrivate::initializeCurrentItem(); |
| |
| if (currentItem) { |
| FxListItemSG *listItem = static_cast<FxListItemSG *>(currentItem); |
| |
| // don't reposition the item if it is already in the visibleItems list |
| FxViewItem *actualItem = visibleItem(currentIndex); |
| if (!actualItem) { |
| if (currentIndex == visibleIndex - 1 && visibleItems.count()) { |
| // We can calculate exact postion in this case |
| listItem->setPosition(visibleItems.constFirst()->position() - currentItem->size() - spacing); |
| } else { |
| // Create current item now and position as best we can. |
| // Its position will be corrected when it becomes visible. |
| listItem->setPosition(positionAt(currentIndex)); |
| } |
| } |
| |
| if (visibleItems.isEmpty()) |
| averageSize = listItem->size(); |
| } |
| } |
| |
| void QQuickListViewPrivate::updateAverage() |
| { |
| if (!visibleItems.count()) |
| return; |
| qreal sum = 0.0; |
| for (FxViewItem *item : qAsConst(visibleItems)) |
| sum += item->size(); |
| averageSize = qRound(sum / visibleItems.count()); |
| } |
| |
| qreal QQuickListViewPrivate::headerSize() const |
| { |
| return header ? header->size() : 0.0; |
| } |
| |
| qreal QQuickListViewPrivate::footerSize() const |
| { |
| return footer ? footer->size() : 0.0; |
| } |
| |
| bool QQuickListViewPrivate::showHeaderForIndex(int index) const |
| { |
| return index == 0; |
| } |
| |
| bool QQuickListViewPrivate::showFooterForIndex(int index) const |
| { |
| return model && index == model->count()-1; |
| } |
| |
| void QQuickListViewPrivate::updateFooter() |
| { |
| Q_Q(QQuickListView); |
| bool created = false; |
| if (!footer) { |
| QQuickItem *item = createComponentItem(footerComponent, 1.0); |
| if (!item) |
| return; |
| footer = new FxListItemSG(item, q, true); |
| footer->trackGeometry(true); |
| created = true; |
| } |
| |
| FxListItemSG *listItem = static_cast<FxListItemSG*>(footer); |
| if (footerPositioning == QQuickListView::OverlayFooter) { |
| listItem->setPosition(isContentFlowReversed() ? -position() - footerSize() : position() + size() - footerSize()); |
| } else if (visibleItems.count()) { |
| if (footerPositioning == QQuickListView::PullBackFooter) { |
| qreal viewPos = isContentFlowReversed() ? -position() : position() + size(); |
| qreal clampedPos = qBound(originPosition() - footerSize() + size(), listItem->position(), lastPosition()); |
| listItem->setPosition(qBound(viewPos - footerSize(), clampedPos, viewPos)); |
| } else { |
| qreal endPos = lastPosition(); |
| if (findLastVisibleIndex() == model->count()-1) { |
| listItem->setPosition(endPos); |
| } else { |
| qreal visiblePos = position() + q->height(); |
| if (endPos <= visiblePos || listItem->position() < endPos) |
| listItem->setPosition(endPos); |
| } |
| } |
| } else { |
| listItem->setPosition(visiblePos); |
| } |
| |
| if (created) |
| emit q->footerItemChanged(); |
| } |
| |
| void QQuickListViewPrivate::updateHeader() |
| { |
| Q_Q(QQuickListView); |
| bool created = false; |
| if (!header) { |
| QQuickItem *item = createComponentItem(headerComponent, 1.0); |
| if (!item) |
| return; |
| header = new FxListItemSG(item, q, true); |
| header->trackGeometry(true); |
| created = true; |
| } |
| |
| FxListItemSG *listItem = static_cast<FxListItemSG*>(header); |
| if (headerPositioning == QQuickListView::OverlayHeader) { |
| listItem->setPosition(isContentFlowReversed() ? -position() - size() : position()); |
| } else if (visibleItems.count()) { |
| if (headerPositioning == QQuickListView::PullBackHeader) { |
| qreal viewPos = isContentFlowReversed() ? -position() - size() : position(); |
| qreal clampedPos = qBound(originPosition() - headerSize(), listItem->position(), lastPosition() - headerSize() - size()); |
| listItem->setPosition(qBound(viewPos - headerSize(), clampedPos, viewPos)); |
| } else { |
| qreal startPos = originPosition(); |
| if (visibleIndex == 0) { |
| listItem->setPosition(startPos - headerSize()); |
| } else { |
| if (position() <= startPos || listItem->position() > startPos - headerSize()) |
| listItem->setPosition(startPos - headerSize()); |
| } |
| } |
| } else { |
| listItem->setPosition(-headerSize()); |
| } |
| |
| if (created) |
| emit q->headerItemChanged(); |
| } |
| |
| bool QQuickListViewPrivate::hasStickyHeader() const |
| { |
| return header && headerPositioning != QQuickListView::InlineHeader; |
| } |
| |
| bool QQuickListViewPrivate::hasStickyFooter() const |
| { |
| return footer && footerPositioning != QQuickListView::InlineFooter; |
| } |
| |
| void QQuickListViewPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, |
| const QRectF &oldGeometry) |
| { |
| Q_Q(QQuickListView); |
| |
| QQuickItemViewPrivate::itemGeometryChanged(item, change, oldGeometry); |
| if (!q->isComponentComplete()) |
| return; |
| |
| if (currentItem && currentItem->item == item) { |
| const bool contentFlowReversed = isContentFlowReversed(); |
| const qreal pos = position(); |
| const qreal sz = size(); |
| const qreal from = contentFlowReversed ? -pos - displayMarginBeginning - sz : pos - displayMarginBeginning; |
| const qreal to = contentFlowReversed ? -pos + displayMarginEnd : pos + sz + displayMarginEnd; |
| QQuickItemPrivate::get(currentItem->item)->setCulled(currentItem->endPosition() < from || currentItem->position() > to); |
| } |
| |
| if (item != contentItem && (!highlight || item != highlight->item)) { |
| if ((orient == QQuickListView::Vertical && change.heightChange()) |
| || (orient == QQuickListView::Horizontal && change.widthChange())) { |
| |
| // if visibleItems.first() has resized, adjust its pos since it is used to |
| // position all subsequent items |
| if (visibleItems.count() && item == visibleItems.constFirst()->item) { |
| FxListItemSG *listItem = static_cast<FxListItemSG*>(visibleItems.constFirst()); |
| if (listItem->transitionScheduledOrRunning()) |
| return; |
| if (orient == QQuickListView::Vertical) { |
| const qreal oldItemEndPosition = verticalLayoutDirection == QQuickItemView::BottomToTop ? -oldGeometry.y() : oldGeometry.y() + oldGeometry.height(); |
| const qreal heightDiff = item->height() - oldGeometry.height(); |
| if (verticalLayoutDirection == QQuickListView::TopToBottom && oldItemEndPosition < q->contentY()) |
| listItem->setPosition(listItem->position() - heightDiff, true); |
| else if (verticalLayoutDirection == QQuickListView::BottomToTop && oldItemEndPosition > q->contentY()) |
| listItem->setPosition(listItem->position() + heightDiff, true); |
| } else { |
| const qreal oldItemEndPosition = q->effectiveLayoutDirection() == Qt::RightToLeft ? -oldGeometry.x() : oldGeometry.x() + oldGeometry.width(); |
| const qreal widthDiff = item->width() - oldGeometry.width(); |
| if (q->effectiveLayoutDirection() == Qt::LeftToRight && oldItemEndPosition < q->contentX()) |
| listItem->setPosition(listItem->position() - widthDiff, true); |
| else if (q->effectiveLayoutDirection() == Qt::RightToLeft && oldItemEndPosition > q->contentX()) |
| listItem->setPosition(listItem->position() + widthDiff, true); |
| } |
| } |
| forceLayoutPolish(); |
| } |
| } |
| } |
| |
| void QQuickListViewPrivate::fixupPosition() |
| { |
| if (orient == QQuickListView::Vertical) |
| fixupY(); |
| else |
| fixupX(); |
| } |
| |
| void QQuickListViewPrivate::fixup(AxisData &data, qreal minExtent, qreal maxExtent) |
| { |
| if (orient == QQuickListView::Horizontal && &data == &vData) { |
| if (flickableDirection != QQuickFlickable::HorizontalFlick) |
| QQuickItemViewPrivate::fixup(data, minExtent, maxExtent); |
| return; |
| } else if (orient == QQuickListView::Vertical && &data == &hData) { |
| if (flickableDirection != QQuickFlickable::VerticalFlick) |
| QQuickItemViewPrivate::fixup(data, minExtent, maxExtent); |
| return; |
| } |
| |
| correctFlick = false; |
| fixupMode = moveReason == Mouse ? fixupMode : Immediate; |
| bool strictHighlightRange = haveHighlightRange && highlightRange == QQuickListView::StrictlyEnforceRange; |
| |
| qreal viewPos = isContentFlowReversed() ? -position()-size() : position(); |
| |
| if (snapMode != QQuickListView::NoSnap && moveReason != QQuickListViewPrivate::SetIndex) { |
| qreal tempPosition = isContentFlowReversed() ? -position()-size() : position(); |
| if (snapMode == QQuickListView::SnapOneItem && moveReason == Mouse) { |
| // if we've been dragged < averageSize/2 then bias towards the next item |
| qreal dist = data.move.value() - data.pressPos; |
| qreal bias = 0; |
| if (data.velocity > 0 && dist > QML_FLICK_SNAPONETHRESHOLD && dist < averageSize/2) |
| bias = averageSize/2; |
| else if (data.velocity < 0 && dist < -QML_FLICK_SNAPONETHRESHOLD && dist > -averageSize/2) |
| bias = -averageSize/2; |
| if (isContentFlowReversed()) |
| bias = -bias; |
| tempPosition -= bias; |
| } |
| FxViewItem *topItem = snapItemAt(tempPosition+highlightRangeStart); |
| if (strictHighlightRange && currentItem && (!topItem || (topItem->index != currentIndex && fixupMode == Immediate))) { |
| // StrictlyEnforceRange always keeps an item in range |
| updateHighlight(); |
| topItem = currentItem; |
| } |
| FxViewItem *bottomItem = snapItemAt(tempPosition+highlightRangeEnd); |
| if (strictHighlightRange && currentItem && (!bottomItem || (bottomItem->index != currentIndex && fixupMode == Immediate))) { |
| // StrictlyEnforceRange always keeps an item in range |
| updateHighlight(); |
| bottomItem = currentItem; |
| } |
| qreal pos; |
| bool isInBounds = -position() > maxExtent && -position() <= minExtent; |
| if (topItem && (isInBounds || strictHighlightRange)) { |
| if (topItem->index == 0 && header && tempPosition+highlightRangeStart < header->position()+header->size()/2 && !strictHighlightRange) { |
| pos = isContentFlowReversed() ? - header->position() + highlightRangeStart - size() : header->position() - highlightRangeStart; |
| } else { |
| if (isContentFlowReversed()) |
| pos = qMax(qMin(-static_cast<FxListItemSG*>(topItem)->itemPosition() + highlightRangeStart - size(), -maxExtent), -minExtent); |
| else |
| pos = qMax(qMin(static_cast<FxListItemSG*>(topItem)->itemPosition() - highlightRangeStart, -maxExtent), -minExtent); |
| } |
| } else if (bottomItem && isInBounds) { |
| if (isContentFlowReversed()) |
| pos = qMax(qMin(-static_cast<FxListItemSG*>(bottomItem)->itemPosition() + highlightRangeEnd - size(), -maxExtent), -minExtent); |
| else |
| pos = qMax(qMin(static_cast<FxListItemSG*>(bottomItem)->itemPosition() - highlightRangeEnd, -maxExtent), -minExtent); |
| } else { |
| QQuickItemViewPrivate::fixup(data, minExtent, maxExtent); |
| return; |
| } |
| |
| qreal dist = qAbs(data.move + pos); |
| if (dist > 0) { |
| timeline.reset(data.move); |
| if (fixupMode != Immediate) { |
| timeline.move(data.move, -pos, QEasingCurve(QEasingCurve::InOutQuad), fixupDuration/2); |
| data.fixingUp = true; |
| } else { |
| timeline.set(data.move, -pos); |
| } |
| vTime = timeline.time(); |
| } |
| } else if (currentItem && strictHighlightRange && moveReason != QQuickListViewPrivate::SetIndex) { |
| updateHighlight(); |
| qreal pos = static_cast<FxListItemSG*>(currentItem)->itemPosition(); |
| if (viewPos < pos + static_cast<FxListItemSG*>(currentItem)->itemSize() - highlightRangeEnd) |
| viewPos = pos + static_cast<FxListItemSG*>(currentItem)->itemSize() - highlightRangeEnd; |
| if (viewPos > pos - highlightRangeStart) |
| viewPos = pos - highlightRangeStart; |
| if (isContentFlowReversed()) |
| viewPos = -viewPos-size(); |
| |
| timeline.reset(data.move); |
| if (viewPos != position()) { |
| if (fixupMode != Immediate) { |
| if (fixupMode == ExtentChanged && data.fixingUp) |
| timeline.move(data.move, -viewPos, QEasingCurve(QEasingCurve::OutQuad), fixupDuration/2); |
| else |
| timeline.move(data.move, -viewPos, QEasingCurve(QEasingCurve::InOutQuad), fixupDuration/2); |
| data.fixingUp = true; |
| } else { |
| timeline.set(data.move, -viewPos); |
| } |
| } |
| vTime = timeline.time(); |
| } else { |
| QQuickItemViewPrivate::fixup(data, minExtent, maxExtent); |
| } |
| data.inOvershoot = false; |
| fixupMode = Normal; |
| } |
| |
| bool QQuickListViewPrivate::flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize, |
| QQuickTimeLineCallback::Callback fixupCallback, qreal velocity) |
| { |
| data.fixingUp = false; |
| moveReason = Mouse; |
| if ((!haveHighlightRange || highlightRange != QQuickListView::StrictlyEnforceRange) && snapMode == QQuickListView::NoSnap) { |
| correctFlick = true; |
| return QQuickItemViewPrivate::flick(data, minExtent, maxExtent, vSize, fixupCallback, velocity); |
| } |
| qreal maxDistance = 0; |
| qreal dataValue = isContentFlowReversed() ? -data.move.value()+size() : data.move.value(); |
| |
| // -ve velocity means list is moving up/left |
| if (velocity > 0) { |
| if (data.move.value() < minExtent) { |
| if (snapMode == QQuickListView::SnapOneItem && !hData.flicking && !vData.flicking) { |
| // if we've been dragged < averageSize/2 then bias towards the next item |
| qreal dist = data.move.value() - data.pressPos; |
| qreal bias = dist < averageSize/2 ? averageSize/2 : 0; |
| if (isContentFlowReversed()) |
| bias = -bias; |
| data.flickTarget = -snapPosAt(-(dataValue - highlightRangeStart) - bias) + highlightRangeStart; |
| maxDistance = qAbs(data.flickTarget - data.move.value()); |
| velocity = maxVelocity; |
| } else { |
| maxDistance = qAbs(minExtent - data.move.value()); |
| } |
| } |
| if (snapMode == QQuickListView::NoSnap && highlightRange != QQuickListView::StrictlyEnforceRange) |
| data.flickTarget = minExtent; |
| } else { |
| if (data.move.value() > maxExtent) { |
| if (snapMode == QQuickListView::SnapOneItem && !hData.flicking && !vData.flicking) { |
| // if we've been dragged < averageSize/2 then bias towards the next item |
| qreal dist = data.move.value() - data.pressPos; |
| qreal bias = -dist < averageSize/2 ? averageSize/2 : 0; |
| if (isContentFlowReversed()) |
| bias = -bias; |
| data.flickTarget = -snapPosAt(-(dataValue - highlightRangeStart) + bias) + highlightRangeStart; |
| maxDistance = qAbs(data.flickTarget - data.move.value()); |
| velocity = -maxVelocity; |
| } else { |
| maxDistance = qAbs(maxExtent - data.move.value()); |
| } |
| } |
| if (snapMode == QQuickListView::NoSnap && highlightRange != QQuickListView::StrictlyEnforceRange) |
| data.flickTarget = maxExtent; |
| } |
| bool overShoot = boundsBehavior & QQuickFlickable::OvershootBounds; |
| if (maxDistance > 0 || overShoot) { |
| // These modes require the list to stop exactly on an item boundary. |
| // The initial flick will estimate the boundary to stop on. |
| // Since list items can have variable sizes, the boundary will be |
| // reevaluated and adjusted as we approach the boundary. |
| qreal v = velocity; |
| if (maxVelocity != -1 && maxVelocity < qAbs(v)) { |
| if (v < 0) |
| v = -maxVelocity; |
| else |
| v = maxVelocity; |
| } |
| if (!hData.flicking && !vData.flicking) { |
| // the initial flick - estimate boundary |
| qreal accel = deceleration; |
| qreal v2 = v * v; |
| overshootDist = 0.0; |
| // + averageSize/4 to encourage moving at least one item in the flick direction |
| qreal dist = v2 / (accel * 2.0) + averageSize/4; |
| if (maxDistance > 0) |
| dist = qMin(dist, maxDistance); |
| if (v > 0) |
| dist = -dist; |
| if ((maxDistance > 0.0 && v2 / (2.0f * maxDistance) < accel) || snapMode == QQuickListView::SnapOneItem) { |
| if (snapMode != QQuickListView::SnapOneItem) { |
| qreal distTemp = isContentFlowReversed() ? -dist : dist; |
| data.flickTarget = -snapPosAt(-(dataValue - highlightRangeStart) + distTemp) + highlightRangeStart; |
| } |
| data.flickTarget = isContentFlowReversed() ? -data.flickTarget+size() : data.flickTarget; |
| if (overShoot) { |
| if (data.flickTarget > minExtent) { |
| overshootDist = overShootDistance(vSize); |
| data.flickTarget += overshootDist; |
| } else if (data.flickTarget < maxExtent) { |
| overshootDist = overShootDistance(vSize); |
| data.flickTarget -= overshootDist; |
| } |
| } |
| qreal adjDist = -data.flickTarget + data.move.value(); |
| if (qAbs(adjDist) > qAbs(dist)) { |
| // Prevent painfully slow flicking - adjust velocity to suit flickDeceleration |
| qreal adjv2 = accel * 2.0f * qAbs(adjDist); |
| if (adjv2 > v2) { |
| v2 = adjv2; |
| v = qSqrt(v2); |
| if (dist > 0) |
| v = -v; |
| } |
| } |
| dist = adjDist; |
| accel = v2 / (2.0f * qAbs(dist)); |
| } else if (overShoot) { |
| data.flickTarget = data.move.value() - dist; |
| if (data.flickTarget > minExtent) { |
| overshootDist = overShootDistance(vSize); |
| data.flickTarget += overshootDist; |
| } else if (data.flickTarget < maxExtent) { |
| overshootDist = overShootDistance(vSize); |
| data.flickTarget -= overshootDist; |
| } |
| } |
| timeline.reset(data.move); |
| timeline.accel(data.move, v, accel, maxDistance + overshootDist); |
| timeline.callback(QQuickTimeLineCallback(&data.move, fixupCallback, this)); |
| correctFlick = true; |
| return true; |
| } else { |
| // reevaluate the target boundary. |
| qreal newtarget = data.flickTarget; |
| if (snapMode != QQuickListView::NoSnap || highlightRange == QQuickListView::StrictlyEnforceRange) { |
| qreal tempFlickTarget = isContentFlowReversed() ? -data.flickTarget+size() : data.flickTarget; |
| newtarget = -snapPosAt(-(tempFlickTarget - highlightRangeStart)) + highlightRangeStart; |
| newtarget = isContentFlowReversed() ? -newtarget+size() : newtarget; |
| } |
| if (velocity < 0 && newtarget <= maxExtent) |
| newtarget = maxExtent - overshootDist; |
| else if (velocity > 0 && newtarget >= minExtent) |
| newtarget = minExtent + overshootDist; |
| if (newtarget == data.flickTarget) { // boundary unchanged - nothing to do |
| if (qAbs(velocity) < MinimumFlickVelocity) |
| correctFlick = false; |
| return false; |
| } |
| data.flickTarget = newtarget; |
| qreal dist = -newtarget + data.move.value(); |
| if ((v < 0 && dist < 0) || (v > 0 && dist > 0)) { |
| correctFlick = false; |
| timeline.reset(data.move); |
| fixup(data, minExtent, maxExtent); |
| return false; |
| } |
| timeline.reset(data.move); |
| timeline.accelDistance(data.move, v, -dist); |
| timeline.callback(QQuickTimeLineCallback(&data.move, fixupCallback, this)); |
| return false; |
| } |
| } else { |
| correctFlick = false; |
| timeline.reset(data.move); |
| fixup(data, minExtent, maxExtent); |
| return false; |
| } |
| } |
| |
| //---------------------------------------------------------------------------- |
| |
| /*! |
| \qmltype ListView |
| \instantiates QQuickListView |
| \inqmlmodule QtQuick |
| \ingroup qtquick-views |
| \inherits Flickable |
| \brief Provides a list view of items provided by a model. |
| |
| A ListView displays data from models created from built-in QML types like ListModel |
| and XmlListModel, or custom model classes defined in C++ that inherit from |
| QAbstractItemModel or QAbstractListModel. |
| |
| A ListView has a \l model, which defines the data to be displayed, and |
| a \l delegate, which defines how the data should be displayed. Items in a |
| ListView are laid out horizontally or vertically. List views are inherently |
| flickable because ListView inherits from \l Flickable. |
| |
| \section1 Example Usage |
| |
| The following example shows the definition of a simple list model defined |
| in a file called \c ContactModel.qml: |
| |
| \snippet qml/listview/ContactModel.qml 0 |
| |
| Another component can display this model data in a ListView, like this: |
| |
| \snippet qml/listview/listview.qml import |
| \codeline |
| \snippet qml/listview/listview.qml classdocs simple |
| |
| \image listview-simple.png |
| |
| Here, the ListView creates a \c ContactModel component for its model, and a \l Text item |
| for its delegate. The view will create a new \l Text component for each item in the model. Notice |
| the delegate is able to access the model's \c name and \c number data directly. |
| |
| An improved list view is shown below. The delegate is visually improved and is moved |
| into a separate \c contactDelegate component. |
| |
| \snippet qml/listview/listview.qml classdocs advanced |
| \image listview-highlight.png |
| |
| The currently selected item is highlighted with a blue \l Rectangle using the \l highlight property, |
| and \c focus is set to \c true to enable keyboard navigation for the list view. |
| The list view itself is a focus scope (see \l{Keyboard Focus in Qt Quick} for more details). |
| |
| Delegates are instantiated as needed and may be destroyed at any time. |
| They are parented to ListView's \l {Flickable::contentItem}{contentItem}, not to the view itself. |
| State should \e never be stored in a delegate. |
| |
| ListView attaches a number of properties to the root item of the delegate, for example |
| \c ListView.isCurrentItem. In the following example, the root delegate item can access |
| this attached property directly as \c ListView.isCurrentItem, while the child |
| \c contactInfo object must refer to this property as \c wrapper.ListView.isCurrentItem. |
| |
| \snippet qml/listview/listview.qml isCurrentItem |
| |
| \note Views do not enable \e clip automatically. If the view |
| is not clipped by another item or the screen, it will be necessary |
| to set \e {clip: true} in order to have the out of view items clipped |
| nicely. |
| |
| |
| \section1 ListView Layouts |
| |
| The layout of the items in a ListView can be controlled by these properties: |
| |
| \list |
| \li \l orientation - controls whether items flow horizontally or vertically. |
| This value can be either Qt.Horizontal or Qt.Vertical. |
| \li \l layoutDirection - controls the horizontal layout direction for a |
| horizontally-oriented view: that is, whether items are laid out from the left side of |
| the view to the right, or vice-versa. This value can be either Qt.LeftToRight or Qt.RightToLeft. |
| \li \l verticalLayoutDirection - controls the vertical layout direction for a vertically-oriented |
| view: that is, whether items are laid out from the top of the view down towards the bottom of |
| the view, or vice-versa. This value can be either ListView.TopToBottom or ListView.BottomToTop. |
| \endlist |
| |
| By default, a ListView has a vertical orientation, and items are laid out from top to bottom. The |
| table below shows the different layouts that a ListView can have, depending on the values of |
| the properties listed above. |
| |
| \table |
| \header |
| \li {2, 1} |
| \b ListViews with Qt.Vertical orientation |
| \row |
| \li Top to bottom |
| \image listview-layout-toptobottom.png |
| \li Bottom to top |
| \image listview-layout-bottomtotop.png |
| \header |
| \li {2, 1} |
| \b ListViews with Qt.Horizontal orientation |
| \row |
| \li Left to right |
| \image listview-layout-lefttoright.png |
| \li Right to left |
| \image listview-layout-righttoleft.png |
| \endtable |
| |
| \section1 Flickable Direction |
| |
| By default, a vertical ListView sets \l {Flickable::}{flickableDirection} to \e Flickable.Vertical, |
| and a horizontal ListView sets it to \e Flickable.Horizontal. Furthermore, a vertical ListView only |
| calculates (estimates) the \l {Flickable::}{contentHeight}, and a horizontal ListView only calculates |
| the \l {Flickable::}{contentWidth}. The other dimension is set to \e -1. |
| |
| Since Qt 5.9 (Qt Quick 2.9), it is possible to make a ListView that can be flicked to both directions. |
| In order to do this, the \l {Flickable::}{flickableDirection} can be set to \e Flickable.AutoFlickDirection |
| or \e Flickable.AutoFlickIfNeeded, and the desired \e contentWidth or \e contentHeight must be provided. |
| |
| \snippet qml/listview/listview.qml flickBothDirections |
| |
| \section1 Stacking Order in ListView |
| |
| The \l {QQuickItem::z}{Z value} of items determines whether they are |
| rendered above or below other items. ListView uses several different |
| default Z values, depending on what type of item is being created: |
| |
| \table |
| \header |
| \li Property |
| \li Default Z value |
| \row |
| \li \l delegate |
| \li 1 |
| \row |
| \li \l footer |
| \li 1 |
| \row |
| \li \l header |
| \li 1 |
| \row |
| \li \l highlight |
| \li 0 |
| \row |
| \li \l section.delegate |
| \li 2 |
| \endtable |
| |
| These default values are set if the Z value of the item is \c 0, so setting |
| the Z value of these items to \c 0 has no effect. Note that the Z value is |
| of type \l [QML] {real}, so it is possible to set fractional |
| values like \c 0.1. |
| |
| \sa {QML Data Models}, GridView, PathView, {Qt Quick Examples - Views} |
| */ |
| QQuickListView::QQuickListView(QQuickItem *parent) |
| : QQuickItemView(*(new QQuickListViewPrivate), parent) |
| { |
| } |
| |
| QQuickListView::~QQuickListView() |
| { |
| } |
| |
| /*! |
| \qmlattachedproperty bool QtQuick::ListView::isCurrentItem |
| This attached property is true if this delegate is the current item; otherwise false. |
| |
| It is attached to each instance of the delegate. |
| |
| This property may be used to adjust the appearance of the current item, for example: |
| |
| \snippet qml/listview/listview.qml isCurrentItem |
| */ |
| |
| /*! |
| \qmlattachedproperty ListView QtQuick::ListView::view |
| This attached property holds the view that manages this delegate instance. |
| |
| It is attached to each instance of the delegate and also to the header, the footer, |
| the section and the highlight delegates. |
| */ |
| |
| /*! |
| \qmlattachedproperty string QtQuick::ListView::previousSection |
| This attached property holds the section of the previous element. |
| |
| It is attached to each instance of the delegate. |
| |
| The section is evaluated using the \l {ListView::section.property}{section} properties. |
| */ |
| |
| /*! |
| \qmlattachedproperty string QtQuick::ListView::nextSection |
| This attached property holds the section of the next element. |
| |
| It is attached to each instance of the delegate. |
| |
| The section is evaluated using the \l {ListView::section.property}{section} properties. |
| */ |
| |
| /*! |
| \qmlattachedproperty string QtQuick::ListView::section |
| This attached property holds the section of this element. |
| |
| It is attached to each instance of the delegate. |
| |
| The section is evaluated using the \l {ListView::section.property}{section} properties. |
| */ |
| |
| /*! |
| \qmlattachedproperty bool QtQuick::ListView::delayRemove |
| |
| This attached property holds whether the delegate may be destroyed. It |
| is attached to each instance of the delegate. The default value is false. |
| |
| It is sometimes necessary to delay the destruction of an item |
| until an animation completes. The example delegate below ensures that the |
| animation completes before the item is removed from the list. |
| |
| \snippet qml/listview/listview.qml delayRemove |
| |
| If a \l remove transition has been specified, it will not be applied until |
| delayRemove is returned to \c false. |
| */ |
| |
| /*! |
| \qmlattachedsignal QtQuick::ListView::add() |
| This attached signal is emitted immediately after an item is added to the view. |
| |
| If an \l add transition is specified, it is applied immediately after |
| this signal is handled. |
| |
| The corresponding handler is \c onAdd. |
| */ |
| |
| /*! |
| \qmlattachedsignal QtQuick::ListView::remove() |
| This attached signal is emitted immediately before an item is removed from the view. |
| |
| If a \l remove transition has been specified, it is applied after |
| this signal is handled, providing that \l delayRemove is false. |
| |
| The corresponding handler is \c onRemove. |
| */ |
| |
| /*! |
| \qmlproperty model QtQuick::ListView::model |
| This property holds the model providing data for the list. |
| |
| The model provides the set of data that is used to create the items |
| in the view. Models can be created directly in QML using \l ListModel, \l XmlListModel |
| or \l ObjectModel, or provided by C++ model classes. If a C++ model class is |
| used, it must be a subclass of \l QAbstractItemModel or a simple list. |
| |
| \sa {qml-data-models}{Data Models} |
| */ |
| |
| /*! |
| \qmlproperty Component QtQuick::ListView::delegate |
| |
| The delegate provides a template defining each item instantiated by the view. |
| The index is exposed as an accessible \c index property. Properties of the |
| model are also available depending upon the type of \l {qml-data-models}{Data Model}. |
| |
| The number of objects and bindings in the delegate has a direct effect on the |
| flicking performance of the view. If at all possible, place functionality |
| that is not needed for the normal display of the delegate in a \l Loader which |
| can load additional components when needed. |
| |
| The ListView will lay out the items based on the size of the root item |
| in the delegate. |
| |
| It is recommended that the delegate's size be a whole number to avoid sub-pixel |
| alignment of items. |
| |
| The default \l {QQuickItem::z}{stacking order} of delegate instances is \c 1. |
| |
| \note Delegates are instantiated as needed and may be destroyed at any time. |
| They are parented to ListView's \l {Flickable::contentItem}{contentItem}, not to the view itself. |
| State should \e never be stored in a delegate. |
| |
| \sa {Stacking Order in ListView} |
| */ |
| /*! |
| \qmlproperty int QtQuick::ListView::currentIndex |
| \qmlproperty Item QtQuick::ListView::currentItem |
| |
| The \c currentIndex property holds the index of the current item, and |
| \c currentItem holds the current item. Setting the currentIndex to -1 |
| will clear the highlight and set currentItem to null. |
| |
| If highlightFollowsCurrentItem is \c true, setting either of these |
| properties will smoothly scroll the ListView so that the current |
| item becomes visible. |
| |
| Note that the position of the current item |
| may only be approximate until it becomes visible in the view. |
| */ |
| |
| /*! |
| \qmlproperty Item QtQuick::ListView::highlightItem |
| |
| This holds the highlight item created from the \l highlight component. |
| |
| The \c highlightItem is managed by the view unless |
| \l highlightFollowsCurrentItem is set to false. |
| The default \l {QQuickItem::z}{stacking order} |
| of the highlight item is \c 0. |
| |
| \sa highlight, highlightFollowsCurrentItem, {Stacking Order in ListView} |
| */ |
| |
| /*! |
| \qmlproperty int QtQuick::ListView::count |
| This property holds the number of items in the view. |
| */ |
| |
| /*! |
| \qmlproperty Component QtQuick::ListView::highlight |
| This property holds the component to use as the highlight. |
| |
| An instance of the highlight component is created for each list. |
| The geometry of the resulting component instance is managed by the list |
| so as to stay with the current item, unless the highlightFollowsCurrentItem |
| property is false. The default \l {QQuickItem::z}{stacking order} of the |
| highlight item is \c 0. |
| |
| \sa highlightItem, highlightFollowsCurrentItem, |
| {Qt Quick Examples - Views#Using Highlight}{ListView Highlight Example}, |
| {Stacking Order in ListView} |
| */ |
| |
| /*! |
| \qmlproperty bool QtQuick::ListView::highlightFollowsCurrentItem |
| This property holds whether the highlight is managed by the view. |
| |
| If this property is true (the default value), the highlight is moved smoothly |
| to follow the current item. Otherwise, the |
| highlight is not moved by the view, and any movement must be implemented |
| by the highlight. |
| |
| Here is a highlight with its motion defined by a \l {SpringAnimation} item: |
| |
| \snippet qml/listview/listview.qml highlightFollowsCurrentItem |
| |
| Note that the highlight animation also affects the way that the view |
| is scrolled. This is because the view moves to maintain the |
| highlight within the preferred highlight range (or visible viewport). |
| |
| \sa highlight, highlightMoveVelocity |
| */ |
| //###Possibly rename these properties, since they are very useful even without a highlight? |
| /*! |
| \qmlproperty real QtQuick::ListView::preferredHighlightBegin |
| \qmlproperty real QtQuick::ListView::preferredHighlightEnd |
| \qmlproperty enumeration QtQuick::ListView::highlightRangeMode |
| |
| These properties define the preferred range of the highlight (for the current item) |
| within the view. The \c preferredHighlightBegin value must be less than the |
| \c preferredHighlightEnd value. |
| |
| These properties affect the position of the current item when the list is scrolled. |
| For example, if the currently selected item should stay in the middle of the |
| list when the view is scrolled, set the \c preferredHighlightBegin and |
| \c preferredHighlightEnd values to the top and bottom coordinates of where the middle |
| item would be. If the \c currentItem is changed programmatically, the list will |
| automatically scroll so that the current item is in the middle of the view. |
| Furthermore, the behavior of the current item index will occur whether or not a |
| highlight exists. |
| |
| Valid values for \c highlightRangeMode are: |
| |
| \list |
| \li ListView.ApplyRange - the view attempts to maintain the highlight within the range. |
| However, the highlight can move outside of the range at the ends of the list or due |
| to mouse interaction. |
| \li ListView.StrictlyEnforceRange - the highlight never moves outside of the range. |
| The current item changes if a keyboard or mouse action would cause the highlight to move |
| outside of the range. |
| \li ListView.NoHighlightRange - this is the default value. |
| \endlist |
| */ |
| void QQuickListView::setHighlightFollowsCurrentItem(bool autoHighlight) |
| { |
| Q_D(QQuickListView); |
| if (d->autoHighlight != autoHighlight) { |
| if (!autoHighlight) { |
| if (d->highlightPosAnimator) |
| d->highlightPosAnimator->stop(); |
| if (d->highlightWidthAnimator) |
| d->highlightWidthAnimator->stop(); |
| if (d->highlightHeightAnimator) |
| d->highlightHeightAnimator->stop(); |
| } |
| QQuickItemView::setHighlightFollowsCurrentItem(autoHighlight); |
| } |
| } |
| |
| /*! |
| \qmlproperty real QtQuick::ListView::spacing |
| |
| This property holds the spacing between items. |
| |
| The default value is 0. |
| */ |
| qreal QQuickListView::spacing() const |
| { |
| Q_D(const QQuickListView); |
| return d->spacing; |
| } |
| |
| void QQuickListView::setSpacing(qreal spacing) |
| { |
| Q_D(QQuickListView); |
| if (spacing != d->spacing) { |
| d->spacing = spacing; |
| d->forceLayoutPolish(); |
| emit spacingChanged(); |
| } |
| } |
| |
| /*! |
| \qmlproperty enumeration QtQuick::ListView::orientation |
| This property holds the orientation of the list. |
| |
| Possible values: |
| |
| \list |
| \li ListView.Horizontal - Items are laid out horizontally |
| \li ListView.Vertical (default) - Items are laid out vertically |
| \endlist |
| |
| \table |
| \row |
| \li Horizontal orientation: |
| \image ListViewHorizontal.png |
| |
| \row |
| \li Vertical orientation: |
| \image listview-highlight.png |
| \endtable |
| |
| \sa {Flickable Direction} |
| */ |
| QQuickListView::Orientation QQuickListView::orientation() const |
| { |
| Q_D(const QQuickListView); |
| return d->orient; |
| } |
| |
| void QQuickListView::setOrientation(QQuickListView::Orientation orientation) |
| { |
| Q_D(QQuickListView); |
| if (d->orient != orientation) { |
| d->orient = orientation; |
| if (d->orient == Vertical) { |
| if (d->flickableDirection == HorizontalFlick) { |
| setFlickableDirection(VerticalFlick); |
| if (isComponentComplete()) |
| setContentWidth(-1); |
| } |
| setContentX(0); |
| } else { |
| if (d->flickableDirection == VerticalFlick) { |
| setFlickableDirection(HorizontalFlick); |
| if (isComponentComplete()) |
| setContentHeight(-1); |
| } |
| setContentY(0); |
| } |
| d->regenerate(true); |
| emit orientationChanged(); |
| } |
| } |
| |
| /*! |
| \qmlproperty enumeration QtQuick::ListView::layoutDirection |
| This property holds the layout direction of a horizontally-oriented list. |
| |
| Possible values: |
| |
| \list |
| \li Qt.LeftToRight (default) - Items will be laid out from left to right. |
| \li Qt.RightToLeft - Items will be laid out from right to let. |
| \endlist |
| |
| Setting this property has no effect if the \l orientation is Qt.Vertical. |
| |
| \sa ListView::effectiveLayoutDirection, ListView::verticalLayoutDirection |
| */ |
| |
| |
| /*! |
| \qmlproperty enumeration QtQuick::ListView::effectiveLayoutDirection |
| This property holds the effective layout direction of a horizontally-oriented list. |
| |
| When using the attached property \l {LayoutMirroring::enabled}{LayoutMirroring::enabled} for locale layouts, |
| the visual layout direction of the horizontal list will be mirrored. However, the |
| property \l {ListView::layoutDirection}{layoutDirection} will remain unchanged. |
| |
| \sa ListView::layoutDirection, {LayoutMirroring}{LayoutMirroring} |
| */ |
| |
| |
| /*! |
| \qmlproperty enumeration QtQuick::ListView::verticalLayoutDirection |
| This property holds the layout direction of a vertically-oriented list. |
| |
| Possible values: |
| |
| \list |
| \li ListView.TopToBottom (default) - Items are laid out from the top of the view down to the bottom of the view. |
| \li ListView.BottomToTop - Items are laid out from the bottom of the view up to the top of the view. |
| \endlist |
| |
| Setting this property has no effect if the \l orientation is Qt.Horizontal. |
| |
| \sa ListView::layoutDirection |
| */ |
| |
| |
| /*! |
| \qmlproperty bool QtQuick::ListView::keyNavigationWraps |
| This property holds whether the list wraps key navigation. |
| |
| If this is true, key navigation that would move the current item selection |
| past the end of the list instead wraps around and moves the selection to |
| the start of the list, and vice-versa. |
| |
| By default, key navigation is not wrapped. |
| */ |
| |
| /*! |
| \qmlproperty bool QtQuick::ListView::keyNavigationEnabled |
| \since 5.7 |
| |
| This property holds whether the key navigation of the list is enabled. |
| |
| If this is \c true, the user can navigate the view with a keyboard. |
| It is useful for applications that need to selectively enable or |
| disable mouse and keyboard interaction. |
| |
| By default, the value of this property is bound to |
| \l {Flickable::}{interactive} to ensure behavior compatibility for |
| existing applications. When explicitly set, it will cease to be bound to |
| the interactive property. |
| |
| \sa {Flickable::}{interactive} |
| */ |
| |
| |
| /*! |
| \qmlproperty int QtQuick::ListView::cacheBuffer |
| This property determines whether delegates are retained outside the |
| visible area of the view. |
| |
| If this value is greater than zero, the view may keep as many delegates |
| instantiated as it can fit within the buffer specified. For example, |
| if in a vertical view the delegate is 20 pixels high and \c cacheBuffer is |
| set to 40, then up to 2 delegates above and 2 delegates below the visible |
| area may be created/retained. The buffered delegates are created asynchronously, |
| allowing creation to occur across multiple frames and reducing the |
| likelihood of skipping frames. In order to improve painting performance |
| delegates outside the visible area are not painted. |
| |
| The default value of this property is platform dependent, but will usually |
| be a value greater than zero. Negative values are ignored. |
| |
| Note that cacheBuffer is not a pixel buffer - it only maintains additional |
| instantiated delegates. |
| |
| \note Setting this property is not a replacement for creating efficient delegates. |
| It can improve the smoothness of scrolling behavior at the expense of additional |
| memory usage. The fewer objects and bindings in a delegate, the faster a |
| view can be scrolled. It is important to realize that setting a cacheBuffer |
| will only postpone issues caused by slow-loading delegates, it is not a |
| solution for this scenario. |
| |
| The cacheBuffer operates outside of any display margins specified by |
| displayMarginBeginning or displayMarginEnd. |
| */ |
| |
| /*! |
| \qmlproperty int QtQuick::ListView::displayMarginBeginning |
| \qmlproperty int QtQuick::ListView::displayMarginEnd |
| \since QtQuick 2.3 |
| |
| This property allows delegates to be displayed outside of the view geometry. |
| |
| If this value is non-zero, the view will create extra delegates before the |
| start of the view, or after the end. The view will create as many delegates |
| as it can fit into the pixel size specified. |
| |
| For example, if in a vertical view the delegate is 20 pixels high and |
| \c displayMarginBeginning and \c displayMarginEnd are both set to 40, |
| then 2 delegates above and 2 delegates below will be created and shown. |
| |
| The default value is 0. |
| |
| This property is meant for allowing certain UI configurations, |
| and not as a performance optimization. If you wish to create delegates |
| outside of the view geometry for performance reasons, you probably |
| want to use the cacheBuffer property instead. |
| */ |
| |
| /*! |
| \qmlpropertygroup QtQuick::ListView::section |
| \qmlproperty string QtQuick::ListView::section.property |
| \qmlproperty enumeration QtQuick::ListView::section.criteria |
| \qmlproperty Component QtQuick::ListView::section.delegate |
| \qmlproperty enumeration QtQuick::ListView::section.labelPositioning |
| |
| These properties determine the expression to be evaluated and appearance |
| of the section labels. |
| |
| \c section.property holds the name of the property that is the basis |
| of each section. |
| |
| \c section.criteria holds the criteria for forming each section based on |
| \c section.property. This value can be one of: |
| |
| \list |
| \li ViewSection.FullString (default) - sections are created based on the |
| \c section.property value. |
| \li ViewSection.FirstCharacter - sections are created based on the first |
| character of the \c section.property value (for example, 'A', 'B', 'C' |
| sections, etc. for an address book) |
| \endlist |
| |
| A case insensitive comparison is used when determining section |
| boundaries. |
| |
| \c section.delegate holds the delegate component for each section. The |
| default \l {QQuickItem::z}{stacking order} of section delegate instances |
| is \c 2. |
| |
| \c section.labelPositioning determines whether the current and/or |
| next section labels stick to the start/end of the view, and whether |
| the labels are shown inline. This value can be a combination of: |
| |
| \list |
| \li ViewSection.InlineLabels - section labels are shown inline between |
| the item delegates separating sections (default). |
| \li ViewSection.CurrentLabelAtStart - the current section label sticks to the |
| start of the view as it is moved. |
| \li ViewSection.NextLabelAtEnd - the next section label (beyond all visible |
| sections) sticks to the end of the view as it is moved. \note Enabling |
| \c ViewSection.NextLabelAtEnd requires the view to scan ahead for the next |
| section, which has performance implications, especially for slower models. |
| \endlist |
| |
| Each item in the list has attached properties named \c ListView.section, |
| \c ListView.previousSection and \c ListView.nextSection. |
| |
| For example, here is a ListView that displays a list of animals, separated |
| into sections. Each item in the ListView is placed in a different section |
| depending on the "size" property of the model item. The \c sectionHeading |
| delegate component provides the light blue bar that marks the beginning of |
| each section. |
| |
| |
| \snippet views/listview/sections.qml 0 |
| |
| \image qml-listview-sections-example.png |
| |
| \note Adding sections to a ListView does not automatically re-order the |
| list items by the section criteria. |
| If the model is not ordered by section, then it is possible that |
| the sections created will not be unique; each boundary between |
| differing sections will result in a section header being created |
| even if that section exists elsewhere. |
| |
| \sa {Qt Quick Examples - Views}{ListView examples}, |
| {Stacking Order in ListView} |
| */ |
| QQuickViewSection *QQuickListView::sectionCriteria() |
| { |
| Q_D(QQuickListView); |
| if (!d->sectionCriteria) |
| d->sectionCriteria = new QQuickViewSection(this); |
| return d->sectionCriteria; |
| } |
| |
| /*! |
| \qmlproperty string QtQuick::ListView::currentSection |
| This property holds the section that is currently at the beginning of the view. |
| */ |
| QString QQuickListView::currentSection() const |
| { |
| Q_D(const QQuickListView); |
| return d->currentSection; |
| } |
| |
| /*! |
| \qmlproperty real QtQuick::ListView::highlightMoveVelocity |
| \qmlproperty int QtQuick::ListView::highlightMoveDuration |
| \qmlproperty real QtQuick::ListView::highlightResizeVelocity |
| \qmlproperty int QtQuick::ListView::highlightResizeDuration |
| |
| These properties control the speed of the move and resize animations for the |
| highlight delegate. |
| |
| \l highlightFollowsCurrentItem must be true for these properties |
| to have effect. |
| |
| The default value for the velocity properties is 400 pixels/second. |
| The default value for the duration properties is -1, i.e. the |
| highlight will take as much time as necessary to move at the set speed. |
| |
| These properties have the same characteristics as a SmoothedAnimation: |
| if both the velocity and duration are set, the animation will use |
| whichever gives the shorter duration. |
| |
| The move velocity and duration properties are used to control movement due |
| to index changes; for example, when incrementCurrentIndex() is called. When |
| the user flicks a ListView, the velocity from the flick is used to control |
| the movement instead. |
| |
| To set only one property, the other can be set to \c -1. For example, |
| if you only want to animate the duration and not velocity, use the |
| following code: |
| |
| \code |
| highlightMoveDuration: 1000 |
| highlightMoveVelocity: -1 |
| \endcode |
| |
| \sa highlightFollowsCurrentItem |
| */ |
| qreal QQuickListView::highlightMoveVelocity() const |
| { |
| Q_D(const QQuickListView); |
| return d->highlightMoveVelocity; |
| } |
| |
| void QQuickListView::setHighlightMoveVelocity(qreal speed) |
| { |
| Q_D(QQuickListView); |
| if (d->highlightMoveVelocity != speed) { |
| d->highlightMoveVelocity = speed; |
| if (d->highlightPosAnimator) |
| d->highlightPosAnimator->velocity = d->highlightMoveVelocity; |
| emit highlightMoveVelocityChanged(); |
| } |
| } |
| |
| void QQuickListView::setHighlightMoveDuration(int duration) |
| { |
| Q_D(QQuickListView); |
| if (d->highlightMoveDuration != duration) { |
| if (d->highlightPosAnimator) |
| d->highlightPosAnimator->userDuration = duration; |
| QQuickItemView::setHighlightMoveDuration(duration); |
| } |
| } |
| |
| qreal QQuickListView::highlightResizeVelocity() const |
| { |
| Q_D(const QQuickListView); |
| return d->highlightResizeVelocity; |
| } |
| |
| void QQuickListView::setHighlightResizeVelocity(qreal speed) |
| { |
| Q_D(QQuickListView); |
| if (d->highlightResizeVelocity != speed) { |
| d->highlightResizeVelocity = speed; |
| if (d->highlightWidthAnimator) |
| d->highlightWidthAnimator->velocity = d->highlightResizeVelocity; |
| if (d->highlightHeightAnimator) |
| d->highlightHeightAnimator->velocity = d->highlightResizeVelocity; |
| emit highlightResizeVelocityChanged(); |
| } |
| } |
| |
| int QQuickListView::highlightResizeDuration() const |
| { |
| Q_D(const QQuickListView); |
| return d->highlightResizeDuration; |
| } |
| |
| void QQuickListView::setHighlightResizeDuration(int duration) |
| { |
| Q_D(QQuickListView); |
| if (d->highlightResizeDuration != duration) { |
| d->highlightResizeDuration = duration; |
| if (d->highlightWidthAnimator) |
| d->highlightWidthAnimator->userDuration = d->highlightResizeDuration; |
| if (d->highlightHeightAnimator) |
| d->highlightHeightAnimator->userDuration = d->highlightResizeDuration; |
| emit highlightResizeDurationChanged(); |
| } |
| } |
| |
| /*! |
| \qmlproperty enumeration QtQuick::ListView::snapMode |
| |
| This property determines how the view scrolling will settle following a drag or flick. |
| The possible values are: |
| |
| \list |
| \li ListView.NoSnap (default) - the view stops anywhere within the visible area. |
| \li ListView.SnapToItem - the view settles with an item aligned with the start of |
| the view. |
| \li ListView.SnapOneItem - the view settles no more than one item away from the first |
| visible item at the time the mouse button is released. This mode is particularly |
| useful for moving one page at a time. When SnapOneItem is enabled, the ListView will |
| show a stronger affinity to neighboring items when movement occurs. For example, a |
| short drag that snaps back to the current item with SnapToItem might snap to a |
| neighboring item with SnapOneItem. |
| |
| \endlist |
| |
| \c snapMode does not affect the \l currentIndex. To update the |
| \l currentIndex as the list is moved, set \l highlightRangeMode |
| to \c ListView.StrictlyEnforceRange. |
| |
| \sa highlightRangeMode |
| */ |
| QQuickListView::SnapMode QQuickListView::snapMode() const |
| { |
| Q_D(const QQuickListView); |
| return d->snapMode; |
| } |
| |
| void QQuickListView::setSnapMode(SnapMode mode) |
| { |
| Q_D(QQuickListView); |
| if (d->snapMode != mode) { |
| d->snapMode = mode; |
| emit snapModeChanged(); |
| d->fixupPosition(); |
| } |
| } |
| |
| |
| /*! |
| \qmlproperty Component QtQuick::ListView::footer |
| This property holds the component to use as the footer. |
| |
| An instance of the footer component is created for each view. The |
| footer is positioned at the end of the view, after any items. The |
| default \l {QQuickItem::z}{stacking order} of the footer is \c 1. |
| |
| \sa header, footerItem, {Stacking Order in ListView} |
| */ |
| |
| |
| /*! |
| \qmlproperty Component QtQuick::ListView::header |
| This property holds the component to use as the header. |
| |
| An instance of the header component is created for each view. The |
| header is positioned at the beginning of the view, before any items. |
| The default \l {QQuickItem::z}{stacking order} of the header is \c 1. |
| |
| \sa footer, headerItem, {Stacking Order in ListView} |
| */ |
| |
| /*! |
| \qmlproperty Item QtQuick::ListView::headerItem |
| This holds the header item created from the \l header component. |
| |
| An instance of the header component is created for each view. The |
| header is positioned at the beginning of the view, before any items. |
| The default \l {QQuickItem::z}{stacking order} of the header is \c 1. |
| |
| \sa header, footerItem, {Stacking Order in ListView} |
| */ |
| |
| /*! |
| \qmlproperty Item QtQuick::ListView::footerItem |
| This holds the footer item created from the \l footer component. |
| |
| An instance of the footer component is created for each view. The |
| footer is positioned at the end of the view, after any items. The |
| default \l {QQuickItem::z}{stacking order} of the footer is \c 1. |
| |
| \sa footer, headerItem, {Stacking Order in ListView} |
| */ |
| |
| /*! |
| \qmlproperty enumeration QtQuick::ListView::headerPositioning |
| \since Qt 5.4 |
| |
| This property determines the positioning of the \l{headerItem}{header item}. |
| |
| The possible values are: |
| \list |
| \li ListView.InlineHeader (default) - the header is positioned in the beginning |
| of the content and moves together with the content like an ordinary item. |
| \li ListView.OverlayHeader - the header is positioned in the beginning of the view. |
| \li ListView.PullBackHeader - the header is positioned in the beginning of the view. |
| The header can be pushed away by moving the content forwards, and pulled back by |
| moving the content backwards. |
| \endlist |
| |
| \note This property has no effect on the \l {QQuickItem::z}{stacking order} |
| of the header. For example, if the header should be shown above the |
| \l delegate items when using \c ListView.OverlayHeader, its Z value |
| should be set to a value higher than that of the delegates. For more |
| information, see \l {Stacking Order in ListView}. |
| */ |
| QQuickListView::HeaderPositioning QQuickListView::headerPositioning() const |
| { |
| Q_D(const QQuickListView); |
| return d->headerPositioning; |
| } |
| |
| void QQuickListView::setHeaderPositioning(QQuickListView::HeaderPositioning positioning) |
| { |
| Q_D(QQuickListView); |
| if (d->headerPositioning != positioning) { |
| d->applyPendingChanges(); |
| d->headerPositioning = positioning; |
| if (isComponentComplete()) { |
| d->updateHeader(); |
| d->updateViewport(); |
| d->fixupPosition(); |
| } |
| emit headerPositioningChanged(); |
| } |
| } |
| |
| /*! |
| \qmlproperty enumeration QtQuick::ListView::footerPositioning |
| \since Qt 5.4 |
| |
| This property determines the positioning of the \l{footerItem}{footer item}. |
| |
| The possible values are: |
| \list |
| \li ListView.InlineFooter (default) - the footer is positioned in the end |
| of the content and moves together with the content like an ordinary item. |
| \li ListView.OverlayFooter - the footer is positioned in the end of the view. |
| \li ListView.PullBackFooter - the footer is positioned in the end of the view. |
| The footer can be pushed away by moving the content backwards, and pulled back by |
| moving the content forwards. |
| \endlist |
| |
| \note This property has no effect on the \l {QQuickItem::z}{stacking order} |
| of the footer. For example, if the footer should be shown above the |
| \l delegate items when using \c ListView.OverlayFooter, its Z value |
| should be set to a value higher than that of the delegates. For more |
| information, see \l {Stacking Order in ListView}. |
| */ |
| QQuickListView::FooterPositioning QQuickListView::footerPositioning() const |
| { |
| Q_D(const QQuickListView); |
| return d->footerPositioning; |
| } |
| |
| void QQuickListView::setFooterPositioning(QQuickListView::FooterPositioning positioning) |
| { |
| Q_D(QQuickListView); |
| if (d->footerPositioning != positioning) { |
| d->applyPendingChanges(); |
| d->footerPositioning = positioning; |
| if (isComponentComplete()) { |
| d->updateFooter(); |
| d->updateViewport(); |
| d->fixupPosition(); |
| } |
| emit footerPositioningChanged(); |
| } |
| } |
| |
| /*! |
| \qmlproperty Transition QtQuick::ListView::populate |
| |
| This property holds the transition to apply to the items that are initially created |
| for a view. |
| |
| It is applied to all items that are created when: |
| |
| \list |
| \li The view is first created |
| \li The view's \l model changes in such a way that the visible delegates are completely replaced |
| \li The view's \l model is \l {QAbstractItemModel::reset()}{reset}, if the model is a QAbstractItemModel subclass |
| \endlist |
| |
| For example, here is a view that specifies such a transition: |
| |
| \code |
| ListView { |
| ... |
| populate: Transition { |
| NumberAnimation { properties: "x,y"; duration: 1000 } |
| } |
| } |
| \endcode |
| |
| When the view is initialized, the view will create all the necessary items for the view, |
| then animate them to their correct positions within the view over one second. |
| |
| However when scrolling the view later, the populate transition does not |
| run, even though delegates are being instantiated as they become visible. |
| When the model changes in a way that new delegates become visible, the |
| \l add transition is the one that runs. So you should not depend on the |
| \c populate transition to initialize properties in the delegate, because it |
| does not apply to every delegate. If your animation sets the \c to value of |
| a property, the property should initially have the \c to value, and the |
| animation should set the \c from value in case it is animated: |
| |
| \code |
| ListView { |
| ... |
| delegate: Rectangle { |
| opacity: 1 // not necessary because it's the default |
| } |
| populate: Transition { |
| NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 1000 } |
| } |
| } |
| \endcode |
| |
| For more details and examples on how to use view transitions, see the ViewTransition |
| documentation. |
| |
| \sa add, ViewTransition |
| */ |
| |
| /*! |
| \qmlproperty Transition QtQuick::ListView::add |
| |
| This property holds the transition to apply to items that are added to the view. |
| |
| For example, here is a view that specifies such a transition: |
| |
| \code |
| ListView { |
| ... |
| add: Transition { |
| NumberAnimation { properties: "x,y"; from: 100; duration: 1000 } |
| } |
| } |
| \endcode |
| |
| Whenever an item is added to the above view, the item will be animated from the position (100,100) |
| to its final x,y position within the view, over one second. The transition only applies to |
| the new items that are added to the view; it does not apply to the items below that are |
| displaced by the addition of the new items. To animate the displaced items, set the \l displaced |
| or \l addDisplaced properties. |
| |
| For more details and examples on how to use view transitions, see the ViewTransition |
| documentation. |
| |
| \note This transition is not applied to the items that are created when the view is initially |
| populated, or when the view's \l model changes. (In those cases, the \l populate transition is |
| applied instead.) Additionally, this transition should \e not animate the height of the new item; |
| doing so will cause any items beneath the new item to be laid out at the wrong position. Instead, |
| the height can be animated within the \l {add}{onAdd} handler in the delegate. |
| |
| \sa addDisplaced, populate, ViewTransition |
| */ |
| |
| /*! |
| \qmlproperty Transition QtQuick::ListView::addDisplaced |
| |
| This property holds the transition to apply to items within the view that are displaced by |
| the addition of other items to the view. |
| |
| For example, here is a view that specifies such a transition: |
| |
| \code |
| ListView { |
| ... |
| addDisplaced: Transition { |
| NumberAnimation { properties: "x,y"; duration: 1000 } |
| } |
| } |
| \endcode |
| |
| Whenever an item is added to the above view, all items beneath the new item are displaced, causing |
| them to move down (or sideways, if horizontally orientated) within the view. As this |
| displacement occurs, the items' movement to their new x,y positions within the view will be |
| animated by a NumberAnimation over one second, as specified. This transition is not applied to |
| the new item that has been added to the view; to animate the added items, set the \l add |
| property. |
| |
| If an item is displaced by multiple types of operations at the same time, it is not defined as to |
| whether the addDisplaced, moveDisplaced or removeDisplaced transition will be applied. Additionally, |
| if it is not necessary to specify different transitions depending on whether an item is displaced |
| by an add, move or remove operation, consider setting the \l displaced property instead. |
| |
| For more details and examples on how to use view transitions, see the ViewTransition |
| documentation. |
| |
| \note This transition is not applied to the items that are created when the view is initially |
| populated, or when the view's \l model changes. In those cases, the \l populate transition is |
| applied instead. |
| |
| \sa displaced, add, populate, ViewTransition |
| */ |
| |
| /*! |
| \qmlproperty Transition QtQuick::ListView::move |
| |
| This property holds the transition to apply to items in the view that are being moved due |
| to a move operation in the view's \l model. |
| |
| For example, here is a view that specifies such a transition: |
| |
| \code |
| ListView { |
| ... |
| move: Transition { |
| NumberAnimation { properties: "x,y"; duration: 1000 } |
| } |
| } |
| \endcode |
| |
| Whenever the \l model performs a move operation to move a particular set of indexes, the |
| respective items in the view will be animated to their new positions in the view over one |
| second. The transition only applies to the items that are the subject of the move operation |
| in the model; it does not apply to items below them that are displaced by the move operation. |
| To animate the displaced items, set the \l displaced or \l moveDisplaced properties. |
| |
| For more details and examples on how to use view transitions, see the ViewTransition |
| documentation. |
| |
| \sa moveDisplaced, ViewTransition |
| */ |
| |
| /*! |
| \qmlproperty Transition QtQuick::ListView::moveDisplaced |
| |
| This property holds the transition to apply to items that are displaced by a move operation in |
| the view's \l model. |
| |
| For example, here is a view that specifies such a transition: |
| |
| \code |
| ListView { |
| ... |
| moveDisplaced: Transition { |
| NumberAnimation { properties: "x,y"; duration: 1000 } |
| } |
| } |
| \endcode |
| |
| Whenever the \l model performs a move operation to move a particular set of indexes, the items |
| between the source and destination indexes of the move operation are displaced, causing them |
| to move upwards or downwards (or sideways, if horizontally orientated) within the view. As this |
| displacement occurs, the items' movement to their new x,y positions within the view will be |
| animated by a NumberAnimation over one second, as specified. This transition is not applied to |
| the items that are the actual subjects of the move operation; to animate the moved items, set |
| the \l move property. |
| |
| If an item is displaced by multiple types of operations at the same time, it is not defined as to |
| whether the addDisplaced, moveDisplaced or removeDisplaced transition will be applied. Additionally, |
| if it is not necessary to specify different transitions depending on whether an item is displaced |
| by an add, move or remove operation, consider setting the \l displaced property instead. |
| |
| For more details and examples on how to use view transitions, see the ViewTransition |
| documentation. |
| |
| \sa displaced, move, ViewTransition |
| */ |
| |
| /*! |
| \qmlproperty Transition QtQuick::ListView::remove |
| |
| This property holds the transition to apply to items that are removed from the view. |
| |
| For example, here is a view that specifies such a transition: |
| |
| \code |
| ListView { |
| ... |
| remove: Transition { |
| ParallelAnimation { |
| NumberAnimation { property: "opacity"; to: 0; duration: 1000 } |
| NumberAnimation { properties: "x,y"; to: 100; duration: 1000 } |
| } |
| } |
| } |
| \endcode |
| |
| Whenever an item is removed from the above view, the item will be animated to the position (100,100) |
| over one second, and in parallel will also change its opacity to 0. The transition |
| only applies to the items that are removed from the view; it does not apply to the items below |
| them that are displaced by the removal of the items. To animate the displaced items, set the |
| \l displaced or \l removeDisplaced properties. |
| |
| Note that by the time the transition is applied, the item has already been removed from the |
| model; any references to the model data for the removed index will not be valid. |
| |
| Additionally, if the \l delayRemove attached property has been set for a delegate item, the |
| remove transition will not be applied until \l delayRemove becomes false again. |
| |
| For more details and examples on how to use view transitions, see the ViewTransition |
| documentation. |
| |
| \sa removeDisplaced, ViewTransition |
| */ |
| |
| /*! |
| \qmlproperty Transition QtQuick::ListView::removeDisplaced |
| |
| This property holds the transition to apply to items in the view that are displaced by the |
| removal of other items in the view. |
| |
| For example, here is a view that specifies such a transition: |
| |
| \code |
| ListView { |
| ... |
| removeDisplaced: Transition { |
| NumberAnimation { properties: "x,y"; duration: 1000 } |
| } |
| } |
| \endcode |
| |
| Whenever an item is removed from the above view, all items beneath it are displaced, causing |
| them to move upwards (or sideways, if horizontally orientated) within the view. As this |
| displacement occurs, the items' movement to their new x,y positions within the view will be |
| animated by a NumberAnimation over one second, as specified. This transition is not applied to |
| the item that has actually been removed from the view; to animate the removed items, set the |
| \l remove property. |
| |
| If an item is displaced by multiple types of operations at the same time, it is not defined as to |
| whether the addDisplaced, moveDisplaced or removeDisplaced transition will be applied. Additionally, |
| if it is not necessary to specify different transitions depending on whether an item is displaced |
| by an add, move or remove operation, consider setting the \l displaced property instead. |
| |
| For more details and examples on how to use view transitions, see the ViewTransition |
| documentation. |
| |
| \sa displaced, remove, ViewTransition |
| */ |
| |
| /*! |
| \qmlproperty Transition QtQuick::ListView::displaced |
| This property holds the generic transition to apply to items that have been displaced by |
| any model operation that affects the view. |
| |
| This is a convenience for specifying the generic transition to be applied to any items |
| that are displaced by an add, move or remove operation, without having to specify the |
| individual addDisplaced, moveDisplaced and removeDisplaced properties. For example, here |
| is a view that specifies a displaced transition: |
| |
| \code |
| ListView { |
| ... |
| displaced: Transition { |
| NumberAnimation { properties: "x,y"; duration: 1000 } |
| } |
| } |
| \endcode |
| |
| When any item is added, moved or removed within the above view, the items below it are |
| displaced, causing them to move down (or sideways, if horizontally orientated) within the |
| view. As this displacement occurs, the items' movement to their new x,y positions within |
| the view will be animated by a NumberAnimation over one second, as specified. |
| |
| If a view specifies this generic displaced transition as well as a specific addDisplaced, |
| moveDisplaced or removeDisplaced transition, the more specific transition will be used |
| instead of the generic displaced transition when the relevant operation occurs, providing that |
| the more specific transition has not been disabled (by setting \l {Transition::enabled}{enabled} |
| to false). If it has indeed been disabled, the generic displaced transition is applied instead. |
| |
| For more details and examples on how to use view transitions, see the ViewTransition |
| documentation. |
| |
| \sa addDisplaced, moveDisplaced, removeDisplaced, ViewTransition |
| */ |
| |
| void QQuickListView::viewportMoved(Qt::Orientations orient) |
| { |
| Q_D(QQuickListView); |
| QQuickItemView::viewportMoved(orient); |
| |
| if (!d->itemCount) { |
| if (d->hasStickyHeader()) |
| d->updateHeader(); |
| if (d->hasStickyFooter()) |
| d->updateFooter(); |
| return; |
| } |
| |
| // Recursion can occur due to refill changing the content size. |
| if (d->inViewportMoved) |
| return; |
| d->inViewportMoved = true; |
| |
| if (yflick()) { |
| if (d->isBottomToTop()) |
| d->bufferMode = d->vData.smoothVelocity < 0 ? QQuickListViewPrivate::BufferAfter : QQuickListViewPrivate::BufferBefore; |
| else |
| d->bufferMode = d->vData.smoothVelocity < 0 ? QQuickListViewPrivate::BufferBefore : QQuickListViewPrivate::BufferAfter; |
| } else { |
| if (d->isRightToLeft()) |
| d->bufferMode = d->hData.smoothVelocity < 0 ? QQuickListViewPrivate::BufferAfter : QQuickListViewPrivate::BufferBefore; |
| else |
| d->bufferMode = d->hData.smoothVelocity < 0 ? QQuickListViewPrivate::BufferBefore : QQuickListViewPrivate::BufferAfter; |
| } |
| |
| d->refillOrLayout(); |
| |
| // Set visibility of items to eliminate cost of items outside the visible area. |
| qreal from = d->isContentFlowReversed() ? -d->position()-d->displayMarginBeginning-d->size() : d->position()-d->displayMarginBeginning; |
| qreal to = d->isContentFlowReversed() ? -d->position()+d->displayMarginEnd : d->position()+d->size()+d->displayMarginEnd; |
| for (FxViewItem *item : qAsConst(d->visibleItems)) { |
| if (item->item) |
| QQuickItemPrivate::get(item->item)->setCulled(item->endPosition() < from || item->position() > to); |
| } |
| if (d->currentItem) |
| QQuickItemPrivate::get(d->currentItem->item)->setCulled(d->currentItem->endPosition() < from || d->currentItem->position() > to); |
| |
| if (d->hData.flicking || d->vData.flicking || d->hData.moving || d->vData.moving) |
| d->moveReason = QQuickListViewPrivate::Mouse; |
| if (d->moveReason != QQuickListViewPrivate::SetIndex) { |
| if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange && d->highlight) { |
| // reposition highlight |
| qreal pos = d->highlight->position(); |
| qreal viewPos = d->isContentFlowReversed() ? -d->position()-d->size() : d->position(); |
| if (pos > viewPos + d->highlightRangeEnd - d->highlight->size()) |
| pos = viewPos + d->highlightRangeEnd - d->highlight->size(); |
| if (pos < viewPos + d->highlightRangeStart) |
| pos = viewPos + d->highlightRangeStart; |
| if (pos != d->highlight->position()) { |
| d->highlightPosAnimator->stop(); |
| static_cast<FxListItemSG*>(d->highlight)->setPosition(pos); |
| } else { |
| d->updateHighlight(); |
| } |
| |
| // update current index |
| if (FxViewItem *snapItem = d->snapItemAt(d->highlight->position())) { |
| if (snapItem->index >= 0 && snapItem->index != d->currentIndex) |
| d->updateCurrent(snapItem->index); |
| } |
| } |
| } |
| |
| if ((d->hData.flicking || d->vData.flicking) && d->correctFlick && !d->inFlickCorrection) { |
| d->inFlickCorrection = true; |
| // Near an end and it seems that the extent has changed? |
| // Recalculate the flick so that we don't end up in an odd position. |
| if (yflick() && !d->vData.inOvershoot) { |
| if (d->vData.velocity > 0) { |
| const qreal minY = minYExtent(); |
| if ((minY - d->vData.move.value() < height()/2 || d->vData.flickTarget - d->vData.move.value() < height()/2) |
| && minY != d->vData.flickTarget) |
| d->flickY(-d->vData.smoothVelocity.value()); |
| } else if (d->vData.velocity < 0) { |
| const qreal maxY = maxYExtent(); |
| if ((d->vData.move.value() - maxY < height()/2 || d->vData.move.value() - d->vData.flickTarget < height()/2) |
| && maxY != d->vData.flickTarget) |
| d->flickY(-d->vData.smoothVelocity.value()); |
| } |
| } |
| |
| if (xflick() && !d->hData.inOvershoot) { |
| if (d->hData.velocity > 0) { |
| const qreal minX = minXExtent(); |
| if ((minX - d->hData.move.value() < width()/2 || d->hData.flickTarget - d->hData.move.value() < width()/2) |
| && minX != d->hData.flickTarget) |
| d->flickX(-d->hData.smoothVelocity.value()); |
| } else if (d->hData.velocity < 0) { |
| const qreal maxX = maxXExtent(); |
| if ((d->hData.move.value() - maxX < width()/2 || d->hData.move.value() - d->hData.flickTarget < width()/2) |
| && maxX != d->hData.flickTarget) |
| d->flickX(-d->hData.smoothVelocity.value()); |
| } |
| } |
| d->inFlickCorrection = false; |
| } |
| if (d->hasStickyHeader()) |
| d->updateHeader(); |
| if (d->hasStickyFooter()) |
| d->updateFooter(); |
| if (d->sectionCriteria) { |
| d->updateCurrentSection(); |
| d->updateStickySections(); |
| } |
| d->inViewportMoved = false; |
| } |
| |
| void QQuickListView::keyPressEvent(QKeyEvent *event) |
| { |
| Q_D(QQuickListView); |
| if (d->model && d->model->count() && ((d->interactive && !d->explicitKeyNavigationEnabled) |
| || (d->explicitKeyNavigationEnabled && d->keyNavigationEnabled))) { |
| if ((d->orient == QQuickListView::Horizontal && !d->isRightToLeft() && event->key() == Qt::Key_Left) |
| || (d->orient == QQuickListView::Horizontal && d->isRightToLeft() && event->key() == Qt::Key_Right) |
| || (d->orient == QQuickListView::Vertical && !d->isBottomToTop() && event->key() == Qt::Key_Up) |
| || (d->orient == QQuickListView::Vertical && d->isBottomToTop() && event->key() == Qt::Key_Down)) { |
| if (currentIndex() > 0 || (d->wrap && !event->isAutoRepeat())) { |
| decrementCurrentIndex(); |
| event->accept(); |
| return; |
| } else if (d->wrap) { |
| event->accept(); |
| return; |
| } |
| } else if ((d->orient == QQuickListView::Horizontal && !d->isRightToLeft() && event->key() == Qt::Key_Right) |
| || (d->orient == QQuickListView::Horizontal && d->isRightToLeft() && event->key() == Qt::Key_Left) |
| || (d->orient == QQuickListView::Vertical && !d->isBottomToTop() && event->key() == Qt::Key_Down) |
| || (d->orient == QQuickListView::Vertical && d->isBottomToTop() && event->key() == Qt::Key_Up)) { |
| if (currentIndex() < d->model->count() - 1 || (d->wrap && !event->isAutoRepeat())) { |
| incrementCurrentIndex(); |
| event->accept(); |
| return; |
| } else if (d->wrap) { |
| event->accept(); |
| return; |
| } |
| } |
| } |
| event->ignore(); |
| QQuickItemView::keyPressEvent(event); |
| } |
| |
| void QQuickListView::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) |
| { |
| Q_D(QQuickListView); |
| if (d->isRightToLeft()) { |
| // maintain position relative to the right edge |
| qreal dx = newGeometry.width() - oldGeometry.width(); |
| setContentX(contentX() - dx); |
| } else if (d->isBottomToTop()) { |
| // maintain position relative to the bottom edge |
| qreal dy = newGeometry.height() - oldGeometry.height(); |
| setContentY(contentY() - dy); |
| } |
| QQuickItemView::geometryChanged(newGeometry, oldGeometry); |
| } |
| |
| void QQuickListView::initItem(int index, QObject *object) |
| { |
| QQuickItemView::initItem(index, object); |
| |
| // setting the view from the FxViewItem wrapper is too late if the delegate |
| // needs access to the view in Component.onCompleted |
| QQuickItem *item = qmlobject_cast<QQuickItem*>(object); |
| if (item) { |
| QQuickListViewAttached *attached = static_cast<QQuickListViewAttached *>( |
| qmlAttachedPropertiesObject<QQuickListView>(item)); |
| if (attached) |
| attached->setView(this); |
| } |
| } |
| |
| qreal QQuickListView::maxYExtent() const |
| { |
| Q_D(const QQuickListView); |
| if (d->layoutOrientation() == Qt::Horizontal && d->flickableDirection != HorizontalFlick) |
| return QQuickFlickable::maxYExtent(); |
| return QQuickItemView::maxYExtent(); |
| } |
| |
| qreal QQuickListView::maxXExtent() const |
| { |
| Q_D(const QQuickListView); |
| if (d->layoutOrientation() == Qt::Vertical && d->flickableDirection != VerticalFlick) |
| return QQuickFlickable::maxXExtent(); |
| return QQuickItemView::maxXExtent(); |
| } |
| |
| /*! |
| \qmlmethod QtQuick::ListView::incrementCurrentIndex() |
| |
| Increments the current index. The current index will wrap |
| if keyNavigationWraps is true and it is currently at the end. |
| This method has no effect if the \l count is zero. |
| |
| \b Note: methods should only be called after the Component has completed. |
| */ |
| void QQuickListView::incrementCurrentIndex() |
| { |
| Q_D(QQuickListView); |
| int count = d->model ? d->model->count() : 0; |
| if (count && (currentIndex() < count - 1 || d->wrap)) { |
| d->moveReason = QQuickListViewPrivate::SetIndex; |
| int index = currentIndex()+1; |
| setCurrentIndex((index >= 0 && index < count) ? index : 0); |
| } |
| } |
| |
| /*! |
| \qmlmethod QtQuick::ListView::decrementCurrentIndex() |
| |
| Decrements the current index. The current index will wrap |
| if keyNavigationWraps is true and it is currently at the beginning. |
| This method has no effect if the \l count is zero. |
| |
| \b Note: methods should only be called after the Component has completed. |
| */ |
| void QQuickListView::decrementCurrentIndex() |
| { |
| Q_D(QQuickListView); |
| int count = d->model ? d->model->count() : 0; |
| if (count && (currentIndex() > 0 || d->wrap)) { |
| d->moveReason = QQuickListViewPrivate::SetIndex; |
| int index = currentIndex()-1; |
| setCurrentIndex((index >= 0 && index < count) ? index : count-1); |
| } |
| } |
| |
| void QQuickListViewPrivate::updateSectionCriteria() |
| { |
| Q_Q(QQuickListView); |
| if (q->isComponentComplete() && model) { |
| QList<QByteArray> roles; |
| if (sectionCriteria && !sectionCriteria->property().isEmpty()) |
| roles << sectionCriteria->property().toUtf8(); |
| model->setWatchedRoles(roles); |
| updateSections(); |
| if (itemCount) |
| forceLayoutPolish(); |
| } |
| } |
| |
| bool QQuickListViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &change, ChangeResult *insertResult, QList<FxViewItem *> *addedItems, QList<MovedItem> *movingIntoView) |
| { |
| int modelIndex = change.index; |
| int count = change.count; |
| |
| qreal tempPos = isContentFlowReversed() ? -position()-size() : position(); |
| int index = visibleItems.count() ? mapFromModel(modelIndex) : 0; |
| qreal lastVisiblePos = buffer + displayMarginEnd + tempPos + size(); |
| |
| if (index < 0) { |
| int i = visibleItems.count() - 1; |
| while (i > 0 && visibleItems.at(i)->index == -1) |
| --i; |
| if (i == 0 && visibleItems.constFirst()->index == -1) { |
| // there are no visible items except items marked for removal |
| index = visibleItems.count(); |
| } else if (visibleItems.at(i)->index + 1 == modelIndex |
| && visibleItems.at(i)->endPosition() <= lastVisiblePos) { |
| // Special case of appending an item to the model. |
| index = visibleItems.count(); |
| } else { |
| if (modelIndex < visibleIndex) { |
| // Insert before visible items |
| visibleIndex += count; |
| for (FxViewItem *item : qAsConst(visibleItems)) { |
| if (item->index != -1 && item->index >= modelIndex) |
| item->index += count; |
| } |
| } |
| return true; |
| } |
| } |
| |
| // index can be the next item past the end of the visible items list (i.e. appended) |
| qreal pos = 0; |
| if (visibleItems.count()) { |
| pos = index < visibleItems.count() ? visibleItems.at(index)->position() |
| : visibleItems.constLast()->endPosition() + spacing; |
| } |
| |
| // Update the indexes of the following visible items. |
| for (FxViewItem *item : qAsConst(visibleItems)) { |
| if (item->index != -1 && item->index >= modelIndex) { |
| item->index += count; |
| if (change.isMove()) |
| item->transitionNextReposition(transitioner, QQuickItemViewTransitioner::MoveTransition, false); |
| else |
| item->transitionNextReposition(transitioner, QQuickItemViewTransitioner::AddTransition, false); |
| } |
| } |
| |
| bool visibleAffected = false; |
| if (insertResult->visiblePos.isValid() && pos < insertResult->visiblePos) { |
| // Insert items before the visible item. |
| int insertionIdx = index; |
| int i = 0; |
| qreal from = tempPos - displayMarginBeginning - buffer; |
| |
| if (insertionIdx < visibleIndex) { |
| if (pos >= from) { |
| // items won't be visible, just note the size for repositioning |
| insertResult->sizeChangesBeforeVisiblePos += count * (averageSize + spacing); |
| } |
| } else { |
| for (i = count-1; i >= 0 && pos >= from; --i) { |
| // item is before first visible e.g. in cache buffer |
| FxViewItem *item = nullptr; |
| if (change.isMove() && (item = currentChanges.removedItems.take(change.moveKey(modelIndex + i)))) |
| item->index = modelIndex + i; |
| if (!item) |
| item = createItem(modelIndex + i, QQmlIncubator::Synchronous); |
| if (!item) |
| return false; |
| |
| visibleAffected = true; |
| visibleItems.insert(insertionIdx, item); |
| if (insertionIdx == 0) |
| insertResult->changedFirstItem = true; |
| if (!change.isMove()) { |
| addedItems->append(item); |
| if (transitioner) |
| item->transitionNextReposition(transitioner, QQuickItemViewTransitioner::AddTransition, true); |
| else |
| static_cast<FxListItemSG *>(item)->setPosition(pos, true); |
| } |
| insertResult->sizeChangesBeforeVisiblePos += item->size() + spacing; |
| pos -= item->size() + spacing; |
| index++; |
| } |
| } |
| |
| int firstOkIdx = -1; |
| for (int i = 0; i <= insertionIdx && i < visibleItems.count() - 1; i++) { |
| if (visibleItems.at(i)->index + 1 != visibleItems.at(i + 1)->index) { |
| firstOkIdx = i + 1; |
| break; |
| } |
| } |
| for (int i = 0; i < firstOkIdx; i++) { |
| FxViewItem *nvItem = visibleItems.takeFirst(); |
| addedItems->removeOne(nvItem); |
| removeItem(nvItem); |
| } |
| |
| } else { |
| for (int i = 0; i < count && pos <= lastVisiblePos; ++i) { |
| visibleAffected = true; |
| FxViewItem *item = nullptr; |
| if (change.isMove() && (item = currentChanges.removedItems.take(change.moveKey(modelIndex + i)))) |
| item->index = modelIndex + i; |
| bool newItem = !item; |
| if (!item) |
| item = createItem(modelIndex + i, QQmlIncubator::Synchronous); |
| if (!item) |
| return false; |
| |
| visibleItems.insert(index, item); |
| if (index == 0) |
| insertResult->changedFirstItem = true; |
| if (change.isMove()) { |
| // we know this is a move target, since move displaced items that are |
| // shuffled into view due to a move would be added in refill() |
| if (newItem && transitioner && transitioner->canTransition(QQuickItemViewTransitioner::MoveTransition, true)) |
| movingIntoView->append(MovedItem(item, change.moveKey(item->index))); |
| } else { |
| addedItems->append(item); |
| if (transitioner) |
| item->transitionNextReposition(transitioner, QQuickItemViewTransitioner::AddTransition, true); |
| else |
| static_cast<FxListItemSG *>(item)->setPosition(pos, true); |
| } |
| insertResult->sizeChangesAfterVisiblePos += item->size() + spacing; |
| pos += item->size() + spacing; |
| ++index; |
| } |
| |
| if (0 < index && index < visibleItems.count()) { |
| FxViewItem *prevItem = visibleItems.at(index - 1); |
| FxViewItem *item = visibleItems.at(index); |
| if (prevItem->index != item->index - 1) { |
| int i = index; |
| qreal prevPos = prevItem->position(); |
| while (i < visibleItems.count()) { |
| FxListItemSG *nvItem = static_cast<FxListItemSG *>(visibleItems.takeLast()); |
| insertResult->sizeChangesAfterVisiblePos -= nvItem->size() + spacing; |
| addedItems->removeOne(nvItem); |
| if (nvItem->transitionScheduledOrRunning()) |
| nvItem->setPosition(prevPos + (nvItem->index - prevItem->index) * averageSize); |
| removeItem(nvItem); |
| } |
| } |
| } |
| } |
| |
| updateVisibleIndex(); |
| |
| return visibleAffected; |
| } |
| |
| void QQuickListViewPrivate::translateAndTransitionItemsAfter(int afterModelIndex, const ChangeResult &insertionResult, const ChangeResult &removalResult) |
| { |
| Q_UNUSED(insertionResult); |
| |
| if (!transitioner) |
| return; |
| |
| int markerItemIndex = -1; |
| for (int i=0; i<visibleItems.count(); i++) { |
| if (visibleItems.at(i)->index == afterModelIndex) { |
| markerItemIndex = i; |
| break; |
| } |
| } |
| if (markerItemIndex < 0) |
| return; |
| |
| const qreal viewEndPos = isContentFlowReversed() ? -position() : position() + size(); |
| qreal sizeRemoved = -removalResult.sizeChangesAfterVisiblePos |
| - (removalResult.countChangeAfterVisibleItems * (averageSize + spacing)); |
| |
| for (int i=markerItemIndex+1; i<visibleItems.count(); i++) { |
| FxListItemSG *listItem = static_cast<FxListItemSG *>(visibleItems.at(i)); |
| if (listItem->position() >= viewEndPos) |
| break; |
| if (!listItem->transitionScheduledOrRunning()) { |
| qreal pos = listItem->position(); |
| listItem->setPosition(pos - sizeRemoved); |
| listItem->transitionNextReposition(transitioner, QQuickItemViewTransitioner::RemoveTransition, false); |
| listItem->setPosition(pos); |
| } |
| } |
| } |
| |
| /*! |
| \qmlmethod QtQuick::ListView::positionViewAtIndex(int index, PositionMode mode) |
| |
| Positions the view such that the \a index is at the position specified by |
| \a mode: |
| |
| \list |
| \li ListView.Beginning - position item at the top (or left for horizontal orientation) of the view. |
| \li ListView.Center - position item in the center of the view. |
| \li ListView.End - position item at bottom (or right for horizontal orientation) of the view. |
| \li ListView.Visible - if any part of the item is visible then take no action, otherwise |
| bring the item into view. |
| \li ListView.Contain - ensure the entire item is visible. If the item is larger than |
| the view the item is positioned at the top (or left for horizontal orientation) of the view. |
| \li ListView.SnapPosition - position the item at \l preferredHighlightBegin. This mode |
| is only valid if \l highlightRangeMode is StrictlyEnforceRange or snapping is enabled |
| via \l snapMode. |
| \endlist |
| |
| If positioning the view at \a index would cause empty space to be displayed at |
| the beginning or end of the view, the view will be positioned at the boundary. |
| |
| It is not recommended to use \l {Flickable::}{contentX} or \l {Flickable::}{contentY} to position the view |
| at a particular index. This is unreliable since removing items from the start |
| of the list does not cause all other items to be repositioned, and because |
| the actual start of the view can vary based on the size of the delegates. |
| The correct way to bring an item into view is with \c positionViewAtIndex. |
| |
| \b Note: methods should only be called after the Component has completed. To position |
| the view at startup, this method should be called by Component.onCompleted. For |
| example, to position the view at the end: |
| |
| \code |
| Component.onCompleted: positionViewAtIndex(count - 1, ListView.Beginning) |
| \endcode |
| */ |
| |
| /*! |
| \qmlmethod QtQuick::ListView::positionViewAtBeginning() |
| \qmlmethod QtQuick::ListView::positionViewAtEnd() |
| |
| Positions the view at the beginning or end, taking into account any header or footer. |
| |
| It is not recommended to use \l {Flickable::}{contentX} or \l {Flickable::}{contentY} to position the view |
| at a particular index. This is unreliable since removing items from the start |
| of the list does not cause all other items to be repositioned, and because |
| the actual start of the view can vary based on the size of the delegates. |
| |
| \b Note: methods should only be called after the Component has completed. To position |
| the view at startup, this method should be called by Component.onCompleted. For |
| example, to position the view at the end on startup: |
| |
| \code |
| Component.onCompleted: positionViewAtEnd() |
| \endcode |
| */ |
| |
| /*! |
| \qmlmethod int QtQuick::ListView::indexAt(real x, real y) |
| |
| Returns the index of the visible item containing the point \a x, \a y in content |
| coordinates. If there is no item at the point specified, or the item is |
| not visible -1 is returned. |
| |
| If the item is outside the visible area, -1 is returned, regardless of |
| whether an item will exist at that point when scrolled into view. |
| |
| \b Note: methods should only be called after the Component has completed. |
| */ |
| |
| /*! |
| \qmlmethod Item QtQuick::ListView::itemAt(real x, real y) |
| |
| Returns the visible item containing the point \a x, \a y in content |
| coordinates. If there is no item at the point specified, or the item is |
| not visible null is returned. |
| |
| If the item is outside the visible area, null is returned, regardless of |
| whether an item will exist at that point when scrolled into view. |
| |
| \b Note: methods should only be called after the Component has completed. |
| */ |
| |
| /*! |
| \qmlmethod Item QtQuick::ListView::itemAtIndex(int index) |
| |
| Returns the item for \a index. If there is no item for that index, for example |
| because it has not been created yet, or because it has been panned out of |
| the visible area and removed from the cache, null is returned. |
| |
| \b Note: this method should only be called after the Component has completed. |
| The returned value should also not be stored since it can turn to null |
| as soon as control goes out of the calling scope, if the view releases that item. |
| |
| \since 5.13 |
| */ |
| |
| /*! |
| \qmlmethod QtQuick::ListView::forceLayout() |
| |
| Responding to changes in the model is usually batched to happen only once |
| per frame. This means that inside script blocks it is possible for the |
| underlying model to have changed, but the ListView has not caught up yet. |
| |
| This method forces the ListView to immediately respond to any outstanding |
| changes in the model. |
| |
| \since 5.1 |
| |
| \b Note: methods should only be called after the Component has completed. |
| */ |
| |
| QQuickListViewAttached *QQuickListView::qmlAttachedProperties(QObject *obj) |
| { |
| return new QQuickListViewAttached(obj); |
| } |
| |
| QT_END_NAMESPACE |
| |
| #include "moc_qquicklistview_p.cpp" |