/**************************************************************************** | |
** | |
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). | |
** All rights reserved. | |
** Contact: Nokia Corporation (qt-info@nokia.com) | |
** | |
** This file is part of the QtDeclarative module of the Qt Toolkit. | |
** | |
** $QT_BEGIN_LICENSE:LGPL$ | |
** GNU Lesser General Public License Usage | |
** This file may be used under the terms of the GNU Lesser General Public | |
** License version 2.1 as published by the Free Software Foundation and | |
** appearing in the file LICENSE.LGPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU Lesser | |
** General Public License version 2.1 requirements will be met: | |
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | |
** | |
** In addition, as a special exception, Nokia gives you certain additional | |
** rights. These rights are described in the Nokia Qt LGPL Exception | |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. | |
** | |
** GNU General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU General | |
** Public License version 3.0 as published by the Free Software Foundation | |
** and appearing in the file LICENSE.GPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU General | |
** Public License version 3.0 requirements will be met: | |
** http://www.gnu.org/copyleft/gpl.html. | |
** | |
** Other Usage | |
** Alternatively, this file may be used in accordance with the terms and | |
** conditions contained in a signed written agreement between you and Nokia. | |
** | |
** | |
** | |
** | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
#include "private/qdeclarativegridview_p.h" | |
#include "private/qdeclarativevisualitemmodel_p.h" | |
#include "private/qdeclarativeflickable_p_p.h" | |
#include "private/qdeclarativesmoothedanimation_p_p.h" | |
#include <qdeclarativeguard_p.h> | |
#include <qlistmodelinterface_p.h> | |
#include <QKeyEvent> | |
#include <qmath.h> | |
#include <math.h> | |
#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<QDeclarativeGridViewAttached*>(qmlAttachedPropertiesObject<QDeclarativeGridView>(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); | |
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() { | |
Q_Q(QDeclarativeGridView); | |
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<QDeclarativeVisualModel> model; | |
QVariant modelVariant; | |
QList<FxGridItem*> visibleItems; | |
QHash<QDeclarativeItem*,int> 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; | |
} | |
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<QDeclarativeItem*,int>::iterator it; | |
for (it = unrequestedItems.begin(); it != unrequestedItems.end(); ++it) | |
*it = model->indexOf(it.key(), q); | |
} | |
void QDeclarativeGridViewPrivate::updateUnrequestedPositions() | |
{ | |
QHash<QDeclarativeItem*,int>::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; | |
delete highlight->item; | |
delete highlight; | |
highlight = 0; | |
delete highlightXAnimator; | |
delete highlightYAnimator; | |
highlightXAnimator = 0; | |
highlightYAnimator = 0; | |
changed = true; | |
} | |
if (currentItem) { | |
QDeclarativeItem *item = 0; | |
if (highlightComponent) { | |
QDeclarativeContext *highlightContext = new QDeclarativeContext(qmlContext(q)); | |
QObject *nobj = highlightComponent->create(highlightContext); | |
if (nobj) { | |
QDeclarative_setParent_noEvent(highlightContext, nobj); | |
item = qobject_cast<QDeclarativeItem *>(nobj); | |
if (!item) | |
delete nobj; | |
} else { | |
delete highlightContext; | |
} | |
} 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 = 0; | |
QDeclarativeContext *context = new QDeclarativeContext(qmlContext(q)); | |
QObject *nobj = footerComponent->create(context); | |
if (nobj) { | |
QDeclarative_setParent_noEvent(context, nobj); | |
item = qobject_cast<QDeclarativeItem *>(nobj); | |
if (!item) | |
delete nobj; | |
} else { | |
delete context; | |
} | |
if (item) { | |
QDeclarative_setParent_noEvent(item, q->contentItem()); | |
item->setParentItem(q->contentItem()); | |
item->setZValue(1); | |
QDeclarativeItemPrivate *itemPrivate = static_cast<QDeclarativeItemPrivate*>(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 = 0; | |
QDeclarativeContext *context = new QDeclarativeContext(qmlContext(q)); | |
QObject *nobj = headerComponent->create(context); | |
if (nobj) { | |
QDeclarative_setParent_noEvent(context, nobj); | |
item = qobject_cast<QDeclarativeItem *>(nobj); | |
if (!item) | |
delete nobj; | |
} else { | |
delete context; | |
} | |
if (item) { | |
QDeclarative_setParent_noEvent(item, q->contentItem()); | |
item->setParentItem(q->contentItem()); | |
item->setZValue(1); | |
QDeclarativeItemPrivate *itemPrivate = static_cast<QDeclarativeItemPrivate*>(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; | |
} | |
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); | |
FxGridItem *bottomItem = snapItemAt(tempPosition+highlightEnd); | |
qreal pos; | |
if (topItem && bottomItem && haveHighlightRange && highlightRange == QDeclarativeGridView::StrictlyEnforceRange) { | |
qreal topPos = qMin(topItem->rowPos() - highlightStart, -maxExtent); | |
qreal bottomPos = qMax(bottomItem->rowPos() - highlightEnd, -minExtent); | |
pos = qAbs(data.move + topPos) < qAbs(data.move + bottomPos) ? topPos : bottomPos; | |
} else if (topItem) { | |
qreal headerPos = 0; | |
if (header) | |
headerPos = isRightToLeftTopToBottom() ? header->rowPos() + cellWidth - headerSize() : header->rowPos(); | |
if (topItem->index == 0 && header && tempPosition+highlightStart < headerPos+headerSize()/2) { | |
pos = isRightToLeftTopToBottom() ? - headerPos + highlightStart - size() : headerPos - 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) { | |
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 = 0.0; | |
if ((maxDistance > 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 * 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); | |
} | |
} | |
//---------------------------------------------------------------------------- | |
/*! | |
\qmlclass GridView 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<QObject*>(model); | |
QDeclarativeVisualModel *vim = 0; | |
if (object && (vim = qobject_cast<QDeclarativeVisualModel *>(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<QDeclarativeVisualDataModel*>(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<QDeclarativeVisualDataModel*>(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<QDeclarativeVisualDataModel*>(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 | |
\o GridView.ApplyRange - the view attempts to maintain the highlight within the range. | |
However, the highlight can move outside of the range at the ends of the view or due | |
to mouse interaction. | |
\o GridView.StrictlyEnforceRange - the highlight never moves outside of the range. | |
The current item changes if a keyboard or mouse action would cause the highlight to move | |
outside of the range. | |
\o GridView.NoHighlightRange - this is the default value. | |
\endlist | |
*/ | |
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 | |
\o Qt.LeftToRight (default) - Items will be laid out starting in the top, left corner. The flow is | |
dependent on the \l GridView::flow property. | |
\o Qt.RightToLeft - Items will be laid out starting in the top, right corner. The flow is dependent | |
on the \l GridView:flow property. | |
\endlist | |
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 | |
\o GridView.LeftToRight (default) - Items are laid out from left to right, and the view scrolls vertically | |
\o GridView.TopToBottom - Items are laid out from top to bottom, and the view scrolls horizontally | |
\endlist | |
*/ | |
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 | |
\o GridView.NoSnap (default) - the view stops anywhere within the visible area. | |
\o GridView.SnapToRow - the view settles with a row (or column for \c GridView.TopToBottom flow) | |
aligned with the start of the view. | |
\o GridView.SnapOneRow - the view will settle no more than one row (or column for \c GridView.TopToBottom flow) | |
away from the first visible row at the time the mouse button is released. | |
This mode is particularly useful for moving one page at a time. | |
\endlist | |
*/ | |
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; | |
if (d->isRightToLeftTopToBottom()) { | |
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 += 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; | |
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. | |
\bold 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. | |
\bold 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. | |
\bold 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. | |
\bold 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<FxGridItem*> 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 | |
\o GridView.Beginning - position item at the top (or left for \c GridView.TopToBottom flow) of the view. | |
\o GridView.Center - position item in the center of the view. | |
\o GridView.End - position item at bottom (or right for horizontal orientation) of the view. | |
\o GridView.Visible - if any part of the item is visible then take no action, otherwise | |
bring the item into view. | |
\o GridView.Contain - ensure the entire item is visible. If the item is larger than | |
the view the item is positioned at the top (or left for \c GridView.TopToBottom flow) of the view. | |
\endlist | |
If positioning the view at the index would cause empty space to be displayed at | |
the beginning or end of the view, the view will be positioned at the boundary. | |
It is not recommended to use \l {Flickable::}{contentX} or \l {Flickable::}{contentY} to position the view | |
at a particular index. This is unreliable since removing items from the start | |
of the view does not cause all other items to be repositioned. | |
The correct way to bring an item into view is with \c positionViewAtIndex. | |
\bold Note: methods should only be called after the Component has completed. To position | |
the view at startup, this method should be called by Component.onCompleted. For | |
example, to position the view at the end: | |
\code | |
Component.onCompleted: positionViewAtIndex(count - 1, GridView.Beginning) | |
\endcode | |
*/ | |
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. | |
\bold Note: methods should only be called after the Component has completed. To position | |
the view at startup, this method should be called by Component.onCompleted. For | |
example, to position the view at the end on startup: | |
\code | |
Component.onCompleted: positionViewAtEnd() | |
\endcode | |
*/ | |
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. | |
\bold 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<FxGridItem*> added; | |
int i = 0; | |
while (i < insertCount && rowPos <= to + d->rowSize()*(d->columns - (colPos/d->colSize()))/qreal(d->columns)) { | |
if (!addedVisible) { | |
d->scheduleLayout(); | |
addedVisible = true; | |
} | |
FxGridItem *item = d->createItem(modelIndex + i); | |
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<FxGridItem*>::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<FxGridItem*>::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<FxGridItem*>::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<int,FxGridItem*> moved; | |
FxGridItem *firstItem = d->firstVisibleItem(); | |
QList<FxGridItem*>::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); | |
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 |