diff options
Diffstat (limited to 'src/quick/items/qquickgridview.cpp')
-rw-r--r-- | src/quick/items/qquickgridview.cpp | 1958 |
1 files changed, 1958 insertions, 0 deletions
diff --git a/src/quick/items/qquickgridview.cpp b/src/quick/items/qquickgridview.cpp new file mode 100644 index 0000000000..44e1f14c6f --- /dev/null +++ b/src/quick/items/qquickgridview.cpp @@ -0,0 +1,1958 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickgridview_p.h" +#include "qquickvisualitemmodel_p.h" +#include "qquickflickable_p_p.h" +#include "qquickitemview_p_p.h" + +#include <private/qdeclarativesmoothedanimation_p_p.h> +#include <private/qlistmodelinterface_p.h> + +#include <QtGui/qevent.h> +#include <QtCore/qmath.h> +#include <QtCore/qcoreapplication.h> +#include <math.h> +#include "qplatformdefs.h" + +QT_BEGIN_NAMESPACE + +#ifndef QML_FLICK_SNAPONETHRESHOLD +#define QML_FLICK_SNAPONETHRESHOLD 30 +#endif + +//---------------------------------------------------------------------------- + +class FxGridItemSG : public FxViewItem +{ +public: + FxGridItemSG(QQuickItem *i, QQuickGridView *v, bool own) : FxViewItem(i, own), view(v) { + attached = static_cast<QQuickGridViewAttached*>(qmlAttachedPropertiesObject<QQuickGridView>(item)); + if (attached) + static_cast<QQuickGridViewAttached*>(attached)->setView(view); + } + + ~FxGridItemSG() {} + + qreal position() const { + return rowPos(); + } + + qreal endPosition() const { + return endRowPos(); + } + + qreal size() const { + return view->flow() == QQuickGridView::LeftToRight ? view->cellHeight() : view->cellWidth(); + } + + qreal sectionSize() const { + return 0.0; + } + + qreal rowPos() const { + if (view->flow() == QQuickGridView::LeftToRight) + return item->y(); + else + return (view->effectiveLayoutDirection() == Qt::RightToLeft ? -view->cellWidth()-item->x() : item->x()); + } + + qreal colPos() const { + if (view->flow() == QQuickGridView::LeftToRight) { + if (view->effectiveLayoutDirection() == Qt::RightToLeft) { + qreal colSize = view->cellWidth(); + int columns = view->width()/colSize; + return colSize * (columns-1) - item->x(); + } else { + return item->x(); + } + } else { + return item->y(); + } + } + qreal endRowPos() const { + if (view->flow() == QQuickGridView::LeftToRight) { + return item->y() + view->cellHeight(); + } else { + if (view->effectiveLayoutDirection() == Qt::RightToLeft) + return -item->x(); + else + return item->x() + view->cellWidth(); + } + } + void setPosition(qreal col, qreal row) { + if (view->effectiveLayoutDirection() == Qt::RightToLeft) { + if (view->flow() == QQuickGridView::LeftToRight) { + int columns = view->width()/view->cellWidth(); + item->setPos(QPointF((view->cellWidth() * (columns-1) - col), row)); + } else { + item->setPos(QPointF(-view->cellWidth()-row, col)); + } + } else { + if (view->flow() == QQuickGridView::LeftToRight) + item->setPos(QPointF(col, row)); + else + item->setPos(QPointF(row, col)); + } + } + bool contains(qreal x, qreal y) const { + return (x >= item->x() && x < item->x() + view->cellWidth() && + y >= item->y() && y < item->y() + view->cellHeight()); + } + + QQuickGridView *view; +}; + +//---------------------------------------------------------------------------- + +class QQuickGridViewPrivate : public QQuickItemViewPrivate +{ + Q_DECLARE_PUBLIC(QQuickGridView) + +public: + virtual Qt::Orientation layoutOrientation() const; + virtual bool isContentFlowReversed() const; + bool isRightToLeftTopToBottom() const; + + virtual qreal positionAt(int index) const; + virtual qreal endPositionAt(int index) const; + virtual qreal originPosition() const; + virtual qreal lastPosition() const; + + qreal rowSize() const; + qreal colSize() const; + qreal colPosAt(int modelIndex) const; + qreal rowPosAt(int modelIndex) const; + qreal snapPosAt(qreal pos) const; + FxViewItem *snapItemAt(qreal pos) const; + int snapIndex() const; + + virtual bool addVisibleItems(qreal fillFrom, qreal fillTo, bool doBuffer); + virtual bool removeNonVisibleItems(qreal bufferFrom, qreal bufferTo); + virtual void visibleItemsChanged(); + + virtual FxViewItem *newViewItem(int index, QQuickItem *item); + virtual void repositionPackageItemAt(QQuickItem *item, int index); + virtual void resetItemPosition(FxViewItem *item, FxViewItem *toItem); + virtual void resetFirstItemPosition(); + virtual void moveItemBy(FxViewItem *item, qreal forwards, qreal backwards); + + virtual void createHighlight(); + virtual void updateHighlight(); + virtual void resetHighlightPosition(); + + virtual void setPosition(qreal pos); + virtual void layoutVisibleItems(); + bool applyInsertionChange(const QDeclarativeChangeSet::Insert &, FxViewItem *, InsertionsResult *); + virtual bool needsRefillForAddedOrRemovedIndex(int index) const; + + virtual qreal headerSize() const; + virtual qreal footerSize() const; + virtual bool showHeaderForIndex(int index) const; + virtual bool showFooterForIndex(int index) const; + virtual void updateHeader(); + virtual void updateFooter(); + + virtual void changedVisibleIndex(int newIndex); + virtual void initializeCurrentItem(); + + virtual void updateViewport(); + virtual void itemGeometryChanged(QQuickItem *item, const QRectF &newGeometry, const QRectF &oldGeometry); + virtual void fixupPosition(); + virtual void fixup(AxisData &data, qreal minExtent, qreal maxExtent); + virtual void flick(QQuickItemViewPrivate::AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize, + QDeclarativeTimeLineCallback::Callback fixupCallback, qreal velocity); + + QQuickGridView::Flow flow; + qreal cellWidth; + qreal cellHeight; + int columns; + QQuickGridView::SnapMode snapMode; + + QSmoothedAnimation *highlightXAnimator; + QSmoothedAnimation *highlightYAnimator; + + QQuickGridViewPrivate() + : flow(QQuickGridView::LeftToRight) + , cellWidth(100), cellHeight(100), columns(1) + , snapMode(QQuickGridView::NoSnap) + , highlightXAnimator(0), highlightYAnimator(0) + {} +}; + +Qt::Orientation QQuickGridViewPrivate::layoutOrientation() const +{ + return flow == QQuickGridView::LeftToRight ? Qt::Vertical : Qt::Horizontal; +} + +bool QQuickGridViewPrivate::isContentFlowReversed() const +{ + return isRightToLeftTopToBottom(); +} + +bool QQuickGridViewPrivate::isRightToLeftTopToBottom() const +{ + Q_Q(const QQuickGridView); + return flow == QQuickGridView::TopToBottom && q->effectiveLayoutDirection() == Qt::RightToLeft; +} + +void QQuickGridViewPrivate::changedVisibleIndex(int newIndex) +{ + visibleIndex = newIndex / columns * columns; +} + +void QQuickGridViewPrivate::setPosition(qreal pos) +{ + Q_Q(QQuickGridView); + if (flow == QQuickGridView::LeftToRight) { + q->QQuickFlickable::setContentY(pos); + q->QQuickFlickable::setContentX(0); + } else { + if (q->effectiveLayoutDirection() == Qt::LeftToRight) + q->QQuickFlickable::setContentX(pos); + else + q->QQuickFlickable::setContentX(-pos-size()); + q->QQuickFlickable::setContentY(0); + } +} + +qreal QQuickGridViewPrivate::originPosition() const +{ + qreal pos = 0; + if (!visibleItems.isEmpty()) + pos = static_cast<FxGridItemSG*>(visibleItems.first())->rowPos() - visibleIndex / columns * rowSize(); + return pos; +} + +qreal QQuickGridViewPrivate::lastPosition() const +{ + qreal pos = 0; + if (model && model->count()) { + // get end position of last item + pos = (rowPosAt(model->count() - 1) + rowSize()); + } + return pos; +} + +qreal QQuickGridViewPrivate::positionAt(int index) const +{ + return rowPosAt(index); +} + +qreal QQuickGridViewPrivate::endPositionAt(int index) const +{ + return rowPosAt(index) + rowSize(); +} + +qreal QQuickGridViewPrivate::rowSize() const { + return flow == QQuickGridView::LeftToRight ? cellHeight : cellWidth; +} +qreal QQuickGridViewPrivate::colSize() const { + return flow == QQuickGridView::LeftToRight ? cellWidth : cellHeight; +} + +qreal QQuickGridViewPrivate::colPosAt(int modelIndex) const +{ + if (FxViewItem *item = visibleItem(modelIndex)) + return static_cast<FxGridItemSG*>(item)->colPos(); + if (!visibleItems.isEmpty()) { + if (modelIndex == visibleIndex) { + FxGridItemSG *firstItem = static_cast<FxGridItemSG*>(visibleItems.first()); + return firstItem->colPos(); + } else if (modelIndex < visibleIndex) { + int count = (visibleIndex - modelIndex) % columns; + int col = static_cast<FxGridItemSG*>(visibleItems.first())->colPos() / colSize(); + col = (columns - count + col) % columns; + return col * colSize(); + } else { + int count = columns - 1 - (modelIndex - visibleItems.last()->index - 1) % columns; + return static_cast<FxGridItemSG*>(visibleItems.last())->colPos() - count * colSize(); + } + } + return (modelIndex % columns) * colSize(); +} + +qreal QQuickGridViewPrivate::rowPosAt(int modelIndex) const +{ + if (FxViewItem *item = visibleItem(modelIndex)) + return static_cast<FxGridItemSG*>(item)->rowPos(); + if (!visibleItems.isEmpty()) { + if (modelIndex == visibleIndex) { + FxGridItemSG *firstItem = static_cast<FxGridItemSG*>(visibleItems.first()); + return firstItem->rowPos(); + } else if (modelIndex < visibleIndex) { + FxGridItemSG *firstItem = static_cast<FxGridItemSG*>(visibleItems.first()); + int firstCol = firstItem->colPos() / colSize(); + int col = visibleIndex - modelIndex + (columns - firstCol - 1); + int rows = col / columns; + return firstItem->rowPos() - rows * rowSize(); + } else { + FxGridItemSG *lastItem = static_cast<FxGridItemSG*>(visibleItems.last()); + int count = modelIndex - lastItem->index; + int col = lastItem->colPos() + count * colSize(); + int rows = col / (columns * colSize()); + return lastItem->rowPos() + rows * rowSize(); + } + } + return (modelIndex / columns) * rowSize(); +} + + +qreal QQuickGridViewPrivate::snapPosAt(qreal pos) const +{ + Q_Q(const QQuickGridView); + qreal snapPos = 0; + if (!visibleItems.isEmpty()) { + qreal highlightStart = highlightRangeStart; + pos += highlightStart; + pos += rowSize()/2; + snapPos = static_cast<FxGridItemSG*>(visibleItems.first())->rowPos() - visibleIndex / columns * rowSize(); + snapPos = pos - fmodf(pos - snapPos, qreal(rowSize())); + snapPos -= highlightStart; + qreal maxExtent; + qreal minExtent; + if (isRightToLeftTopToBottom()) { + maxExtent = q->minXExtent()-size(); + minExtent = q->maxXExtent()-size(); + } else { + maxExtent = flow == QQuickGridView::LeftToRight ? -q->maxYExtent() : -q->maxXExtent(); + minExtent = flow == QQuickGridView::LeftToRight ? -q->minYExtent() : -q->minXExtent(); + } + if (snapPos > maxExtent) + snapPos = maxExtent; + if (snapPos < minExtent) + snapPos = minExtent; + } + return snapPos; +} + +FxViewItem *QQuickGridViewPrivate::snapItemAt(qreal pos) const +{ + for (int i = 0; i < visibleItems.count(); ++i) { + FxViewItem *item = visibleItems.at(i); + if (item->index == -1) + continue; + qreal itemTop = item->position(); + if (itemTop+rowSize()/2 >= pos && itemTop - rowSize()/2 <= pos) + return item; + } + return 0; +} + +int QQuickGridViewPrivate::snapIndex() const +{ + int index = currentIndex; + for (int i = 0; i < visibleItems.count(); ++i) { + FxGridItemSG *item = static_cast<FxGridItemSG*>(visibleItems.at(i)); + if (item->index == -1) + continue; + qreal itemTop = item->position(); + FxGridItemSG *hItem = static_cast<FxGridItemSG*>(highlight); + if (itemTop >= hItem->rowPos()-rowSize()/2 && itemTop < hItem->rowPos()+rowSize()/2) { + index = item->index; + if (item->colPos() >= hItem->colPos()-colSize()/2 && item->colPos() < hItem->colPos()+colSize()/2) + return item->index; + } + } + return index; +} + +FxViewItem *QQuickGridViewPrivate::newViewItem(int modelIndex, QQuickItem *item) +{ + Q_Q(QQuickGridView); + Q_UNUSED(modelIndex); + return new FxGridItemSG(item, q, false); +} + +bool QQuickGridViewPrivate::addVisibleItems(qreal fillFrom, qreal fillTo, bool doBuffer) +{ + qreal colPos = colPosAt(visibleIndex); + qreal rowPos = rowPosAt(visibleIndex); + if (visibleItems.count()) { + FxGridItemSG *lastItem = static_cast<FxGridItemSG*>(visibleItems.last()); + rowPos = lastItem->rowPos(); + int colNum = qFloor((lastItem->colPos()+colSize()/2) / colSize()); + if (++colNum >= columns) { + colNum = 0; + rowPos += rowSize(); + } + colPos = colNum * colSize(); + } + + int modelIndex = findLastVisibleIndex(); + modelIndex = modelIndex < 0 ? visibleIndex : modelIndex + 1; + + if (visibleItems.count() && (fillFrom > rowPos + rowSize()*2 + || fillTo < rowPosAt(visibleIndex) - rowSize())) { + // We've jumped more than a page. Estimate which items are now + // visible and fill from there. + int count = (fillFrom - (rowPos + rowSize())) / (rowSize()) * columns; + for (int i = 0; i < visibleItems.count(); ++i) + releaseItem(visibleItems.at(i)); + visibleItems.clear(); + modelIndex += count; + if (modelIndex >= model->count()) + modelIndex = model->count() - 1; + else if (modelIndex < 0) + modelIndex = 0; + modelIndex = modelIndex / columns * columns; + visibleIndex = modelIndex; + colPos = colPosAt(visibleIndex); + rowPos = rowPosAt(visibleIndex); + } + + int colNum = qFloor((colPos+colSize()/2) / colSize()); + FxGridItemSG *item = 0; + bool changed = false; + + while (modelIndex < model->count() && rowPos <= fillTo + rowSize()*(columns - colNum)/(columns+1)) { +// qDebug() << "refill: append item" << modelIndex << colPos << rowPos; + if (!(item = static_cast<FxGridItemSG*>(createItem(modelIndex, doBuffer)))) + break; + item->setPosition(colPos, rowPos); + item->item->setVisible(!doBuffer); + visibleItems.append(item); + if (++colNum >= columns) { + colNum = 0; + rowPos += rowSize(); + } + colPos = colNum * colSize(); + ++modelIndex; + changed = true; + } + + // Find first column + if (visibleItems.count()) { + FxGridItemSG *firstItem = static_cast<FxGridItemSG*>(visibleItems.first()); + rowPos = firstItem->rowPos(); + colNum = qFloor((firstItem->colPos()+colSize()/2) / colSize()); + if (--colNum < 0) { + colNum = columns - 1; + rowPos -= rowSize(); + } + } else { + colNum = qFloor((colPos+colSize()/2) / colSize()); + } + + // Prepend + colPos = colNum * colSize(); + while (visibleIndex > 0 && rowPos + rowSize() - 1 >= fillFrom - rowSize()*(colNum+1)/(columns+1)){ +// qDebug() << "refill: prepend item" << visibleIndex-1 << "top pos" << rowPos << colPos; + if (!(item = static_cast<FxGridItemSG*>(createItem(visibleIndex-1, doBuffer)))) + break; + --visibleIndex; + item->setPosition(colPos, rowPos); + item->item->setVisible(!doBuffer); + visibleItems.prepend(item); + if (--colNum < 0) { + colNum = columns-1; + rowPos -= rowSize(); + } + colPos = colNum * colSize(); + changed = true; + } + + return changed; +} + +bool QQuickGridViewPrivate::removeNonVisibleItems(qreal bufferFrom, qreal bufferTo) +{ + Q_Q(QQuickGridView); + FxGridItemSG *item = 0; + bool changed = false; + + while (visibleItems.count() > 1 + && (item = static_cast<FxGridItemSG*>(visibleItems.first())) + && item->rowPos()+rowSize()-1 < bufferFrom - rowSize()*(item->colPos()/colSize()+1)/(columns+1)) { + if (item->attached->delayRemove()) + break; +// qDebug() << "refill: remove first" << visibleIndex << "top end pos" << item->endRowPos(); + if (item->index != -1) + visibleIndex++; + visibleItems.removeFirst(); + releaseItem(item); + changed = true; + } + while (visibleItems.count() > 1 + && (item = static_cast<FxGridItemSG*>(visibleItems.last())) + && item->rowPos() > bufferTo + rowSize()*(columns - item->colPos()/colSize())/(columns+1)) { + if (item->attached->delayRemove()) + break; +// qDebug() << "refill: remove last" << visibleIndex+visibleItems.count()-1; + visibleItems.removeLast(); + releaseItem(item); + changed = true; + } + + return changed; +} + +void QQuickGridViewPrivate::visibleItemsChanged() +{ + updateHeader(); + updateFooter(); + updateViewport(); +} + +void QQuickGridViewPrivate::updateViewport() +{ + Q_Q(QQuickGridView); + qreal length = flow == QQuickGridView::LeftToRight ? q->width() : q->height(); + columns = (int)qMax((length + colSize()/2) / colSize(), qreal(1.)); + QQuickItemViewPrivate::updateViewport(); +} + +void QQuickGridViewPrivate::layoutVisibleItems() +{ + if (visibleItems.count()) { + FxGridItemSG *firstItem = static_cast<FxGridItemSG*>(visibleItems.first()); + qreal rowPos = firstItem->rowPos(); + qreal colPos = firstItem->colPos(); + int col = visibleIndex % columns; + if (colPos != col * colSize()) { + colPos = col * colSize(); + firstItem->setPosition(colPos, rowPos); + } + for (int i = 1; i < visibleItems.count(); ++i) { + FxGridItemSG *item = static_cast<FxGridItemSG*>(visibleItems.at(i)); + if (++col >= columns) { + col = 0; + rowPos += rowSize(); + } + colPos = col * colSize(); + item->setPosition(colPos, rowPos); + } + } +} + +void QQuickGridViewPrivate::repositionPackageItemAt(QQuickItem *item, int index) +{ + Q_Q(QQuickGridView); + qreal pos = position(); + if (flow == QQuickGridView::LeftToRight) { + if (item->y() + item->height() > pos && item->y() < pos + q->height()) + item->setPos(QPointF(colPosAt(index), rowPosAt(index))); + } else { + if (item->x() + item->width() > pos && item->x() < pos + q->width()) { + if (isRightToLeftTopToBottom()) + item->setPos(QPointF(-rowPosAt(index)-item->width(), colPosAt(index))); + else + item->setPos(QPointF(rowPosAt(index), colPosAt(index))); + } + } +} + +void QQuickGridViewPrivate::resetItemPosition(FxViewItem *item, FxViewItem *toItem) +{ + if (item == toItem) + return; + FxGridItemSG *toGridItem = static_cast<FxGridItemSG*>(toItem); + static_cast<FxGridItemSG*>(item)->setPosition(toGridItem->colPos(), toGridItem->rowPos()); +} + +void QQuickGridViewPrivate::resetFirstItemPosition() +{ + FxGridItemSG *item = static_cast<FxGridItemSG*>(visibleItems.first()); + item->setPosition(0, 0); +} + +void QQuickGridViewPrivate::moveItemBy(FxViewItem *item, qreal forwards, qreal backwards) +{ + int moveCount = (forwards / rowSize()) - (backwards / rowSize()); + + FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(item); + gridItem->setPosition(gridItem->colPos(), gridItem->rowPos() + ((moveCount / columns) * rowSize())); +} + +void QQuickGridViewPrivate::createHighlight() +{ + Q_Q(QQuickGridView); + bool changed = false; + if (highlight) { + if (trackedItem == highlight) + trackedItem = 0; + delete highlight; + highlight = 0; + + delete highlightXAnimator; + delete highlightYAnimator; + highlightXAnimator = 0; + highlightYAnimator = 0; + + changed = true; + } + + if (currentItem) { + QQuickItem *item = createHighlightItem(); + if (item) { + FxGridItemSG *newHighlight = new FxGridItemSG(item, q, true); + if (autoHighlight) + resetHighlightPosition(); + highlightXAnimator = new QSmoothedAnimation(q); + highlightXAnimator->target = QDeclarativeProperty(item, QLatin1String("x")); + highlightXAnimator->userDuration = highlightMoveDuration; + highlightYAnimator = new QSmoothedAnimation(q); + highlightYAnimator->target = QDeclarativeProperty(item, QLatin1String("y")); + highlightYAnimator->userDuration = highlightMoveDuration; + + highlight = newHighlight; + changed = true; + } + } + if (changed) + emit q->highlightItemChanged(); +} + +void QQuickGridViewPrivate::updateHighlight() +{ + applyPendingChanges(); + + if ((!currentItem && highlight) || (currentItem && !highlight)) + createHighlight(); + bool strictHighlight = haveHighlightRange && highlightRange == QQuickGridView::StrictlyEnforceRange; + if (currentItem && autoHighlight && highlight && (!strictHighlight || !pressed)) { + // auto-update highlight + highlightXAnimator->to = currentItem->item->x(); + highlightYAnimator->to = currentItem->item->y(); + highlight->item->setWidth(currentItem->item->width()); + highlight->item->setHeight(currentItem->item->height()); + + highlightXAnimator->restart(); + highlightYAnimator->restart(); + } + updateTrackedItem(); +} + +void QQuickGridViewPrivate::resetHighlightPosition() +{ + if (highlight && currentItem) { + FxGridItemSG *cItem = static_cast<FxGridItemSG*>(currentItem); + static_cast<FxGridItemSG*>(highlight)->setPosition(cItem->colPos(), cItem->rowPos()); + } +} + +qreal QQuickGridViewPrivate::headerSize() const +{ + if (!header) + return 0.0; + return flow == QQuickGridView::LeftToRight ? header->item->height() : header->item->width(); +} + +qreal QQuickGridViewPrivate::footerSize() const +{ + if (!footer) + return 0.0; + return flow == QQuickGridView::LeftToRight? footer->item->height() : footer->item->width(); +} + +bool QQuickGridViewPrivate::showHeaderForIndex(int index) const +{ + return index / columns == 0; +} + +bool QQuickGridViewPrivate::showFooterForIndex(int index) const +{ + return index / columns == (model->count()-1) / columns; +} + +void QQuickGridViewPrivate::updateFooter() +{ + Q_Q(QQuickGridView); + bool created = false; + if (!footer) { + QQuickItem *item = createComponentItem(footerComponent, true); + if (!item) + return; + item->setZ(1); + footer = new FxGridItemSG(item, q, true); + created = true; + } + + FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(footer); + qreal colOffset = 0; + qreal rowOffset = 0; + if (q->effectiveLayoutDirection() == Qt::RightToLeft) { + if (flow == QQuickGridView::TopToBottom) + rowOffset = gridItem->item->width() - cellWidth; + else + colOffset = gridItem->item->width() - cellWidth; + } + if (visibleItems.count()) { + qreal endPos = lastPosition(); + if (findLastVisibleIndex() == model->count()-1) { + gridItem->setPosition(colOffset, endPos + rowOffset); + } else { + qreal visiblePos = isRightToLeftTopToBottom() ? -position() : position() + size(); + if (endPos <= visiblePos || gridItem->endPosition() <= endPos + rowOffset) + gridItem->setPosition(colOffset, endPos + rowOffset); + } + } else { + gridItem->setPosition(colOffset, rowOffset); + } + + if (created) + emit q->footerItemChanged(); +} + +void QQuickGridViewPrivate::updateHeader() +{ + Q_Q(QQuickGridView); + bool created = false; + if (!header) { + QQuickItem *item = createComponentItem(headerComponent, true); + if (!item) + return; + item->setZ(1); + header = new FxGridItemSG(item, q, true); + created = true; + } + + FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(header); + qreal colOffset = 0; + qreal rowOffset = -headerSize(); + if (q->effectiveLayoutDirection() == Qt::RightToLeft) { + if (flow == QQuickGridView::TopToBottom) + rowOffset += gridItem->item->width()-cellWidth; + else + colOffset = gridItem->item->width()-cellWidth; + } + if (visibleItems.count()) { + qreal startPos = originPosition(); + if (visibleIndex == 0) { + gridItem->setPosition(colOffset, startPos + rowOffset); + } else { + qreal tempPos = isRightToLeftTopToBottom() ? -position()-size() : position(); + qreal headerPos = isRightToLeftTopToBottom() ? gridItem->rowPos() + cellWidth - headerSize() : gridItem->rowPos(); + if (tempPos <= startPos || headerPos > startPos + rowOffset) + gridItem->setPosition(colOffset, startPos + rowOffset); + } + } else { + if (isRightToLeftTopToBottom()) + gridItem->setPosition(colOffset, rowOffset); + else + gridItem->setPosition(colOffset, -headerSize()); + } + + if (created) + emit q->headerItemChanged(); +} + +void QQuickGridViewPrivate::initializeCurrentItem() +{ + if (currentItem && currentIndex >= 0) { + FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(currentItem); + if (gridItem) + gridItem->setPosition(colPosAt(currentIndex), rowPosAt(currentIndex)); + } +} + +void QQuickGridViewPrivate::itemGeometryChanged(QQuickItem *item, const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_Q(QQuickGridView); + QQuickItemViewPrivate::itemGeometryChanged(item, newGeometry, oldGeometry); + if (!q->isComponentComplete()) + return; + if (item == q) { + if (newGeometry.height() != oldGeometry.height() || newGeometry.width() != oldGeometry.width()) { + updateViewport(); + forceLayout = true; + q->polish(); + } + } +} + +void QQuickGridViewPrivate::fixupPosition() +{ + moveReason = Other; + if (flow == QQuickGridView::LeftToRight) + fixupY(); + else + fixupX(); +} + +void QQuickGridViewPrivate::fixup(AxisData &data, qreal minExtent, qreal maxExtent) +{ + if ((flow == QQuickGridView::TopToBottom && &data == &vData) + || (flow == QQuickGridView::LeftToRight && &data == &hData)) + return; + + fixupMode = moveReason == Mouse ? fixupMode : Immediate; + + qreal viewPos = isRightToLeftTopToBottom() ? -position()-size() : position(); + + bool strictHighlightRange = haveHighlightRange && highlightRange == QQuickGridView::StrictlyEnforceRange; + if (snapMode != QQuickGridView::NoSnap) { + qreal tempPosition = isRightToLeftTopToBottom() ? -position()-size() : position(); + if (snapMode == QQuickGridView::SnapOneRow && moveReason == Mouse) { + // if we've been dragged < rowSize()/2 then bias towards the next row + qreal dist = data.move.value() - (data.pressPos - data.dragStartOffset); + qreal bias = 0; + if (data.velocity > 0 && dist > QML_FLICK_SNAPONETHRESHOLD && dist < rowSize()/2) + bias = rowSize()/2; + else if (data.velocity < 0 && dist < -QML_FLICK_SNAPONETHRESHOLD && dist > -rowSize()/2) + bias = -rowSize()/2; + if (isRightToLeftTopToBottom()) + bias = -bias; + tempPosition -= bias; + } + FxViewItem *topItem = snapItemAt(tempPosition+highlightRangeStart); + if (!topItem && strictHighlightRange && currentItem) { + // StrictlyEnforceRange always keeps an item in range + updateHighlight(); + topItem = currentItem; + } + FxViewItem *bottomItem = snapItemAt(tempPosition+highlightRangeEnd); + if (!bottomItem && strictHighlightRange && currentItem) { + // StrictlyEnforceRange always keeps an item in range + updateHighlight(); + bottomItem = currentItem; + } + qreal pos; + bool isInBounds = -position() > maxExtent && -position() <= minExtent; + if (topItem && (isInBounds || strictHighlightRange)) { + qreal headerPos = header ? static_cast<FxGridItemSG*>(header)->rowPos() : 0; + if (topItem->index == 0 && header && tempPosition+highlightRangeStart < headerPos+headerSize()/2 && !strictHighlightRange) { + pos = isRightToLeftTopToBottom() ? - headerPos + highlightRangeStart - size() : headerPos - highlightRangeStart; + } else { + if (isRightToLeftTopToBottom()) + pos = qMax(qMin(-topItem->position() + highlightRangeStart - size(), -maxExtent), -minExtent); + else + pos = qMax(qMin(topItem->position() - highlightRangeStart, -maxExtent), -minExtent); + } + } else if (bottomItem && isInBounds) { + if (isRightToLeftTopToBottom()) + pos = qMax(qMin(-bottomItem->position() + highlightRangeEnd - size(), -maxExtent), -minExtent); + else + pos = qMax(qMin(bottomItem->position() - 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 (haveHighlightRange && highlightRange == QQuickGridView::StrictlyEnforceRange) { + if (currentItem) { + updateHighlight(); + qreal pos = static_cast<FxGridItemSG*>(currentItem)->rowPos(); + if (viewPos < pos + rowSize() - highlightRangeEnd) + viewPos = pos + rowSize() - highlightRangeEnd; + if (viewPos > pos - highlightRangeStart) + viewPos = pos - highlightRangeStart; + if (isRightToLeftTopToBottom()) + viewPos = -viewPos-size(); + timeline.reset(data.move); + if (viewPos != position()) { + if (fixupMode != Immediate) { + 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; +} + +void QQuickGridViewPrivate::flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize, + QDeclarativeTimeLineCallback::Callback fixupCallback, qreal velocity) +{ + Q_Q(QQuickGridView); + data.fixingUp = false; + moveReason = Mouse; + if ((!haveHighlightRange || highlightRange != QQuickGridView::StrictlyEnforceRange) + && snapMode == QQuickGridView::NoSnap) { + QQuickItemViewPrivate::flick(data, minExtent, maxExtent, vSize, fixupCallback, velocity); + return; + } + qreal maxDistance = 0; + qreal dataValue = isRightToLeftTopToBottom() ? -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 == QQuickGridView::SnapOneRow) { + // if we've been dragged < averageSize/2 then bias towards the next item + qreal dist = data.move.value() - (data.pressPos - data.dragStartOffset); + qreal bias = dist < rowSize()/2 ? rowSize()/2 : 0; + if (isRightToLeftTopToBottom()) + bias = -bias; + data.flickTarget = -snapPosAt(-dataValue - bias); + maxDistance = qAbs(data.flickTarget - data.move.value()); + velocity = maxVelocity; + } else { + maxDistance = qAbs(minExtent - data.move.value()); + } + } + if (snapMode == QQuickGridView::NoSnap && highlightRange != QQuickGridView::StrictlyEnforceRange) + data.flickTarget = minExtent; + } else { + if (data.move.value() > maxExtent) { + if (snapMode == QQuickGridView::SnapOneRow) { + // if we've been dragged < averageSize/2 then bias towards the next item + qreal dist = data.move.value() - (data.pressPos - data.dragStartOffset); + qreal bias = -dist < rowSize()/2 ? rowSize()/2 : 0; + if (isRightToLeftTopToBottom()) + bias = -bias; + data.flickTarget = -snapPosAt(-dataValue + bias); + maxDistance = qAbs(data.flickTarget - data.move.value()); + velocity = -maxVelocity; + } else { + maxDistance = qAbs(maxExtent - data.move.value()); + } + } + if (snapMode == QQuickGridView::NoSnap && highlightRange != QQuickGridView::StrictlyEnforceRange) + data.flickTarget = maxExtent; + } + bool overShoot = boundsBehavior == QQuickFlickable::DragAndOvershootBounds; + if (maxDistance > 0 || overShoot) { + // This mode requires the grid to stop exactly on a row boundary. + qreal v = velocity; + if (maxVelocity != -1 && maxVelocity < qAbs(v)) { + if (v < 0) + v = -maxVelocity; + else + v = maxVelocity; + } + qreal accel = deceleration; + qreal v2 = v * v; + qreal overshootDist = 0.0; + if ((maxDistance > 0.0 && v2 / (2.0f * maxDistance) < accel) || snapMode == QQuickGridView::SnapOneRow) { + // + rowSize()/4 to encourage moving at least one item in the flick direction + qreal dist = v2 / (accel * 2.0) + rowSize()/4; + dist = qMin(dist, maxDistance); + if (v > 0) + dist = -dist; + if (snapMode != QQuickGridView::SnapOneRow) { + qreal distTemp = isRightToLeftTopToBottom() ? -dist : dist; + data.flickTarget = -snapPosAt(-dataValue + distTemp); + } + data.flickTarget = isRightToLeftTopToBottom() ? -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 { + data.flickTarget = velocity > 0 ? minExtent : maxExtent; + overshootDist = overShoot ? overShootDistance(vSize) : 0; + } + timeline.reset(data.move); + timeline.accel(data.move, v, accel, maxDistance + overshootDist); + timeline.callback(QDeclarativeTimeLineCallback(&data.move, fixupCallback, this)); + if (!hData.flicking && q->xflick()) { + hData.flicking = true; + emit q->flickingChanged(); + emit q->flickingHorizontallyChanged(); + emit q->flickStarted(); + } + if (!vData.flicking && q->yflick()) { + vData.flicking = true; + emit q->flickingChanged(); + emit q->flickingVerticallyChanged(); + emit q->flickStarted(); + } + } else { + timeline.reset(data.move); + fixup(data, minExtent, maxExtent); + } +} + + +//---------------------------------------------------------------------------- +/*! + \qmlclass GridView QQuickGridView + \inqmlmodule QtQuick 2 + \ingroup qml-view-elements + + \inherits Flickable + \brief The GridView item provides a grid view of items provided by a model. + + A GridView displays data from models created from built-in QML elements like ListModel + and XmlListModel, or custom model classes defined in C++ that inherit from + QAbstractListModel. + + A GridView 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 + GridView are laid out horizontally or vertically. Grid views are inherently flickable + as GridView 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 doc/src/snippets/declarative/gridview/ContactModel.qml 0 + + \div {class="float-right"} + \inlineimage gridview-simple.png + \enddiv + + This model can be referenced as \c ContactModel in other QML files. See \l{QML Modules} + for more information about creating reusable components like this. + + Another component can display this model data in a GridView, as in the following + example, which creates a \c ContactModel component for its model, and a \l Column element + (containing \l Image and \l Text elements) for its delegate. + + \clearfloat + \snippet doc/src/snippets/declarative/gridview/gridview.qml import + \codeline + \snippet doc/src/snippets/declarative/gridview/gridview.qml classdocs simple + + \div {class="float-right"} + \inlineimage gridview-highlight.png + \enddiv + + The view will create a new delegate for each item in the model. Note that the delegate + is able to access the model's \c name and \c portrait data directly. + + An improved grid view is shown below. The delegate is visually improved and is moved + into a separate \c contactDelegate component. + + \clearfloat + \snippet doc/src/snippets/declarative/gridview/gridview.qml classdocs advanced + + 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 grid view. + The grid view itself is a focus scope (see \l{qmlfocus#Acquiring Focus and Focus Scopes}{the focus documentation page} for more details). + + Delegates are instantiated as needed and may be destroyed at any time. + State should \e never be stored in a delegate. + + GridView attaches a number of properties to the root item of the delegate, for example + \c {GridView.isCurrentItem}. In the following example, the root delegate item can access + this attached property directly as \c GridView.isCurrentItem, while the child + \c contactInfo object must refer to this property as \c wrapper.GridView.isCurrentItem. + + \snippet doc/src/snippets/declarative/gridview/gridview.qml isCurrentItem + + \note Views do not set the \l{Item::}{clip} property automatically. + If the view is not clipped by another item or the screen, it will be necessary + to set this property to true in order to clip the items that are partially or + fully outside the view. + + \sa {declarative/modelviews/gridview}{GridView example} +*/ + +QQuickGridView::QQuickGridView(QQuickItem *parent) + : QQuickItemView(*(new QQuickGridViewPrivate), parent) +{ +} + +QQuickGridView::~QQuickGridView() +{ +} + +void QQuickGridView::setHighlightFollowsCurrentItem(bool autoHighlight) +{ + Q_D(QQuickGridView); + if (d->autoHighlight != autoHighlight) { + if (!autoHighlight && d->highlightXAnimator) { + d->highlightXAnimator->stop(); + d->highlightYAnimator->stop(); + } + QQuickItemView::setHighlightFollowsCurrentItem(autoHighlight); + } +} + +/*! + \qmlattachedproperty bool QtQuick2::GridView::isCurrentItem + This attached property is true if this delegate is the current item; otherwise false. + + It is attached to each instance of the delegate. +*/ + +/*! + \qmlattachedproperty GridView QtQuick2::GridView::view + This attached property holds the view that manages this delegate instance. + + It is attached to each instance of the delegate. + + \snippet doc/src/snippets/declarative/gridview/gridview.qml isCurrentItem +*/ + +/*! + \qmlattachedproperty bool QtQuick2::GridView::delayRemove + This attached property holds whether the delegate may be destroyed. + + It is attached to each instance of the delegate. + + It is sometimes necessary to delay the destruction of an item + until an animation completes. + + The example below ensures that the animation completes before + the item is removed from the grid. + + \snippet doc/src/snippets/declarative/gridview/gridview.qml delayRemove +*/ + +/*! + \qmlattachedsignal QtQuick2::GridView::onAdd() + This attached handler is called immediately after an item is added to the view. +*/ + +/*! + \qmlattachedsignal QtQuick2::GridView::onRemove() + This attached handler is called immediately before an item is removed from the view. +*/ + + +/*! + \qmlproperty model QtQuick2::GridView::model + This property holds the model providing data for the grid. + + 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 VisualItemModel, 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 {qmlmodels}{Data Models} +*/ + +/*! + \qmlproperty Component QtQuick2::GridView::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 {qmlmodels}{Data Model}. + + The number of elements 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 elements when needed. + + The GridView will layout the items based on the size of the root item + in the delegate. + + \note Delegates are instantiated as needed and may be destroyed at any time. + State should \e never be stored in a delegate. +*/ + +/*! + \qmlproperty int QtQuick2::GridView::currentIndex + \qmlproperty Item QtQuick2::GridView::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 GridView 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 QtQuick2::GridView::highlightItem + + This holds the highlight item created from the \l highlight component. + + The highlightItem is managed by the view unless + \l highlightFollowsCurrentItem is set to false. + + \sa highlight, highlightFollowsCurrentItem +*/ + + +/*! + \qmlproperty int QtQuick2::GridView::count + This property holds the number of items in the view. +*/ + + +/*! + \qmlproperty Component QtQuick2::GridView::highlight + This property holds the component to use as the highlight. + + An instance of the highlight component is created for each view. + The geometry of the resulting component instance will be managed by the view + so as to stay with the current item, unless the highlightFollowsCurrentItem property is false. + + \sa highlightItem, highlightFollowsCurrentItem +*/ + +/*! + \qmlproperty bool QtQuick2::GridView::highlightFollowsCurrentItem + This property sets 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 doc/src/snippets/declarative/gridview/gridview.qml highlightFollowsCurrentItem +*/ + + +/*! + \qmlproperty int QtQuick2::GridView::highlightMoveDuration + This property holds the move animation duration of the highlight delegate. + + highlightFollowsCurrentItem must be true for this property + to have effect. + + The default value for the duration is 150ms. + + \sa highlightFollowsCurrentItem +*/ + +/*! + \qmlproperty real QtQuick2::GridView::preferredHighlightBegin + \qmlproperty real QtQuick2::GridView::preferredHighlightEnd + \qmlproperty enumeration QtQuick2::GridView::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 view is scrolled. + For example, if the currently selected item should stay in the middle of the + view when it 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 view 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 + \o GridView.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 view or due + to mouse interaction. + \o GridView.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. + \o GridView.NoHighlightRange - this is the default value. + \endlist +*/ + + +/*! + \qmlproperty enumeration QtQuick2::GridView::layoutDirection + This property holds the layout direction of the grid. + + Possible values: + + \list + \o Qt.LeftToRight (default) - Items will be laid out starting in the top, left corner. The flow is + dependent on the \l GridView::flow property. + \o Qt.RightToLeft - Items will be laid out starting in the top, right corner. The flow is dependent + on the \l GridView::flow property. + \endlist + + \bold Note: If GridView::flow is set to GridView.LeftToRight, this is not to be confused if + GridView::layoutDirection is set to Qt.RightToLeft. The GridView.LeftToRight flow value simply + indicates that the flow is horizontal. +*/ + + +/*! + \qmlproperty enumeration QtQuick2::GridView::effectiveLayoutDirection + This property holds the effective layout direction of the grid. + + When using the attached property \l {LayoutMirroring::enabled}{LayoutMirroring::enabled} for locale layouts, + the visual layout direction of the grid will be mirrored. However, the + property \l {GridView::layoutDirection}{layoutDirection} will remain unchanged. + + \sa GridView::layoutDirection, {LayoutMirroring}{LayoutMirroring} +*/ +/*! + \qmlproperty bool QtQuick2::GridView::keyNavigationWraps + This property holds whether the grid wraps key navigation + + If this is true, key navigation that would move the current item selection + past one end of the view instead wraps around and moves the selection to + the other end of the view. + + By default, key navigation is not wrapped. +*/ +/*! + \qmlproperty int QtQuick2::GridView::cacheBuffer + This property determines whether delegates are retained outside the + visible area of the view. + + If non-zero the view may keep as many delegates + instantiated as will fit within the buffer specified. For example, + if in a vertical view the delegate is 20 pixels high, there are 3 + columns and \c cacheBuffer is + set to 40, then up to 6 delegates above and 6 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 have their \l visible property set to + false until they are moved into the visible area. + + Note that cacheBuffer is not a pixel buffer - it only maintains additional + instantiated delegates. + + Setting this value can make scrolling the list smoother at the expense + of additional memory usage. It is not a substitute for creating efficient + delegates; the fewer elements in a delegate, the faster a view may be + scrolled. +*/ +void QQuickGridView::setHighlightMoveDuration(int duration) +{ + Q_D(QQuickGridView); + if (d->highlightMoveDuration != duration) { + if (d->highlightYAnimator) { + d->highlightXAnimator->userDuration = duration; + d->highlightYAnimator->userDuration = duration; + } + QQuickItemView::setHighlightMoveDuration(duration); + } +} + +/*! + \qmlproperty enumeration QtQuick2::GridView::flow + This property holds the flow of the grid. + + Possible values: + + \list + \o GridView.LeftToRight (default) - Items are laid out from left to right, and the view scrolls vertically + \o GridView.TopToBottom - Items are laid out from top to bottom, and the view scrolls horizontally + \endlist +*/ +QQuickGridView::Flow QQuickGridView::flow() const +{ + Q_D(const QQuickGridView); + return d->flow; +} + +void QQuickGridView::setFlow(Flow flow) +{ + Q_D(QQuickGridView); + if (d->flow != flow) { + d->flow = flow; + if (d->flow == LeftToRight) { + setContentWidth(-1); + setFlickableDirection(VerticalFlick); + } else { + setContentHeight(-1); + setFlickableDirection(HorizontalFlick); + } + setContentX(0); + setContentY(0); + d->regenerate(); + emit flowChanged(); + } +} + + +/*! + \qmlproperty real QtQuick2::GridView::cellWidth + \qmlproperty real QtQuick2::GridView::cellHeight + + These properties holds the width and height of each cell in the grid. + + The default cell size is 100x100. +*/ +qreal QQuickGridView::cellWidth() const +{ + Q_D(const QQuickGridView); + return d->cellWidth; +} + +void QQuickGridView::setCellWidth(qreal cellWidth) +{ + Q_D(QQuickGridView); + if (cellWidth != d->cellWidth && cellWidth > 0) { + d->cellWidth = qMax(qreal(1), cellWidth); + d->updateViewport(); + emit cellWidthChanged(); + d->forceLayout = true; + d->layout(); + } +} + +qreal QQuickGridView::cellHeight() const +{ + Q_D(const QQuickGridView); + return d->cellHeight; +} + +void QQuickGridView::setCellHeight(qreal cellHeight) +{ + Q_D(QQuickGridView); + if (cellHeight != d->cellHeight && cellHeight > 0) { + d->cellHeight = qMax(qreal(1), cellHeight); + d->updateViewport(); + emit cellHeightChanged(); + d->forceLayout = true; + d->layout(); + } +} +/*! + \qmlproperty enumeration QtQuick2::GridView::snapMode + + This property determines how the view scrolling will settle following a drag or flick. + The possible values are: + + \list + \o GridView.NoSnap (default) - the view stops anywhere within the visible area. + \o GridView.SnapToRow - the view settles with a row (or column for \c GridView.TopToBottom flow) + aligned with the start of the view. + \o GridView.SnapOneRow - the view will settle no more than one row (or column for \c GridView.TopToBottom flow) + away from the first visible row at the time the mouse button is released. + This mode is particularly useful for moving one page at a time. + \endlist + +*/ +QQuickGridView::SnapMode QQuickGridView::snapMode() const +{ + Q_D(const QQuickGridView); + return d->snapMode; +} + +void QQuickGridView::setSnapMode(SnapMode mode) +{ + Q_D(QQuickGridView); + if (d->snapMode != mode) { + d->snapMode = mode; + emit snapModeChanged(); + } +} + + +/*! + \qmlproperty Component QtQuick2::GridView::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. + + \sa header +*/ +/*! + \qmlproperty Component QtQuick2::GridView::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. + + \sa footer +*/ +void QQuickGridView::viewportMoved() +{ + Q_D(QQuickGridView); + QQuickItemView::viewportMoved(); + if (!d->itemCount) + return; + if (d->inViewportMoved) + return; + d->inViewportMoved = true; + + // Set visibility of items to eliminate cost of items outside the visible area. + qreal from = d->isContentFlowReversed() ? -d->position()-d->size() : d->position(); + qreal to = d->isContentFlowReversed() ? -d->position() : d->position()+d->size(); + for (int i = 0; i < d->visibleItems.count(); ++i) { + FxGridItemSG *item = static_cast<FxGridItemSG*>(d->visibleItems.at(i)); + item->item->setVisible(item->rowPos() + d->rowSize() >= from && item->rowPos() <= to); + } + + if (yflick()) + d->bufferMode = d->vData.smoothVelocity < 0 ? QQuickItemViewPrivate::BufferBefore : QQuickItemViewPrivate::BufferAfter; + else if (d->isRightToLeftTopToBottom()) + d->bufferMode = d->hData.smoothVelocity < 0 ? QQuickItemViewPrivate::BufferAfter : QQuickItemViewPrivate::BufferBefore; + else + d->bufferMode = d->hData.smoothVelocity < 0 ? QQuickItemViewPrivate::BufferBefore : QQuickItemViewPrivate::BufferAfter; + + d->refill(); + if (d->hData.flicking || d->vData.flicking || d->hData.moving || d->vData.moving) + d->moveReason = QQuickGridViewPrivate::Mouse; + if (d->moveReason != QQuickGridViewPrivate::SetIndex) { + if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange && d->highlight) { + // reposition highlight + qreal pos = d->highlight->position(); + qreal viewPos = d->isRightToLeftTopToBottom() ? -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->highlightXAnimator->stop(); + d->highlightYAnimator->stop(); + static_cast<FxGridItemSG*>(d->highlight)->setPosition(static_cast<FxGridItemSG*>(d->highlight)->colPos(), pos); + } else { + d->updateHighlight(); + } + + // update current index + int idx = d->snapIndex(); + if (idx >= 0 && idx != d->currentIndex) { + d->updateCurrent(idx); + if (d->currentItem && static_cast<FxGridItemSG*>(d->currentItem)->colPos() != static_cast<FxGridItemSG*>(d->highlight)->colPos() && d->autoHighlight) { + if (d->flow == LeftToRight) + d->highlightXAnimator->to = d->currentItem->item->x(); + else + d->highlightYAnimator->to = d->currentItem->item->y(); + } + } + } + } + + d->inViewportMoved = false; +} + +void QQuickGridView::keyPressEvent(QKeyEvent *event) +{ + Q_D(QQuickGridView); + if (d->model && d->model->count() && d->interactive) { + d->moveReason = QQuickGridViewPrivate::SetIndex; + int oldCurrent = currentIndex(); + switch (event->key()) { + case Qt::Key_Up: + moveCurrentIndexUp(); + break; + case Qt::Key_Down: + moveCurrentIndexDown(); + break; + case Qt::Key_Left: + moveCurrentIndexLeft(); + break; + case Qt::Key_Right: + moveCurrentIndexRight(); + break; + default: + break; + } + if (oldCurrent != currentIndex()) { + event->accept(); + return; + } + } + event->ignore(); + QQuickItemView::keyPressEvent(event); +} +/*! + \qmlmethod QtQuick2::GridView::moveCurrentIndexUp() + + Move the currentIndex up one item in the view. + 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. + + \bold Note: methods should only be called after the Component has completed. +*/ + + +void QQuickGridView::moveCurrentIndexUp() +{ + Q_D(QQuickGridView); + const int count = d->model ? d->model->count() : 0; + if (!count) + return; + if (d->flow == QQuickGridView::LeftToRight) { + if (currentIndex() >= d->columns || d->wrap) { + int index = currentIndex() - d->columns; + setCurrentIndex((index >= 0 && index < count) ? index : count-1); + } + } else { + if (currentIndex() > 0 || d->wrap) { + int index = currentIndex() - 1; + setCurrentIndex((index >= 0 && index < count) ? index : count-1); + } + } +} + +/*! + \qmlmethod QtQuick2::GridView::moveCurrentIndexDown() + + Move the currentIndex down one item in the view. + 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. + + \bold Note: methods should only be called after the Component has completed. +*/ +void QQuickGridView::moveCurrentIndexDown() +{ + Q_D(QQuickGridView); + const int count = d->model ? d->model->count() : 0; + if (!count) + return; + if (d->flow == QQuickGridView::LeftToRight) { + if (currentIndex() < count - d->columns || d->wrap) { + int index = currentIndex()+d->columns; + setCurrentIndex((index >= 0 && index < count) ? index : 0); + } + } else { + if (currentIndex() < count - 1 || d->wrap) { + int index = currentIndex() + 1; + setCurrentIndex((index >= 0 && index < count) ? index : 0); + } + } +} + +/*! + \qmlmethod QtQuick2::GridView::moveCurrentIndexLeft() + + Move the currentIndex left one item in the view. + 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. + + \bold Note: methods should only be called after the Component has completed. +*/ +void QQuickGridView::moveCurrentIndexLeft() +{ + Q_D(QQuickGridView); + const int count = d->model ? d->model->count() : 0; + if (!count) + return; + if (effectiveLayoutDirection() == Qt::LeftToRight) { + if (d->flow == QQuickGridView::LeftToRight) { + if (currentIndex() > 0 || d->wrap) { + int index = currentIndex() - 1; + setCurrentIndex((index >= 0 && index < count) ? index : count-1); + } + } else { + if (currentIndex() >= d->columns || d->wrap) { + int index = currentIndex() - d->columns; + setCurrentIndex((index >= 0 && index < count) ? index : count-1); + } + } + } else { + if (d->flow == QQuickGridView::LeftToRight) { + if (currentIndex() < count - 1 || d->wrap) { + int index = currentIndex() + 1; + setCurrentIndex((index >= 0 && index < count) ? index : 0); + } + } else { + if (currentIndex() < count - d->columns || d->wrap) { + int index = currentIndex() + d->columns; + setCurrentIndex((index >= 0 && index < count) ? index : 0); + } + } + } +} + + +/*! + \qmlmethod QtQuick2::GridView::moveCurrentIndexRight() + + Move the currentIndex right one item in the view. + 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. + + \bold Note: methods should only be called after the Component has completed. +*/ +void QQuickGridView::moveCurrentIndexRight() +{ + Q_D(QQuickGridView); + const int count = d->model ? d->model->count() : 0; + if (!count) + return; + if (effectiveLayoutDirection() == Qt::LeftToRight) { + if (d->flow == QQuickGridView::LeftToRight) { + if (currentIndex() < count - 1 || d->wrap) { + int index = currentIndex() + 1; + setCurrentIndex((index >= 0 && index < count) ? index : 0); + } + } else { + if (currentIndex() < count - d->columns || d->wrap) { + int index = currentIndex()+d->columns; + setCurrentIndex((index >= 0 && index < count) ? index : 0); + } + } + } else { + if (d->flow == QQuickGridView::LeftToRight) { + if (currentIndex() > 0 || d->wrap) { + int index = currentIndex() - 1; + setCurrentIndex((index >= 0 && index < count) ? index : count-1); + } + } else { + if (currentIndex() >= d->columns || d->wrap) { + int index = currentIndex() - d->columns; + setCurrentIndex((index >= 0 && index < count) ? index : count-1); + } + } + } +} + +bool QQuickGridViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::Insert &change, FxViewItem *firstVisible, InsertionsResult *insertResult) +{ + Q_Q(QQuickGridView); + + int modelIndex = change.index; + int count = change.count; + + int index = visibleItems.count() ? mapFromModel(modelIndex) : 0; + + if (index < 0) { + int i = visibleItems.count() - 1; + while (i > 0 && visibleItems.at(i)->index == -1) + --i; + if (visibleItems.at(i)->index + 1 == modelIndex) { + // Special case of appending an item to the model. + index = visibleItems.count(); + } else { + if (modelIndex <= visibleIndex) { + // Insert before visible items + visibleIndex += count; + for (int i = 0; i < visibleItems.count(); ++i) { + FxViewItem *item = visibleItems.at(i); + if (item->index != -1 && item->index >= modelIndex) + item->index += count; + } + } + return true; + } + } + + qreal tempPos = isRightToLeftTopToBottom() ? -position()-size()+q->width()+1 : position(); + qreal colPos = 0; + qreal rowPos = 0; + int colNum = 0; + if (visibleItems.count()) { + if (index < visibleItems.count()) { + FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(visibleItems.at(index)); + colPos = gridItem->colPos(); + rowPos = gridItem->rowPos(); + colNum = qFloor((colPos+colSize()/2) / colSize()); + } else { + // appending items to visible list + FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(visibleItems.at(index-1)); + rowPos = gridItem->rowPos(); + colNum = qFloor((gridItem->colPos()+colSize()/2) / colSize()); + if (++colNum >= columns) { + colNum = 0; + rowPos += rowSize(); + } + colPos = colNum * colSize(); + } + } + + // Update the indexes of the following visible items. + for (int i = 0; i < visibleItems.count(); ++i) { + FxViewItem *item = visibleItems.at(i); + if (item->index != -1 && item->index >= modelIndex) + item->index += count; + } + + int prevAddedCount = insertResult->addedItems.count(); + if (firstVisible && rowPos < firstVisible->position()) { + // Insert items before the visible item. + int insertionIdx = index; + int i = count - 1; + int from = tempPos - buffer; + + while (i >= 0) { + if (rowPos > from && insertionIdx < visibleIndex) { + // item won't be visible, just note the size for repositioning + insertResult->sizeAddedBeforeVisible += rowSize(); + } else { + // item is before first visible e.g. in cache buffer + FxViewItem *item = 0; + if (change.isMove() && (item = currentChanges.removedItems.take(change.moveKey(modelIndex + i)))) { + if (item->index > modelIndex + i) + insertResult->movedBackwards.append(item); + item->index = modelIndex + i; + } + if (!item) + item = createItem(modelIndex + i); + if (!item) + return false; + + item->item->setVisible(true); + visibleItems.insert(insertionIdx, item); + if (!change.isMove()) { + insertResult->addedItems.append(item); + insertResult->sizeAddedBeforeVisible += rowSize(); + } + } + + if (--colNum < 0 ) { + colNum = columns - 1; + rowPos -= rowSize(); + } + colPos = colNum * colSize(); + index++; + i--; + } + } else { + int i = 0; + int to = buffer+tempPos+size()-1; + while (i < count && rowPos <= to + rowSize()*(columns - (colPos/colSize()))/qreal(columns)) { + FxViewItem *item = 0; + if (change.isMove() && (item = currentChanges.removedItems.take(change.moveKey(modelIndex + i)))) { + if (item->index > modelIndex + i) + insertResult->movedBackwards.append(item); + item->index = modelIndex + i; + } + if (!item) + item = createItem(modelIndex + i); + if (!item) + return false; + + item->item->setVisible(true); + visibleItems.insert(index, item); + if (!change.isMove()) + insertResult->addedItems.append(item); + if (++colNum >= columns) { + colNum = 0; + rowPos += rowSize(); + } + colPos = colNum * colSize(); + ++index; + ++i; + } + } + + updateVisibleIndex(); + + return insertResult->addedItems.count() > prevAddedCount; +} + +bool QQuickGridViewPrivate::needsRefillForAddedOrRemovedIndex(int modelIndex) const +{ + // If we add or remove items before visible items, a layout may be + // required to ensure item 0 is in the first column. + return modelIndex < visibleIndex; +} + +/*! + \qmlmethod QtQuick2::GridView::positionViewAtIndex(int index, PositionMode mode) + + Positions the view such that the \a index is at the position specified by + \a mode: + + \list + \o GridView.Beginning - position item at the top (or left for \c GridView.TopToBottom flow) of the view. + \o GridView.Center - position item in the center of the view. + \o GridView.End - position item at bottom (or right for horizontal orientation) of the view. + \o GridView.Visible - if any part of the item is visible then take no action, otherwise + bring the item into view. + \o GridView.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 \c GridView.TopToBottom flow) of the view. + \endlist + + If positioning the view at the 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 view does not cause all other items to be repositioned. + The correct way to bring an item into view is with \c positionViewAtIndex. + + \bold 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, GridView.Beginning) + \endcode +*/ + +/*! + \qmlmethod QtQuick2::GridView::positionViewAtBeginning() + \qmlmethod QtQuick2::GridView::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. + + \bold 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 QtQuick2::GridView::indexAt(int x, int 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. + + \bold Note: methods should only be called after the Component has completed. +*/ + +QQuickGridViewAttached *QQuickGridView::qmlAttachedProperties(QObject *obj) +{ + return new QQuickGridViewAttached(obj); +} + +QT_END_NAMESPACE |