aboutsummaryrefslogtreecommitdiffstats
path: root/src/declarative/items/qsgpathview.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/declarative/items/qsgpathview.cpp')
-rw-r--r--src/declarative/items/qsgpathview.cpp1416
1 files changed, 1416 insertions, 0 deletions
diff --git a/src/declarative/items/qsgpathview.cpp b/src/declarative/items/qsgpathview.cpp
new file mode 100644
index 0000000000..fc3d39aef7
--- /dev/null
+++ b/src/declarative/items/qsgpathview.cpp
@@ -0,0 +1,1416 @@
+// Commit: 806f031efeda71d3f4d7d2f949b437493e79cf52
+/****************************************************************************
+**
+** 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$
+** 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 Technology Preview License Agreement accompanying
+** this package.
+**
+** 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.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsgpathview_p.h"
+#include "qsgpathview_p_p.h"
+#include "qsgcanvas.h"
+
+#include <private/qdeclarativestate_p.h>
+#include <private/qdeclarativeopenmetaobject_p.h>
+#include <private/qlistmodelinterface_p.h>
+
+#include <QtGui/qevent.h>
+#include <QtGui/qgraphicssceneevent.h>
+#include <QtGui/qapplication.h>
+#include <QtCore/qmath.h>
+#include <math.h>
+
+QT_BEGIN_NAMESPACE
+
+inline qreal qmlMod(qreal x, qreal y)
+{
+#ifdef QT_USE_MATH_H_FLOATS
+ if(sizeof(qreal) == sizeof(float))
+ return fmodf(float(x), float(y));
+ else
+#endif
+ return fmod(x, y);
+}
+
+static QDeclarativeOpenMetaObjectType *qPathViewAttachedType = 0;
+
+QSGPathViewAttached::QSGPathViewAttached(QObject *parent)
+: QObject(parent), m_percent(-1), m_view(0), m_onPath(false), m_isCurrent(false)
+{
+ if (qPathViewAttachedType) {
+ m_metaobject = new QDeclarativeOpenMetaObject(this, qPathViewAttachedType);
+ m_metaobject->setCached(true);
+ } else {
+ m_metaobject = new QDeclarativeOpenMetaObject(this);
+ }
+}
+
+QSGPathViewAttached::~QSGPathViewAttached()
+{
+}
+
+QVariant QSGPathViewAttached::value(const QByteArray &name) const
+{
+ return m_metaobject->value(name);
+}
+void QSGPathViewAttached::setValue(const QByteArray &name, const QVariant &val)
+{
+ m_metaobject->setValue(name, val);
+}
+
+
+void QSGPathViewPrivate::init()
+{
+ Q_Q(QSGPathView);
+ offset = 0;
+ q->setAcceptedMouseButtons(Qt::LeftButton);
+ q->setFlag(QSGItem::ItemIsFocusScope);
+ q->setFiltersChildMouseEvents(true);
+ q->connect(&tl, SIGNAL(updated()), q, SLOT(ticked()));
+ lastPosTime.invalidate();
+ static int timelineCompletedIdx = -1;
+ static int movementEndingIdx = -1;
+ if (timelineCompletedIdx == -1) {
+ timelineCompletedIdx = QDeclarativeTimeLine::staticMetaObject.indexOfSignal("completed()");
+ movementEndingIdx = QSGPathView::staticMetaObject.indexOfSlot("movementEnding()");
+ }
+ QMetaObject::connect(&tl, timelineCompletedIdx,
+ q, movementEndingIdx, Qt::DirectConnection);
+}
+
+QSGItem *QSGPathViewPrivate::getItem(int modelIndex)
+{
+ Q_Q(QSGPathView);
+ requestedIndex = modelIndex;
+ QSGItem *item = model->item(modelIndex, false);
+ if (item) {
+ if (!attType) {
+ // pre-create one metatype to share with all attached objects
+ attType = new QDeclarativeOpenMetaObjectType(&QSGPathViewAttached::staticMetaObject, qmlEngine(q));
+ foreach(const QString &attr, path->attributes())
+ attType->createProperty(attr.toUtf8());
+ }
+ qPathViewAttachedType = attType;
+ QSGPathViewAttached *att = static_cast<QSGPathViewAttached *>(qmlAttachedPropertiesObject<QSGPathView>(item));
+ qPathViewAttachedType = 0;
+ if (att) {
+ att->m_view = q;
+ att->setOnPath(true);
+ }
+ item->setParentItem(q);
+ QSGItemPrivate *itemPrivate = QSGItemPrivate::get(item);
+ itemPrivate->addItemChangeListener(this, QSGItemPrivate::Geometry);
+ }
+ requestedIndex = -1;
+ return item;
+}
+
+void QSGPathViewPrivate::releaseItem(QSGItem *item)
+{
+ if (!item || !model)
+ return;
+ QSGItemPrivate *itemPrivate = QSGItemPrivate::get(item);
+ itemPrivate->removeItemChangeListener(this, QSGItemPrivate::Geometry);
+ if (model->release(item) == 0) {
+ // item was not destroyed, and we no longer reference it.
+ if (QSGPathViewAttached *att = attached(item))
+ att->setOnPath(false);
+ }
+}
+
+QSGPathViewAttached *QSGPathViewPrivate::attached(QSGItem *item)
+{
+ return static_cast<QSGPathViewAttached *>(qmlAttachedPropertiesObject<QSGPathView>(item, false));
+}
+
+void QSGPathViewPrivate::clear()
+{
+ for (int i=0; i<items.count(); i++){
+ QSGItem *p = items[i];
+ releaseItem(p);
+ }
+ items.clear();
+}
+
+void QSGPathViewPrivate::updateMappedRange()
+{
+ if (model && pathItems != -1 && pathItems < modelCount)
+ mappedRange = qreal(pathItems)/modelCount;
+ else
+ mappedRange = 1.0;
+}
+
+qreal QSGPathViewPrivate::positionOfIndex(qreal index) const
+{
+ qreal pos = -1.0;
+
+ if (model && index >= 0 && index < modelCount) {
+ qreal start = 0.0;
+ if (haveHighlightRange && highlightRangeMode != QSGPathView::NoHighlightRange)
+ start = highlightRangeStart;
+ qreal globalPos = index + offset;
+ globalPos = qmlMod(globalPos, qreal(modelCount)) / modelCount;
+ if (pathItems != -1 && pathItems < modelCount) {
+ globalPos += start * mappedRange;
+ globalPos = qmlMod(globalPos, 1.0);
+ if (globalPos < mappedRange)
+ pos = globalPos / mappedRange;
+ } else {
+ pos = qmlMod(globalPos + start, 1.0);
+ }
+ }
+
+ return pos;
+}
+
+void QSGPathViewPrivate::createHighlight()
+{
+ Q_Q(QSGPathView);
+ if (!q->isComponentComplete())
+ return;
+
+ bool changed = false;
+ if (highlightItem) {
+ highlightItem->setParentItem(0);
+ highlightItem->deleteLater();
+ highlightItem = 0;
+ changed = true;
+ }
+
+ QSGItem *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<QSGItem *>(nobj);
+ if (!item)
+ delete nobj;
+ } else {
+ delete highlightContext;
+ }
+ } else {
+ item = new QSGItem;
+ }
+ if (item) {
+ QDeclarative_setParent_noEvent(item, q);
+ item->setParentItem(q);
+ highlightItem = item;
+ changed = true;
+ }
+ if (changed)
+ emit q->highlightItemChanged();
+}
+
+void QSGPathViewPrivate::updateHighlight()
+{
+ Q_Q(QSGPathView);
+ if (!q->isComponentComplete() || !isValid())
+ return;
+ if (highlightItem) {
+ if (haveHighlightRange && highlightRangeMode == QSGPathView::StrictlyEnforceRange) {
+ updateItem(highlightItem, highlightRangeStart);
+ } else {
+ qreal target = currentIndex;
+
+ offsetAdj = 0.0;
+ tl.reset(moveHighlight);
+ moveHighlight.setValue(highlightPosition);
+
+ const int duration = highlightMoveDuration;
+
+ if (target - highlightPosition > modelCount/2) {
+ highlightUp = false;
+ qreal distance = modelCount - target + highlightPosition;
+ tl.move(moveHighlight, 0.0, QEasingCurve(QEasingCurve::InQuad), int(duration * highlightPosition / distance));
+ tl.set(moveHighlight, modelCount-0.01);
+ tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::OutQuad), int(duration * (modelCount-target) / distance));
+ } else if (target - highlightPosition <= -modelCount/2) {
+ highlightUp = true;
+ qreal distance = modelCount - highlightPosition + target;
+ tl.move(moveHighlight, modelCount-0.01, QEasingCurve(QEasingCurve::InQuad), int(duration * (modelCount-highlightPosition) / distance));
+ tl.set(moveHighlight, 0.0);
+ tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::OutQuad), int(duration * target / distance));
+ } else {
+ highlightUp = highlightPosition - target < 0;
+ tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::InOutQuad), duration);
+ }
+ }
+ }
+}
+
+void QSGPathViewPrivate::setHighlightPosition(qreal pos)
+{
+ if (pos != highlightPosition) {
+ qreal start = 0.0;
+ qreal end = 1.0;
+ if (haveHighlightRange && highlightRangeMode != QSGPathView::NoHighlightRange) {
+ start = highlightRangeStart;
+ end = highlightRangeEnd;
+ }
+
+ qreal range = qreal(modelCount);
+ // calc normalized position of highlight relative to offset
+ qreal relativeHighlight = qmlMod(pos + offset, range) / range;
+
+ if (!highlightUp && relativeHighlight > end * mappedRange) {
+ qreal diff = 1.0 - relativeHighlight;
+ setOffset(offset + diff * range);
+ } else if (highlightUp && relativeHighlight >= (end - start) * mappedRange) {
+ qreal diff = relativeHighlight - (end - start) * mappedRange;
+ setOffset(offset - diff * range - 0.00001);
+ }
+
+ highlightPosition = pos;
+ qreal pathPos = positionOfIndex(pos);
+ updateItem(highlightItem, pathPos);
+ if (QSGPathViewAttached *att = attached(highlightItem))
+ att->setOnPath(pathPos != -1.0);
+ }
+}
+
+void QSGPathView::pathUpdated()
+{
+ Q_D(QSGPathView);
+ QList<QSGItem*>::iterator it = d->items.begin();
+ while (it != d->items.end()) {
+ QSGItem *item = *it;
+ if (QSGPathViewAttached *att = d->attached(item))
+ att->m_percent = -1;
+ ++it;
+ }
+ refill();
+}
+
+void QSGPathViewPrivate::updateItem(QSGItem *item, qreal percent)
+{
+ if (QSGPathViewAttached *att = attached(item)) {
+ if (qFuzzyCompare(att->m_percent, percent))
+ return;
+ att->m_percent = percent;
+ foreach(const QString &attr, path->attributes())
+ att->setValue(attr.toUtf8(), path->attributeAt(attr, percent));
+ }
+ QPointF pf = path->pointAt(percent);
+ item->setX(qRound(pf.x() - item->width()/2));
+ item->setY(qRound(pf.y() - item->height()/2));
+}
+
+void QSGPathViewPrivate::regenerate()
+{
+ Q_Q(QSGPathView);
+ if (!q->isComponentComplete())
+ return;
+
+ clear();
+
+ if (!isValid())
+ return;
+
+ firstIndex = -1;
+ updateMappedRange();
+ q->refill();
+}
+
+QSGPathView::QSGPathView(QSGItem *parent)
+ : QSGItem(*(new QSGPathViewPrivate), parent)
+{
+ Q_D(QSGPathView);
+ d->init();
+}
+
+QSGPathView::~QSGPathView()
+{
+ Q_D(QSGPathView);
+ d->clear();
+ if (d->attType)
+ d->attType->release();
+ if (d->ownModel)
+ delete d->model;
+}
+
+QVariant QSGPathView::model() const
+{
+ Q_D(const QSGPathView);
+ return d->modelVariant;
+}
+
+void QSGPathView::setModel(const QVariant &model)
+{
+ Q_D(QSGPathView);
+ 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,QSGItem*)), this, SLOT(createdItem(int,QSGItem*)));
+ for (int i=0; i<d->items.count(); i++){
+ QSGItem *p = d->items[i];
+ d->model->release(p);
+ }
+ d->items.clear();
+ }
+
+ d->modelVariant = model;
+ QObject *object = qvariant_cast<QObject*>(model);
+ QSGVisualModel *vim = 0;
+ if (object && (vim = qobject_cast<QSGVisualModel *>(object))) {
+ if (d->ownModel) {
+ delete d->model;
+ d->ownModel = false;
+ }
+ d->model = vim;
+ } else {
+ if (!d->ownModel) {
+ d->model = new QSGVisualDataModel(qmlContext(this), this);
+ d->ownModel = true;
+ }
+ if (QSGVisualDataModel *dataModel = qobject_cast<QSGVisualDataModel*>(d->model))
+ dataModel->setModel(model);
+ }
+ d->modelCount = 0;
+ if (d->model) {
+ 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,QSGItem*)), this, SLOT(createdItem(int,QSGItem*)));
+ d->modelCount = d->model->count();
+ if (d->model->count())
+ d->offset = qmlMod(d->offset, qreal(d->model->count()));
+ if (d->offset < 0)
+ d->offset = d->model->count() + d->offset;
+}
+ d->regenerate();
+ d->fixOffset();
+ emit countChanged();
+ emit modelChanged();
+}
+
+int QSGPathView::count() const
+{
+ Q_D(const QSGPathView);
+ return d->model ? d->modelCount : 0;
+}
+
+QDeclarativePath *QSGPathView::path() const
+{
+ Q_D(const QSGPathView);
+ return d->path;
+}
+
+void QSGPathView::setPath(QDeclarativePath *path)
+{
+ Q_D(QSGPathView);
+ if (d->path == path)
+ return;
+ if (d->path)
+ disconnect(d->path, SIGNAL(changed()), this, SLOT(pathUpdated()));
+ d->path = path;
+ connect(d->path, SIGNAL(changed()), this, SLOT(pathUpdated()));
+ if (d->isValid() && isComponentComplete()) {
+ d->clear();
+ if (d->attType) {
+ d->attType->release();
+ d->attType = 0;
+ }
+ d->regenerate();
+ }
+ emit pathChanged();
+}
+
+int QSGPathView::currentIndex() const
+{
+ Q_D(const QSGPathView);
+ return d->currentIndex;
+}
+
+void QSGPathView::setCurrentIndex(int idx)
+{
+ Q_D(QSGPathView);
+ if (d->model && d->modelCount)
+ idx = qAbs(idx % d->modelCount);
+ if (d->model && idx != d->currentIndex) {
+ if (d->modelCount) {
+ int itemIndex = (d->currentIndex - d->firstIndex + d->modelCount) % d->modelCount;
+ if (itemIndex < d->items.count()) {
+ if (QSGItem *item = d->items.at(itemIndex)) {
+ if (QSGPathViewAttached *att = d->attached(item))
+ att->setIsCurrentItem(false);
+ }
+ }
+ }
+ d->currentItem = 0;
+ d->moveReason = QSGPathViewPrivate::SetIndex;
+ d->currentIndex = idx;
+ if (d->modelCount) {
+ if (d->haveHighlightRange && d->highlightRangeMode == QSGPathView::StrictlyEnforceRange)
+ d->snapToCurrent();
+ int itemIndex = (idx - d->firstIndex + d->modelCount) % d->modelCount;
+ if (itemIndex < d->items.count()) {
+ d->currentItem = d->items.at(itemIndex);
+ d->currentItem->setFocus(true);
+ if (QSGPathViewAttached *att = d->attached(d->currentItem))
+ att->setIsCurrentItem(true);
+ }
+ d->currentItemOffset = d->positionOfIndex(d->currentIndex);
+ d->updateHighlight();
+ }
+ emit currentIndexChanged();
+ }
+}
+
+void QSGPathView::incrementCurrentIndex()
+{
+ Q_D(QSGPathView);
+ d->moveDirection = QSGPathViewPrivate::Positive;
+ setCurrentIndex(currentIndex()+1);
+}
+
+void QSGPathView::decrementCurrentIndex()
+{
+ Q_D(QSGPathView);
+ if (d->model && d->modelCount) {
+ int idx = currentIndex()-1;
+ if (idx < 0)
+ idx = d->modelCount - 1;
+ d->moveDirection = QSGPathViewPrivate::Negative;
+ setCurrentIndex(idx);
+ }
+}
+
+qreal QSGPathView::offset() const
+{
+ Q_D(const QSGPathView);
+ return d->offset;
+}
+
+void QSGPathView::setOffset(qreal offset)
+{
+ Q_D(QSGPathView);
+ d->setOffset(offset);
+ d->updateCurrent();
+}
+
+void QSGPathViewPrivate::setOffset(qreal o)
+{
+ Q_Q(QSGPathView);
+ if (offset != o) {
+ if (isValid() && q->isComponentComplete()) {
+ offset = qmlMod(o, qreal(modelCount));
+ if (offset < 0)
+ offset += qreal(modelCount);
+ q->refill();
+ } else {
+ offset = o;
+ }
+ emit q->offsetChanged();
+ }
+}
+
+void QSGPathViewPrivate::setAdjustedOffset(qreal o)
+{
+ setOffset(o+offsetAdj);
+}
+
+QDeclarativeComponent *QSGPathView::highlight() const
+{
+ Q_D(const QSGPathView);
+ return d->highlightComponent;
+}
+
+void QSGPathView::setHighlight(QDeclarativeComponent *highlight)
+{
+ Q_D(QSGPathView);
+ if (highlight != d->highlightComponent) {
+ d->highlightComponent = highlight;
+ d->createHighlight();
+ d->updateHighlight();
+ emit highlightChanged();
+ }
+}
+
+QSGItem *QSGPathView::highlightItem()
+{
+ Q_D(const QSGPathView);
+ return d->highlightItem;
+}
+
+qreal QSGPathView::preferredHighlightBegin() const
+{
+ Q_D(const QSGPathView);
+ return d->highlightRangeStart;
+}
+
+void QSGPathView::setPreferredHighlightBegin(qreal start)
+{
+ Q_D(QSGPathView);
+ if (d->highlightRangeStart == start || start < 0 || start > 1.0)
+ return;
+ d->highlightRangeStart = start;
+ d->haveHighlightRange = d->highlightRangeMode != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd;
+ refill();
+ emit preferredHighlightBeginChanged();
+}
+
+qreal QSGPathView::preferredHighlightEnd() const
+{
+ Q_D(const QSGPathView);
+ return d->highlightRangeEnd;
+}
+
+void QSGPathView::setPreferredHighlightEnd(qreal end)
+{
+ Q_D(QSGPathView);
+ if (d->highlightRangeEnd == end || end < 0 || end > 1.0)
+ return;
+ d->highlightRangeEnd = end;
+ d->haveHighlightRange = d->highlightRangeMode != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd;
+ refill();
+ emit preferredHighlightEndChanged();
+}
+
+QSGPathView::HighlightRangeMode QSGPathView::highlightRangeMode() const
+{
+ Q_D(const QSGPathView);
+ return d->highlightRangeMode;
+}
+
+void QSGPathView::setHighlightRangeMode(HighlightRangeMode mode)
+{
+ Q_D(QSGPathView);
+ if (d->highlightRangeMode == mode)
+ return;
+ d->highlightRangeMode = mode;
+ d->haveHighlightRange = d->highlightRangeMode != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd;
+ emit highlightRangeModeChanged();
+}
+
+int QSGPathView::highlightMoveDuration() const
+{
+ Q_D(const QSGPathView);
+ return d->highlightMoveDuration;
+}
+
+void QSGPathView::setHighlightMoveDuration(int duration)
+{
+ Q_D(QSGPathView);
+ if (d->highlightMoveDuration == duration)
+ return;
+ d->highlightMoveDuration = duration;
+ emit highlightMoveDurationChanged();
+}
+
+qreal QSGPathView::dragMargin() const
+{
+ Q_D(const QSGPathView);
+ return d->dragMargin;
+}
+
+void QSGPathView::setDragMargin(qreal dragMargin)
+{
+ Q_D(QSGPathView);
+ if (d->dragMargin == dragMargin)
+ return;
+ d->dragMargin = dragMargin;
+ emit dragMarginChanged();
+}
+
+qreal QSGPathView::flickDeceleration() const
+{
+ Q_D(const QSGPathView);
+ return d->deceleration;
+}
+
+void QSGPathView::setFlickDeceleration(qreal dec)
+{
+ Q_D(QSGPathView);
+ if (d->deceleration == dec)
+ return;
+ d->deceleration = dec;
+ emit flickDecelerationChanged();
+}
+
+bool QSGPathView::isInteractive() const
+{
+ Q_D(const QSGPathView);
+ return d->interactive;
+}
+
+void QSGPathView::setInteractive(bool interactive)
+{
+ Q_D(QSGPathView);
+ if (interactive != d->interactive) {
+ d->interactive = interactive;
+ if (!interactive)
+ d->tl.clear();
+ emit interactiveChanged();
+ }
+}
+
+bool QSGPathView::isMoving() const
+{
+ Q_D(const QSGPathView);
+ return d->moving;
+}
+
+bool QSGPathView::isFlicking() const
+{
+ Q_D(const QSGPathView);
+ return d->flicking;
+}
+
+QDeclarativeComponent *QSGPathView::delegate() const
+{
+ Q_D(const QSGPathView);
+ if (d->model) {
+ if (QSGVisualDataModel *dataModel = qobject_cast<QSGVisualDataModel*>(d->model))
+ return dataModel->delegate();
+ }
+
+ return 0;
+}
+
+void QSGPathView::setDelegate(QDeclarativeComponent *delegate)
+{
+ Q_D(QSGPathView);
+ if (delegate == this->delegate())
+ return;
+ if (!d->ownModel) {
+ d->model = new QSGVisualDataModel(qmlContext(this));
+ d->ownModel = true;
+ }
+ if (QSGVisualDataModel *dataModel = qobject_cast<QSGVisualDataModel*>(d->model)) {
+ int oldCount = dataModel->count();
+ dataModel->setDelegate(delegate);
+ d->modelCount = dataModel->count();
+ d->regenerate();
+ if (oldCount != dataModel->count())
+ emit countChanged();
+ emit delegateChanged();
+ }
+}
+
+int QSGPathView::pathItemCount() const
+{
+ Q_D(const QSGPathView);
+ return d->pathItems;
+}
+
+void QSGPathView::setPathItemCount(int i)
+{
+ Q_D(QSGPathView);
+ if (i == d->pathItems)
+ return;
+ if (i < 1)
+ i = 1;
+ d->pathItems = i;
+ d->updateMappedRange();
+ if (d->isValid() && isComponentComplete()) {
+ d->regenerate();
+ }
+ emit pathItemCountChanged();
+}
+
+QPointF QSGPathViewPrivate::pointNear(const QPointF &point, qreal *nearPercent) const
+{
+ //XXX maybe do recursively at increasing resolution.
+ qreal mindist = 1e10; // big number
+ QPointF nearPoint = path->pointAt(0);
+ qreal nearPc = 0;
+ for (qreal i=1; i < 1000; i++) {
+ QPointF pt = path->pointAt(i/1000.0);
+ QPointF diff = pt - point;
+ qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
+ if (dist < mindist) {
+ nearPoint = pt;
+ nearPc = i;
+ mindist = dist;
+ }
+ }
+
+ if (nearPercent)
+ *nearPercent = nearPc / 1000.0;
+
+ return nearPoint;
+}
+
+void QSGPathView::mousePressEvent(QGraphicsSceneMouseEvent *event)
+{
+ Q_D(QSGPathView);
+ if (d->interactive) {
+ d->handleMousePressEvent(event);
+ event->accept();
+ } else {
+ QSGItem::mousePressEvent(event);
+ }
+}
+
+void QSGPathViewPrivate::handleMousePressEvent(QGraphicsSceneMouseEvent *event)
+{
+ Q_Q(QSGPathView);
+ if (!interactive || !items.count())
+ return;
+ QPointF scenePoint = q->mapToScene(event->pos());
+ int idx = 0;
+ for (; idx < items.count(); ++idx) {
+ QRectF rect = items.at(idx)->boundingRect();
+ rect = items.at(idx)->mapRectToScene(rect);
+ if (rect.contains(scenePoint))
+ break;
+ }
+ if (idx == items.count() && dragMargin == 0.) // didn't click on an item
+ return;
+
+ startPoint = pointNear(event->pos(), &startPc);
+ if (idx == items.count()) {
+ qreal distance = qAbs(event->pos().x() - startPoint.x()) + qAbs(event->pos().y() - startPoint.y());
+ if (distance > dragMargin)
+ return;
+ }
+
+ if (tl.isActive() && flicking)
+ stealMouse = true; // If we've been flicked then steal the click.
+ else
+ stealMouse = false;
+
+ lastElapsed = 0;
+ lastDist = 0;
+ QSGItemPrivate::start(lastPosTime);
+ tl.clear();
+}
+
+void QSGPathView::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
+{
+ Q_D(QSGPathView);
+ if (d->interactive) {
+ d->handleMouseMoveEvent(event);
+ if (d->stealMouse)
+ setKeepMouseGrab(true);
+ event->accept();
+ } else {
+ QSGItem::mouseMoveEvent(event);
+ }
+}
+
+void QSGPathViewPrivate::handleMouseMoveEvent(QGraphicsSceneMouseEvent *event)
+{
+ Q_Q(QSGPathView);
+ if (!interactive || !lastPosTime.isValid())
+ return;
+
+ qreal newPc;
+ QPointF pathPoint = pointNear(event->pos(), &newPc);
+ if (!stealMouse) {
+ QPointF delta = pathPoint - startPoint;
+ if (qAbs(delta.x()) > QApplication::startDragDistance() || qAbs(delta.y()) > QApplication::startDragDistance()) {
+ stealMouse = true;
+ startPc = newPc;
+ }
+ }
+
+ if (stealMouse) {
+ moveReason = QSGPathViewPrivate::Mouse;
+ qreal diff = (newPc - startPc)*modelCount*mappedRange;
+ if (diff) {
+ q->setOffset(offset + diff);
+
+ if (diff > modelCount/2)
+ diff -= modelCount;
+ else if (diff < -modelCount/2)
+ diff += modelCount;
+
+ lastElapsed = QSGItemPrivate::restart(lastPosTime);
+ lastDist = diff;
+ startPc = newPc;
+ }
+ if (!moving) {
+ moving = true;
+ emit q->movingChanged();
+ emit q->movementStarted();
+ }
+ }
+}
+
+void QSGPathView::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
+{
+ Q_D(QSGPathView);
+ if (d->interactive) {
+ d->handleMouseReleaseEvent(event);
+ event->accept();
+ ungrabMouse();
+ } else {
+ QSGItem::mouseReleaseEvent(event);
+ }
+}
+
+void QSGPathViewPrivate::handleMouseReleaseEvent(QGraphicsSceneMouseEvent *)
+{
+ Q_Q(QSGPathView);
+ stealMouse = false;
+ q->setKeepMouseGrab(false);
+ if (!interactive || !lastPosTime.isValid())
+ return;
+
+ qreal elapsed = qreal(lastElapsed + QSGItemPrivate::elapsed(lastPosTime)) / 1000.;
+ qreal velocity = elapsed > 0. ? lastDist / elapsed : 0;
+ if (model && modelCount && qAbs(velocity) > 1.) {
+ qreal count = pathItems == -1 ? modelCount : pathItems;
+ if (qAbs(velocity) > count * 2) // limit velocity
+ velocity = (velocity > 0 ? count : -count) * 2;
+ // Calculate the distance to be travelled
+ qreal v2 = velocity*velocity;
+ qreal accel = deceleration/10;
+ // + 0.25 to encourage moving at least one item in the flick direction
+ qreal dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2.0) + 0.25));
+ if (haveHighlightRange && highlightRangeMode == QSGPathView::StrictlyEnforceRange) {
+ // round to nearest item.
+ if (velocity > 0.)
+ dist = qRound(dist + offset) - offset;
+ else
+ dist = qRound(dist - offset) + offset;
+ // Calculate accel required to stop on item boundary
+ if (dist <= 0.) {
+ dist = 0.;
+ accel = 0.;
+ } else {
+ accel = v2 / (2.0f * qAbs(dist));
+ }
+ }
+ offsetAdj = 0.0;
+ moveOffset.setValue(offset);
+ tl.accel(moveOffset, velocity, accel, dist);
+ tl.callback(QDeclarativeTimeLineCallback(&moveOffset, fixOffsetCallback, this));
+ if (!flicking) {
+ flicking = true;
+ emit q->flickingChanged();
+ emit q->flickStarted();
+ }
+ } else {
+ fixOffset();
+ }
+
+ lastPosTime.invalidate();
+ if (!tl.isActive())
+ q->movementEnding();
+}
+
+bool QSGPathView::sendMouseEvent(QGraphicsSceneMouseEvent *event)
+{
+ Q_D(QSGPathView);
+ QGraphicsSceneMouseEvent mouseEvent(event->type());
+ QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height()));
+ QSGCanvas *c = canvas();
+ QSGItem *grabber = c ? c->mouseGrabberItem() : 0;
+ bool stealThisEvent = d->stealMouse;
+ if ((stealThisEvent || myRect.contains(event->scenePos().toPoint())) && (!grabber || !grabber->keepMouseGrab())) {
+ mouseEvent.setAccepted(false);
+ for (int i = 0x1; i <= 0x10; i <<= 1) {
+ if (event->buttons() & i) {
+ Qt::MouseButton button = Qt::MouseButton(i);
+ mouseEvent.setButtonDownPos(button, mapFromScene(event->buttonDownPos(button)));
+ }
+ }
+ mouseEvent.setScenePos(event->scenePos());
+ mouseEvent.setLastScenePos(event->lastScenePos());
+ mouseEvent.setPos(mapFromScene(event->scenePos()));
+ mouseEvent.setLastPos(mapFromScene(event->lastScenePos()));
+
+ switch(mouseEvent.type()) {
+ case QEvent::GraphicsSceneMouseMove:
+ d->handleMouseMoveEvent(&mouseEvent);
+ break;
+ case QEvent::GraphicsSceneMousePress:
+ d->handleMousePressEvent(&mouseEvent);
+ stealThisEvent = d->stealMouse; // Update stealThisEvent in case changed by function call above
+ break;
+ case QEvent::GraphicsSceneMouseRelease:
+ d->handleMouseReleaseEvent(&mouseEvent);
+ break;
+ default:
+ break;
+ }
+ grabber = c->mouseGrabberItem();
+ if (grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this)
+ grabMouse();
+
+ return d->stealMouse;
+ } else if (d->lastPosTime.isValid()) {
+ d->lastPosTime.invalidate();
+ }
+ if (mouseEvent.type() == QEvent::GraphicsSceneMouseRelease)
+ d->stealMouse = false;
+ return false;
+}
+
+bool QSGPathView::childMouseEventFilter(QSGItem *i, QEvent *e)
+{
+ Q_D(QSGPathView);
+ if (!isVisible() || !d->interactive)
+ return QSGItem::childMouseEventFilter(i, e);
+
+ switch (e->type()) {
+ case QEvent::GraphicsSceneMousePress:
+ case QEvent::GraphicsSceneMouseMove:
+ case QEvent::GraphicsSceneMouseRelease:
+ return sendMouseEvent(static_cast<QGraphicsSceneMouseEvent *>(e));
+ default:
+ break;
+ }
+
+ return QSGItem::childMouseEventFilter(i, e);
+}
+
+void QSGPathView::updatePolish()
+{
+ QSGItem::updatePolish();
+ refill();
+}
+
+void QSGPathView::componentComplete()
+{
+ Q_D(QSGPathView);
+ QSGItem::componentComplete();
+ d->createHighlight();
+ // It is possible that a refill has already happended to to Path
+ // bindings being handled in the componentComplete(). If so
+ // don't do it again.
+ if (d->items.count() == 0 && d->model) {
+ d->modelCount = d->model->count();
+ d->regenerate();
+ }
+ d->updateHighlight();
+}
+
+void QSGPathView::refill()
+{
+ Q_D(QSGPathView);
+ if (!d->isValid() || !isComponentComplete())
+ return;
+
+ d->layoutScheduled = false;
+ bool currentVisible = false;
+
+ // first move existing items and remove items off path
+ int idx = d->firstIndex;
+ QList<QSGItem*>::iterator it = d->items.begin();
+ while (it != d->items.end()) {
+ qreal pos = d->positionOfIndex(idx);
+ QSGItem *item = *it;
+ if (pos >= 0.0) {
+ d->updateItem(item, pos);
+ if (idx == d->currentIndex) {
+ currentVisible = true;
+ d->currentItemOffset = pos;
+ }
+ ++it;
+ } else {
+// qDebug() << "release";
+ d->updateItem(item, 1.0);
+ d->releaseItem(item);
+ if (it == d->items.begin()) {
+ if (++d->firstIndex >= d->modelCount)
+ d->firstIndex = 0;
+ }
+ it = d->items.erase(it);
+ }
+ ++idx;
+ if (idx >= d->modelCount)
+ idx = 0;
+ }
+ if (!d->items.count())
+ d->firstIndex = -1;
+
+ if (d->modelCount) {
+ // add items to beginning and end
+ int count = d->pathItems == -1 ? d->modelCount : qMin(d->pathItems, d->modelCount);
+ if (d->items.count() < count) {
+ int idx = qRound(d->modelCount - d->offset) % d->modelCount;
+ qreal startPos = 0.0;
+ if (d->haveHighlightRange && d->highlightRangeMode != QSGPathView::NoHighlightRange)
+ startPos = d->highlightRangeStart;
+ if (d->firstIndex >= 0) {
+ startPos = d->positionOfIndex(d->firstIndex);
+ idx = (d->firstIndex + d->items.count()) % d->modelCount;
+ }
+ qreal pos = d->positionOfIndex(idx);
+ while ((pos > startPos || !d->items.count()) && d->items.count() < count) {
+ // qDebug() << "append" << idx;
+ QSGItem *item = d->getItem(idx);
+ if (d->model->completePending())
+ item->setZ(idx+1);
+ if (d->currentIndex == idx) {
+ item->setFocus(true);
+ if (QSGPathViewAttached *att = d->attached(item))
+ att->setIsCurrentItem(true);
+ currentVisible = true;
+ d->currentItemOffset = pos;
+ d->currentItem = item;
+ }
+ if (d->items.count() == 0)
+ d->firstIndex = idx;
+ d->items.append(item);
+ d->updateItem(item, pos);
+ if (d->model->completePending())
+ d->model->completeItem();
+ ++idx;
+ if (idx >= d->modelCount)
+ idx = 0;
+ pos = d->positionOfIndex(idx);
+ }
+
+ idx = d->firstIndex - 1;
+ if (idx < 0)
+ idx = d->modelCount - 1;
+ pos = d->positionOfIndex(idx);
+ while (pos >= 0.0 && pos < startPos) {
+ // qDebug() << "prepend" << idx;
+ QSGItem *item = d->getItem(idx);
+ if (d->model->completePending())
+ item->setZ(idx+1);
+ if (d->currentIndex == idx) {
+ item->setFocus(true);
+ if (QSGPathViewAttached *att = d->attached(item))
+ att->setIsCurrentItem(true);
+ currentVisible = true;
+ d->currentItemOffset = pos;
+ d->currentItem = item;
+ }
+ d->items.prepend(item);
+ d->updateItem(item, pos);
+ if (d->model->completePending())
+ d->model->completeItem();
+ d->firstIndex = idx;
+ idx = d->firstIndex - 1;
+ if (idx < 0)
+ idx = d->modelCount - 1;
+ pos = d->positionOfIndex(idx);
+ }
+ }
+ }
+
+ if (!currentVisible)
+ d->currentItemOffset = 1.0;
+
+ if (d->highlightItem && d->haveHighlightRange && d->highlightRangeMode == QSGPathView::StrictlyEnforceRange) {
+ d->updateItem(d->highlightItem, d->highlightRangeStart);
+ if (QSGPathViewAttached *att = d->attached(d->highlightItem))
+ att->setOnPath(true);
+ } else if (d->highlightItem && d->moveReason != QSGPathViewPrivate::SetIndex) {
+ d->updateItem(d->highlightItem, d->currentItemOffset);
+ if (QSGPathViewAttached *att = d->attached(d->highlightItem))
+ att->setOnPath(currentVisible);
+ }
+ while (d->itemCache.count())
+ d->releaseItem(d->itemCache.takeLast());
+}
+
+void QSGPathView::itemsInserted(int modelIndex, int count)
+{
+ //XXX support animated insertion
+ Q_D(QSGPathView);
+ if (!d->isValid() || !isComponentComplete())
+ return;
+
+ if (d->modelCount) {
+ d->itemCache += d->items;
+ d->items.clear();
+ if (modelIndex <= d->currentIndex) {
+ d->currentIndex += count;
+ emit currentIndexChanged();
+ } else if (d->offset != 0) {
+ d->offset += count;
+ d->offsetAdj += count;
+ }
+ }
+
+ d->modelCount += count;
+ if (d->flicking || d->moving) {
+ d->regenerate();
+ d->updateCurrent();
+ } else {
+ d->firstIndex = -1;
+ d->updateMappedRange();
+ d->scheduleLayout();
+ }
+ emit countChanged();
+}
+
+void QSGPathView::itemsRemoved(int modelIndex, int count)
+{
+ //XXX support animated removal
+ Q_D(QSGPathView);
+ if (!d->model || !d->modelCount || !d->model->isValid() || !d->path || !isComponentComplete())
+ return;
+
+ // fix current
+ bool currentChanged = false;
+ if (d->currentIndex >= modelIndex + count) {
+ d->currentIndex -= count;
+ currentChanged = true;
+ } else if (d->currentIndex >= modelIndex && d->currentIndex < modelIndex + count) {
+ // current item has been removed.
+ d->currentIndex = qMin(modelIndex, d->modelCount-count-1);
+ if (d->currentItem) {
+ if (QSGPathViewAttached *att = d->attached(d->currentItem))
+ att->setIsCurrentItem(true);
+ }
+ currentChanged = true;
+ }
+
+ d->itemCache += d->items;
+ d->items.clear();
+
+ bool changedOffset = false;
+ if (modelIndex > d->currentIndex) {
+ if (d->offset >= count) {
+ changedOffset = true;
+ d->offset -= count;
+ d->offsetAdj -= count;
+ }
+ }
+
+ d->modelCount -= count;
+ if (!d->modelCount) {
+ while (d->itemCache.count())
+ d->releaseItem(d->itemCache.takeLast());
+ d->offset = 0;
+ changedOffset = true;
+ d->tl.reset(d->moveOffset);
+ } else {
+ d->regenerate();
+ d->updateCurrent();
+ if (!d->flicking && !d->moving && d->haveHighlightRange && d->highlightRangeMode == QSGPathView::StrictlyEnforceRange)
+ d->snapToCurrent();
+ }
+ if (changedOffset)
+ emit offsetChanged();
+ if (currentChanged)
+ emit currentIndexChanged();
+ emit countChanged();
+}
+
+void QSGPathView::itemsMoved(int /*from*/, int /*to*/, int /*count*/)
+{
+ Q_D(QSGPathView);
+ if (!d->isValid() || !isComponentComplete())
+ return;
+
+ QList<QSGItem *> removedItems = d->items;
+ d->items.clear();
+ d->regenerate();
+ while (removedItems.count())
+ d->releaseItem(removedItems.takeLast());
+
+ // Fix current index
+ if (d->currentIndex >= 0 && d->currentItem) {
+ int oldCurrent = d->currentIndex;
+ d->currentIndex = d->model->indexOf(d->currentItem, this);
+ if (oldCurrent != d->currentIndex)
+ emit currentIndexChanged();
+ }
+ d->updateCurrent();
+}
+
+void QSGPathView::modelReset()
+{
+ Q_D(QSGPathView);
+ d->modelCount = d->model->count();
+ d->regenerate();
+ emit countChanged();
+}
+
+void QSGPathView::createdItem(int index, QSGItem *item)
+{
+ Q_D(QSGPathView);
+ if (d->requestedIndex != index) {
+ if (!d->attType) {
+ // pre-create one metatype to share with all attached objects
+ d->attType = new QDeclarativeOpenMetaObjectType(&QSGPathViewAttached::staticMetaObject, qmlEngine(this));
+ foreach(const QString &attr, d->path->attributes())
+ d->attType->createProperty(attr.toUtf8());
+ }
+ qPathViewAttachedType = d->attType;
+ QSGPathViewAttached *att = static_cast<QSGPathViewAttached *>(qmlAttachedPropertiesObject<QSGPathView>(item));
+ qPathViewAttachedType = 0;
+ if (att) {
+ att->m_view = this;
+ att->setOnPath(false);
+ }
+ item->setParentItem(this);
+ d->updateItem(item, index < d->firstIndex ? 0.0 : 1.0);
+ }
+}
+
+void QSGPathView::destroyingItem(QSGItem *item)
+{
+ Q_UNUSED(item);
+}
+
+void QSGPathView::ticked()
+{
+ Q_D(QSGPathView);
+ d->updateCurrent();
+}
+
+void QSGPathView::movementEnding()
+{
+ Q_D(QSGPathView);
+ if (d->flicking) {
+ d->flicking = false;
+ emit flickingChanged();
+ emit flickEnded();
+ }
+ if (d->moving && !d->stealMouse) {
+ d->moving = false;
+ emit movingChanged();
+ emit movementEnded();
+ }
+}
+
+// find the item closest to the snap position
+int QSGPathViewPrivate::calcCurrentIndex()
+{
+ int current = -1;
+ if (modelCount && model && items.count()) {
+ offset = qmlMod(offset, modelCount);
+ if (offset < 0)
+ offset += modelCount;
+ current = qRound(qAbs(qmlMod(modelCount - offset, modelCount)));
+ current = current % modelCount;
+ }
+
+ return current;
+}
+
+void QSGPathViewPrivate::updateCurrent()
+{
+ Q_Q(QSGPathView);
+ if (moveReason != Mouse)
+ return;
+ if (!modelCount || !haveHighlightRange || highlightRangeMode != QSGPathView::StrictlyEnforceRange)
+ return;
+
+ int idx = calcCurrentIndex();
+ if (model && idx != currentIndex) {
+ int itemIndex = (currentIndex - firstIndex + modelCount) % modelCount;
+ if (itemIndex < items.count()) {
+ if (QSGItem *item = items.at(itemIndex)) {
+ if (QSGPathViewAttached *att = attached(item))
+ att->setIsCurrentItem(false);
+ }
+ }
+ currentIndex = idx;
+ currentItem = 0;
+ itemIndex = (idx - firstIndex + modelCount) % modelCount;
+ if (itemIndex < items.count()) {
+ currentItem = items.at(itemIndex);
+ currentItem->setFocus(true);
+ if (QSGPathViewAttached *att = attached(currentItem))
+ att->setIsCurrentItem(true);
+ }
+ emit q->currentIndexChanged();
+ }
+}
+
+void QSGPathViewPrivate::fixOffsetCallback(void *d)
+{
+ ((QSGPathViewPrivate *)d)->fixOffset();
+}
+
+void QSGPathViewPrivate::fixOffset()
+{
+ Q_Q(QSGPathView);
+ if (model && items.count()) {
+ if (haveHighlightRange && highlightRangeMode == QSGPathView::StrictlyEnforceRange) {
+ int curr = calcCurrentIndex();
+ if (curr != currentIndex)
+ q->setCurrentIndex(curr);
+ else
+ snapToCurrent();
+ }
+ }
+}
+
+void QSGPathViewPrivate::snapToCurrent()
+{
+ if (!model || modelCount <= 0)
+ return;
+
+ qreal targetOffset = qmlMod(modelCount - currentIndex, modelCount);
+
+ moveReason = Other;
+ offsetAdj = 0.0;
+ tl.reset(moveOffset);
+ moveOffset.setValue(offset);
+
+ const int duration = highlightMoveDuration;
+
+ if (moveDirection == Positive || (moveDirection == Shortest && targetOffset - offset > modelCount/2)) {
+ qreal distance = modelCount - targetOffset + offset;
+ if (targetOffset > moveOffset) {
+ tl.move(moveOffset, 0.0, QEasingCurve(QEasingCurve::InQuad), int(duration * offset / distance));
+ tl.set(moveOffset, modelCount);
+ tl.move(moveOffset, targetOffset, QEasingCurve(offset == 0.0 ? QEasingCurve::InOutQuad : QEasingCurve::OutQuad), int(duration * (modelCount-targetOffset) / distance));
+ } else {
+ tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
+ }
+ } else if (moveDirection == Negative || targetOffset - offset <= -modelCount/2) {
+ qreal distance = modelCount - offset + targetOffset;
+ if (targetOffset < moveOffset) {
+ tl.move(moveOffset, modelCount, QEasingCurve(targetOffset == 0 ? QEasingCurve::InOutQuad : QEasingCurve::InQuad), int(duration * (modelCount-offset) / distance));
+ tl.set(moveOffset, 0.0);
+ tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::OutQuad), int(duration * targetOffset / distance));
+ } else {
+ tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
+ }
+ } else {
+ tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
+ }
+ moveDirection = Shortest;
+}
+
+QSGPathViewAttached *QSGPathView::qmlAttachedProperties(QObject *obj)
+{
+ return new QSGPathViewAttached(obj);
+}
+
+QT_END_NAMESPACE
+