/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtDeclarative 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 Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/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 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, Digia gives you certain additional ** rights. These rights are described in the Digia 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. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "private/qdeclarativegridview_p.h" #include "private/qdeclarativevisualitemmodel_p.h" #include "private/qdeclarativeflickable_p_p.h" #include "private/qdeclarativesmoothedanimation_p_p.h" #include #include #include #include #include #include "qplatformdefs.h" QT_BEGIN_NAMESPACE #ifndef QML_FLICK_SNAPONETHRESHOLD #define QML_FLICK_SNAPONETHRESHOLD 30 #endif //---------------------------------------------------------------------------- class FxGridItem { public: FxGridItem(QDeclarativeItem *i, QDeclarativeGridView *v) : item(i), view(v) { attached = static_cast(qmlAttachedPropertiesObject(item)); if (attached) attached->setView(view); } ~FxGridItem() {} qreal rowPos() const { qreal rowPos = 0; if (view->flow() == QDeclarativeGridView::LeftToRight) { rowPos = item->y(); } else { if (view->effectiveLayoutDirection() == Qt::RightToLeft) rowPos = -view->cellWidth()-item->x(); else rowPos = item->x(); } return rowPos; } qreal colPos() const { qreal colPos = 0; if (view->flow() == QDeclarativeGridView::LeftToRight) { if (view->effectiveLayoutDirection() == Qt::RightToLeft) { int colSize = view->cellWidth(); int columns = view->width()/colSize; colPos = colSize * (columns-1) - item->x(); } else { colPos = item->x(); } } else { colPos = item->y(); } return colPos; } qreal endRowPos() const { if (view->flow() == QDeclarativeGridView::LeftToRight) { return item->y() + view->cellHeight() - 1; } else { if (view->effectiveLayoutDirection() == Qt::RightToLeft) return -item->x() - 1; else return item->x() + view->cellWidth() - 1; } } void setPosition(qreal col, qreal row) { if (view->effectiveLayoutDirection() == Qt::RightToLeft) { if (view->flow() == QDeclarativeGridView::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() == QDeclarativeGridView::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()); } QDeclarativeItem *item; QDeclarativeGridView *view; QDeclarativeGridViewAttached *attached; int index; }; //---------------------------------------------------------------------------- class QDeclarativeGridViewPrivate : public QDeclarativeFlickablePrivate { Q_DECLARE_PUBLIC(QDeclarativeGridView) public: QDeclarativeGridViewPrivate() : currentItem(0), layoutDirection(Qt::LeftToRight), flow(QDeclarativeGridView::LeftToRight) , visibleIndex(0) , currentIndex(-1) , cellWidth(100), cellHeight(100), columns(1), requestedIndex(-1), itemCount(0) , highlightRangeStart(0), highlightRangeEnd(0) , highlightRangeStartValid(false), highlightRangeEndValid(false) , highlightRange(QDeclarativeGridView::NoHighlightRange) , highlightComponent(0), highlight(0), trackedItem(0) , moveReason(Other), buffer(0), highlightXAnimator(0), highlightYAnimator(0) , highlightMoveDuration(150) , footerComponent(0), footer(0), headerComponent(0), header(0) , bufferMode(BufferBefore | BufferAfter), snapMode(QDeclarativeGridView::NoSnap) , ownModel(false), wrap(false), autoHighlight(true) , fixCurrentVisibility(false), lazyRelease(false), layoutScheduled(false) , deferredRelease(false), haveHighlightRange(false), currentIndexCleared(false) {} void init(); void clear(); FxGridItem *createItem(int modelIndex); void releaseItem(FxGridItem *item); QDeclarativeItem *createComponentItem(QDeclarativeComponent *component); void refill(qreal from, qreal to, bool doBuffer=false); void updateGrid(); void scheduleLayout(); void layout(); void updateUnrequestedIndexes(); void updateUnrequestedPositions(); void updateTrackedItem(); void createHighlight(); void updateHighlight(); void updateCurrent(int modelIndex); void updateHeader(); void updateFooter(); void fixupPosition(); FxGridItem *visibleItem(int modelIndex) const { if (modelIndex >= visibleIndex && modelIndex < visibleIndex + visibleItems.count()) { for (int i = modelIndex - visibleIndex; i < visibleItems.count(); ++i) { FxGridItem *item = visibleItems.at(i); if (item->index == modelIndex) return item; } } return 0; } bool isRightToLeftTopToBottom() const { Q_Q(const QDeclarativeGridView); return flow == QDeclarativeGridView::TopToBottom && q->effectiveLayoutDirection() == Qt::RightToLeft; } void regenerate() { Q_Q(QDeclarativeGridView); if (q->isComponentComplete()) { clear(); updateGrid(); setPosition(0); q->refill(); updateCurrent(currentIndex); } } void mirrorChange() { regenerate(); } qreal position() const { Q_Q(const QDeclarativeGridView); return flow == QDeclarativeGridView::LeftToRight ? q->contentY() : q->contentX(); } void setPosition(qreal pos) { Q_Q(QDeclarativeGridView); if (flow == QDeclarativeGridView::LeftToRight) { q->QDeclarativeFlickable::setContentY(pos); q->QDeclarativeFlickable::setContentX(0); } else { if (q->effectiveLayoutDirection() == Qt::LeftToRight) q->QDeclarativeFlickable::setContentX(pos); else q->QDeclarativeFlickable::setContentX(-pos-size()); q->QDeclarativeFlickable::setContentY(0); } } int size() const { Q_Q(const QDeclarativeGridView); return flow == QDeclarativeGridView::LeftToRight ? q->height() : q->width(); } qreal originPosition() const { qreal pos = 0; if (!visibleItems.isEmpty()) pos = visibleItems.first()->rowPos() - visibleIndex / columns * rowSize(); return pos; } qreal lastPosition() const { qreal pos = 0; if (model && model->count()) pos = rowPosAt(model->count() - 1) + rowSize(); return pos; } qreal startPosition() const { return isRightToLeftTopToBottom() ? -lastPosition()+1 : originPosition(); } qreal endPosition() const { return isRightToLeftTopToBottom() ? -originPosition()+1 : lastPosition(); } bool isValid() const { return model && model->count() && model->isValid(); } int rowSize() const { return flow == QDeclarativeGridView::LeftToRight ? cellHeight : cellWidth; } int colSize() const { return flow == QDeclarativeGridView::LeftToRight ? cellWidth : cellHeight; } qreal colPosAt(int modelIndex) const { if (FxGridItem *item = visibleItem(modelIndex)) return item->colPos(); if (!visibleItems.isEmpty()) { if (modelIndex < visibleIndex) { int count = (visibleIndex - modelIndex) % columns; int col = visibleItems.first()->colPos() / colSize(); col = (columns - count + col) % columns; return col * colSize(); } else { int count = columns - 1 - (modelIndex - visibleItems.last()->index - 1) % columns; return visibleItems.last()->colPos() - count * colSize(); } } else { return (modelIndex % columns) * colSize(); } return 0; } qreal rowPosAt(int modelIndex) const { if (FxGridItem *item = visibleItem(modelIndex)) return item->rowPos(); if (!visibleItems.isEmpty()) { if (modelIndex < visibleIndex) { int firstCol = visibleItems.first()->colPos() / colSize(); int col = visibleIndex - modelIndex + (columns - firstCol - 1); int rows = col / columns; return visibleItems.first()->rowPos() - rows * rowSize(); } else { int count = modelIndex - visibleItems.last()->index; int col = visibleItems.last()->colPos() + count * colSize(); int rows = col / (columns * colSize()); return visibleItems.last()->rowPos() + rows * rowSize(); } } else { qreal pos = (modelIndex / columns) * rowSize(); if (header) pos += headerSize(); return pos; } return 0; } FxGridItem *firstVisibleItem() const { const qreal pos = isRightToLeftTopToBottom() ? -position()-size() : position(); for (int i = 0; i < visibleItems.count(); ++i) { FxGridItem *item = visibleItems.at(i); if (item->index != -1 && item->endRowPos() > pos) return item; } return visibleItems.count() ? visibleItems.first() : 0; } int lastVisibleIndex() const { for (int i = 0; i < visibleItems.count(); ++i) { FxGridItem *item = visibleItems.at(i); if (item->index != -1) return item->index; } return -1; } // Map a model index to visibleItems list index. // These may differ if removed items are still present in the visible list, // e.g. doing a removal animation int mapFromModel(int modelIndex) const { if (modelIndex < visibleIndex || modelIndex >= visibleIndex + visibleItems.count()) return -1; for (int i = 0; i < visibleItems.count(); ++i) { FxGridItem *listItem = visibleItems.at(i); if (listItem->index == modelIndex) return i + visibleIndex; if (listItem->index > modelIndex) return -1; } return -1; // Not in visibleList } qreal snapPosAt(qreal pos) const { Q_Q(const QDeclarativeGridView); qreal snapPos = 0; if (!visibleItems.isEmpty()) { qreal highlightStart = isRightToLeftTopToBottom() && highlightRangeStartValid ? size()-highlightRangeEnd : highlightRangeStart; pos += highlightStart; pos += rowSize()/2; snapPos = visibleItems.first()->rowPos() - visibleIndex / columns * rowSize(); snapPos = pos - fmodf(pos - snapPos, qreal(rowSize())); snapPos -= highlightStart; qreal maxExtent; qreal minExtent; if (isRightToLeftTopToBottom()) { maxExtent = q->minXExtent(); minExtent = q->maxXExtent(); } else { maxExtent = flow == QDeclarativeGridView::LeftToRight ? -q->maxYExtent() : -q->maxXExtent(); minExtent = flow == QDeclarativeGridView::LeftToRight ? -q->minYExtent() : -q->minXExtent(); } if (snapPos > maxExtent) snapPos = maxExtent; if (snapPos < minExtent) snapPos = minExtent; } return snapPos; } FxGridItem *snapItemAt(qreal pos) { for (int i = 0; i < visibleItems.count(); ++i) { FxGridItem *item = visibleItems[i]; if (item->index == -1) continue; qreal itemTop = item->rowPos(); if (itemTop+rowSize()/2 >= pos && itemTop - rowSize()/2 <= pos) return item; } return 0; } int snapIndex() { int index = currentIndex; for (int i = 0; i < visibleItems.count(); ++i) { FxGridItem *item = visibleItems[i]; if (item->index == -1) continue; qreal itemTop = item->rowPos(); if (itemTop >= highlight->rowPos()-rowSize()/2 && itemTop < highlight->rowPos()+rowSize()/2) { index = item->index; if (item->colPos() >= highlight->colPos()-colSize()/2 && item->colPos() < highlight->colPos()+colSize()/2) return item->index; } } return index; } qreal headerSize() const { if (!header) return 0.0; return flow == QDeclarativeGridView::LeftToRight ? header->item->height() : header->item->width(); } virtual void itemGeometryChanged(QDeclarativeItem *item, const QRectF &newGeometry, const QRectF &oldGeometry) { Q_Q(const QDeclarativeGridView); QDeclarativeFlickablePrivate::itemGeometryChanged(item, newGeometry, oldGeometry); if (item == q) { if (newGeometry.height() != oldGeometry.height() || newGeometry.width() != oldGeometry.width()) { if (q->isComponentComplete()) { updateGrid(); scheduleLayout(); } } } else if ((header && header->item == item) || (footer && footer->item == item)) { if (header) updateHeader(); if (footer) updateFooter(); } } void positionViewAtIndex(int index, int mode); virtual void fixup(AxisData &data, qreal minExtent, qreal maxExtent); virtual void flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize, QDeclarativeTimeLineCallback::Callback fixupCallback, qreal velocity); // for debugging only void checkVisible() const { int skip = 0; for (int i = 0; i < visibleItems.count(); ++i) { FxGridItem *listItem = visibleItems.at(i); if (listItem->index == -1) { ++skip; } else if (listItem->index != visibleIndex + i - skip) { for (int j = 0; j < visibleItems.count(); j++) qDebug() << " index" << j << "item index" << visibleItems.at(j)->index; qFatal("index %d %d %d", visibleIndex, i, listItem->index); } } } QDeclarativeGuard model; QVariant modelVariant; QList visibleItems; QHash unrequestedItems; FxGridItem *currentItem; Qt::LayoutDirection layoutDirection; QDeclarativeGridView::Flow flow; int visibleIndex; int currentIndex; int cellWidth; int cellHeight; int columns; int requestedIndex; int itemCount; qreal highlightRangeStart; qreal highlightRangeEnd; bool highlightRangeStartValid; bool highlightRangeEndValid; QDeclarativeGridView::HighlightRangeMode highlightRange; QDeclarativeComponent *highlightComponent; FxGridItem *highlight; FxGridItem *trackedItem; enum MovementReason { Other, SetIndex, Mouse }; MovementReason moveReason; int buffer; QSmoothedAnimation *highlightXAnimator; QSmoothedAnimation *highlightYAnimator; int highlightMoveDuration; QDeclarativeComponent *footerComponent; FxGridItem *footer; QDeclarativeComponent *headerComponent; FxGridItem *header; enum BufferMode { NoBuffer = 0x00, BufferBefore = 0x01, BufferAfter = 0x02 }; int bufferMode; QDeclarativeGridView::SnapMode snapMode; bool ownModel : 1; bool wrap : 1; bool autoHighlight : 1; bool fixCurrentVisibility : 1; bool lazyRelease : 1; bool layoutScheduled : 1; bool deferredRelease : 1; bool haveHighlightRange : 1; bool currentIndexCleared : 1; }; void QDeclarativeGridViewPrivate::init() { Q_Q(QDeclarativeGridView); QObject::connect(q, SIGNAL(movementEnded()), q, SLOT(animStopped())); q->setFlag(QGraphicsItem::ItemIsFocusScope); q->setFlickableDirection(QDeclarativeFlickable::VerticalFlick); addItemChangeListener(this, Geometry); } void QDeclarativeGridViewPrivate::clear() { for (int i = 0; i < visibleItems.count(); ++i) releaseItem(visibleItems.at(i)); visibleItems.clear(); visibleIndex = 0; releaseItem(currentItem); currentItem = 0; createHighlight(); trackedItem = 0; itemCount = 0; } FxGridItem *QDeclarativeGridViewPrivate::createItem(int modelIndex) { Q_Q(QDeclarativeGridView); // create object requestedIndex = modelIndex; FxGridItem *listItem = 0; if (QDeclarativeItem *item = model->item(modelIndex, false)) { listItem = new FxGridItem(item, q); listItem->index = modelIndex; if (model->completePending()) { // complete listItem->item->setZValue(1); listItem->item->setParentItem(q->contentItem()); model->completeItem(); } else { listItem->item->setParentItem(q->contentItem()); } unrequestedItems.remove(listItem->item); } requestedIndex = -1; return listItem; } void QDeclarativeGridViewPrivate::releaseItem(FxGridItem *item) { Q_Q(QDeclarativeGridView); if (!item || !model) return; if (trackedItem == item) { QObject::disconnect(trackedItem->item, SIGNAL(yChanged()), q, SLOT(trackedPositionChanged())); QObject::disconnect(trackedItem->item, SIGNAL(xChanged()), q, SLOT(trackedPositionChanged())); trackedItem = 0; } if (model->release(item->item) == 0) { // item was not destroyed, and we no longer reference it. unrequestedItems.insert(item->item, model->indexOf(item->item, q)); } delete item; } QDeclarativeItem *QDeclarativeGridViewPrivate::createComponentItem(QDeclarativeComponent *component) { Q_Q(QDeclarativeGridView); QDeclarativeItem *item = 0; QDeclarativeContext *creationContext = component->creationContext(); QDeclarativeContext *context = new QDeclarativeContext( creationContext ? creationContext : qmlContext(q)); QObject *nobj = component->create(context); if (nobj) { QDeclarative_setParent_noEvent(context, nobj); item = qobject_cast(nobj); if (!item) delete nobj; } else { delete context; } return item; } void QDeclarativeGridViewPrivate::refill(qreal from, qreal to, bool doBuffer) { Q_Q(QDeclarativeGridView); if (!isValid() || !q->isComponentComplete()) return; itemCount = model->count(); qreal bufferFrom = from - buffer; qreal bufferTo = to + buffer; qreal fillFrom = from; qreal fillTo = to; if (doBuffer && (bufferMode & BufferAfter)) fillTo = bufferTo; if (doBuffer && (bufferMode & BufferBefore)) fillFrom = bufferFrom; bool changed = false; int colPos = colPosAt(visibleIndex); int rowPos = rowPosAt(visibleIndex); int modelIndex = visibleIndex; if (visibleItems.count()) { rowPos = visibleItems.last()->rowPos(); colPos = visibleItems.last()->colPos() + colSize(); if (colPos > colSize() * (columns-1)) { colPos = 0; rowPos += rowSize(); } int i = visibleItems.count() - 1; while (i > 0 && visibleItems.at(i)->index == -1) --i; modelIndex = visibleItems.at(i)->index + 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 = colPos / colSize(); FxGridItem *item = 0; // Item creation and release is staggered in order to avoid // creating/releasing multiple items in one frame // while flicking (as much as possible). while (modelIndex < model->count() && rowPos <= fillTo + rowSize()*(columns - colNum)/(columns+1)) { // qDebug() << "refill: append item" << modelIndex; if (!(item = createItem(modelIndex))) break; item->setPosition(colPos, rowPos); visibleItems.append(item); colPos += colSize(); colNum++; if (colPos > colSize() * (columns-1)) { colPos = 0; colNum = 0; rowPos += rowSize(); } ++modelIndex; changed = true; if (doBuffer) // never buffer more than one item per frame break; } if (visibleItems.count()) { rowPos = visibleItems.first()->rowPos(); colPos = visibleItems.first()->colPos() - colSize(); if (colPos < 0) { colPos = colSize() * (columns - 1); rowPos -= rowSize(); } } colNum = colPos / 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 = createItem(visibleIndex-1))) break; --visibleIndex; item->setPosition(colPos, rowPos); visibleItems.prepend(item); colPos -= colSize(); colNum--; if (colPos < 0) { colPos = colSize() * (columns - 1); colNum = columns-1; rowPos -= rowSize(); } changed = true; if (doBuffer) // never buffer more than one item per frame break; } if (!lazyRelease || !changed || deferredRelease) { // avoid destroying items in the same frame that we create while (visibleItems.count() > 1 && (item = 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 = 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; } deferredRelease = false; } else { deferredRelease = true; } if (changed) { if (header) updateHeader(); if (footer) updateFooter(); if (flow == QDeclarativeGridView::LeftToRight) q->setContentHeight(endPosition() - startPosition()); else q->setContentWidth(endPosition() - startPosition()); } else if (!doBuffer && buffer && bufferMode != NoBuffer) { refill(from, to, true); } lazyRelease = false; } void QDeclarativeGridViewPrivate::updateGrid() { Q_Q(QDeclarativeGridView); columns = (int)qMax((flow == QDeclarativeGridView::LeftToRight ? q->width() : q->height()) / colSize(), qreal(1.)); if (isValid()) { if (flow == QDeclarativeGridView::LeftToRight) q->setContentHeight(endPosition() - startPosition()); else q->setContentWidth(lastPosition() - originPosition()); } } void QDeclarativeGridViewPrivate::scheduleLayout() { Q_Q(QDeclarativeGridView); if (!layoutScheduled) { layoutScheduled = true; QCoreApplication::postEvent(q, new QEvent(QEvent::User), Qt::HighEventPriority); } } void QDeclarativeGridViewPrivate::layout() { Q_Q(QDeclarativeGridView); layoutScheduled = false; if (!isValid() && !visibleItems.count()) { clear(); return; } if (visibleItems.count()) { qreal rowPos = visibleItems.first()->rowPos(); qreal colPos = visibleItems.first()->colPos(); int col = visibleIndex % columns; if (colPos != col * colSize()) { colPos = col * colSize(); visibleItems.first()->setPosition(colPos, rowPos); } for (int i = 1; i < visibleItems.count(); ++i) { FxGridItem *item = visibleItems.at(i); colPos += colSize(); if (colPos > colSize() * (columns-1)) { colPos = 0; rowPos += rowSize(); } item->setPosition(colPos, rowPos); } } if (header) updateHeader(); if (footer) updateFooter(); q->refill(); updateHighlight(); moveReason = Other; if (flow == QDeclarativeGridView::LeftToRight) { q->setContentHeight(endPosition() - startPosition()); fixupY(); } else { q->setContentWidth(endPosition() - startPosition()); fixupX(); } updateUnrequestedPositions(); } void QDeclarativeGridViewPrivate::updateUnrequestedIndexes() { Q_Q(QDeclarativeGridView); QHash::iterator it; for (it = unrequestedItems.begin(); it != unrequestedItems.end(); ++it) *it = model->indexOf(it.key(), q); } void QDeclarativeGridViewPrivate::updateUnrequestedPositions() { QHash::const_iterator it; for (it = unrequestedItems.begin(); it != unrequestedItems.end(); ++it) { QDeclarativeItem *item = it.key(); if (flow == QDeclarativeGridView::LeftToRight) { item->setPos(QPointF(colPosAt(*it), rowPosAt(*it))); } else { if (isRightToLeftTopToBottom()) item->setPos(QPointF(-rowPosAt(*it)-item->width(), colPosAt(*it))); else item->setPos(QPointF(rowPosAt(*it), colPosAt(*it))); } } } void QDeclarativeGridViewPrivate::updateTrackedItem() { Q_Q(QDeclarativeGridView); FxGridItem *item = currentItem; if (highlight) item = highlight; if (trackedItem && item != trackedItem) { QObject::disconnect(trackedItem->item, SIGNAL(yChanged()), q, SLOT(trackedPositionChanged())); QObject::disconnect(trackedItem->item, SIGNAL(xChanged()), q, SLOT(trackedPositionChanged())); trackedItem = 0; } if (!trackedItem && item) { trackedItem = item; QObject::connect(trackedItem->item, SIGNAL(yChanged()), q, SLOT(trackedPositionChanged())); QObject::connect(trackedItem->item, SIGNAL(xChanged()), q, SLOT(trackedPositionChanged())); } if (trackedItem) q->trackedPositionChanged(); } void QDeclarativeGridViewPrivate::createHighlight() { Q_Q(QDeclarativeGridView); bool changed = false; if (highlight) { if (trackedItem == highlight) trackedItem = 0; if (highlight->item->scene()) highlight->item->scene()->removeItem(highlight->item); highlight->item->deleteLater(); delete highlight; highlight = 0; delete highlightXAnimator; delete highlightYAnimator; highlightXAnimator = 0; highlightYAnimator = 0; changed = true; } if (currentItem) { QDeclarativeItem *item = 0; if (highlightComponent) { item = createComponentItem(highlightComponent); } else { item = new QDeclarativeItem; QDeclarative_setParent_noEvent(item, q->contentItem()); item->setParentItem(q->contentItem()); } if (item) { QDeclarative_setParent_noEvent(item, q->contentItem()); item->setParentItem(q->contentItem()); highlight = new FxGridItem(item, q); if (currentItem && autoHighlight) highlight->setPosition(currentItem->colPos(), currentItem->rowPos()); highlightXAnimator = new QSmoothedAnimation(q); highlightXAnimator->target = QDeclarativeProperty(highlight->item, QLatin1String("x")); highlightXAnimator->userDuration = highlightMoveDuration; highlightYAnimator = new QSmoothedAnimation(q); highlightYAnimator->target = QDeclarativeProperty(highlight->item, QLatin1String("y")); highlightYAnimator->userDuration = highlightMoveDuration; if (autoHighlight) { highlightXAnimator->restart(); highlightYAnimator->restart(); } changed = true; } } if (changed) emit q->highlightItemChanged(); } void QDeclarativeGridViewPrivate::updateHighlight() { if ((!currentItem && highlight) || (currentItem && !highlight)) createHighlight(); if (currentItem && autoHighlight && highlight && !hData.moving && !vData.moving) { // 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 QDeclarativeGridViewPrivate::updateCurrent(int modelIndex) { Q_Q(QDeclarativeGridView); if (!q->isComponentComplete() || !isValid() || modelIndex < 0 || modelIndex >= model->count()) { if (currentItem) { currentItem->attached->setIsCurrentItem(false); releaseItem(currentItem); currentItem = 0; currentIndex = modelIndex; emit q->currentIndexChanged(); updateHighlight(); } else if (currentIndex != modelIndex) { currentIndex = modelIndex; emit q->currentIndexChanged(); } return; } if (currentItem && currentIndex == modelIndex) { updateHighlight(); return; } FxGridItem *oldCurrentItem = currentItem; currentIndex = modelIndex; currentItem = createItem(modelIndex); fixCurrentVisibility = true; if (oldCurrentItem && (!currentItem || oldCurrentItem->item != currentItem->item)) oldCurrentItem->attached->setIsCurrentItem(false); if (currentItem) { currentItem->setPosition(colPosAt(modelIndex), rowPosAt(modelIndex)); currentItem->item->setFocus(true); currentItem->attached->setIsCurrentItem(true); } updateHighlight(); emit q->currentIndexChanged(); releaseItem(oldCurrentItem); } void QDeclarativeGridViewPrivate::updateFooter() { Q_Q(QDeclarativeGridView); if (!footer && footerComponent) { QDeclarativeItem *item = createComponentItem(footerComponent); if (item) { QDeclarative_setParent_noEvent(item, q->contentItem()); item->setParentItem(q->contentItem()); item->setZValue(1); QDeclarativeItemPrivate *itemPrivate = static_cast(QGraphicsItemPrivate::get(item)); itemPrivate->addItemChangeListener(this, QDeclarativeItemPrivate::Geometry); footer = new FxGridItem(item, q); } } if (footer) { qreal colOffset = 0; qreal rowOffset; if (isRightToLeftTopToBottom()) { rowOffset = footer->item->width()-cellWidth; } else { rowOffset = 0; if (q->effectiveLayoutDirection() == Qt::RightToLeft) colOffset = footer->item->width()-cellWidth; } if (visibleItems.count()) { qreal endPos = lastPosition(); if (lastVisibleIndex() == model->count()-1) { footer->setPosition(colOffset, endPos + rowOffset); } else { qreal visiblePos = isRightToLeftTopToBottom() ? -position() : position() + size(); if (endPos <= visiblePos || footer->endRowPos() < endPos + rowOffset) footer->setPosition(colOffset, endPos + rowOffset); } } else { qreal endPos = 0; if (header) { endPos += flow == QDeclarativeGridView::LeftToRight ? header->item->height() : header->item->width(); } footer->setPosition(colOffset, endPos); } } } void QDeclarativeGridViewPrivate::updateHeader() { Q_Q(QDeclarativeGridView); if (!header && headerComponent) { QDeclarativeItem *item = createComponentItem(headerComponent); if (item) { QDeclarative_setParent_noEvent(item, q->contentItem()); item->setParentItem(q->contentItem()); item->setZValue(1); QDeclarativeItemPrivate *itemPrivate = static_cast(QGraphicsItemPrivate::get(item)); itemPrivate->addItemChangeListener(this, QDeclarativeItemPrivate::Geometry); header = new FxGridItem(item, q); } } if (header) { qreal colOffset = 0; qreal rowOffset; if (isRightToLeftTopToBottom()) { rowOffset = -cellWidth; } else { rowOffset = -headerSize(); if (q->effectiveLayoutDirection() == Qt::RightToLeft) colOffset = header->item->width()-cellWidth; } if (visibleItems.count()) { qreal startPos = originPosition(); if (visibleIndex == 0) { header->setPosition(colOffset, startPos + rowOffset); } else { qreal tempPos = isRightToLeftTopToBottom() ? -position()-size() : position(); qreal headerPos = isRightToLeftTopToBottom() ? header->rowPos() + cellWidth - headerSize() : header->rowPos(); if (tempPos <= startPos || headerPos > startPos + rowOffset) header->setPosition(colOffset, startPos + rowOffset); } } else { header->setPosition(colOffset, 0); } } } void QDeclarativeGridViewPrivate::fixupPosition() { moveReason = Other; if (flow == QDeclarativeGridView::LeftToRight) fixupY(); else fixupX(); } void QDeclarativeGridViewPrivate::fixup(AxisData &data, qreal minExtent, qreal maxExtent) { if ((flow == QDeclarativeGridView::TopToBottom && &data == &vData) || (flow == QDeclarativeGridView::LeftToRight && &data == &hData)) return; fixupMode = moveReason == Mouse ? fixupMode : Immediate; qreal highlightStart; qreal highlightEnd; qreal viewPos; if (isRightToLeftTopToBottom()) { // Handle Right-To-Left exceptions viewPos = -position()-size(); highlightStart = highlightRangeStartValid ? size()-highlightRangeEnd : highlightRangeStart; highlightEnd = highlightRangeEndValid ? size()-highlightRangeStart : highlightRangeEnd; } else { viewPos = position(); highlightStart = highlightRangeStart; highlightEnd = highlightRangeEnd; } bool strictHighlightRange = haveHighlightRange && highlightRange == QDeclarativeGridView::StrictlyEnforceRange; if (snapMode != QDeclarativeGridView::NoSnap) { qreal tempPosition = isRightToLeftTopToBottom() ? -position()-size() : position(); if (snapMode == QDeclarativeGridView::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; } FxGridItem *topItem = snapItemAt(tempPosition+highlightStart); if (!topItem && strictHighlightRange && currentItem) { // StrictlyEnforceRange always keeps an item in range updateHighlight(); topItem = currentItem; } FxGridItem *bottomItem = snapItemAt(tempPosition+highlightEnd); 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)) { if (topItem->index == 0 && header && tempPosition+highlightStart < header->rowPos()+headerSize()/2 && !strictHighlightRange) { pos = isRightToLeftTopToBottom() ? - header->rowPos() + highlightStart - size() : header->rowPos() - highlightStart; } else { if (isRightToLeftTopToBottom()) pos = qMax(qMin(-topItem->rowPos() + highlightStart - size(), -maxExtent), -minExtent); else pos = qMax(qMin(topItem->rowPos() - highlightStart, -maxExtent), -minExtent); } } else if (bottomItem && isInBounds) { if (isRightToLeftTopToBottom()) pos = qMax(qMin(-bottomItem->rowPos() + highlightEnd - size(), -maxExtent), -minExtent); else pos = qMax(qMin(bottomItem->rowPos() - highlightEnd, -maxExtent), -minExtent); } else { QDeclarativeFlickablePrivate::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 == QDeclarativeGridView::StrictlyEnforceRange) { if (currentItem) { updateHighlight(); qreal pos = currentItem->rowPos(); if (viewPos < pos + rowSize() - highlightEnd) viewPos = pos + rowSize() - highlightEnd; if (viewPos > pos - highlightStart) viewPos = pos - highlightStart; 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 { QDeclarativeFlickablePrivate::fixup(data, minExtent, maxExtent); } data.inOvershoot = false; fixupMode = Normal; } void QDeclarativeGridViewPrivate::flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize, QDeclarativeTimeLineCallback::Callback fixupCallback, qreal velocity) { Q_Q(QDeclarativeGridView); data.fixingUp = false; moveReason = Mouse; if ((!haveHighlightRange || highlightRange != QDeclarativeGridView::StrictlyEnforceRange) && snapMode == QDeclarativeGridView::NoSnap) { QDeclarativeFlickablePrivate::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 == QDeclarativeGridView::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 == QDeclarativeGridView::NoSnap && highlightRange != QDeclarativeGridView::StrictlyEnforceRange) data.flickTarget = minExtent; } else { if (data.move.value() > maxExtent) { if (snapMode == QDeclarativeGridView::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 == QDeclarativeGridView::NoSnap && highlightRange != QDeclarativeGridView::StrictlyEnforceRange) data.flickTarget = maxExtent; } bool overShoot = boundsBehavior == QDeclarativeFlickable::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 = qreal(0.0); if ((maxDistance > qreal(0.0) && v2 / (2.0f * maxDistance) < accel) || snapMode == QDeclarativeGridView::SnapOneRow) { // + rowSize()/4 to encourage moving at least one item in the flick direction qreal dist = v2 / (accel * qreal(2.0)) + rowSize()/4; dist = qMin(dist, maxDistance); if (v > 0) dist = -dist; if (snapMode != QDeclarativeGridView::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); } } //---------------------------------------------------------------------------- /*! \qmltype GridView \instantiates QDeclarativeGridView \since 4.7 \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} */ QDeclarativeGridView::QDeclarativeGridView(QDeclarativeItem *parent) : QDeclarativeFlickable(*(new QDeclarativeGridViewPrivate), parent) { Q_D(QDeclarativeGridView); d->init(); } QDeclarativeGridView::~QDeclarativeGridView() { Q_D(QDeclarativeGridView); d->clear(); if (d->ownModel) delete d->model; delete d->header; delete d->footer; } /*! \qmlattachedproperty bool 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 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 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 GridView::onAdd() This attached handler is called immediately after an item is added to the view. */ /*! \qmlattachedsignal GridView::onRemove() This attached handler is called immediately before an item is removed from the view. */ /*! \qmlproperty model 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} */ QVariant QDeclarativeGridView::model() const { Q_D(const QDeclarativeGridView); return d->modelVariant; } // For internal use int QDeclarativeGridView::modelCount() const { Q_D(const QDeclarativeGridView); return d->model->count(); } void QDeclarativeGridView::setModel(const QVariant &model) { Q_D(QDeclarativeGridView); if (d->modelVariant == model) return; if (d->model) { disconnect(d->model, SIGNAL(itemsInserted(int,int)), this, SLOT(itemsInserted(int,int))); disconnect(d->model, SIGNAL(itemsRemoved(int,int)), this, SLOT(itemsRemoved(int,int))); disconnect(d->model, SIGNAL(itemsMoved(int,int,int)), this, SLOT(itemsMoved(int,int,int))); disconnect(d->model, SIGNAL(modelReset()), this, SLOT(modelReset())); disconnect(d->model, SIGNAL(createdItem(int,QDeclarativeItem*)), this, SLOT(createdItem(int,QDeclarativeItem*))); disconnect(d->model, SIGNAL(destroyingItem(QDeclarativeItem*)), this, SLOT(destroyingItem(QDeclarativeItem*))); } d->clear(); d->modelVariant = model; QObject *object = qvariant_cast(model); QDeclarativeVisualModel *vim = 0; if (object && (vim = qobject_cast(object))) { if (d->ownModel) { delete d->model; d->ownModel = false; } d->model = vim; } else { if (!d->ownModel) { d->model = new QDeclarativeVisualDataModel(qmlContext(this), this); d->ownModel = true; } if (QDeclarativeVisualDataModel *dataModel = qobject_cast(d->model)) dataModel->setModel(model); } if (d->model) { d->bufferMode = QDeclarativeGridViewPrivate::BufferBefore | QDeclarativeGridViewPrivate::BufferAfter; if (isComponentComplete()) { refill(); if ((d->currentIndex >= d->model->count() || d->currentIndex < 0) && !d->currentIndexCleared) { setCurrentIndex(0); } else { d->moveReason = QDeclarativeGridViewPrivate::SetIndex; d->updateCurrent(d->currentIndex); if (d->highlight && d->currentItem) { if (d->autoHighlight) d->highlight->setPosition(d->currentItem->colPos(), d->currentItem->rowPos()); d->updateTrackedItem(); } d->moveReason = QDeclarativeGridViewPrivate::Other; } } connect(d->model, SIGNAL(itemsInserted(int,int)), this, SLOT(itemsInserted(int,int))); connect(d->model, SIGNAL(itemsRemoved(int,int)), this, SLOT(itemsRemoved(int,int))); connect(d->model, SIGNAL(itemsMoved(int,int,int)), this, SLOT(itemsMoved(int,int,int))); connect(d->model, SIGNAL(modelReset()), this, SLOT(modelReset())); connect(d->model, SIGNAL(createdItem(int,QDeclarativeItem*)), this, SLOT(createdItem(int,QDeclarativeItem*))); connect(d->model, SIGNAL(destroyingItem(QDeclarativeItem*)), this, SLOT(destroyingItem(QDeclarativeItem*))); emit countChanged(); } emit modelChanged(); } /*! \qmlproperty Component 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. */ QDeclarativeComponent *QDeclarativeGridView::delegate() const { Q_D(const QDeclarativeGridView); if (d->model) { if (QDeclarativeVisualDataModel *dataModel = qobject_cast(d->model)) return dataModel->delegate(); } return 0; } void QDeclarativeGridView::setDelegate(QDeclarativeComponent *delegate) { Q_D(QDeclarativeGridView); if (delegate == this->delegate()) return; if (!d->ownModel) { d->model = new QDeclarativeVisualDataModel(qmlContext(this)); d->ownModel = true; } if (QDeclarativeVisualDataModel *dataModel = qobject_cast(d->model)) { int oldCount = dataModel->count(); dataModel->setDelegate(delegate); if (isComponentComplete()) { for (int i = 0; i < d->visibleItems.count(); ++i) d->releaseItem(d->visibleItems.at(i)); d->visibleItems.clear(); d->releaseItem(d->currentItem); d->currentItem = 0; refill(); d->moveReason = QDeclarativeGridViewPrivate::SetIndex; d->updateCurrent(d->currentIndex); if (d->highlight && d->currentItem) { if (d->autoHighlight) d->highlight->setPosition(d->currentItem->colPos(), d->currentItem->rowPos()); d->updateTrackedItem(); } d->moveReason = QDeclarativeGridViewPrivate::Other; } if (oldCount != dataModel->count()) emit countChanged(); emit delegateChanged(); } } /*! \qmlproperty int GridView::currentIndex \qmlproperty Item 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. */ int QDeclarativeGridView::currentIndex() const { Q_D(const QDeclarativeGridView); return d->currentIndex; } void QDeclarativeGridView::setCurrentIndex(int index) { Q_D(QDeclarativeGridView); if (d->requestedIndex >= 0) // currently creating item return; d->currentIndexCleared = (index == -1); if (index == d->currentIndex) return; if (isComponentComplete() && d->isValid()) { if (d->layoutScheduled) d->layout(); d->moveReason = QDeclarativeGridViewPrivate::SetIndex; d->updateCurrent(index); } else { d->currentIndex = index; emit currentIndexChanged(); } } QDeclarativeItem *QDeclarativeGridView::currentItem() { Q_D(QDeclarativeGridView); if (!d->currentItem) return 0; return d->currentItem->item; } /*! \qmlproperty Item 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 */ QDeclarativeItem *QDeclarativeGridView::highlightItem() { Q_D(QDeclarativeGridView); if (!d->highlight) return 0; return d->highlight->item; } /*! \qmlproperty int GridView::count This property holds the number of items in the view. */ int QDeclarativeGridView::count() const { Q_D(const QDeclarativeGridView); if (d->model) return d->model->count(); return 0; } /*! \qmlproperty Component 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 */ QDeclarativeComponent *QDeclarativeGridView::highlight() const { Q_D(const QDeclarativeGridView); return d->highlightComponent; } void QDeclarativeGridView::setHighlight(QDeclarativeComponent *highlight) { Q_D(QDeclarativeGridView); if (highlight != d->highlightComponent) { d->highlightComponent = highlight; d->updateCurrent(d->currentIndex); emit highlightChanged(); } } /*! \qmlproperty bool 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 */ bool QDeclarativeGridView::highlightFollowsCurrentItem() const { Q_D(const QDeclarativeGridView); return d->autoHighlight; } void QDeclarativeGridView::setHighlightFollowsCurrentItem(bool autoHighlight) { Q_D(QDeclarativeGridView); if (d->autoHighlight != autoHighlight) { d->autoHighlight = autoHighlight; if (autoHighlight) { d->updateHighlight(); } else if (d->highlightXAnimator) { d->highlightXAnimator->stop(); d->highlightYAnimator->stop(); } } } /*! \qmlproperty int 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 */ int QDeclarativeGridView::highlightMoveDuration() const { Q_D(const QDeclarativeGridView); return d->highlightMoveDuration; } void QDeclarativeGridView::setHighlightMoveDuration(int duration) { Q_D(QDeclarativeGridView); if (d->highlightMoveDuration != duration) { d->highlightMoveDuration = duration; if (d->highlightYAnimator) { d->highlightXAnimator->userDuration = d->highlightMoveDuration; d->highlightYAnimator->userDuration = d->highlightMoveDuration; } emit highlightMoveDurationChanged(); } } /*! \qmlproperty real GridView::preferredHighlightBegin \qmlproperty real GridView::preferredHighlightEnd \qmlproperty enumeration 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 \li 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. \li 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. \li GridView.NoHighlightRange - this is the default value. \endlist */ qreal QDeclarativeGridView::preferredHighlightBegin() const { Q_D(const QDeclarativeGridView); return d->highlightRangeStart; } void QDeclarativeGridView::setPreferredHighlightBegin(qreal start) { Q_D(QDeclarativeGridView); d->highlightRangeStartValid = true; if (d->highlightRangeStart == start) return; d->highlightRangeStart = start; d->haveHighlightRange = d->highlightRange != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd; emit preferredHighlightBeginChanged(); } void QDeclarativeGridView::resetPreferredHighlightBegin() { Q_D(QDeclarativeGridView); d->highlightRangeStartValid = false; if (d->highlightRangeStart == 0) return; d->highlightRangeStart = 0; emit preferredHighlightBeginChanged(); } qreal QDeclarativeGridView::preferredHighlightEnd() const { Q_D(const QDeclarativeGridView); return d->highlightRangeEnd; } void QDeclarativeGridView::setPreferredHighlightEnd(qreal end) { Q_D(QDeclarativeGridView); d->highlightRangeEndValid = true; if (d->highlightRangeEnd == end) return; d->highlightRangeEnd = end; d->haveHighlightRange = d->highlightRange != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd; emit preferredHighlightEndChanged(); } void QDeclarativeGridView::resetPreferredHighlightEnd() { Q_D(QDeclarativeGridView); d->highlightRangeEndValid = false; if (d->highlightRangeEnd == 0) return; d->highlightRangeEnd = 0; emit preferredHighlightEndChanged(); } QDeclarativeGridView::HighlightRangeMode QDeclarativeGridView::highlightRangeMode() const { Q_D(const QDeclarativeGridView); return d->highlightRange; } void QDeclarativeGridView::setHighlightRangeMode(HighlightRangeMode mode) { Q_D(QDeclarativeGridView); if (d->highlightRange == mode) return; d->highlightRange = mode; d->haveHighlightRange = d->highlightRange != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd; emit highlightRangeModeChanged(); } /*! \qmlproperty enumeration GridView::layoutDirection This property holds the layout direction of the grid. Possible values: \list \li Qt.LeftToRight (default) - Items will be laid out starting in the top, left corner. The flow is dependent on the \l GridView::flow property. \li Qt.RightToLeft - Items will be laid out starting in the top, right corner. The flow is dependent on the \l GridView::flow property. \endlist When using the attached property \l {LayoutMirroring::enabled} for locale layouts, the layout direction of the grid view will be mirrored. However, the actual property \c layoutDirection will remain unchanged. You can use the property \l {LayoutMirroring::enabled} to determine whether the direction has been mirrored. \sa {LayoutMirroring}{LayoutMirroring} */ Qt::LayoutDirection QDeclarativeGridView::layoutDirection() const { Q_D(const QDeclarativeGridView); return d->layoutDirection; } void QDeclarativeGridView::setLayoutDirection(Qt::LayoutDirection layoutDirection) { Q_D(QDeclarativeGridView); if (d->layoutDirection != layoutDirection) { d->layoutDirection = layoutDirection; d->regenerate(); emit layoutDirectionChanged(); } } Qt::LayoutDirection QDeclarativeGridView::effectiveLayoutDirection() const { Q_D(const QDeclarativeGridView); if (d->effectiveLayoutMirror) return d->layoutDirection == Qt::RightToLeft ? Qt::LeftToRight : Qt::RightToLeft; else return d->layoutDirection; } /*! \qmlproperty enumeration GridView::flow This property holds the flow of the grid. Possible values: \list \li GridView.LeftToRight (default) - Items are laid out from left to right, and the view scrolls vertically \li GridView.TopToBottom - Items are laid out from top to bottom, and the view scrolls horizontally \endlist */ QDeclarativeGridView::Flow QDeclarativeGridView::flow() const { Q_D(const QDeclarativeGridView); return d->flow; } void QDeclarativeGridView::setFlow(Flow flow) { Q_D(QDeclarativeGridView); if (d->flow != flow) { d->flow = flow; if (d->flow == LeftToRight) { setContentWidth(-1); setFlickableDirection(QDeclarativeFlickable::VerticalFlick); } else { setContentHeight(-1); setFlickableDirection(QDeclarativeFlickable::HorizontalFlick); } setContentX(0); setContentY(0); d->regenerate(); emit flowChanged(); } } /*! \qmlproperty bool 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. */ bool QDeclarativeGridView::isWrapEnabled() const { Q_D(const QDeclarativeGridView); return d->wrap; } void QDeclarativeGridView::setWrapEnabled(bool wrap) { Q_D(QDeclarativeGridView); if (d->wrap == wrap) return; d->wrap = wrap; emit keyNavigationWrapsChanged(); } /*! \qmlproperty int GridView::cacheBuffer This property determines whether delegates are retained outside the visible area of the view. If non-zero the view will 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 and \c cacheBuffer is set to 40, then up to 2 delegates above and 2 delegates below the visible area may be retained. 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. */ int QDeclarativeGridView::cacheBuffer() const { Q_D(const QDeclarativeGridView); return d->buffer; } void QDeclarativeGridView::setCacheBuffer(int buffer) { Q_D(QDeclarativeGridView); if (d->buffer != buffer) { d->buffer = buffer; if (isComponentComplete()) refill(); emit cacheBufferChanged(); } } /*! \qmlproperty int GridView::cellWidth \qmlproperty int GridView::cellHeight These properties holds the width and height of each cell in the grid. The default cell size is 100x100. */ int QDeclarativeGridView::cellWidth() const { Q_D(const QDeclarativeGridView); return d->cellWidth; } void QDeclarativeGridView::setCellWidth(int cellWidth) { Q_D(QDeclarativeGridView); if (cellWidth != d->cellWidth && cellWidth > 0) { d->cellWidth = qMax(1, cellWidth); d->updateGrid(); emit cellWidthChanged(); d->layout(); } } int QDeclarativeGridView::cellHeight() const { Q_D(const QDeclarativeGridView); return d->cellHeight; } void QDeclarativeGridView::setCellHeight(int cellHeight) { Q_D(QDeclarativeGridView); if (cellHeight != d->cellHeight && cellHeight > 0) { d->cellHeight = qMax(1, cellHeight); d->updateGrid(); emit cellHeightChanged(); d->layout(); } } /*! \qmlproperty enumeration GridView::snapMode This property determines how the view scrolling will settle following a drag or flick. The possible values are: \list \li GridView.NoSnap (default) - the view stops anywhere within the visible area. \li GridView.SnapToRow - the view settles with a row (or column for \c GridView.TopToBottom flow) aligned with the start of the view. \li 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 */ QDeclarativeGridView::SnapMode QDeclarativeGridView::snapMode() const { Q_D(const QDeclarativeGridView); return d->snapMode; } void QDeclarativeGridView::setSnapMode(SnapMode mode) { Q_D(QDeclarativeGridView); if (d->snapMode != mode) { d->snapMode = mode; emit snapModeChanged(); } } /*! \qmlproperty Component 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 */ QDeclarativeComponent *QDeclarativeGridView::footer() const { Q_D(const QDeclarativeGridView); return d->footerComponent; } void QDeclarativeGridView::setFooter(QDeclarativeComponent *footer) { Q_D(QDeclarativeGridView); if (d->footerComponent != footer) { if (d->footer) { if (scene()) scene()->removeItem(d->footer->item); d->footer->item->deleteLater(); delete d->footer; d->footer = 0; } d->footerComponent = footer; if (isComponentComplete()) { d->updateFooter(); d->updateGrid(); d->fixupPosition(); } emit footerChanged(); } } /*! \qmlproperty Component 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 */ QDeclarativeComponent *QDeclarativeGridView::header() const { Q_D(const QDeclarativeGridView); return d->headerComponent; } void QDeclarativeGridView::setHeader(QDeclarativeComponent *header) { Q_D(QDeclarativeGridView); if (d->headerComponent != header) { if (d->header) { if (scene()) scene()->removeItem(d->header->item); d->header->item->deleteLater(); delete d->header; d->header = 0; } d->headerComponent = header; if (isComponentComplete()) { d->updateHeader(); d->updateFooter(); d->updateGrid(); d->fixupPosition(); } emit headerChanged(); } } void QDeclarativeGridView::setContentX(qreal pos) { Q_D(QDeclarativeGridView); // Positioning the view manually should override any current movement state d->moveReason = QDeclarativeGridViewPrivate::Other; QDeclarativeFlickable::setContentX(pos); } void QDeclarativeGridView::setContentY(qreal pos) { Q_D(QDeclarativeGridView); // Positioning the view manually should override any current movement state d->moveReason = QDeclarativeGridViewPrivate::Other; QDeclarativeFlickable::setContentY(pos); } bool QDeclarativeGridView::event(QEvent *event) { Q_D(QDeclarativeGridView); if (event->type() == QEvent::User) { if (d->layoutScheduled) d->layout(); return true; } return QDeclarativeFlickable::event(event); } void QDeclarativeGridView::viewportMoved() { Q_D(QDeclarativeGridView); QDeclarativeFlickable::viewportMoved(); if (!d->itemCount) return; d->lazyRelease = true; if (d->hData.flicking || d->vData.flicking) { if (yflick()) { if (d->vData.velocity > 0) d->bufferMode = QDeclarativeGridViewPrivate::BufferBefore; else if (d->vData.velocity < 0) d->bufferMode = QDeclarativeGridViewPrivate::BufferAfter; } if (xflick()) { if (d->hData.velocity > 0) d->bufferMode = QDeclarativeGridViewPrivate::BufferBefore; else if (d->hData.velocity < 0) d->bufferMode = QDeclarativeGridViewPrivate::BufferAfter; } } refill(); if (d->hData.flicking || d->vData.flicking || d->hData.moving || d->vData.moving) d->moveReason = QDeclarativeGridViewPrivate::Mouse; if (d->moveReason != QDeclarativeGridViewPrivate::SetIndex) { if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange && d->highlight) { // reposition highlight qreal pos = d->highlight->rowPos(); qreal viewPos; qreal highlightStart; qreal highlightEnd; if (d->isRightToLeftTopToBottom()) { highlightStart = d->highlightRangeStartValid ? d->size()-d->highlightRangeEnd : d->highlightRangeStart; highlightEnd = d->highlightRangeEndValid ? d->size()-d->highlightRangeStart : d->highlightRangeEnd; viewPos = -d->position()-d->size(); } else { highlightStart = d->highlightRangeStart; highlightEnd = d->highlightRangeEnd; viewPos = d->position(); } if (pos > viewPos + highlightEnd - d->rowSize()) pos = viewPos + highlightEnd - d->rowSize(); if (pos < viewPos + highlightStart) pos = viewPos + highlightStart; d->highlight->setPosition(d->highlight->colPos(), qRound(pos)); // update current index int idx = d->snapIndex(); if (idx >= 0 && idx != d->currentIndex) { d->updateCurrent(idx); if (d->currentItem && d->currentItem->colPos() != d->highlight->colPos() && d->autoHighlight) { if (d->flow == LeftToRight) d->highlightXAnimator->to = d->currentItem->item->x(); else d->highlightYAnimator->to = d->currentItem->item->y(); } } } } } qreal QDeclarativeGridView::minYExtent() const { Q_D(const QDeclarativeGridView); if (d->flow == QDeclarativeGridView::TopToBottom) return QDeclarativeFlickable::minYExtent(); qreal extent = -d->startPosition(); if (d->header && d->visibleItems.count()) extent += d->header->item->height(); if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange) { extent += d->highlightRangeStart; extent = qMax(extent, -(d->rowPosAt(0) + d->rowSize() - d->highlightRangeEnd)); } return extent; } qreal QDeclarativeGridView::maxYExtent() const { Q_D(const QDeclarativeGridView); if (d->flow == QDeclarativeGridView::TopToBottom) return QDeclarativeFlickable::maxYExtent(); qreal extent; if (!d->model || !d->model->count()) { extent = 0; } else if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange) { extent = -(d->rowPosAt(d->model->count()-1) - d->highlightRangeStart); if (d->highlightRangeEnd != d->highlightRangeStart) extent = qMin(extent, -(d->endPosition() - d->highlightRangeEnd + 1)); } else { extent = -(d->endPosition() - height()); } if (d->footer) extent -= d->footer->item->height(); const qreal minY = minYExtent(); if (extent > minY) extent = minY; return extent; } qreal QDeclarativeGridView::minXExtent() const { Q_D(const QDeclarativeGridView); if (d->flow == QDeclarativeGridView::LeftToRight) return QDeclarativeFlickable::minXExtent(); qreal extent = -d->startPosition(); qreal highlightStart; qreal highlightEnd; qreal endPositionFirstItem = 0; if (d->isRightToLeftTopToBottom()) { if (d->model && d->model->count()) endPositionFirstItem = d->rowPosAt(d->model->count()-1); highlightStart = d->highlightRangeStartValid ? d->highlightRangeStart - (d->lastPosition()-endPositionFirstItem) : d->size() - (d->lastPosition()-endPositionFirstItem); highlightEnd = d->highlightRangeEndValid ? d->highlightRangeEnd : d->size(); if (d->footer && d->visibleItems.count()) extent += d->footer->item->width(); } else { endPositionFirstItem = d->rowPosAt(0)+d->rowSize(); highlightStart = d->highlightRangeStart; highlightEnd = d->highlightRangeEnd; if (d->header && d->visibleItems.count()) extent += d->header->item->width(); } if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange) { extent += d->isRightToLeftTopToBottom() ? -highlightStart : highlightStart; extent = qMax(extent, -(endPositionFirstItem - highlightEnd)); } return extent; } qreal QDeclarativeGridView::maxXExtent() const { Q_D(const QDeclarativeGridView); if (d->flow == QDeclarativeGridView::LeftToRight) return QDeclarativeFlickable::maxXExtent(); qreal extent; qreal highlightStart; qreal highlightEnd; qreal lastItemPosition = 0; if (d->isRightToLeftTopToBottom()){ highlightStart = d->highlightRangeStartValid ? d->highlightRangeEnd : d->size(); highlightEnd = d->highlightRangeEndValid ? d->highlightRangeStart : d->size(); lastItemPosition = d->endPosition(); } else { highlightStart = d->highlightRangeStart; highlightEnd = d->highlightRangeEnd; lastItemPosition = 0; if (d->model && d->model->count()) lastItemPosition = d->rowPosAt(d->model->count()-1); } if (!d->model || !d->model->count()) { extent = 0; } else if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange) { extent = -(lastItemPosition - highlightStart); if (highlightEnd != highlightStart) extent = d->isRightToLeftTopToBottom() ? qMax(extent, -(d->endPosition() - highlightEnd + 1)) : qMin(extent, -(d->endPosition() - highlightEnd + 1)); } else { extent = -(d->endPosition() - width()); } if (d->isRightToLeftTopToBottom()) { if (d->header) extent -= d->header->item->width(); } else { if (d->footer) extent -= d->footer->item->width(); } const qreal minX = minXExtent(); if (extent > minX) extent = minX; return extent; } void QDeclarativeGridView::keyPressEvent(QKeyEvent *event) { Q_D(QDeclarativeGridView); keyPressPreHandler(event); if (event->isAccepted()) return; if (d->model && d->model->count() && d->interactive) { d->moveReason = QDeclarativeGridViewPrivate::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; } } d->moveReason = QDeclarativeGridViewPrivate::Other; event->ignore(); QDeclarativeFlickable::keyPressEvent(event); } /*! \qmlmethod 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. \b Note: methods should only be called after the Component has completed. */ void QDeclarativeGridView::moveCurrentIndexUp() { Q_D(QDeclarativeGridView); const int count = d->model ? d->model->count() : 0; if (!count) return; if (d->flow == QDeclarativeGridView::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 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. \b Note: methods should only be called after the Component has completed. */ void QDeclarativeGridView::moveCurrentIndexDown() { Q_D(QDeclarativeGridView); const int count = d->model ? d->model->count() : 0; if (!count) return; if (d->flow == QDeclarativeGridView::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 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. \b Note: methods should only be called after the Component has completed. */ void QDeclarativeGridView::moveCurrentIndexLeft() { Q_D(QDeclarativeGridView); const int count = d->model ? d->model->count() : 0; if (!count) return; if (effectiveLayoutDirection() == Qt::LeftToRight) { if (d->flow == QDeclarativeGridView::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 == QDeclarativeGridView::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 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. \b Note: methods should only be called after the Component has completed. */ void QDeclarativeGridView::moveCurrentIndexRight() { Q_D(QDeclarativeGridView); const int count = d->model ? d->model->count() : 0; if (!count) return; if (effectiveLayoutDirection() == Qt::LeftToRight) { if (d->flow == QDeclarativeGridView::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 == QDeclarativeGridView::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); } } } } void QDeclarativeGridViewPrivate::positionViewAtIndex(int index, int mode) { Q_Q(QDeclarativeGridView); if (!isValid()) return; if (mode < QDeclarativeGridView::Beginning || mode > QDeclarativeGridView::Contain) return; int idx = qMax(qMin(index, model->count()-1), 0); if (layoutScheduled) layout(); qreal pos = isRightToLeftTopToBottom() ? -position() - size() : position(); FxGridItem *item = visibleItem(idx); qreal maxExtent; if (flow == QDeclarativeGridView::LeftToRight) maxExtent = -q->maxYExtent(); else maxExtent = isRightToLeftTopToBottom() ? q->minXExtent()-size() : -q->maxXExtent(); if (!item) { int itemPos = rowPosAt(idx); // save the currently visible items in case any of them end up visible again QList oldVisible = visibleItems; visibleItems.clear(); visibleIndex = idx - idx % columns; if (flow == QDeclarativeGridView::LeftToRight) maxExtent = -q->maxYExtent(); else maxExtent = isRightToLeftTopToBottom() ? q->minXExtent()-size() : -q->maxXExtent(); setPosition(qMin(qreal(itemPos), maxExtent)); // now release the reference to all the old visible items. for (int i = 0; i < oldVisible.count(); ++i) releaseItem(oldVisible.at(i)); item = visibleItem(idx); } if (item) { qreal itemPos = item->rowPos(); switch (mode) { case QDeclarativeGridView::Beginning: pos = itemPos; if (index < 0 && header) { pos -= flow == QDeclarativeGridView::LeftToRight ? header->item->height() : header->item->width(); } break; case QDeclarativeGridView::Center: pos = itemPos - (size() - rowSize())/2; break; case QDeclarativeGridView::End: pos = itemPos - size() + rowSize(); if (index >= model->count() && footer) { pos += flow == QDeclarativeGridView::LeftToRight ? footer->item->height() : footer->item->width(); } break; case QDeclarativeGridView::Visible: if (itemPos > pos + size()) pos = itemPos - size() + rowSize(); else if (item->endRowPos() < pos) pos = itemPos; break; case QDeclarativeGridView::Contain: if (item->endRowPos() > pos + size()) pos = itemPos - size() + rowSize(); if (itemPos < pos) pos = itemPos; } pos = qMin(pos, maxExtent); qreal minExtent; if (flow == QDeclarativeGridView::LeftToRight) minExtent = -q->minYExtent(); else minExtent = isRightToLeftTopToBottom() ? q->maxXExtent()-size() : -q->minXExtent(); pos = qMax(pos, minExtent); moveReason = QDeclarativeGridViewPrivate::Other; q->cancelFlick(); setPosition(pos); } fixupPosition(); } /*! \qmlmethod GridView::positionViewAtIndex(int index, PositionMode mode) Positions the view such that the \a index is at the position specified by \a mode: \list \li GridView.Beginning - position item at the top (or left for \c GridView.TopToBottom flow) of the view. \li GridView.Center - position item in the center of the view. \li GridView.End - position item at bottom (or right for horizontal orientation) of the view. \li GridView.Visible - if any part of the item is visible then take no action, otherwise bring the item into view. \li 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. \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, GridView.Beginning) \endcode */ void QDeclarativeGridView::positionViewAtIndex(int index, int mode) { Q_D(QDeclarativeGridView); if (!d->isValid() || index < 0 || index >= d->model->count()) return; d->positionViewAtIndex(index, mode); } /*! \qmlmethod GridView::positionViewAtBeginning() \qmlmethod GridView::positionViewAtEnd() \since QtQuick 1.1 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 */ void QDeclarativeGridView::positionViewAtBeginning() { Q_D(QDeclarativeGridView); if (!d->isValid()) return; d->positionViewAtIndex(-1, Beginning); } void QDeclarativeGridView::positionViewAtEnd() { Q_D(QDeclarativeGridView); if (!d->isValid()) return; d->positionViewAtIndex(d->model->count(), End); } /*! \qmlmethod int 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. \b Note: methods should only be called after the Component has completed. */ int QDeclarativeGridView::indexAt(qreal x, qreal y) const { Q_D(const QDeclarativeGridView); for (int i = 0; i < d->visibleItems.count(); ++i) { const FxGridItem *listItem = d->visibleItems.at(i); if(listItem->contains(x, y)) return listItem->index; } return -1; } void QDeclarativeGridView::componentComplete() { Q_D(QDeclarativeGridView); QDeclarativeFlickable::componentComplete(); d->updateHeader(); d->updateFooter(); d->updateGrid(); if (d->isValid()) { refill(); d->moveReason = QDeclarativeGridViewPrivate::SetIndex; if (d->currentIndex < 0 && !d->currentIndexCleared) d->updateCurrent(0); else d->updateCurrent(d->currentIndex); if (d->highlight && d->currentItem) { if (d->autoHighlight) d->highlight->setPosition(d->currentItem->colPos(), d->currentItem->rowPos()); d->updateTrackedItem(); } d->moveReason = QDeclarativeGridViewPrivate::Other; d->fixupPosition(); } } void QDeclarativeGridView::trackedPositionChanged() { Q_D(QDeclarativeGridView); if (!d->trackedItem || !d->currentItem) return; if (d->moveReason == QDeclarativeGridViewPrivate::SetIndex) { const qreal trackedPos = d->trackedItem->rowPos(); qreal viewPos; qreal highlightStart; qreal highlightEnd; if (d->isRightToLeftTopToBottom()) { viewPos = -d->position()-d->size(); highlightStart = d->highlightRangeStartValid ? d->size()-d->highlightRangeEnd : d->highlightRangeStart; highlightEnd = d->highlightRangeEndValid ? d->size()-d->highlightRangeStart : d->highlightRangeEnd; } else { viewPos = d->position(); highlightStart = d->highlightRangeStart; highlightEnd = d->highlightRangeEnd; } qreal pos = viewPos; if (d->haveHighlightRange) { if (d->highlightRange == StrictlyEnforceRange) { if (trackedPos > pos + highlightEnd - d->rowSize()) pos = trackedPos - highlightEnd + d->rowSize(); if (trackedPos < pos + highlightStart) pos = trackedPos - highlightStart; } else { if (trackedPos < d->startPosition() + highlightStart) { pos = d->startPosition(); } else if (d->trackedItem->endRowPos() > d->endPosition() - d->size() + highlightEnd) { pos = d->endPosition() - d->size() + 1; if (pos < d->startPosition()) pos = d->startPosition(); } else { if (trackedPos < viewPos + highlightStart) { pos = trackedPos - highlightStart; } else if (trackedPos > viewPos + highlightEnd - d->rowSize()) { pos = trackedPos - highlightEnd + d->rowSize(); } } } } else { if (trackedPos < viewPos && d->currentItem->rowPos() < viewPos) { pos = qMax(trackedPos, d->currentItem->rowPos()); } else if (d->trackedItem->endRowPos() >= viewPos + d->size() && d->currentItem->endRowPos() >= viewPos + d->size()) { if (d->trackedItem->endRowPos() <= d->currentItem->endRowPos()) { pos = d->trackedItem->endRowPos() - d->size() + 1; if (d->rowSize() > d->size()) pos = trackedPos; } else { pos = d->currentItem->endRowPos() - d->size() + 1; if (d->rowSize() > d->size()) pos = d->currentItem->rowPos(); } } } if (viewPos != pos) { cancelFlick(); d->calcVelocity = true; d->setPosition(pos); d->calcVelocity = false; } } } void QDeclarativeGridView::itemsInserted(int modelIndex, int count) { Q_D(QDeclarativeGridView); if (!isComponentComplete()) return; int index = d->visibleItems.count() ? d->mapFromModel(modelIndex) : 0; if (index < 0) { int i = d->visibleItems.count() - 1; while (i > 0 && d->visibleItems.at(i)->index == -1) --i; if (d->visibleItems.at(i)->index + 1 == modelIndex) { // Special case of appending an item to the model. index = d->visibleIndex + d->visibleItems.count(); } else { if (modelIndex <= d->visibleIndex) { // Insert before visible items d->visibleIndex += count; for (int i = 0; i < d->visibleItems.count(); ++i) { FxGridItem *listItem = d->visibleItems.at(i); if (listItem->index != -1 && listItem->index >= modelIndex) listItem->index += count; } } if (d->currentIndex >= modelIndex) { // adjust current item index d->currentIndex += count; if (d->currentItem) d->currentItem->index = d->currentIndex; emit currentIndexChanged(); } d->scheduleLayout(); d->itemCount += count; emit countChanged(); return; } } int insertCount = count; if (index < d->visibleIndex && d->visibleItems.count()) { insertCount -= d->visibleIndex - index; index = d->visibleIndex; modelIndex = d->visibleIndex; } qreal tempPos = d->isRightToLeftTopToBottom() ? -d->position()-d->size()+d->width()+1 : d->position(); int to = d->buffer+tempPos+d->size()-1; int colPos = 0; int rowPos = 0; if (d->visibleItems.count()) { index -= d->visibleIndex; if (index < d->visibleItems.count()) { colPos = d->visibleItems.at(index)->colPos(); rowPos = d->visibleItems.at(index)->rowPos(); } else { // appending items to visible list colPos = d->visibleItems.at(index-1)->colPos() + d->colSize(); rowPos = d->visibleItems.at(index-1)->rowPos(); if (colPos > d->colSize() * (d->columns-1)) { colPos = 0; rowPos += d->rowSize(); } } } else if (d->itemCount == 0 && d->header) { rowPos = d->headerSize(); } // Update the indexes of the following visible items. for (int i = 0; i < d->visibleItems.count(); ++i) { FxGridItem *listItem = d->visibleItems.at(i); if (listItem->index != -1 && listItem->index >= modelIndex) listItem->index += count; } bool addedVisible = false; QList added; int i = 0; while (i < insertCount && rowPos <= to + d->rowSize()*(d->columns - (colPos/d->colSize()))/qreal(d->columns + 1)) { if (!addedVisible) { d->scheduleLayout(); addedVisible = true; } FxGridItem *item = d->createItem(modelIndex + i); if (!item) { // broken or no delegate d->clear(); return; } d->visibleItems.insert(index, item); item->setPosition(colPos, rowPos); added.append(item); colPos += d->colSize(); if (colPos > d->colSize() * (d->columns-1)) { colPos = 0; rowPos += d->rowSize(); } ++index; ++i; } if (i < insertCount) { // We didn't insert all our new items, which means anything // beyond the current index is not visible - remove it. while (d->visibleItems.count() > index) { d->releaseItem(d->visibleItems.takeLast()); } } // update visibleIndex d->visibleIndex = 0; for (QList::Iterator it = d->visibleItems.begin(); it != d->visibleItems.end(); ++it) { if ((*it)->index != -1) { d->visibleIndex = (*it)->index; break; } } if (d->itemCount && d->currentIndex >= modelIndex) { // adjust current item index d->currentIndex += count; if (d->currentItem) { d->currentItem->index = d->currentIndex; d->currentItem->setPosition(d->colPosAt(d->currentIndex), d->rowPosAt(d->currentIndex)); } emit currentIndexChanged(); } else if (d->itemCount == 0 && (!d->currentIndex || (d->currentIndex < 0 && !d->currentIndexCleared))) { setCurrentIndex(0); } // everything is in order now - emit add() signal for (int j = 0; j < added.count(); ++j) added.at(j)->attached->emitAdd(); d->itemCount += count; emit countChanged(); } void QDeclarativeGridView::itemsRemoved(int modelIndex, int count) { Q_D(QDeclarativeGridView); if (!isComponentComplete()) return; d->itemCount -= count; bool currentRemoved = d->currentIndex >= modelIndex && d->currentIndex < modelIndex + count; bool removedVisible = false; // Remove the items from the visible list, skipping anything already marked for removal QList::Iterator it = d->visibleItems.begin(); while (it != d->visibleItems.end()) { FxGridItem *item = *it; if (item->index == -1 || item->index < modelIndex) { // already removed, or before removed items if (item->index < modelIndex && !removedVisible) { d->scheduleLayout(); removedVisible = true; } ++it; } else if (item->index >= modelIndex + count) { // after removed items item->index -= count; ++it; } else { // removed item if (!removedVisible) { d->scheduleLayout(); removedVisible = true; } item->attached->emitRemove(); if (item->attached->delayRemove()) { item->index = -1; connect(item->attached, SIGNAL(delayRemoveChanged()), this, SLOT(destroyRemoved()), Qt::QueuedConnection); ++it; } else { it = d->visibleItems.erase(it); d->releaseItem(item); } } } // If we removed items before visible items a layout may be // required to ensure item 0 is in the first column. if (!removedVisible && modelIndex < d->visibleIndex) d->scheduleLayout(); // fix current if (d->currentIndex >= modelIndex + count) { d->currentIndex -= count; if (d->currentItem) d->currentItem->index -= count; emit currentIndexChanged(); } else if (currentRemoved) { // current item has been removed. d->releaseItem(d->currentItem); d->currentItem = 0; d->currentIndex = -1; if (d->itemCount) d->updateCurrent(qMin(modelIndex, d->itemCount-1)); else emit currentIndexChanged(); } // update visibleIndex d->visibleIndex = 0; for (it = d->visibleItems.begin(); it != d->visibleItems.end(); ++it) { if ((*it)->index != -1) { d->visibleIndex = (*it)->index; break; } } if (removedVisible && d->visibleItems.isEmpty()) { d->timeline.clear(); if (d->itemCount == 0) { d->setPosition(0); d->updateHeader(); d->updateFooter(); update(); } } emit countChanged(); } void QDeclarativeGridView::destroyRemoved() { Q_D(QDeclarativeGridView); for (QList::Iterator it = d->visibleItems.begin(); it != d->visibleItems.end();) { FxGridItem *listItem = *it; if (listItem->index == -1 && listItem->attached->delayRemove() == false) { d->releaseItem(listItem); it = d->visibleItems.erase(it); } else { ++it; } } // Correct the positioning of the items d->layout(); } void QDeclarativeGridView::itemsMoved(int from, int to, int count) { Q_D(QDeclarativeGridView); if (!isComponentComplete()) return; QHash moved; FxGridItem *firstItem = d->firstVisibleItem(); QList::Iterator it = d->visibleItems.begin(); while (it != d->visibleItems.end()) { FxGridItem *item = *it; if (item->index >= from && item->index < from + count) { // take the items that are moving item->index += (to-from); moved.insert(item->index, item); it = d->visibleItems.erase(it); } else { if (item->index > from && item->index != -1) { // move everything after the moved items. item->index -= count; if (item->index < d->visibleIndex) d->visibleIndex = item->index; } ++it; } } int remaining = count; int endIndex = d->visibleIndex; it = d->visibleItems.begin(); while (it != d->visibleItems.end()) { FxGridItem *item = *it; if (remaining && item->index >= to && item->index < to + count) { // place items in the target position, reusing any existing items FxGridItem *movedItem = moved.take(item->index); if (!movedItem) movedItem = d->createItem(item->index); if (!movedItem) { // broken or no delegate d->clear(); return; } it = d->visibleItems.insert(it, movedItem); if (it == d->visibleItems.begin() && firstItem) movedItem->setPosition(firstItem->colPos(), firstItem->rowPos()); ++it; --remaining; } else { if (item->index != -1) { if (item->index >= to) { // update everything after the moved items. item->index += count; } endIndex = item->index; } ++it; } } // If we have moved items to the end of the visible items // then add any existing moved items that we have while (FxGridItem *item = moved.take(endIndex+1)) { d->visibleItems.append(item); ++endIndex; } // update visibleIndex for (it = d->visibleItems.begin(); it != d->visibleItems.end(); ++it) { if ((*it)->index != -1) { d->visibleIndex = (*it)->index; break; } } // Fix current index if (d->currentIndex >= 0 && d->currentItem) { int oldCurrent = d->currentIndex; d->currentIndex = d->model->indexOf(d->currentItem->item, this); if (oldCurrent != d->currentIndex) { d->currentItem->index = d->currentIndex; emit currentIndexChanged(); } } // Whatever moved items remain are no longer visible items. while (moved.count()) { int idx = moved.begin().key(); FxGridItem *item = moved.take(idx); if (d->currentItem && item->item == d->currentItem->item) item->setPosition(d->colPosAt(idx), d->rowPosAt(idx)); d->releaseItem(item); } d->layout(); } void QDeclarativeGridView::modelReset() { Q_D(QDeclarativeGridView); d->clear(); refill(); d->moveReason = QDeclarativeGridViewPrivate::SetIndex; d->updateCurrent(d->currentIndex); if (d->highlight && d->currentItem) { if (d->autoHighlight) d->highlight->setPosition(d->currentItem->colPos(), d->currentItem->rowPos()); d->updateTrackedItem(); } d->moveReason = QDeclarativeGridViewPrivate::Other; emit countChanged(); } void QDeclarativeGridView::createdItem(int index, QDeclarativeItem *item) { Q_D(QDeclarativeGridView); if (d->requestedIndex != index) { item->setParentItem(this); d->unrequestedItems.insert(item, index); if (d->flow == QDeclarativeGridView::LeftToRight) { item->setPos(QPointF(d->colPosAt(index), d->rowPosAt(index))); } else { item->setPos(QPointF(d->rowPosAt(index), d->colPosAt(index))); } } } void QDeclarativeGridView::destroyingItem(QDeclarativeItem *item) { Q_D(QDeclarativeGridView); d->unrequestedItems.remove(item); } void QDeclarativeGridView::animStopped() { Q_D(QDeclarativeGridView); d->bufferMode = QDeclarativeGridViewPrivate::NoBuffer; if (d->haveHighlightRange && d->highlightRange == QDeclarativeGridView::StrictlyEnforceRange) d->updateHighlight(); } void QDeclarativeGridView::refill() { Q_D(QDeclarativeGridView); if (d->isRightToLeftTopToBottom()) d->refill(-d->position()-d->size()+1, -d->position()); else d->refill(d->position(), d->position()+d->size()-1); } QDeclarativeGridViewAttached *QDeclarativeGridView::qmlAttachedProperties(QObject *obj) { return new QDeclarativeGridViewAttached(obj); } QT_END_NAMESPACE