source: trunk/src/declarative/graphicsitems/qdeclarativepathview.cpp@ 1111

Last change on this file since 1111 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 55.1 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation (qt-info@nokia.com)
6**
7** This file is part of the QtDeclarative module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at qt-info@nokia.com.
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "private/qdeclarativepathview_p.h"
43#include "private/qdeclarativepathview_p_p.h"
44
45#include <qdeclarativestate_p.h>
46#include <qdeclarativeopenmetaobject_p.h>
47#include <QDebug>
48#include <QEvent>
49#include <qlistmodelinterface_p.h>
50#include <QGraphicsSceneEvent>
51
52#include <qmath.h>
53#include <math.h>
54
55QT_BEGIN_NAMESPACE
56
57inline qreal qmlMod(qreal x, qreal y)
58{
59#ifdef QT_USE_MATH_H_FLOATS
60 if(sizeof(qreal) == sizeof(float))
61 return fmodf(float(x), float(y));
62 else
63#endif
64 return fmod(x, y);
65}
66
67static QDeclarativeOpenMetaObjectType *qPathViewAttachedType = 0;
68
69QDeclarativePathViewAttached::QDeclarativePathViewAttached(QObject *parent)
70: QObject(parent), m_percent(-1), m_view(0), m_onPath(false), m_isCurrent(false)
71{
72 if (qPathViewAttachedType) {
73 m_metaobject = new QDeclarativeOpenMetaObject(this, qPathViewAttachedType);
74 m_metaobject->setCached(true);
75 } else {
76 m_metaobject = new QDeclarativeOpenMetaObject(this);
77 }
78}
79
80QDeclarativePathViewAttached::~QDeclarativePathViewAttached()
81{
82}
83
84QVariant QDeclarativePathViewAttached::value(const QByteArray &name) const
85{
86 return m_metaobject->value(name);
87}
88void QDeclarativePathViewAttached::setValue(const QByteArray &name, const QVariant &val)
89{
90 m_metaobject->setValue(name, val);
91}
92
93
94void QDeclarativePathViewPrivate::init()
95{
96 Q_Q(QDeclarativePathView);
97 offset = 0;
98 q->setAcceptedMouseButtons(Qt::LeftButton);
99 q->setFlag(QGraphicsItem::ItemIsFocusScope);
100 q->setFiltersChildEvents(true);
101 q->connect(&tl, SIGNAL(updated()), q, SLOT(ticked()));
102 lastPosTime.invalidate();
103 static int timelineCompletedIdx = -1;
104 static int movementEndingIdx = -1;
105 if (timelineCompletedIdx == -1) {
106 timelineCompletedIdx = QDeclarativeTimeLine::staticMetaObject.indexOfSignal("completed()");
107 movementEndingIdx = QDeclarativePathView::staticMetaObject.indexOfSlot("movementEnding()");
108 }
109 QMetaObject::connect(&tl, timelineCompletedIdx,
110 q, movementEndingIdx, Qt::DirectConnection);
111}
112
113QDeclarativeItem *QDeclarativePathViewPrivate::getItem(int modelIndex)
114{
115 Q_Q(QDeclarativePathView);
116 requestedIndex = modelIndex;
117 QDeclarativeItem *item = model->item(modelIndex, false);
118 if (item) {
119 if (!attType) {
120 // pre-create one metatype to share with all attached objects
121 attType = new QDeclarativeOpenMetaObjectType(&QDeclarativePathViewAttached::staticMetaObject, qmlEngine(q));
122 foreach(const QString &attr, path->attributes())
123 attType->createProperty(attr.toUtf8());
124 }
125 qPathViewAttachedType = attType;
126 QDeclarativePathViewAttached *att = static_cast<QDeclarativePathViewAttached *>(qmlAttachedPropertiesObject<QDeclarativePathView>(item));
127 qPathViewAttachedType = 0;
128 if (att) {
129 att->m_view = q;
130 att->setOnPath(true);
131 }
132 item->setParentItem(q);
133 QDeclarativeItemPrivate *itemPrivate = static_cast<QDeclarativeItemPrivate*>(QGraphicsItemPrivate::get(item));
134 itemPrivate->addItemChangeListener(this, QDeclarativeItemPrivate::Geometry);
135 }
136 requestedIndex = -1;
137 return item;
138}
139
140void QDeclarativePathViewPrivate::releaseItem(QDeclarativeItem *item)
141{
142 if (!item || !model)
143 return;
144 QDeclarativeItemPrivate *itemPrivate = static_cast<QDeclarativeItemPrivate*>(QGraphicsItemPrivate::get(item));
145 itemPrivate->removeItemChangeListener(this, QDeclarativeItemPrivate::Geometry);
146 if (model->release(item) == 0) {
147 // item was not destroyed, and we no longer reference it.
148 if (QDeclarativePathViewAttached *att = attached(item))
149 att->setOnPath(false);
150 }
151}
152
153QDeclarativePathViewAttached *QDeclarativePathViewPrivate::attached(QDeclarativeItem *item)
154{
155 return static_cast<QDeclarativePathViewAttached *>(qmlAttachedPropertiesObject<QDeclarativePathView>(item, false));
156}
157
158void QDeclarativePathViewPrivate::clear()
159{
160 for (int i=0; i<items.count(); i++){
161 QDeclarativeItem *p = items[i];
162 releaseItem(p);
163 }
164 items.clear();
165}
166
167void QDeclarativePathViewPrivate::updateMappedRange()
168{
169 if (model && pathItems != -1 && pathItems < modelCount)
170 mappedRange = qreal(pathItems)/modelCount;
171 else
172 mappedRange = 1.0;
173}
174
175qreal QDeclarativePathViewPrivate::positionOfIndex(qreal index) const
176{
177 qreal pos = -1.0;
178
179 if (model && index >= 0 && index < modelCount) {
180 qreal start = 0.0;
181 if (haveHighlightRange && highlightRangeMode != QDeclarativePathView::NoHighlightRange)
182 start = highlightRangeStart;
183 qreal globalPos = index + offset;
184 globalPos = qmlMod(globalPos, qreal(modelCount)) / modelCount;
185 if (pathItems != -1 && pathItems < modelCount) {
186 globalPos += start * mappedRange;
187 globalPos = qmlMod(globalPos, 1.0);
188 if (globalPos < mappedRange)
189 pos = globalPos / mappedRange;
190 } else {
191 pos = qmlMod(globalPos + start, 1.0);
192 }
193 }
194
195 return pos;
196}
197
198void QDeclarativePathViewPrivate::createHighlight()
199{
200 Q_Q(QDeclarativePathView);
201 if (!q->isComponentComplete())
202 return;
203
204 bool changed = false;
205 if (highlightItem) {
206 delete highlightItem;
207 highlightItem = 0;
208 changed = true;
209 }
210
211 QDeclarativeItem *item = 0;
212 if (highlightComponent) {
213 QDeclarativeContext *highlightContext = new QDeclarativeContext(qmlContext(q));
214 QObject *nobj = highlightComponent->create(highlightContext);
215 if (nobj) {
216 QDeclarative_setParent_noEvent(highlightContext, nobj);
217 item = qobject_cast<QDeclarativeItem *>(nobj);
218 if (!item)
219 delete nobj;
220 } else {
221 delete highlightContext;
222 }
223 } else {
224 item = new QDeclarativeItem;
225 }
226 if (item) {
227 QDeclarative_setParent_noEvent(item, q);
228 item->setParentItem(q);
229 highlightItem = item;
230 changed = true;
231 }
232 if (changed)
233 emit q->highlightItemChanged();
234}
235
236void QDeclarativePathViewPrivate::updateHighlight()
237{
238 Q_Q(QDeclarativePathView);
239 if (!q->isComponentComplete() || !isValid())
240 return;
241 if (highlightItem) {
242 if (haveHighlightRange && highlightRangeMode == QDeclarativePathView::StrictlyEnforceRange) {
243 updateItem(highlightItem, highlightRangeStart);
244 } else {
245 qreal target = currentIndex;
246
247 offsetAdj = 0.0;
248 tl.reset(moveHighlight);
249 moveHighlight.setValue(highlightPosition);
250
251 const int duration = highlightMoveDuration;
252
253 if (target - highlightPosition > modelCount/2) {
254 highlightUp = false;
255 qreal distance = modelCount - target + highlightPosition;
256 tl.move(moveHighlight, 0.0, QEasingCurve(QEasingCurve::InQuad), int(duration * highlightPosition / distance));
257 tl.set(moveHighlight, modelCount-0.01);
258 tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::OutQuad), int(duration * (modelCount-target) / distance));
259 } else if (target - highlightPosition <= -modelCount/2) {
260 highlightUp = true;
261 qreal distance = modelCount - highlightPosition + target;
262 tl.move(moveHighlight, modelCount-0.01, QEasingCurve(QEasingCurve::InQuad), int(duration * (modelCount-highlightPosition) / distance));
263 tl.set(moveHighlight, 0.0);
264 tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::OutQuad), int(duration * target / distance));
265 } else {
266 highlightUp = highlightPosition - target < 0;
267 tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::InOutQuad), duration);
268 }
269 }
270 }
271}
272
273void QDeclarativePathViewPrivate::setHighlightPosition(qreal pos)
274{
275 if (pos != highlightPosition) {
276 qreal start = 0.0;
277 qreal end = 1.0;
278 if (haveHighlightRange && highlightRangeMode != QDeclarativePathView::NoHighlightRange) {
279 start = highlightRangeStart;
280 end = highlightRangeEnd;
281 }
282
283 qreal range = qreal(modelCount);
284 // calc normalized position of highlight relative to offset
285 qreal relativeHighlight = qmlMod(pos + offset, range) / range;
286
287 if (!highlightUp && relativeHighlight > end * mappedRange) {
288 qreal diff = 1.0 - relativeHighlight;
289 setOffset(offset + diff * range);
290 } else if (highlightUp && relativeHighlight >= (end - start) * mappedRange) {
291 qreal diff = relativeHighlight - (end - start) * mappedRange;
292 setOffset(offset - diff * range - 0.00001);
293 }
294
295 highlightPosition = pos;
296 qreal pathPos = positionOfIndex(pos);
297 updateItem(highlightItem, pathPos);
298 if (QDeclarativePathViewAttached *att = attached(highlightItem))
299 att->setOnPath(pathPos != -1.0);
300 }
301}
302
303void QDeclarativePathView::pathUpdated()
304{
305 Q_D(QDeclarativePathView);
306 QList<QDeclarativeItem*>::iterator it = d->items.begin();
307 while (it != d->items.end()) {
308 QDeclarativeItem *item = *it;
309 if (QDeclarativePathViewAttached *att = d->attached(item))
310 att->m_percent = -1;
311 ++it;
312 }
313 refill();
314}
315
316void QDeclarativePathViewPrivate::updateItem(QDeclarativeItem *item, qreal percent)
317{
318 if (QDeclarativePathViewAttached *att = attached(item)) {
319 if (qFuzzyCompare(att->m_percent, percent))
320 return;
321 att->m_percent = percent;
322 foreach(const QString &attr, path->attributes())
323 att->setValue(attr.toUtf8(), path->attributeAt(attr, percent));
324 }
325 QPointF pf = path->pointAt(percent);
326 item->setX(qRound(pf.x() - item->width()/2));
327 item->setY(qRound(pf.y() - item->height()/2));
328}
329
330void QDeclarativePathViewPrivate::regenerate()
331{
332 Q_Q(QDeclarativePathView);
333 if (!q->isComponentComplete())
334 return;
335
336 clear();
337
338 if (!isValid())
339 return;
340
341 firstIndex = -1;
342 updateMappedRange();
343 q->refill();
344}
345
346/*!
347 \qmlclass PathView QDeclarativePathView
348 \ingroup qml-view-elements
349 \since 4.7
350 \brief The PathView element lays out model-provided items on a path.
351 \inherits Item
352
353 A PathView displays data from models created from built-in QML elements like ListModel
354 and XmlListModel, or custom model classes defined in C++ that inherit from
355 QAbstractListModel.
356
357 The view has a \l model, which defines the data to be displayed, and
358 a \l delegate, which defines how the data should be displayed.
359 The \l delegate is instantiated for each item on the \l path.
360 The items may be flicked to move them along the path.
361
362 For example, if there is a simple list model defined in a file \c ContactModel.qml like this:
363
364 \snippet doc/src/snippets/declarative/pathview/ContactModel.qml 0
365
366 This data can be represented as a PathView, like this:
367
368 \snippet doc/src/snippets/declarative/pathview/pathview.qml 0
369
370 \image pathview.gif
371
372 (Note the above example uses PathAttribute to scale and modify the
373 opacity of the items as they rotate. This additional code can be seen in the
374 PathAttribute documentation.)
375
376 PathView does not automatically handle keyboard navigation. This is because
377 the keys to use for navigation will depend upon the shape of the path. Navigation
378 can be added quite simply by setting \c focus to \c true and calling
379 \l decrementCurrentIndex() or \l incrementCurrentIndex(), for example to navigate
380 using the left and right arrow keys:
381
382 \code
383 PathView {
384 ...
385 focus: true
386 Keys.onLeftPressed: decrementCurrentIndex()
387 Keys.onRightPressed: incrementCurrentIndex()
388 }
389 \endcode
390
391 The path view itself is a focus scope (see \l{qmlfocus#Acquiring Focus and Focus Scopes}{the focus documentation page} for more details).
392
393 Delegates are instantiated as needed and may be destroyed at any time.
394 State should \e never be stored in a delegate.
395
396 PathView attaches a number of properties to the root item of the delegate, for example
397 \c {PathView.isCurrentItem}. In the following example, the root delegate item can access
398 this attached property directly as \c PathView.isCurrentItem, while the child
399 \c nameText object must refer to this property as \c wrapper.PathView.isCurrentItem.
400
401 \snippet doc/src/snippets/declarative/pathview/pathview.qml 1
402
403 \bold Note that views do not enable \e clip automatically. If the view
404 is not clipped by another item or the screen, it will be necessary
405 to set \e {clip: true} in order to have the out of view items clipped
406 nicely.
407
408 \sa Path, {declarative/modelviews/pathview}{PathView example}
409*/
410
411QDeclarativePathView::QDeclarativePathView(QDeclarativeItem *parent)
412 : QDeclarativeItem(*(new QDeclarativePathViewPrivate), parent)
413{
414 Q_D(QDeclarativePathView);
415 d->init();
416}
417
418QDeclarativePathView::~QDeclarativePathView()
419{
420 Q_D(QDeclarativePathView);
421 d->clear();
422 if (d->attType)
423 d->attType->release();
424 if (d->ownModel)
425 delete d->model;
426}
427
428/*!
429 \qmlattachedproperty PathView PathView::view
430 This attached property holds the view that manages this delegate instance.
431
432 It is attached to each instance of the delegate.
433*/
434
435/*!
436 \qmlattachedproperty bool PathView::onPath
437 This attached property holds whether the item is currently on the path.
438
439 If a pathItemCount has been set, it is possible that some items may
440 be instantiated, but not considered to be currently on the path.
441 Usually, these items would be set invisible, for example:
442
443 \qml
444 Component {
445 Rectangle {
446 visible: PathView.onPath
447 ...
448 }
449 }
450 \endqml
451
452 It is attached to each instance of the delegate.
453*/
454
455/*!
456 \qmlattachedproperty bool PathView::isCurrentItem
457 This attached property is true if this delegate is the current item; otherwise false.
458
459 It is attached to each instance of the delegate.
460
461 This property may be used to adjust the appearance of the current item.
462
463 \snippet doc/src/snippets/declarative/pathview/pathview.qml 1
464*/
465
466/*!
467 \qmlproperty model PathView::model
468 This property holds the model providing data for the view.
469
470 The model provides a set of data that is used to create the items for the view.
471 For large or dynamic datasets the model is usually provided by a C++ model object.
472 Models can also be created directly in QML, using the ListModel element.
473
474 \sa {qmlmodels}{Data Models}
475*/
476QVariant QDeclarativePathView::model() const
477{
478 Q_D(const QDeclarativePathView);
479 return d->modelVariant;
480}
481
482void QDeclarativePathView::setModel(const QVariant &model)
483{
484 Q_D(QDeclarativePathView);
485 if (d->modelVariant == model)
486 return;
487
488 if (d->model) {
489 disconnect(d->model, SIGNAL(itemsInserted(int,int)), this, SLOT(itemsInserted(int,int)));
490 disconnect(d->model, SIGNAL(itemsRemoved(int,int)), this, SLOT(itemsRemoved(int,int)));
491 disconnect(d->model, SIGNAL(itemsMoved(int,int,int)), this, SLOT(itemsMoved(int,int,int)));
492 disconnect(d->model, SIGNAL(modelReset()), this, SLOT(modelReset()));
493 disconnect(d->model, SIGNAL(createdItem(int,QDeclarativeItem*)), this, SLOT(createdItem(int,QDeclarativeItem*)));
494 for (int i=0; i<d->items.count(); i++){
495 QDeclarativeItem *p = d->items[i];
496 d->model->release(p);
497 }
498 d->items.clear();
499 }
500
501 d->modelVariant = model;
502 QObject *object = qvariant_cast<QObject*>(model);
503 QDeclarativeVisualModel *vim = 0;
504 if (object && (vim = qobject_cast<QDeclarativeVisualModel *>(object))) {
505 if (d->ownModel) {
506 delete d->model;
507 d->ownModel = false;
508 }
509 d->model = vim;
510 } else {
511 if (!d->ownModel) {
512 d->model = new QDeclarativeVisualDataModel(qmlContext(this), this);
513 d->ownModel = true;
514 }
515 if (QDeclarativeVisualDataModel *dataModel = qobject_cast<QDeclarativeVisualDataModel*>(d->model))
516 dataModel->setModel(model);
517 }
518 d->modelCount = 0;
519 if (d->model) {
520 connect(d->model, SIGNAL(itemsInserted(int,int)), this, SLOT(itemsInserted(int,int)));
521 connect(d->model, SIGNAL(itemsRemoved(int,int)), this, SLOT(itemsRemoved(int,int)));
522 connect(d->model, SIGNAL(itemsMoved(int,int,int)), this, SLOT(itemsMoved(int,int,int)));
523 connect(d->model, SIGNAL(modelReset()), this, SLOT(modelReset()));
524 connect(d->model, SIGNAL(createdItem(int,QDeclarativeItem*)), this, SLOT(createdItem(int,QDeclarativeItem*)));
525 d->modelCount = d->model->count();
526 if (d->model->count())
527 d->offset = qmlMod(d->offset, qreal(d->model->count()));
528 if (d->offset < 0)
529 d->offset = d->model->count() + d->offset;
530}
531 d->regenerate();
532 d->fixOffset();
533 emit countChanged();
534 emit modelChanged();
535}
536
537/*!
538 \qmlproperty int PathView::count
539 This property holds the number of items in the model.
540*/
541int QDeclarativePathView::count() const
542{
543 Q_D(const QDeclarativePathView);
544 return d->model ? d->modelCount : 0;
545}
546
547/*!
548 \qmlproperty Path PathView::path
549 This property holds the path used to lay out the items.
550 For more information see the \l Path documentation.
551*/
552QDeclarativePath *QDeclarativePathView::path() const
553{
554 Q_D(const QDeclarativePathView);
555 return d->path;
556}
557
558void QDeclarativePathView::setPath(QDeclarativePath *path)
559{
560 Q_D(QDeclarativePathView);
561 if (d->path == path)
562 return;
563 if (d->path)
564 disconnect(d->path, SIGNAL(changed()), this, SLOT(pathUpdated()));
565 d->path = path;
566 connect(d->path, SIGNAL(changed()), this, SLOT(pathUpdated()));
567 if (d->isValid() && isComponentComplete()) {
568 d->clear();
569 if (d->attType) {
570 d->attType->release();
571 d->attType = 0;
572 }
573 d->regenerate();
574 }
575 emit pathChanged();
576}
577
578/*!
579 \qmlproperty int PathView::currentIndex
580 This property holds the index of the current item.
581*/
582int QDeclarativePathView::currentIndex() const
583{
584 Q_D(const QDeclarativePathView);
585 return d->currentIndex;
586}
587
588void QDeclarativePathView::setCurrentIndex(int idx)
589{
590 Q_D(QDeclarativePathView);
591 if (d->model && d->modelCount)
592 idx = qAbs(idx % d->modelCount);
593 if (d->model && idx != d->currentIndex) {
594 if (d->modelCount) {
595 int itemIndex = (d->currentIndex - d->firstIndex + d->modelCount) % d->modelCount;
596 if (itemIndex < d->items.count()) {
597 if (QDeclarativeItem *item = d->items.at(itemIndex)) {
598 if (QDeclarativePathViewAttached *att = d->attached(item))
599 att->setIsCurrentItem(false);
600 }
601 }
602 }
603 d->currentItem = 0;
604 d->moveReason = QDeclarativePathViewPrivate::SetIndex;
605 d->currentIndex = idx;
606 if (d->modelCount) {
607 if (d->haveHighlightRange && d->highlightRangeMode == QDeclarativePathView::StrictlyEnforceRange)
608 d->snapToCurrent();
609 int itemIndex = (idx - d->firstIndex + d->modelCount) % d->modelCount;
610 if (itemIndex < d->items.count()) {
611 d->currentItem = d->items.at(itemIndex);
612 d->currentItem->setFocus(true);
613 if (QDeclarativePathViewAttached *att = d->attached(d->currentItem))
614 att->setIsCurrentItem(true);
615 }
616 d->currentItemOffset = d->positionOfIndex(d->currentIndex);
617 d->updateHighlight();
618 }
619 emit currentIndexChanged();
620 }
621}
622
623/*!
624 \qmlmethod PathView::incrementCurrentIndex()
625
626 Increments the current index.
627
628 \bold Note: methods should only be called after the Component has completed.
629*/
630void QDeclarativePathView::incrementCurrentIndex()
631{
632 Q_D(QDeclarativePathView);
633 d->moveDirection = QDeclarativePathViewPrivate::Positive;
634 setCurrentIndex(currentIndex()+1);
635}
636
637
638/*!
639 \qmlmethod PathView::decrementCurrentIndex()
640
641 Decrements the current index.
642
643 \bold Note: methods should only be called after the Component has completed.
644*/
645void QDeclarativePathView::decrementCurrentIndex()
646{
647 Q_D(QDeclarativePathView);
648 if (d->model && d->modelCount) {
649 int idx = currentIndex()-1;
650 if (idx < 0)
651 idx = d->modelCount - 1;
652 d->moveDirection = QDeclarativePathViewPrivate::Negative;
653 setCurrentIndex(idx);
654 }
655}
656
657/*!
658 \qmlproperty real PathView::offset
659
660 The offset specifies how far along the path the items are from their initial positions.
661 This is a real number that ranges from 0.0 to the count of items in the model.
662*/
663qreal QDeclarativePathView::offset() const
664{
665 Q_D(const QDeclarativePathView);
666 return d->offset;
667}
668
669void QDeclarativePathView::setOffset(qreal offset)
670{
671 Q_D(QDeclarativePathView);
672 d->setOffset(offset);
673 d->updateCurrent();
674}
675
676void QDeclarativePathViewPrivate::setOffset(qreal o)
677{
678 Q_Q(QDeclarativePathView);
679 if (offset != o) {
680 if (isValid() && q->isComponentComplete()) {
681 offset = qmlMod(o, qreal(modelCount));
682 if (offset < 0)
683 offset += qreal(modelCount);
684 q->refill();
685 } else {
686 offset = o;
687 }
688 emit q->offsetChanged();
689 }
690}
691
692void QDeclarativePathViewPrivate::setAdjustedOffset(qreal o)
693{
694 setOffset(o+offsetAdj);
695}
696
697/*!
698 \qmlproperty Component PathView::highlight
699 This property holds the component to use as the highlight.
700
701 An instance of the highlight component will be created for each view.
702 The geometry of the resultant component instance will be managed by the view
703 so as to stay with the current item.
704
705 The below example demonstrates how to make a simple highlight. Note the use
706 of the \l{PathView::onPath}{PathView.onPath} attached property to ensure that
707 the highlight is hidden when flicked away from the path.
708
709 \code
710 Component {
711 Rectangle {
712 visible: PathView.onPath
713 ...
714 }
715 }
716 \endcode
717
718 \sa highlightItem, highlightRangeMode
719*/
720
721QDeclarativeComponent *QDeclarativePathView::highlight() const
722{
723 Q_D(const QDeclarativePathView);
724 return d->highlightComponent;
725}
726
727void QDeclarativePathView::setHighlight(QDeclarativeComponent *highlight)
728{
729 Q_D(QDeclarativePathView);
730 if (highlight != d->highlightComponent) {
731 d->highlightComponent = highlight;
732 d->createHighlight();
733 d->updateHighlight();
734 emit highlightChanged();
735 }
736}
737
738/*!
739 \qmlproperty Item PathView::highlightItem
740
741 \c highlightItem holds the highlight item, which was created
742 from the \l highlight component.
743
744 \sa highlight
745*/
746QDeclarativeItem *QDeclarativePathView::highlightItem()
747{
748 Q_D(const QDeclarativePathView);
749 return d->highlightItem;
750}
751/*!
752 \qmlproperty real PathView::preferredHighlightBegin
753 \qmlproperty real PathView::preferredHighlightEnd
754 \qmlproperty enumeration PathView::highlightRangeMode
755
756 These properties set the preferred range of the highlight (current item)
757 within the view. The preferred values must be in the range 0.0-1.0.
758
759 If highlightRangeMode is set to \e PathView.NoHighlightRange
760
761 If highlightRangeMode is set to \e PathView.ApplyRange the view will
762 attempt to maintain the highlight within the range, however
763 the highlight can move outside of the range at the ends of the path
764 or due to a mouse interaction.
765
766 If highlightRangeMode is set to \e PathView.StrictlyEnforceRange the highlight will never
767 move outside of the range. This means that the current item will change
768 if a keyboard or mouse action would cause the highlight to move
769 outside of the range.
770
771 Note that this is the correct way to influence where the
772 current item ends up when the view moves. For example, if you want the
773 currently selected item to be in the middle of the path, then set the
774 highlight range to be 0.5,0.5 and highlightRangeMode to PathView.StrictlyEnforceRange.
775 Then, when the path scrolls,
776 the currently selected item will be the item at that position. This also applies to
777 when the currently selected item changes - it will scroll to within the preferred
778 highlight range. Furthermore, the behaviour of the current item index will occur
779 whether or not a highlight exists.
780
781 The default value is \e PathView.StrictlyEnforceRange.
782
783 Note that a valid range requires preferredHighlightEnd to be greater
784 than or equal to preferredHighlightBegin.
785*/
786qreal QDeclarativePathView::preferredHighlightBegin() const
787{
788 Q_D(const QDeclarativePathView);
789 return d->highlightRangeStart;
790}
791
792void QDeclarativePathView::setPreferredHighlightBegin(qreal start)
793{
794 Q_D(QDeclarativePathView);
795 if (d->highlightRangeStart == start || start < 0 || start > 1.0)
796 return;
797 d->highlightRangeStart = start;
798 d->haveHighlightRange = d->highlightRangeMode != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd;
799 emit preferredHighlightBeginChanged();
800}
801
802qreal QDeclarativePathView::preferredHighlightEnd() const
803{
804 Q_D(const QDeclarativePathView);
805 return d->highlightRangeEnd;
806}
807
808void QDeclarativePathView::setPreferredHighlightEnd(qreal end)
809{
810 Q_D(QDeclarativePathView);
811 if (d->highlightRangeEnd == end || end < 0 || end > 1.0)
812 return;
813 d->highlightRangeEnd = end;
814 d->haveHighlightRange = d->highlightRangeMode != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd;
815 emit preferredHighlightEndChanged();
816}
817
818QDeclarativePathView::HighlightRangeMode QDeclarativePathView::highlightRangeMode() const
819{
820 Q_D(const QDeclarativePathView);
821 return d->highlightRangeMode;
822}
823
824void QDeclarativePathView::setHighlightRangeMode(HighlightRangeMode mode)
825{
826 Q_D(QDeclarativePathView);
827 if (d->highlightRangeMode == mode)
828 return;
829 d->highlightRangeMode = mode;
830 d->haveHighlightRange = d->highlightRangeMode != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd;
831 emit highlightRangeModeChanged();
832}
833
834
835/*!
836 \qmlproperty int PathView::highlightMoveDuration
837 This property holds the move animation duration of the highlight delegate.
838
839 If the highlightRangeMode is StrictlyEnforceRange then this property
840 determines the speed that the items move along the path.
841
842 The default value for the duration is 300ms.
843*/
844int QDeclarativePathView::highlightMoveDuration() const
845{
846 Q_D(const QDeclarativePathView);
847 return d->highlightMoveDuration;
848}
849
850void QDeclarativePathView::setHighlightMoveDuration(int duration)
851{
852 Q_D(QDeclarativePathView);
853 if (d->highlightMoveDuration == duration)
854 return;
855 d->highlightMoveDuration = duration;
856 emit highlightMoveDurationChanged();
857}
858
859/*!
860 \qmlproperty real PathView::dragMargin
861 This property holds the maximum distance from the path that initiate mouse dragging.
862
863 By default the path can only be dragged by clicking on an item. If
864 dragMargin is greater than zero, a drag can be initiated by clicking
865 within dragMargin pixels of the path.
866*/
867qreal QDeclarativePathView::dragMargin() const
868{
869 Q_D(const QDeclarativePathView);
870 return d->dragMargin;
871}
872
873void QDeclarativePathView::setDragMargin(qreal dragMargin)
874{
875 Q_D(QDeclarativePathView);
876 if (d->dragMargin == dragMargin)
877 return;
878 d->dragMargin = dragMargin;
879 emit dragMarginChanged();
880}
881
882/*!
883 \qmlproperty real PathView::flickDeceleration
884 This property holds the rate at which a flick will decelerate.
885
886 The default is 100.
887*/
888qreal QDeclarativePathView::flickDeceleration() const
889{
890 Q_D(const QDeclarativePathView);
891 return d->deceleration;
892}
893
894void QDeclarativePathView::setFlickDeceleration(qreal dec)
895{
896 Q_D(QDeclarativePathView);
897 if (d->deceleration == dec)
898 return;
899 d->deceleration = dec;
900 emit flickDecelerationChanged();
901}
902
903/*!
904 \qmlproperty bool PathView::interactive
905
906 A user cannot drag or flick a PathView that is not interactive.
907
908 This property is useful for temporarily disabling flicking. This allows
909 special interaction with PathView's children.
910*/
911bool QDeclarativePathView::isInteractive() const
912{
913 Q_D(const QDeclarativePathView);
914 return d->interactive;
915}
916
917void QDeclarativePathView::setInteractive(bool interactive)
918{
919 Q_D(QDeclarativePathView);
920 if (interactive != d->interactive) {
921 d->interactive = interactive;
922 if (!interactive)
923 d->tl.clear();
924 emit interactiveChanged();
925 }
926}
927
928/*!
929 \qmlproperty bool PathView::moving
930
931 This property holds whether the view is currently moving
932 due to the user either dragging or flicking the view.
933*/
934bool QDeclarativePathView::isMoving() const
935{
936 Q_D(const QDeclarativePathView);
937 return d->moving;
938}
939
940/*!
941 \qmlproperty bool PathView::flicking
942
943 This property holds whether the view is currently moving
944 due to the user flicking the view.
945*/
946bool QDeclarativePathView::isFlicking() const
947{
948 Q_D(const QDeclarativePathView);
949 return d->flicking;
950}
951
952/*!
953 \qmlsignal PathView::onMovementStarted()
954
955 This handler is called when the view begins moving due to user
956 interaction.
957*/
958
959/*!
960 \qmlsignal PathView::onMovementEnded()
961
962 This handler is called when the view stops moving due to user
963 interaction. If a flick was generated, this handler will
964 be triggered once the flick stops. If a flick was not
965 generated, the handler will be triggered when the
966 user stops dragging - i.e. a mouse or touch release.
967*/
968
969/*!
970 \qmlsignal PathView::onFlickStarted()
971
972 This handler is called when the view is flicked. A flick
973 starts from the point that the mouse or touch is released,
974 while still in motion.
975*/
976
977/*!
978 \qmlsignal PathView::onFlickEnded()
979
980 This handler is called when the view stops moving due to a flick.
981*/
982
983/*!
984 \qmlproperty Component PathView::delegate
985
986 The delegate provides a template defining each item instantiated by the view.
987 The index is exposed as an accessible \c index property. Properties of the
988 model are also available depending upon the type of \l {qmlmodels}{Data Model}.
989
990 The number of elements in the delegate has a direct effect on the
991 flicking performance of the view when pathItemCount is specified. If at all possible, place functionality
992 that is not needed for the normal display of the delegate in a \l Loader which
993 can load additional elements when needed.
994
995 Note that the PathView will layout the items based on the size of the root
996 item in the delegate.
997
998 Here is an example delegate:
999 \snippet doc/src/snippets/declarative/pathview/pathview.qml 1
1000*/
1001QDeclarativeComponent *QDeclarativePathView::delegate() const
1002{
1003 Q_D(const QDeclarativePathView);
1004 if (d->model) {
1005 if (QDeclarativeVisualDataModel *dataModel = qobject_cast<QDeclarativeVisualDataModel*>(d->model))
1006 return dataModel->delegate();
1007 }
1008
1009 return 0;
1010}
1011
1012void QDeclarativePathView::setDelegate(QDeclarativeComponent *delegate)
1013{
1014 Q_D(QDeclarativePathView);
1015 if (delegate == this->delegate())
1016 return;
1017 if (!d->ownModel) {
1018 d->model = new QDeclarativeVisualDataModel(qmlContext(this));
1019 d->ownModel = true;
1020 }
1021 if (QDeclarativeVisualDataModel *dataModel = qobject_cast<QDeclarativeVisualDataModel*>(d->model)) {
1022 dataModel->setDelegate(delegate);
1023 d->modelCount = dataModel->count();
1024 d->regenerate();
1025 emit delegateChanged();
1026 }
1027}
1028
1029/*!
1030 \qmlproperty int PathView::pathItemCount
1031 This property holds the number of items visible on the path at any one time.
1032*/
1033int QDeclarativePathView::pathItemCount() const
1034{
1035 Q_D(const QDeclarativePathView);
1036 return d->pathItems;
1037}
1038
1039void QDeclarativePathView::setPathItemCount(int i)
1040{
1041 Q_D(QDeclarativePathView);
1042 if (i == d->pathItems)
1043 return;
1044 if (i < 1)
1045 i = 1;
1046 d->pathItems = i;
1047 d->updateMappedRange();
1048 if (d->isValid() && isComponentComplete()) {
1049 d->regenerate();
1050 }
1051 emit pathItemCountChanged();
1052}
1053
1054QPointF QDeclarativePathViewPrivate::pointNear(const QPointF &point, qreal *nearPercent) const
1055{
1056 //XXX maybe do recursively at increasing resolution.
1057 qreal mindist = 1e10; // big number
1058 QPointF nearPoint = path->pointAt(0);
1059 qreal nearPc = 0;
1060 for (qreal i=1; i < 1000; i++) {
1061 QPointF pt = path->pointAt(i/1000.0);
1062 QPointF diff = pt - point;
1063 qreal dist = diff.x()*diff.x() + diff.y()*diff.y();
1064 if (dist < mindist) {
1065 nearPoint = pt;
1066 nearPc = i;
1067 mindist = dist;
1068 }
1069 }
1070
1071 if (nearPercent)
1072 *nearPercent = nearPc / 1000.0;
1073
1074 return nearPoint;
1075}
1076
1077void QDeclarativePathView::mousePressEvent(QGraphicsSceneMouseEvent *event)
1078{
1079 Q_D(QDeclarativePathView);
1080 if (d->interactive) {
1081 d->handleMousePressEvent(event);
1082 event->accept();
1083 } else {
1084 QDeclarativeItem::mousePressEvent(event);
1085 }
1086}
1087
1088void QDeclarativePathViewPrivate::handleMousePressEvent(QGraphicsSceneMouseEvent *event)
1089{
1090 Q_Q(QDeclarativePathView);
1091 if (!interactive || !items.count())
1092 return;
1093 QPointF scenePoint = q->mapToScene(event->pos());
1094 int idx = 0;
1095 for (; idx < items.count(); ++idx) {
1096 QRectF rect = items.at(idx)->boundingRect();
1097 rect = items.at(idx)->mapToScene(rect).boundingRect();
1098 if (rect.contains(scenePoint))
1099 break;
1100 }
1101 if (idx == items.count() && dragMargin == 0.) // didn't click on an item
1102 return;
1103
1104 startPoint = pointNear(event->pos(), &startPc);
1105 if (idx == items.count()) {
1106 qreal distance = qAbs(event->pos().x() - startPoint.x()) + qAbs(event->pos().y() - startPoint.y());
1107 if (distance > dragMargin)
1108 return;
1109 }
1110
1111 if (tl.isActive() && flicking)
1112 stealMouse = true; // If we've been flicked then steal the click.
1113 else
1114 stealMouse = false;
1115
1116 lastElapsed = 0;
1117 lastDist = 0;
1118 QDeclarativeItemPrivate::start(lastPosTime);
1119 tl.clear();
1120}
1121
1122void QDeclarativePathView::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
1123{
1124 Q_D(QDeclarativePathView);
1125 if (d->interactive) {
1126 d->handleMouseMoveEvent(event);
1127 if (d->stealMouse)
1128 setKeepMouseGrab(true);
1129 event->accept();
1130 } else {
1131 QDeclarativeItem::mouseMoveEvent(event);
1132 }
1133}
1134
1135void QDeclarativePathViewPrivate::handleMouseMoveEvent(QGraphicsSceneMouseEvent *event)
1136{
1137 Q_Q(QDeclarativePathView);
1138 if (!interactive || !lastPosTime.isValid())
1139 return;
1140
1141 qreal newPc;
1142 QPointF pathPoint = pointNear(event->pos(), &newPc);
1143 if (!stealMouse) {
1144 QPointF delta = pathPoint - startPoint;
1145 if (qAbs(delta.x()) > QApplication::startDragDistance() || qAbs(delta.y()) > QApplication::startDragDistance()) {
1146 stealMouse = true;
1147 startPc = newPc;
1148 }
1149 }
1150
1151 if (stealMouse) {
1152 moveReason = QDeclarativePathViewPrivate::Mouse;
1153 qreal diff = (newPc - startPc)*modelCount*mappedRange;
1154 if (diff) {
1155 q->setOffset(offset + diff);
1156
1157 if (diff > modelCount/2)
1158 diff -= modelCount;
1159 else if (diff < -modelCount/2)
1160 diff += modelCount;
1161
1162 lastElapsed = QDeclarativeItemPrivate::restart(lastPosTime);
1163 lastDist = diff;
1164 startPc = newPc;
1165 }
1166 if (!moving) {
1167 moving = true;
1168 emit q->movingChanged();
1169 emit q->movementStarted();
1170 }
1171 }
1172}
1173
1174void QDeclarativePathView::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
1175{
1176 Q_D(QDeclarativePathView);
1177 if (d->interactive) {
1178 d->handleMouseReleaseEvent(event);
1179 event->accept();
1180 ungrabMouse();
1181 } else {
1182 QDeclarativeItem::mouseReleaseEvent(event);
1183 }
1184}
1185
1186void QDeclarativePathViewPrivate::handleMouseReleaseEvent(QGraphicsSceneMouseEvent *)
1187{
1188 Q_Q(QDeclarativePathView);
1189 stealMouse = false;
1190 q->setKeepMouseGrab(false);
1191 if (!interactive || !lastPosTime.isValid())
1192 return;
1193
1194 qreal elapsed = qreal(lastElapsed + QDeclarativeItemPrivate::elapsed(lastPosTime)) / 1000.;
1195 qreal velocity = elapsed > 0. ? lastDist / elapsed : 0;
1196 if (model && modelCount && qAbs(velocity) > 1.) {
1197 qreal count = pathItems == -1 ? modelCount : pathItems;
1198 if (qAbs(velocity) > count * 2) // limit velocity
1199 velocity = (velocity > 0 ? count : -count) * 2;
1200 // Calculate the distance to be travelled
1201 qreal v2 = velocity*velocity;
1202 qreal accel = deceleration/10;
1203 // + 0.25 to encourage moving at least one item in the flick direction
1204 qreal dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2.0) + 0.25));
1205 if (haveHighlightRange && highlightRangeMode == QDeclarativePathView::StrictlyEnforceRange) {
1206 // round to nearest item.
1207 if (velocity > 0.)
1208 dist = qRound(dist + offset) - offset;
1209 else
1210 dist = qRound(dist - offset) + offset;
1211 // Calculate accel required to stop on item boundary
1212 if (dist <= 0.) {
1213 dist = 0.;
1214 accel = 0.;
1215 } else {
1216 accel = v2 / (2.0f * qAbs(dist));
1217 }
1218 }
1219 offsetAdj = 0.0;
1220 moveOffset.setValue(offset);
1221 tl.accel(moveOffset, velocity, accel, dist);
1222 tl.callback(QDeclarativeTimeLineCallback(&moveOffset, fixOffsetCallback, this));
1223 if (!flicking) {
1224 flicking = true;
1225 emit q->flickingChanged();
1226 emit q->flickStarted();
1227 }
1228 } else {
1229 fixOffset();
1230 }
1231
1232 lastPosTime.invalidate();
1233 if (!tl.isActive())
1234 q->movementEnding();
1235}
1236
1237bool QDeclarativePathView::sendMouseEvent(QGraphicsSceneMouseEvent *event)
1238{
1239 Q_D(QDeclarativePathView);
1240 QGraphicsSceneMouseEvent mouseEvent(event->type());
1241 QRectF myRect = mapToScene(QRectF(0, 0, width(), height())).boundingRect();
1242 QGraphicsScene *s = scene();
1243 QDeclarativeItem *grabber = s ? qobject_cast<QDeclarativeItem*>(s->mouseGrabberItem()) : 0;
1244 bool stealThisEvent = d->stealMouse;
1245 if ((stealThisEvent || myRect.contains(event->scenePos().toPoint())) && (!grabber || !grabber->keepMouseGrab())) {
1246 mouseEvent.setAccepted(false);
1247 for (int i = 0x1; i <= 0x10; i <<= 1) {
1248 if (event->buttons() & i) {
1249 Qt::MouseButton button = Qt::MouseButton(i);
1250 mouseEvent.setButtonDownPos(button, mapFromScene(event->buttonDownPos(button)));
1251 }
1252 }
1253 mouseEvent.setScenePos(event->scenePos());
1254 mouseEvent.setLastScenePos(event->lastScenePos());
1255 mouseEvent.setPos(mapFromScene(event->scenePos()));
1256 mouseEvent.setLastPos(mapFromScene(event->lastScenePos()));
1257
1258 switch(mouseEvent.type()) {
1259 case QEvent::GraphicsSceneMouseMove:
1260 d->handleMouseMoveEvent(&mouseEvent);
1261 break;
1262 case QEvent::GraphicsSceneMousePress:
1263 d->handleMousePressEvent(&mouseEvent);
1264 stealThisEvent = d->stealMouse; // Update stealThisEvent in case changed by function call above
1265 break;
1266 case QEvent::GraphicsSceneMouseRelease:
1267 d->handleMouseReleaseEvent(&mouseEvent);
1268 break;
1269 default:
1270 break;
1271 }
1272 grabber = qobject_cast<QDeclarativeItem*>(s->mouseGrabberItem());
1273 if (grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this)
1274 grabMouse();
1275
1276 return d->stealMouse;
1277 } else if (d->lastPosTime.isValid()) {
1278 d->lastPosTime.invalidate();
1279 }
1280 if (mouseEvent.type() == QEvent::GraphicsSceneMouseRelease)
1281 d->stealMouse = false;
1282 return false;
1283}
1284
1285bool QDeclarativePathView::sceneEventFilter(QGraphicsItem *i, QEvent *e)
1286{
1287 Q_D(QDeclarativePathView);
1288 if (!isVisible() || !d->interactive)
1289 return QDeclarativeItem::sceneEventFilter(i, e);
1290
1291 switch (e->type()) {
1292 case QEvent::GraphicsSceneMousePress:
1293 case QEvent::GraphicsSceneMouseMove:
1294 case QEvent::GraphicsSceneMouseRelease:
1295 return sendMouseEvent(static_cast<QGraphicsSceneMouseEvent *>(e));
1296 default:
1297 break;
1298 }
1299
1300 return QDeclarativeItem::sceneEventFilter(i, e);
1301}
1302
1303bool QDeclarativePathView::event(QEvent *event)
1304{
1305 if (event->type() == QEvent::User) {
1306 refill();
1307 return true;
1308 }
1309
1310 return QDeclarativeItem::event(event);
1311}
1312
1313void QDeclarativePathView::componentComplete()
1314{
1315 Q_D(QDeclarativePathView);
1316 QDeclarativeItem::componentComplete();
1317 d->createHighlight();
1318 // It is possible that a refill has already happended to to Path
1319 // bindings being handled in the componentComplete(). If so
1320 // don't do it again.
1321 if (d->items.count() == 0 && d->model) {
1322 d->modelCount = d->model->count();
1323 d->regenerate();
1324 }
1325 d->updateHighlight();
1326}
1327
1328void QDeclarativePathView::refill()
1329{
1330 Q_D(QDeclarativePathView);
1331 if (!d->isValid() || !isComponentComplete())
1332 return;
1333
1334 d->layoutScheduled = false;
1335 bool currentVisible = false;
1336
1337 // first move existing items and remove items off path
1338 int idx = d->firstIndex;
1339 QList<QDeclarativeItem*>::iterator it = d->items.begin();
1340 while (it != d->items.end()) {
1341 qreal pos = d->positionOfIndex(idx);
1342 QDeclarativeItem *item = *it;
1343 if (pos >= 0.0) {
1344 d->updateItem(item, pos);
1345 if (idx == d->currentIndex) {
1346 currentVisible = true;
1347 d->currentItemOffset = pos;
1348 }
1349 ++it;
1350 } else {
1351// qDebug() << "release";
1352 d->updateItem(item, 1.0);
1353 d->releaseItem(item);
1354 if (it == d->items.begin()) {
1355 if (++d->firstIndex >= d->modelCount)
1356 d->firstIndex = 0;
1357 }
1358 it = d->items.erase(it);
1359 }
1360 ++idx;
1361 if (idx >= d->modelCount)
1362 idx = 0;
1363 }
1364 if (!d->items.count())
1365 d->firstIndex = -1;
1366
1367 if (d->modelCount) {
1368 // add items to beginning and end
1369 int count = d->pathItems == -1 ? d->modelCount : qMin(d->pathItems, d->modelCount);
1370 if (d->items.count() < count) {
1371 int idx = qRound(d->modelCount - d->offset) % d->modelCount;
1372 qreal startPos = 0.0;
1373 if (d->haveHighlightRange && d->highlightRangeMode != QDeclarativePathView::NoHighlightRange)
1374 startPos = d->highlightRangeStart;
1375 if (d->firstIndex >= 0) {
1376 startPos = d->positionOfIndex(d->firstIndex);
1377 idx = (d->firstIndex + d->items.count()) % d->modelCount;
1378 }
1379 qreal pos = d->positionOfIndex(idx);
1380 while ((pos > startPos || !d->items.count()) && d->items.count() < count) {
1381 // qDebug() << "append" << idx;
1382 QDeclarativeItem *item = d->getItem(idx);
1383 if (d->model->completePending())
1384 item->setZValue(idx+1);
1385 if (d->currentIndex == idx) {
1386 item->setFocus(true);
1387 if (QDeclarativePathViewAttached *att = d->attached(item))
1388 att->setIsCurrentItem(true);
1389 currentVisible = true;
1390 d->currentItemOffset = pos;
1391 d->currentItem = item;
1392 }
1393 if (d->items.count() == 0)
1394 d->firstIndex = idx;
1395 d->items.append(item);
1396 d->updateItem(item, pos);
1397 if (d->model->completePending())
1398 d->model->completeItem();
1399 ++idx;
1400 if (idx >= d->modelCount)
1401 idx = 0;
1402 pos = d->positionOfIndex(idx);
1403 }
1404
1405 idx = d->firstIndex - 1;
1406 if (idx < 0)
1407 idx = d->modelCount - 1;
1408 pos = d->positionOfIndex(idx);
1409 while (pos >= 0.0 && pos < startPos) {
1410 // qDebug() << "prepend" << idx;
1411 QDeclarativeItem *item = d->getItem(idx);
1412 if (d->model->completePending())
1413 item->setZValue(idx+1);
1414 if (d->currentIndex == idx) {
1415 item->setFocus(true);
1416 if (QDeclarativePathViewAttached *att = d->attached(item))
1417 att->setIsCurrentItem(true);
1418 currentVisible = true;
1419 d->currentItemOffset = pos;
1420 d->currentItem = item;
1421 }
1422 d->items.prepend(item);
1423 d->updateItem(item, pos);
1424 if (d->model->completePending())
1425 d->model->completeItem();
1426 d->firstIndex = idx;
1427 idx = d->firstIndex - 1;
1428 if (idx < 0)
1429 idx = d->modelCount - 1;
1430 pos = d->positionOfIndex(idx);
1431 }
1432 }
1433 }
1434
1435 if (!currentVisible)
1436 d->currentItemOffset = 1.0;
1437
1438 if (d->highlightItem && d->haveHighlightRange && d->highlightRangeMode == QDeclarativePathView::StrictlyEnforceRange) {
1439 d->updateItem(d->highlightItem, d->highlightRangeStart);
1440 if (QDeclarativePathViewAttached *att = d->attached(d->highlightItem))
1441 att->setOnPath(true);
1442 } else if (d->highlightItem && d->moveReason != QDeclarativePathViewPrivate::SetIndex) {
1443 d->updateItem(d->highlightItem, d->currentItemOffset);
1444 if (QDeclarativePathViewAttached *att = d->attached(d->highlightItem))
1445 att->setOnPath(currentVisible);
1446 }
1447 while (d->itemCache.count())
1448 d->releaseItem(d->itemCache.takeLast());
1449}
1450
1451void QDeclarativePathView::itemsInserted(int modelIndex, int count)
1452{
1453 //XXX support animated insertion
1454 Q_D(QDeclarativePathView);
1455 if (!d->isValid() || !isComponentComplete())
1456 return;
1457
1458 d->itemCache += d->items;
1459 d->items.clear();
1460 if (modelIndex <= d->currentIndex) {
1461 d->currentIndex += count;
1462 emit currentIndexChanged();
1463 } else if (d->offset != 0) {
1464 d->offset += count;
1465 d->offsetAdj += count;
1466 }
1467
1468 d->modelCount = d->model->count();
1469 if (d->flicking || d->moving) {
1470 d->regenerate();
1471 d->updateCurrent();
1472 } else {
1473 d->firstIndex = -1;
1474 d->updateMappedRange();
1475 d->scheduleLayout();
1476 }
1477 emit countChanged();
1478}
1479
1480void QDeclarativePathView::itemsRemoved(int modelIndex, int count)
1481{
1482 //XXX support animated removal
1483 Q_D(QDeclarativePathView);
1484 if (!d->model || !d->modelCount || !d->model->isValid() || !d->path || !isComponentComplete())
1485 return;
1486
1487 // fix current
1488 bool currentChanged = false;
1489 if (d->currentIndex >= modelIndex + count) {
1490 d->currentIndex -= count;
1491 currentChanged = true;
1492 } else if (d->currentIndex >= modelIndex && d->currentIndex < modelIndex + count) {
1493 // current item has been removed.
1494 d->currentIndex = qMin(modelIndex, d->modelCount-count-1);
1495 if (d->currentItem) {
1496 if (QDeclarativePathViewAttached *att = d->attached(d->currentItem))
1497 att->setIsCurrentItem(true);
1498 }
1499 currentChanged = true;
1500 }
1501
1502 d->itemCache += d->items;
1503 d->items.clear();
1504
1505 if (modelIndex > d->currentIndex) {
1506 if (d->offset >= count) {
1507 d->offset -= count;
1508 d->offsetAdj -= count;
1509 }
1510 }
1511
1512 d->modelCount = d->model->count();
1513 d->regenerate();
1514 d->updateCurrent();
1515 if (!d->modelCount)
1516 update();
1517 if (currentChanged)
1518 emit currentIndexChanged();
1519 emit countChanged();
1520}
1521
1522void QDeclarativePathView::itemsMoved(int /*from*/, int /*to*/, int /*count*/)
1523{
1524 Q_D(QDeclarativePathView);
1525 if (!d->isValid() || !isComponentComplete())
1526 return;
1527
1528 QList<QDeclarativeItem *> removedItems = d->items;
1529 d->items.clear();
1530 d->regenerate();
1531 while (removedItems.count())
1532 d->releaseItem(removedItems.takeLast());
1533
1534 // Fix current index
1535 if (d->currentIndex >= 0 && d->currentItem) {
1536 int oldCurrent = d->currentIndex;
1537 d->currentIndex = d->model->indexOf(d->currentItem, this);
1538 if (oldCurrent != d->currentIndex)
1539 emit currentIndexChanged();
1540 }
1541 d->updateCurrent();
1542}
1543
1544void QDeclarativePathView::modelReset()
1545{
1546 Q_D(QDeclarativePathView);
1547 d->modelCount = d->model->count();
1548 d->regenerate();
1549 emit countChanged();
1550}
1551
1552void QDeclarativePathView::createdItem(int index, QDeclarativeItem *item)
1553{
1554 Q_D(QDeclarativePathView);
1555 if (d->requestedIndex != index) {
1556 if (!d->attType) {
1557 // pre-create one metatype to share with all attached objects
1558 d->attType = new QDeclarativeOpenMetaObjectType(&QDeclarativePathViewAttached::staticMetaObject, qmlEngine(this));
1559 foreach(const QString &attr, d->path->attributes())
1560 d->attType->createProperty(attr.toUtf8());
1561 }
1562 qPathViewAttachedType = d->attType;
1563 QDeclarativePathViewAttached *att = static_cast<QDeclarativePathViewAttached *>(qmlAttachedPropertiesObject<QDeclarativePathView>(item));
1564 qPathViewAttachedType = 0;
1565 if (att) {
1566 att->m_view = this;
1567 att->setOnPath(false);
1568 }
1569 item->setParentItem(this);
1570 d->updateItem(item, index < d->firstIndex ? 0.0 : 1.0);
1571 }
1572}
1573
1574void QDeclarativePathView::destroyingItem(QDeclarativeItem *item)
1575{
1576 Q_UNUSED(item);
1577}
1578
1579void QDeclarativePathView::ticked()
1580{
1581 Q_D(QDeclarativePathView);
1582 d->updateCurrent();
1583}
1584
1585void QDeclarativePathView::movementEnding()
1586{
1587 Q_D(QDeclarativePathView);
1588 if (d->flicking) {
1589 d->flicking = false;
1590 emit flickingChanged();
1591 emit flickEnded();
1592 }
1593 if (d->moving && !d->stealMouse) {
1594 d->moving = false;
1595 emit movingChanged();
1596 emit movementEnded();
1597 }
1598}
1599
1600// find the item closest to the snap position
1601int QDeclarativePathViewPrivate::calcCurrentIndex()
1602{
1603 int current = -1;
1604 if (model && items.count()) {
1605 offset = qmlMod(offset, modelCount);
1606 if (offset < 0)
1607 offset += modelCount;
1608 current = qRound(qAbs(qmlMod(modelCount - offset, modelCount)));
1609 current = current % modelCount;
1610 }
1611
1612 return current;
1613}
1614
1615void QDeclarativePathViewPrivate::updateCurrent()
1616{
1617 Q_Q(QDeclarativePathView);
1618 if (moveReason != Mouse)
1619 return;
1620 if (!haveHighlightRange || highlightRangeMode != QDeclarativePathView::StrictlyEnforceRange)
1621 return;
1622
1623 int idx = calcCurrentIndex();
1624 if (model && idx != currentIndex) {
1625 int itemIndex = (currentIndex - firstIndex + modelCount) % modelCount;
1626 if (itemIndex < items.count()) {
1627 if (QDeclarativeItem *item = items.at(itemIndex)) {
1628 if (QDeclarativePathViewAttached *att = attached(item))
1629 att->setIsCurrentItem(false);
1630 }
1631 }
1632 currentIndex = idx;
1633 currentItem = 0;
1634 itemIndex = (idx - firstIndex + modelCount) % modelCount;
1635 if (itemIndex < items.count()) {
1636 currentItem = items.at(itemIndex);
1637 currentItem->setFocus(true);
1638 if (QDeclarativePathViewAttached *att = attached(currentItem))
1639 att->setIsCurrentItem(true);
1640 }
1641 emit q->currentIndexChanged();
1642 }
1643}
1644
1645void QDeclarativePathViewPrivate::fixOffsetCallback(void *d)
1646{
1647 ((QDeclarativePathViewPrivate *)d)->fixOffset();
1648}
1649
1650void QDeclarativePathViewPrivate::fixOffset()
1651{
1652 Q_Q(QDeclarativePathView);
1653 if (model && items.count()) {
1654 if (haveHighlightRange && highlightRangeMode == QDeclarativePathView::StrictlyEnforceRange) {
1655 int curr = calcCurrentIndex();
1656 if (curr != currentIndex)
1657 q->setCurrentIndex(curr);
1658 else
1659 snapToCurrent();
1660 }
1661 }
1662}
1663
1664void QDeclarativePathViewPrivate::snapToCurrent()
1665{
1666 if (!model || modelCount <= 0)
1667 return;
1668
1669 qreal targetOffset = qmlMod(modelCount - currentIndex, modelCount);
1670
1671 moveReason = Other;
1672 offsetAdj = 0.0;
1673 tl.reset(moveOffset);
1674 moveOffset.setValue(offset);
1675
1676 const int duration = highlightMoveDuration;
1677
1678 if (moveDirection == Positive || (moveDirection == Shortest && targetOffset - offset > modelCount/2)) {
1679 qreal distance = modelCount - targetOffset + offset;
1680 if (targetOffset > moveOffset) {
1681 tl.move(moveOffset, 0.0, QEasingCurve(QEasingCurve::InQuad), int(duration * offset / distance));
1682 tl.set(moveOffset, modelCount);
1683 tl.move(moveOffset, targetOffset, QEasingCurve(offset == 0.0 ? QEasingCurve::InOutQuad : QEasingCurve::OutQuad), int(duration * (modelCount-targetOffset) / distance));
1684 } else {
1685 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
1686 }
1687 } else if (moveDirection == Negative || targetOffset - offset <= -modelCount/2) {
1688 qreal distance = modelCount - offset + targetOffset;
1689 if (targetOffset < moveOffset) {
1690 tl.move(moveOffset, modelCount, QEasingCurve(targetOffset == 0 ? QEasingCurve::InOutQuad : QEasingCurve::InQuad), int(duration * (modelCount-offset) / distance));
1691 tl.set(moveOffset, 0.0);
1692 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::OutQuad), int(duration * targetOffset / distance));
1693 } else {
1694 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
1695 }
1696 } else {
1697 tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration);
1698 }
1699 moveDirection = Shortest;
1700}
1701
1702QDeclarativePathViewAttached *QDeclarativePathView::qmlAttachedProperties(QObject *obj)
1703{
1704 return new QDeclarativePathViewAttached(obj);
1705}
1706
1707QT_END_NAMESPACE
1708
Note: See TracBrowser for help on using the repository browser.