diff options
Diffstat (limited to 'src/declarative/fx/qfxgridview.cpp')
-rw-r--r-- | src/declarative/fx/qfxgridview.cpp | 1469 |
1 files changed, 1469 insertions, 0 deletions
diff --git a/src/declarative/fx/qfxgridview.cpp b/src/declarative/fx/qfxgridview.cpp new file mode 100644 index 0000000..f9a9f8c --- /dev/null +++ b/src/declarative/fx/qfxgridview.cpp @@ -0,0 +1,1469 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfxvisualitemmodel.h" +#include "qlistmodelinterface.h" +#include "qmlfollow.h" +#include "private/qfxflickable_p.h" +#include "qfxgridview.h" + +QT_BEGIN_NAMESPACE + +class QFxGridViewAttached : public QObject +{ + Q_OBJECT +public: + QFxGridViewAttached(QObject *parent) + : QObject(parent), m_isCurrent(false), m_delayRemove(false) {} + ~QFxGridViewAttached() { + attachedProperties.remove(parent()); + } + + Q_PROPERTY(QFxGridView *view READ view); + QFxGridView *view() { return m_view; } + + Q_PROPERTY(bool isCurrentItem READ isCurrentItem NOTIFY currentItemChanged); + bool isCurrentItem() const { return m_isCurrent; } + void setIsCurrentItem(bool c) { + if (m_isCurrent != c) { + m_isCurrent = c; + emit currentItemChanged(); + } + } + + Q_PROPERTY(bool delayRemove READ delayRemove WRITE setDelayRemove NOTIFY delayRemoveChanged); + bool delayRemove() const { return m_delayRemove; } + void setDelayRemove(bool delay) { + if (m_delayRemove != delay) { + m_delayRemove = delay; + emit delayRemoveChanged(); + } + } + + static QFxGridViewAttached *properties(QObject *obj) { + if(!attachedProperties.contains(obj)) { + QFxGridViewAttached *rv = new QFxGridViewAttached(obj); + attachedProperties.insert(obj, rv); + return rv; + } + return attachedProperties.value(obj); + } + + void emitAdd() { emit add(); } + void emitRemove() { emit remove(); } + +signals: + void currentItemChanged(); + void delayRemoveChanged(); + void add(); + void remove(); + +public: + QFxGridView *m_view; + bool m_isCurrent; + bool m_delayRemove; + + static QHash<QObject*, QFxGridViewAttached*> attachedProperties; +}; + +QHash<QObject*, QFxGridViewAttached*> QFxGridViewAttached::attachedProperties; + + +//---------------------------------------------------------------------------- + +class FxGridItem +{ +public: + FxGridItem(QFxItem *i, QFxGridView *v) : item(i), view(v) { + attached = QFxGridViewAttached::properties(item); + attached->m_view = view; + } + ~FxGridItem() {} + + qreal rowPos() const { return (view->flow() == QFxGridView::LeftToRight ? item->y() : item->x()); } + qreal colPos() const { return (view->flow() == QFxGridView::LeftToRight ? item->x() : item->y()); } + qreal endRowPos() const { + return (view->flow() == QFxGridView::LeftToRight + ? item->y() + (item->height() > 0 ? item->height() : 1) + : item->x() + (item->width() > 0 ? item->width() : 1)) - 1; + } + void setPosition(qreal col, qreal row) { + if (view->flow() == QFxGridView::LeftToRight) { + item->setPos(QPointF(col, row)); + } else { + item->setPos(QPointF(row, col)); + } + } + + QFxItem *item; + QFxGridView *view; + QFxGridViewAttached *attached; + int index; +}; + +//---------------------------------------------------------------------------- + +class QFxGridViewPrivate : public QFxFlickablePrivate +{ + Q_DECLARE_PUBLIC(QFxGridView); + +public: + QFxGridViewPrivate() + : model(0), currentItem(0), tmpCurrent(0), flow(QFxGridView::LeftToRight) + , visiblePos(0), visibleIndex(0) , currentIndex(-1) + , cellWidth(100), cellHeight(100), columns(1) + , highlightComponent(0), highlight(0), trackedItem(0) + , moveReason(Other), buffer(0), highlightXAnimator(0), highlightYAnimator(0) + , keyPressed(false), ownModel(false), wrap(false), autoHighlight(true) + , fixCurrentVisibility(false) {} + + void init(); + void clear(); + FxGridItem *getItem(int modelIndex); + FxGridItem *createItem(int modelIndex); + void releaseItem(FxGridItem *item); + void refill(qreal from, qreal to); + + void updateGrid(); + void layout(bool removed=false); + void updateTrackedItem(); + void createHighlight(); + void updateHighlight(); + void updateCurrent(int modelIndex); + + 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; + } + + qreal position() const { + Q_Q(const QFxGridView); + return flow == QFxGridView::LeftToRight ? q->yPosition() : q->xPosition(); + } + void setPosition(qreal pos) { + Q_Q(QFxGridView); + if (flow == QFxGridView::LeftToRight) + q->setYPosition(pos); + else + q->setXPosition(pos); + } + int size() const { + Q_Q(const QFxGridView); + return flow == QFxGridView::LeftToRight ? q->height() : q->width(); + } + qreal startPosition() const { + qreal pos = 0; + if (!visibleItems.isEmpty()) + pos = visibleItems.first()->rowPos() - visibleIndex / columns * rowSize(); + return pos; + } + + qreal endPosition() const { + qreal pos = 0; + if (model && model->count()) + pos = rowPosAt(model->count() - 1) + rowSize(); + return pos; + } + + bool isValid() const { + return model && model->count() && (!ownModel || model->delegate()); + } + + int rowSize() const { + return flow == QFxGridView::LeftToRight ? cellHeight : cellWidth; + } + int colSize() const { + return flow == QFxGridView::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(); + } + } + 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(); + } + } + return 0; + } + + // 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 + } + + // 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) { + qDebug() << "index" << visibleIndex << i << listItem->index; + for (int j = 0; j < visibleItems.count(); j++) + qDebug() << " index" << j << "item index" << visibleItems.at(j)->index; + abort(); + } + } + } + + QFxVisualItemModel *model; + QVariant modelVariant; + QList<FxGridItem*> visibleItems; + FxGridItem *currentItem; + QFxItem *tmpCurrent; + QFxGridView::Flow flow; + int visiblePos; + int visibleIndex; + int currentIndex; + int cellWidth; + int cellHeight; + int columns; + QmlComponent *highlightComponent; + FxGridItem *highlight; + FxGridItem *trackedItem; + enum MovementReason { Other, Key, Mouse }; + MovementReason moveReason; + int buffer; + QmlFollow *highlightXAnimator; + QmlFollow *highlightYAnimator; + + int keyPressed : 1; + int ownModel : 1; + int wrap : 1; + int autoHighlight : 1; + int fixCurrentVisibility : 1; +}; + +void QFxGridViewPrivate::init() +{ + Q_Q(QFxGridView); + q->setOptions(QFxGridView::IsFocusRealm); +} + +void QFxGridViewPrivate::clear() +{ + for (int i = 0; i < visibleItems.count(); ++i) + releaseItem(visibleItems.at(i)); + visibleItems.clear(); + visiblePos = 0; + visibleIndex = 0; + if (currentItem) { + FxGridItem *tmpItem = currentItem; + currentItem = 0; + currentIndex = -1; + releaseItem(tmpItem); + } + createHighlight(); + trackedItem = 0; +} + +FxGridItem *QFxGridViewPrivate::getItem(int modelIndex) +{ + if (currentItem && modelIndex == currentIndex) + return currentItem; + if (FxGridItem *listItem = visibleItem(modelIndex)) + return listItem; + return createItem(modelIndex); +} + +FxGridItem *QFxGridViewPrivate::createItem(int modelIndex) +{ + Q_Q(QFxGridView); + // create object + FxGridItem *listItem = 0; + if (QFxItem *item = model->item(modelIndex, false)) { + listItem = new FxGridItem(item, q); + listItem->index = modelIndex; + // complete + model->completeItem(); + listItem->item->setZ(modelIndex + 1); + listItem->item->setParent(q->viewport()); + } + return listItem; +} + + +void QFxGridViewPrivate::releaseItem(FxGridItem *item) +{ + Q_Q(QFxGridView); + if (item != currentItem) { + if (trackedItem == item) { + QObject::disconnect(trackedItem->item, SIGNAL(topChanged()), q, SLOT(trackedPositionChanged())); + QObject::disconnect(trackedItem->item, SIGNAL(leftChanged()), q, SLOT(trackedPositionChanged())); + trackedItem = 0; + } + model->release(item->item); + delete item; + } +} + +void QFxGridViewPrivate::refill(qreal from, qreal to) +{ + Q_Q(QFxGridView); + if (!isValid() || !q->isComponentComplete()) + return; + + from -= buffer; + to += buffer; + bool changed = false; + + int colPos = 0; + int rowPos = 0; + int modelIndex = 0; + 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; + } + + FxGridItem *item = 0; + while (modelIndex < model->count() && rowPos <= to) { + //qDebug() << "refill: append item" << modelIndex; + item = getItem(modelIndex); + item->setPosition(colPos, rowPos); + visibleItems.append(item); + colPos += colSize(); + if (colPos > colSize() * (columns-1)) { + colPos = 0; + rowPos += rowSize(); + } + ++modelIndex; + changed = true; + } + + if (visibleItems.count()) { + rowPos = visibleItems.first()->rowPos(); + colPos = visibleItems.first()->colPos() - colSize(); + if (colPos < 0) { + colPos = colSize() * (columns - 1); + rowPos -= rowSize(); + } + } + while (visibleIndex > 0 && rowPos + rowSize() - 1 >= from){ + //qDebug() << "refill: prepend item" << visibleIndex-1 << "top pos" << rowPos << colPos; + item = getItem(visibleIndex-1); + --visibleIndex; + item->setPosition(colPos, rowPos); + visibleItems.prepend(item); + colPos -= colSize(); + if (colPos < 0) { + colPos = colSize() * (columns - 1); + rowPos -= rowSize(); + } + changed = true; + } + + while (visibleItems.count() > 1 && (item = visibleItems.first()) && item->endRowPos() < from) { + 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() > to) { + if (item->attached->delayRemove()) + break; + //qDebug() << "refill: remove last" << visibleIndex+visibleItems.count()-1; + visibleItems.removeLast(); + releaseItem(item); + changed = true; + } + if (changed) { + if (flow == QFxGridView::LeftToRight) + q->setViewportHeight(endPosition() - startPosition()); + else + q->setViewportWidth(endPosition() - startPosition()); + } +} + +void QFxGridViewPrivate::updateGrid() +{ + Q_Q(QFxGridView); + columns = (int)qMax((flow == QFxGridView::LeftToRight ? q->width() : q->height()) / colSize(), 1.); + if (isValid()) { + if (flow == QFxGridView::LeftToRight) + q->setViewportHeight(endPosition() - startPosition()); + else + q->setViewportWidth(endPosition() - startPosition()); + } +} + +void QFxGridViewPrivate::layout(bool removed) +{ + Q_Q(QFxGridView); + if (visibleItems.count()) { + qreal rowPos = visibleItems.first()->rowPos(); + qreal colPos = visibleItems.first()->colPos(); + if (visibleIndex % columns != 0) { + if (removed) + rowPos -= rowSize(); + colPos = (visibleIndex % columns) * colSize(); + visibleItems.first()->setPosition(colPos, rowPos); + } else if (colPos != 0) { + colPos = 0; + 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); + } + } + q->refill(); + q->trackedPositionChanged(); + updateHighlight(); + if (flow == QFxGridView::LeftToRight) { + q->setViewportHeight(endPosition() - startPosition()); + fixupY(); + } else { + q->setViewportWidth(endPosition() - startPosition()); + fixupX(); + } +} + +void QFxGridViewPrivate::updateTrackedItem() +{ + Q_Q(QFxGridView); + FxGridItem *item = currentItem; + if (highlight) + item = highlight; + + if (trackedItem && item != trackedItem) { + QObject::disconnect(trackedItem->item, SIGNAL(topChanged()), q, SLOT(trackedPositionChanged())); + QObject::disconnect(trackedItem->item, SIGNAL(leftChanged()), q, SLOT(trackedPositionChanged())); + trackedItem = 0; + } + + if (!trackedItem && item) { + trackedItem = item; + QObject::connect(trackedItem->item, SIGNAL(topChanged()), q, SLOT(trackedPositionChanged())); + QObject::connect(trackedItem->item, SIGNAL(leftChanged()), q, SLOT(trackedPositionChanged())); + q->trackedPositionChanged(); + } + if (trackedItem) + q->trackedPositionChanged(); +} + +void QFxGridViewPrivate::createHighlight() +{ + Q_Q(QFxGridView); + if (highlight) { + if (trackedItem == highlight) + trackedItem = 0; + delete highlight->item; + delete highlight; + highlight = 0; + delete highlightXAnimator; + delete highlightYAnimator; + highlightXAnimator = 0; + highlightYAnimator = 0; + } + + if (!highlightComponent) + return; + + if (currentItem) { + QmlContext *highlightContext = new QmlContext(q->itemContext()); + QObject *nobj = highlightComponent->create(highlightContext); + if (nobj) { + highlightContext->setParent(nobj); + QFxItem *item = qobject_cast<QFxItem *>(nobj); + if (item) { + item->setParent(q->viewport()); + highlight = new FxGridItem(item, q); + highlightXAnimator = new QmlFollow(q); + highlightXAnimator->setTarget(QmlMetaProperty(highlight->item, QLatin1String("x"))); + highlightXAnimator->setSpring(3); + highlightXAnimator->setDamping(0.3); + highlightXAnimator->setEnabled(autoHighlight); + highlightYAnimator = new QmlFollow(q); + highlightYAnimator->setTarget(QmlMetaProperty(highlight->item, QLatin1String("y"))); + highlightYAnimator->setSpring(3); + highlightYAnimator->setDamping(0.3); + highlightYAnimator->setEnabled(autoHighlight); + } else { + delete highlightContext; + } + } + } +} + +void QFxGridViewPrivate::updateHighlight() +{ + if ((!currentItem && highlight) || (currentItem && !highlight)) + createHighlight(); + updateTrackedItem(); + if (currentItem && autoHighlight && highlight) { + // auto-update highlight + highlightXAnimator->setSourceValue(currentItem->item->x()); + highlightYAnimator->setSourceValue(currentItem->item->y()); + highlight->item->setWidth(currentItem->item->width()); + highlight->item->setHeight(currentItem->item->height()); + } +} + +void QFxGridViewPrivate::updateCurrent(int modelIndex) +{ + Q_Q(QFxGridView); + if (!isValid() || modelIndex < 0 || modelIndex >= model->count()) { + if (currentItem) { + FxGridItem *item = currentItem; + currentItem = 0; + currentIndex = 0; + updateHighlight(); + releaseItem(item); + emit q->currentIndexChanged(); + } + return; + } + + if (currentItem && currentIndex == modelIndex) { + updateHighlight(); + return; + } + + if (tmpCurrent) { + delete tmpCurrent; + tmpCurrent = 0; + } + int oldCurrentIndex = currentIndex; + FxGridItem *oldCurrentItem = currentItem; + currentIndex = -1; + currentItem = visibleItem(modelIndex); + if (!currentItem) { + currentItem = getItem(modelIndex); + currentItem->setPosition(colPosAt(modelIndex), rowPosAt(modelIndex)); + } + currentIndex = modelIndex; + fixCurrentVisibility = true; + if (oldCurrentItem && oldCurrentItem->item != currentItem->item) + oldCurrentItem->attached->setIsCurrentItem(false); + currentItem->item->setFocus(true); + currentItem->attached->setIsCurrentItem(true); + updateHighlight(); + emit q->currentIndexChanged(); + // Release the old current item + if (oldCurrentItem && !visibleItem(oldCurrentIndex)) + releaseItem(oldCurrentItem); +} + +//---------------------------------------------------------------------------- + +/*! + \qmlclass GridView + \inherits Flickable + \brief The GridView element provides a grid view of items provided by a model. + + The model is typically provided by a QAbstractListModel "C++ model object", + but can also be created directly in XML. + + The items are laid out top to bottom (vertically) or left to right (horizontally) + and may be flicked to scroll. + + The below example creates a very simple grid, using an XML model. + \code + <resources> + <ListModel id="contactModel"> + <Contact> + <firstName>John</firstName> + <lastName>Smith</lastName> + </Contact> + <Contact> + <firstName>Bill</firstName> + <lastName>Jones</lastName> + </Contact> + <Contact> + <firstName>Jane</firstName> + <lastName>Doe</lastName> + </Contact> + </ListModel> + <Component id="contactDelegate"> + <Rect pen.color="blue" z="-1" height="20" width="80" color="white" radius="2"> + <Text id="name" text="{firstName + ' ' + lastName}" font.size="11"/> + </Rect> + </Component> + </resources> + + <GridView id="Grid" width="160" height="240" cellWidth="80" cellHeight="20" clip="true" + model="{contactModel}" delegate="{contactDelegate}"/> + \endcode +*/ +QFxGridView::QFxGridView(QFxItem *parent) + : QFxFlickable(*(new QFxGridViewPrivate), parent) +{ + Q_D(QFxGridView); + d->init(); +} + +QFxGridView::~QFxGridView() +{ + Q_D(QFxGridView); + if (d->ownModel) + delete d->model; +} + +/*! + \qmlproperty model GridView::model + This property holds the model providing data for the grid. + + The model provides a set of data that is used to create the items for the view. + For large or dynamic datasets the model is usually provided by a C++ model object. + The C++ model object must be a \l QListModelInterface subclass, a \l VisualModel, + or a simple list. + + Models can also be created directly in XML, using the \l ListModel element. For example: + \code + <ListModel id="contactModel"> + <Contact> + <firstName>John</firstName> + <lastName>Smith</lastName> + </Contact> + <Contact> + <firstName>Bill</firstName> + <lastName>Jones</lastName> + </Contact> + <Contact> + <firstName>Jane</firstName> + <lastName>Doe</lastName> + </Contact> + </ListModel> + + <GridView model="{contactModel}" .../> + \endcode +*/ +QVariant QFxGridView::model() const +{ + Q_D(const QFxGridView); + return d->modelVariant; +} + +void QFxGridView::setModel(const QVariant &model) +{ + Q_D(QFxGridView); + 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))); + } + d->clear(); + d->modelVariant = model; + QObject *object = qvariant_cast<QObject*>(model); + QFxVisualItemModel *vim = 0; + if (object && (vim = qobject_cast<QFxVisualItemModel *>(object))) { + if (d->ownModel) { + delete d->model; + d->ownModel = false; + } + d->model = vim; + } else { + if (!d->ownModel) { + d->model = new QFxVisualItemModel(itemContext()); + d->ownModel = true; + } + d->model->setModel(model); + } + if (d->model) { + if (d->currentIndex >= d->model->count() || d->currentIndex < 0) + setCurrentIndex(0); + else + d->updateCurrent(d->currentIndex); + connect(d->model, SIGNAL(itemsInserted(int,int)), this, SLOT(itemsInserted(int,int))); + connect(d->model, SIGNAL(itemsRemoved(int,int)), this, SLOT(itemsRemoved(int,int))); + refill(); + emit countChanged(); + } +} + +/*! + \qmlproperty component GridView::delegate + + The delegate provides a template describing what each item in the view should look and act like. + + Here is an example delegate: + \code + <Component id="contactDelegate"> + <Item id="wrapper"> + <Image id="pic" width="100" height="100" file="{portrait}"/> + <Text id="name" text="{firstName + ' ' + lastName}" + anchors.left="{pic.right}" anchors.leftMargin="5"/> + </Item> + </Component> + ... + <GridView delegate="{contactDelegate}" .../> + \endcode +*/ +QmlComponent *QFxGridView::delegate() const +{ + Q_D(const QFxGridView); + return d->model ? d->model->delegate() : 0; +} + +void QFxGridView::setDelegate(QmlComponent *delegate) +{ + Q_D(QFxGridView); + if (!d->ownModel) { + d->model = new QFxVisualItemModel(itemContext()); + d->ownModel = true; + } + d->model->setDelegate(delegate); + d->updateCurrent(d->currentIndex); + refill(); +} + +/*! + \qmlproperty int GridView::currentIndex + \qmlproperty Item GridView::current + + \c currentIndex holds the index of the current item. + \c current is the current item. Note that the position of the current item + may only be approximate until it becomes visible in the view. +*/ +int QFxGridView::currentIndex() const +{ + Q_D(const QFxGridView); + return d->currentIndex; +} + +void QFxGridView::setCurrentIndex(int index) +{ + Q_D(QFxGridView); + if (d->isValid() && index != d->currentIndex && index < d->model->count() && index >= 0) + d->updateCurrent(index); + else + d->currentIndex = index; +} + +QFxItem *QFxGridView::currentItem() +{ + Q_D(QFxGridView); + if (!d->currentItem) { + // Always return something valid + if (!d->tmpCurrent) + d->tmpCurrent = new QFxItem(viewport()); + return d->tmpCurrent; + } + return d->currentItem->item; +} + +/*! + \qmlproperty int GridView::count + This property holds the number of items in the view. +*/ +int QFxGridView::count() const +{ + Q_D(const QFxGridView); + 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 will be created for each view. + The geometry of the resultant component instance will be managed by the view + so as to stay with the current item, unless the autoHighlight property is false. + + The below example demonstrates how to make a simple highlight: + \code + <Component id="ListHighlight"> + <Rect color="lightsteelblue" radius="4"/> + </Component> + <GridView highlight="{ListHighlight}"> + \endcode + + \sa autoHighlight +*/ +QmlComponent *QFxGridView::highlight() const +{ + Q_D(const QFxGridView); + return d->highlightComponent; +} + +void QFxGridView::setHighlight(QmlComponent *highlight) +{ + Q_D(QFxGridView); + delete d->highlightComponent; + d->highlightComponent = highlight; + d->updateCurrent(d->currentIndex); +} + +/*! + \qmlproperty component GridView::autoHighlight + This property sets whether the highlight is managed by the view. + + If autoHighlight is true, the highlight will be moved smoothly + to follow the current item. If autoHighlight is false, the + highlight will not be moved by the view, and must be implemented + by the highlight, for example: + + \code + <Component id="Highlight"> + <Rect id="Wrapper" color="#242424" radius="4" width="320" height="60" > + <y> + <Follow source="{Wrapper.GridView.view.current.y}" spring="3" damping="0.2"/> + </y> + <x> + <Follow source="{Wrapper.GridView.view.current.x}" spring="3" damping="0.2"/> + </x> + </Rect> + </Component> + \endcode +*/ +bool QFxGridView::autoHighlight() const +{ + Q_D(const QFxGridView); + return d->autoHighlight; +} + +void QFxGridView::setAutoHighlight(bool autoHighlight) +{ + Q_D(QFxGridView); + d->autoHighlight = autoHighlight; + if (d->highlightXAnimator) { + d->highlightXAnimator->setEnabled(d->autoHighlight); + d->highlightYAnimator->setEnabled(d->autoHighlight); + } + d->updateHighlight(); +} + +/*! + \qmlproperty enumeration GridView::flow + This property holds the flow of the grid. + + Possible values are \c LeftToRight (default) and \c TopToBottom. + + If \a flow is \c LeftToRight, the view will scroll vertically. + If \a flow is \c TopToBottom, the view will scroll horizontally. +*/ +QFxGridView::Flow QFxGridView::flow() const +{ + Q_D(const QFxGridView); + return d->flow; +} + +void QFxGridView::setFlow(Flow flow) +{ + Q_D(QFxGridView); + if (d->flow != flow) { + d->flow = flow; + if (d->flow == LeftToRight) + setViewportWidth(-1); + else + setViewportHeight(-1); + d->clear(); + d->updateGrid(); + refill(); + d->updateCurrent(d->currentIndex); + } +} + +/*! + \qmlproperty bool GridView::wrap + This property holds whether the grid wraps key navigation + + If this property is true then key presses to move off of one end of the grid will cause the + selection to jump to the other side. +*/ +bool QFxGridView::isWrapEnabled() const +{ + Q_D(const QFxGridView); + return d->wrap; +} + +void QFxGridView::setWrapEnabled(bool wrap) +{ + Q_D(QFxGridView); + d->wrap = wrap; +} + +/*! + \qmlproperty int GridView::cacheBuffer + This property holds the number of off-screen pixels to cache. + + This property determines the number of pixels above the top of the view + and below the bottom of the view to cache. Setting this value can make + scrolling the view smoother at the expense of additional memory usage. +*/ + +/*! + \property QFxGridView::cacheBuffer + \brief sets the number of off-screen pixels to cache. + + This property determines the number of pixels above the top of the view + and below the bottom of the view to cache. Setting this value can make + scrolling the view smoother at the expense of additional memory usage. +*/ +int QFxGridView::cacheBuffer() const +{ + Q_D(const QFxGridView); + return d->buffer; +} + +void QFxGridView::setCacheBuffer(int buffer) +{ + Q_D(QFxGridView); + if(d->buffer != buffer) { + d->buffer = buffer; + if (isComponentComplete()) + refill(); + } +} + +/*! + \qmlproperty int GridView::cellWidth + This property holds the width of each cell in the grid + + The default cellWidth is 100. +*/ +int QFxGridView::cellWidth() const +{ + Q_D(const QFxGridView); + return d->cellWidth; +} + +void QFxGridView::setCellWidth(int cellWidth) +{ + Q_D(QFxGridView); + if (cellWidth != d->cellWidth && cellWidth > 0) { + d->cellWidth = qMax(1, cellWidth); + d->updateGrid(); + emit cellSizeChanged(); + d->layout(); + } +} + +/*! + \qmlproperty int GridView::cellHeight + This property holds the height of each cell in the grid + + The default cellHeight is 100. +*/ +int QFxGridView::cellHeight() const +{ + Q_D(const QFxGridView); + return d->cellHeight; +} + +void QFxGridView::setCellHeight(int cellHeight) +{ + Q_D(QFxGridView); + if (cellHeight != d->cellHeight && cellHeight > 0) { + d->cellHeight = qMax(1, cellHeight); + d->updateGrid(); + emit cellSizeChanged(); + d->layout(); + } +} + +/*! + \reimp +*/ +void QFxGridView::setHeight(int height) +{ + Q_D(QFxGridView); + QFxFlickable::setHeight(height); + if (isComponentComplete()) { + d->updateGrid(); + d->layout(); + } +} + +/*! + \reimp +*/ +void QFxGridView::setWidth(int width) +{ + Q_D(QFxGridView); + QFxFlickable::setWidth(width); + if (isComponentComplete()) { + d->updateGrid(); + d->layout(); + } +} + +/*! + \reimp +*/ +void QFxGridView::viewportMoved() +{ + QFxFlickable::viewportMoved(); + refill(); +} + +/*! + \reimp +*/ +qreal QFxGridView::minYExtent() const +{ + Q_D(const QFxGridView); + if (d->flow == QFxGridView::TopToBottom) + return QFxFlickable::minYExtent(); + return -d->startPosition(); +} + +/*! + \reimp +*/ +qreal QFxGridView::maxYExtent() const +{ + Q_D(const QFxGridView); + if (d->flow == QFxGridView::TopToBottom) + return QFxFlickable::maxYExtent(); + return -(d->endPosition() - height()); +} + +/*! + \reimp +*/ +qreal QFxGridView::minXExtent() const +{ + Q_D(const QFxGridView); + if (d->flow == QFxGridView::LeftToRight) + return QFxFlickable::minXExtent(); + return -d->startPosition(); +} + +/*! + \reimp +*/ +qreal QFxGridView::maxXExtent() const +{ + Q_D(const QFxGridView); + if (d->flow == QFxGridView::LeftToRight) + return QFxFlickable::maxXExtent(); + return -(d->endPosition() - height()); +} + +/*! + \reimp +*/ +void QFxGridView::keyPressEvent(QKeyEvent *event) +{ + Q_D(QFxGridView); + if (d->model && d->model->count() && !d->locked) { + if ((d->flow == QFxGridView::LeftToRight && event->key() == Qt::Key_Up) + || (d->flow == QFxGridView::TopToBottom && event->key() == Qt::Key_Left)) { + if (currentIndex() >= d->columns || d->wrap) { + d->keyPressed = true; + d->moveReason = QFxGridViewPrivate::Key; + int index = currentIndex() - d->columns; + setCurrentIndex(index >= 0 ? index : d->model->count()-1); + event->accept(); + } + return; + } else if ((d->flow == QFxGridView::LeftToRight && event->key() == Qt::Key_Down) + || (d->flow == QFxGridView::TopToBottom && event->key() == Qt::Key_Right)) { + if (currentIndex() < d->model->count() - d->columns || d->wrap) { + d->keyPressed = true; + d->moveReason = QFxGridViewPrivate::Key; + int index = currentIndex()+d->columns; + setCurrentIndex(index < d->model->count() ? index : 0); + event->accept(); + } + return; + } else if ((d->flow == QFxGridView::LeftToRight && event->key() == Qt::Key_Left) + || (d->flow == QFxGridView::TopToBottom && event->key() == Qt::Key_Up)) { + if (currentIndex() > 0 || d->wrap) { + d->keyPressed = true; + d->moveReason = QFxGridViewPrivate::Key; + int index = currentIndex() - 1; + setCurrentIndex(index >= 0 ? index : d->model->count()-1); + event->accept(); + } + return; + } else if ((d->flow == QFxGridView::LeftToRight && event->key() == Qt::Key_Right) + || (d->flow == QFxGridView::TopToBottom && event->key() == Qt::Key_Down)) { + if (currentIndex() < d->model->count() - 1 || d->wrap) { + d->keyPressed = true; + d->moveReason = QFxGridViewPrivate::Key; + int index = currentIndex() + 1; + setCurrentIndex(index < d->model->count() ? index : 0); + event->accept(); + } + return; + } + } + d->moveReason = QFxGridViewPrivate::Other; + QFxFlickable::keyPressEvent(event); +} + +/*! + \reimp +*/ +void QFxGridView::keyReleaseEvent(QKeyEvent *event) +{ + Q_D(QFxGridView); + d->keyPressed = false; + QFxFlickable::keyReleaseEvent(event); +} + +/*! + \reimp +*/ +void QFxGridView::componentComplete() +{ + Q_D(QFxGridView); + QFxFlickable::componentComplete(); + d->updateGrid(); + if (d->currentIndex < 0) + d->updateCurrent(0); + refill(); +} + +void QFxGridView::trackedPositionChanged() +{ + Q_D(QFxGridView); + if (!d->trackedItem) + return; + if (!isFlicking() && !d->pressed && d->moveReason == QFxGridViewPrivate::Key) { + if (d->trackedItem->rowPos() < d->position()) { + d->setPosition(d->trackedItem->rowPos()); + } else if (d->trackedItem->endRowPos() > d->position() + d->size()) { + qreal pos = d->trackedItem->endRowPos() - d->size(); + if (d->rowSize() > d->size()) + pos = d->trackedItem->rowPos(); + d->setPosition(pos); + } + } +} + +void QFxGridView::itemsInserted(int modelIndex, int count) +{ + Q_D(QFxGridView); + if (!d->visibleItems.count() || d->model->count() <= 1) { + refill(); + d->updateCurrent(qMax(0, qMin(d->currentIndex, d->model->count()-1))); + emit countChanged(); + return; + } + + int index = d->mapFromModel(modelIndex); + if (index == -1) { + 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 + count - 1 < 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 != d->currentItem) + listItem->index += count; + } + } + if (d->currentIndex >= modelIndex) { + // adjust current item index + d->currentIndex += count; + if (d->currentItem) + d->currentItem->index = d->currentIndex; + } + d->layout(); + emit countChanged(); + return; + } + } + + // At least some of the added items will be visible + int insertCount = count; + if (index < d->visibleIndex) { + insertCount -= d->visibleIndex - index; + index = d->visibleIndex; + modelIndex = d->visibleIndex; + } + + index -= d->visibleIndex; + int to = d->buffer+d->position()+d->size()-1; + int colPos, rowPos; + 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(); + } + } + + QList<FxGridItem*> added; + int i = 0; + for (; i < insertCount && rowPos + d->rowSize() - 1 <= to; ++i) { + int mod = (modelIndex+i) % d->columns; + while (mod++ < d->columns && modelIndex + i < d->model->count() && i < insertCount) { + 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 (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)); + } + } + 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()); + } else { + // Update the indexes of the following visible items. + for (; index < d->visibleItems.count(); ++index) { + FxGridItem *listItem = d->visibleItems.at(index); + if (listItem != d->currentItem) { + if (listItem->index != -1) + listItem->index += count; + } + } + } + // everything is in order now - emit add() signal + foreach(FxGridItem *item, added) + item->attached->emitAdd(); + d->layout(); + emit countChanged(); +} + +void QFxGridView::itemsRemoved(int modelIndex, int count) +{ + Q_D(QFxGridView); + + int index = d->mapFromModel(modelIndex); + if (index == -1) { + if (modelIndex + count - 1 < d->visibleIndex) { + // Items removed before our 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 != d->currentItem) + listItem->index -= count; + } + } + if (d->currentIndex >= modelIndex + count) { + d->currentIndex -= count; + if (d->currentItem) + d->currentItem->index -= count; + } else if (d->currentIndex >= modelIndex && d->currentIndex < modelIndex + count) { + // current item has been removed. + if (d->currentItem) { + FxGridItem *item = d->currentItem; + d->currentItem = 0; + d->releaseItem(item); + } + d->currentIndex = -1; + d->updateCurrent(qMin(modelIndex, d->model->count()-1)); + } + d->layout(true); + emit countChanged(); + return; + } + + // 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 + ++it; + } else if (item->index >= modelIndex + count) { + // after removed items + if (item != d->currentItem) + item->index -= count; + ++it; + } else { + // removed item + 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); + } + } + } + + // fix current + if (d->currentIndex >= modelIndex + count) { + d->currentIndex -= count; + if (d->currentItem) + d->currentItem->index -= count; + } else if (d->currentIndex >= modelIndex && d->currentIndex < modelIndex + count) { + // current item has been removed. + if (d->currentItem && !d->currentItem->attached->delayRemove()) { + FxGridItem *item = d->currentItem; + d->currentItem = 0; + d->releaseItem(item); + } + d->currentItem = 0; + d->currentIndex = -1; + d->updateCurrent(qMin(modelIndex, d->model->count()-1)); + } + + // update visibleIndex + for (it = d->visibleItems.begin(); it != d->visibleItems.end(); ++it) { + if ((*it)->index != -1) { + d->visibleIndex = (*it)->index; + break; + } + } + + if (d->visibleItems.isEmpty()) { + d->visibleIndex = 0; + d->setPosition(0); + refill(); + } else { + // Correct the positioning of the items + d->layout(); + } + emit countChanged(); +} + +void QFxGridView::destroyRemoved() +{ + Q_D(QFxGridView); + 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 QFxGridView::refill() +{ + Q_D(QFxGridView); + d->refill(d->position(), d->position()+d->size()-1); +} + + +QObject *QFxGridView::qmlAttachedProperties(QObject *obj) +{ + return QFxGridViewAttached::properties(obj); +} + +QML_DEFINE_TYPE(QFxGridView,GridView); + +QT_END_NAMESPACE + +#include "qfxgridview.moc" |