1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the QtDeclarative module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file. Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "private/qdeclarativelistview_p.h"
43
44 #include "private/qdeclarativeflickable_p_p.h"
45 #include "private/qdeclarativevisualitemmodel_p.h"
46
47 #include "private/qdeclarativesmoothedanimation_p_p.h"
48 #include <qdeclarativeexpression.h>
49 #include <qdeclarativeengine.h>
50 #include <qdeclarativeguard_p.h>
51 #include <qdeclarativeinfo.h>
52
53 #include <qlistmodelinterface_p.h>
54 #include <qmath.h>
55 #include <QKeyEvent>
56 #include "qplatformdefs.h"
57
58 QT_BEGIN_NAMESPACE
59
60 #ifndef QML_FLICK_SNAPONETHRESHOLD
61 #define QML_FLICK_SNAPONETHRESHOLD 30
62 #endif
63
setProperty(const QString & property)64 void QDeclarativeViewSection::setProperty(const QString &property)
65 {
66 if (property != m_property) {
67 m_property = property;
68 emit propertyChanged();
69 }
70 }
71
setCriteria(QDeclarativeViewSection::SectionCriteria criteria)72 void QDeclarativeViewSection::setCriteria(QDeclarativeViewSection::SectionCriteria criteria)
73 {
74 if (criteria != m_criteria) {
75 m_criteria = criteria;
76 emit criteriaChanged();
77 }
78 }
79
setDelegate(QDeclarativeComponent * delegate)80 void QDeclarativeViewSection::setDelegate(QDeclarativeComponent *delegate)
81 {
82 if (delegate != m_delegate) {
83 m_delegate = delegate;
84 emit delegateChanged();
85 }
86 }
87
sectionString(const QString & value)88 QString QDeclarativeViewSection::sectionString(const QString &value)
89 {
90 if (m_criteria == FirstCharacter)
91 return value.isEmpty() ? QString() : value.at(0);
92 else
93 return value;
94 }
95
96 //----------------------------------------------------------------------------
97
98 class FxListItem
99 {
100 public:
FxListItem(QDeclarativeItem * i,QDeclarativeListView * v)101 FxListItem(QDeclarativeItem *i, QDeclarativeListView *v) : item(i), section(0), view(v) {
102 attached = static_cast<QDeclarativeListViewAttached*>(qmlAttachedPropertiesObject<QDeclarativeListView>(item));
103 if (attached)
104 attached->setView(view);
105 }
~FxListItem()106 ~FxListItem() {}
position() const107 qreal position() const {
108 if (section) {
109 if (view->orientation() == QDeclarativeListView::Vertical)
110 return section->y();
111 else
112 return (view->effectiveLayoutDirection() == Qt::RightToLeft ? -section->width()-section->x() : section->x());
113 } else {
114 return itemPosition();
115 }
116 }
117
itemPosition() const118 qreal itemPosition() const {
119 if (view->orientation() == QDeclarativeListView::Vertical)
120 return item->y();
121 else
122 return (view->effectiveLayoutDirection() == Qt::RightToLeft ? -item->width()-item->x() : item->x());
123 }
size() const124 qreal size() const {
125 if (section)
126 return (view->orientation() == QDeclarativeListView::Vertical ? item->height()+section->height() : item->width()+section->width());
127 else
128 return (view->orientation() == QDeclarativeListView::Vertical ? item->height() : item->width());
129 }
itemSize() const130 qreal itemSize() const {
131 return (view->orientation() == QDeclarativeListView::Vertical ? item->height() : item->width());
132 }
sectionSize() const133 qreal sectionSize() const {
134 if (section)
135 return (view->orientation() == QDeclarativeListView::Vertical ? section->height() : section->width());
136 return 0.0;
137 }
endPosition() const138 qreal endPosition() const {
139 if (view->orientation() == QDeclarativeListView::Vertical) {
140 return item->y() + (item->height() >= 1.0 ? item->height() : 1) - 1;
141 } else {
142 return (view->effectiveLayoutDirection() == Qt::RightToLeft
143 ? -item->width()-item->x() + (item->width() >= 1.0 ? item->width() : 1)
144 : item->x() + (item->width() >= 1.0 ? item->width() : 1)) - 1;
145 }
146 }
setPosition(qreal pos)147 void setPosition(qreal pos) {
148 if (view->orientation() == QDeclarativeListView::Vertical) {
149 if (section) {
150 section->setY(pos);
151 pos += section->height();
152 }
153 item->setY(pos);
154 } else {
155 if (view->effectiveLayoutDirection() == Qt::RightToLeft) {
156 if (section) {
157 section->setX(-section->width()-pos);
158 pos += section->width();
159 }
160 item->setX(-item->width()-pos);
161 } else {
162 if (section) {
163 section->setX(pos);
164 pos += section->width();
165 }
166 item->setX(pos);
167 }
168 }
169 }
setSize(qreal size)170 void setSize(qreal size) {
171 if (view->orientation() == QDeclarativeListView::Vertical)
172 item->setHeight(size);
173 else
174 item->setWidth(size);
175 }
contains(qreal x,qreal y) const176 bool contains(qreal x, qreal y) const {
177 return (x >= item->x() && x < item->x() + item->width() &&
178 y >= item->y() && y < item->y() + item->height());
179 }
180
181 QDeclarativeItem *item;
182 QDeclarativeItem *section;
183 QDeclarativeListView *view;
184 QDeclarativeListViewAttached *attached;
185 int index;
186 };
187
188 //----------------------------------------------------------------------------
189
190 class QDeclarativeListViewPrivate : public QDeclarativeFlickablePrivate
191 {
192 Q_DECLARE_PUBLIC(QDeclarativeListView)
193
194 public:
QDeclarativeListViewPrivate()195 QDeclarativeListViewPrivate()
196 : currentItem(0), orient(QDeclarativeListView::Vertical), layoutDirection(Qt::LeftToRight)
197 , visiblePos(0), visibleIndex(0)
198 , averageSize(100.0), currentIndex(-1), requestedIndex(-1)
199 , itemCount(0), highlightRangeStart(0), highlightRangeEnd(0)
200 , highlightRangeStartValid(false), highlightRangeEndValid(false)
201 , highlightComponent(0), highlight(0), trackedItem(0)
202 , moveReason(Other), buffer(0), highlightPosAnimator(0), highlightSizeAnimator(0)
203 , sectionCriteria(0), spacing(0.0)
204 , highlightMoveSpeed(400), highlightMoveDuration(-1)
205 , highlightResizeSpeed(400), highlightResizeDuration(-1), highlightRange(QDeclarativeListView::NoHighlightRange)
206 , snapMode(QDeclarativeListView::NoSnap), overshootDist(0.0)
207 , footerComponent(0), footer(0), headerComponent(0), header(0)
208 , bufferMode(BufferBefore | BufferAfter)
209 , ownModel(false), wrap(false), autoHighlight(true), haveHighlightRange(false)
210 , correctFlick(false), inFlickCorrection(false), lazyRelease(false)
211 , deferredRelease(false), layoutScheduled(false), currentIndexCleared(false)
212 , inViewportMoved(false)
213 , minExtentDirty(true), maxExtentDirty(true)
214 {}
215
216 void init();
217 void clear();
218 FxListItem *createItem(int modelIndex);
219 void releaseItem(FxListItem *item);
220 QDeclarativeItem *createComponentItem(QDeclarativeComponent *component);
221
visibleItem(int modelIndex) const222 FxListItem *visibleItem(int modelIndex) const {
223 if (modelIndex >= visibleIndex && modelIndex < visibleIndex + visibleItems.count()) {
224 for (int i = modelIndex - visibleIndex; i < visibleItems.count(); ++i) {
225 FxListItem *item = visibleItems.at(i);
226 if (item->index == modelIndex)
227 return item;
228 }
229 }
230 return 0;
231 }
232
firstVisibleItem() const233 FxListItem *firstVisibleItem() const {
234 const qreal pos = isRightToLeft() ? -position()-size() : position();
235 for (int i = 0; i < visibleItems.count(); ++i) {
236 FxListItem *item = visibleItems.at(i);
237 if (item->index != -1 && item->endPosition() > pos)
238 return item;
239 }
240 return visibleItems.count() ? visibleItems.first() : 0;
241 }
242
243 // Returns the item before modelIndex, if created.
244 // May return an item marked for removal.
itemBefore(int modelIndex) const245 FxListItem *itemBefore(int modelIndex) const {
246 if (modelIndex < visibleIndex)
247 return 0;
248 int idx = 1;
249 int lastIndex = -1;
250 while (idx < visibleItems.count()) {
251 FxListItem *item = visibleItems.at(idx);
252 if (item->index != -1)
253 lastIndex = item->index;
254 if (item->index == modelIndex)
255 return visibleItems.at(idx-1);
256 ++idx;
257 }
258 if (lastIndex == modelIndex-1)
259 return visibleItems.last();
260 return 0;
261 }
262
regenerate()263 void regenerate() {
264 Q_Q(QDeclarativeListView);
265 if (q->isComponentComplete()) {
266 if (header) {
267 if (q->scene())
268 q->scene()->removeItem(header->item);
269 header->item->deleteLater();
270 delete header;
271 header = 0;
272 }
273 if (footer) {
274 if (q->scene())
275 q->scene()->removeItem(footer->item);
276 footer->item->deleteLater();
277 delete footer;
278 footer = 0;
279 }
280 updateHeader();
281 updateFooter();
282 clear();
283 setPosition(0);
284 q->refill();
285 updateCurrent(currentIndex);
286 }
287 }
288
mirrorChange()289 void mirrorChange() {
290 regenerate();
291 }
292
isRightToLeft() const293 bool isRightToLeft() const {
294 Q_Q(const QDeclarativeListView);
295 return orient == QDeclarativeListView::Horizontal && q->effectiveLayoutDirection() == Qt::RightToLeft;
296 }
297
position() const298 qreal position() const {
299 Q_Q(const QDeclarativeListView);
300 return orient == QDeclarativeListView::Vertical ? q->contentY() : q->contentX();
301 }
302
setPosition(qreal pos)303 void setPosition(qreal pos) {
304 Q_Q(QDeclarativeListView);
305 if (orient == QDeclarativeListView::Vertical) {
306 q->QDeclarativeFlickable::setContentY(pos);
307 } else {
308 if (isRightToLeft())
309 q->QDeclarativeFlickable::setContentX(-pos-size());
310 else
311 q->QDeclarativeFlickable::setContentX(pos);
312 }
313 }
size() const314 qreal size() const {
315 Q_Q(const QDeclarativeListView);
316 return orient == QDeclarativeListView::Vertical ? q->height() : q->width();
317 }
318
originPosition() const319 qreal originPosition() const {
320 qreal pos = 0;
321 if (!visibleItems.isEmpty()) {
322 pos = (*visibleItems.constBegin())->position();
323 if (visibleIndex > 0)
324 pos -= visibleIndex * (averageSize + spacing);
325 }
326 return pos;
327 }
328
lastPosition() const329 qreal lastPosition() const {
330 qreal pos = 0;
331 if (!visibleItems.isEmpty()) {
332 int invisibleCount = visibleItems.count() - visibleIndex;
333 for (int i = visibleItems.count()-1; i >= 0; --i) {
334 if (visibleItems.at(i)->index != -1) {
335 invisibleCount = model->count() - visibleItems.at(i)->index - 1;
336 break;
337 }
338 }
339 pos = (*(--visibleItems.constEnd()))->endPosition() + invisibleCount * (averageSize + spacing);
340 } else if (model && model->count()) {
341 pos = model->count() * averageSize + (model->count()-1) * spacing;
342 }
343 return pos;
344 }
345
startPosition() const346 qreal startPosition() const {
347 return isRightToLeft() ? -lastPosition()-1 : originPosition();
348 }
349
endPosition() const350 qreal endPosition() const {
351 return isRightToLeft() ? -originPosition()-1 : lastPosition();
352 }
353
positionAt(int modelIndex) const354 qreal positionAt(int modelIndex) const {
355 if (FxListItem *item = visibleItem(modelIndex))
356 return item->position();
357 if (!visibleItems.isEmpty()) {
358 if (modelIndex < visibleIndex) {
359 int count = visibleIndex - modelIndex;
360 qreal cs = 0;
361 if (modelIndex == currentIndex && currentItem) {
362 cs = currentItem->size() + spacing;
363 --count;
364 }
365 return (*visibleItems.constBegin())->position() - count * (averageSize + spacing) - cs;
366 } else {
367 int idx = visibleItems.count() - 1;
368 while (idx >= 0 && visibleItems.at(idx)->index == -1)
369 --idx;
370 if (idx < 0)
371 idx = visibleIndex;
372 else
373 idx = visibleItems.at(idx)->index;
374 int count = modelIndex - idx - 1;
375
376 return (*(--visibleItems.constEnd()))->endPosition() + spacing + count * (averageSize + spacing) + 1;
377 }
378 }
379 return 0;
380 }
381
endPositionAt(int modelIndex) const382 qreal endPositionAt(int modelIndex) const {
383 if (FxListItem *item = visibleItem(modelIndex))
384 return item->endPosition();
385 if (!visibleItems.isEmpty()) {
386 if (modelIndex < visibleIndex) {
387 int count = visibleIndex - modelIndex;
388 return (*visibleItems.constBegin())->position() - (count - 1) * (averageSize + spacing) - spacing - 1;
389 } else {
390 int idx = visibleItems.count() - 1;
391 while (idx >= 0 && visibleItems.at(idx)->index == -1)
392 --idx;
393 if (idx < 0)
394 idx = visibleIndex;
395 else
396 idx = visibleItems.at(idx)->index;
397 int count = modelIndex - idx - 1;
398 return (*(--visibleItems.constEnd()))->endPosition() + count * (averageSize + spacing);
399 }
400 }
401 return 0;
402 }
403
sectionAt(int modelIndex)404 QString sectionAt(int modelIndex) {
405 if (FxListItem *item = visibleItem(modelIndex))
406 return item->attached->section();
407
408 QString section;
409 if (sectionCriteria) {
410 QString propValue = model->stringValue(modelIndex, sectionCriteria->property());
411 section = sectionCriteria->sectionString(propValue);
412 }
413
414 return section;
415 }
416
isValid() const417 bool isValid() const {
418 return model && model->count() && model->isValid();
419 }
420
snapPosAt(qreal pos)421 qreal snapPosAt(qreal pos) {
422 if (FxListItem *snapItem = snapItemAt(pos))
423 return snapItem->position();
424 if (visibleItems.count()) {
425 qreal firstPos = visibleItems.first()->position();
426 qreal endPos = visibleItems.last()->position();
427 if (pos < firstPos) {
428 return firstPos - qRound((firstPos - pos) / averageSize) * averageSize;
429 } else if (pos > endPos)
430 return endPos + qRound((pos - endPos) / averageSize) * averageSize;
431 }
432 return qRound((pos - originPosition()) / averageSize) * averageSize + originPosition();
433 }
434
snapItemAt(qreal pos)435 FxListItem *snapItemAt(qreal pos) {
436 FxListItem *snapItem = 0;
437 for (int i = 0; i < visibleItems.count(); ++i) {
438 FxListItem *item = visibleItems[i];
439 if (item->index == -1)
440 continue;
441 qreal itemTop = item->position();
442 if (highlight && itemTop >= pos && item->endPosition() <= pos + highlight->size() - 1)
443 return item;
444 if (itemTop+item->size()/2 >= pos && itemTop-item->size()/2 < pos)
445 snapItem = item;
446 }
447 return snapItem;
448 }
449
lastVisibleIndex() const450 int lastVisibleIndex() const {
451 int lastIndex = -1;
452 for (int i = visibleItems.count()-1; i >= 0; --i) {
453 FxListItem *listItem = visibleItems.at(i);
454 if (listItem->index != -1) {
455 lastIndex = listItem->index;
456 break;
457 }
458 }
459 return lastIndex;
460 }
461
462 // map a model index to visibleItems index.
mapFromModel(int modelIndex) const463 int mapFromModel(int modelIndex) const {
464 if (modelIndex < visibleIndex || modelIndex >= visibleIndex + visibleItems.count())
465 return -1;
466 for (int i = 0; i < visibleItems.count(); ++i) {
467 FxListItem *listItem = visibleItems.at(i);
468 if (listItem->index == modelIndex)
469 return i;
470 if (listItem->index > modelIndex)
471 return -1;
472 }
473 return -1; // Not in visibleList
474 }
475
updateViewport()476 void updateViewport() {
477 Q_Q(QDeclarativeListView);
478 if (orient == QDeclarativeListView::Vertical) {
479 q->setContentHeight(endPosition() - startPosition() + 1);
480 } else {
481 q->setContentWidth(endPosition() - startPosition() + 1);
482 }
483 }
484
itemGeometryChanged(QDeclarativeItem * item,const QRectF & newGeometry,const QRectF & oldGeometry)485 void itemGeometryChanged(QDeclarativeItem *item, const QRectF &newGeometry, const QRectF &oldGeometry) {
486 Q_Q(QDeclarativeListView);
487 QDeclarativeFlickablePrivate::itemGeometryChanged(item, newGeometry, oldGeometry);
488 if (!q->isComponentComplete())
489 return;
490 if (item != contentItem && (!highlight || item != highlight->item)) {
491 if ((orient == QDeclarativeListView::Vertical && newGeometry.height() != oldGeometry.height())
492 || (orient == QDeclarativeListView::Horizontal && newGeometry.width() != oldGeometry.width())) {
493 scheduleLayout();
494 }
495 }
496 if ((header && header->item == item) || (footer && footer->item == item)) {
497 if (header)
498 updateHeader();
499 if (footer)
500 updateFooter();
501 }
502 if (currentItem && currentItem->item == item)
503 updateHighlight();
504 if (trackedItem && trackedItem->item == item)
505 q->trackedPositionChanged();
506 }
507
508 // for debugging only
checkVisible() const509 void checkVisible() const {
510 int skip = 0;
511 for (int i = 0; i < visibleItems.count(); ++i) {
512 FxListItem *listItem = visibleItems.at(i);
513 if (listItem->index == -1) {
514 ++skip;
515 } else if (listItem->index != visibleIndex + i - skip) {
516 qFatal("index %d %d %d", visibleIndex, i, listItem->index);
517 }
518 }
519 }
520
521 void refill(qreal from, qreal to, bool doBuffer = false);
522 void scheduleLayout();
523 void layout();
524 void updateUnrequestedIndexes();
525 void updateUnrequestedPositions();
526 void updateTrackedItem();
527 void createHighlight();
528 void updateHighlight();
529 void createSection(FxListItem *);
530 void updateSections();
531 void updateCurrentSection();
532 void updateCurrent(int);
533 void updateAverage();
534 void updateHeader();
535 void updateFooter();
536 void fixupPosition();
537 void positionViewAtIndex(int index, int mode);
538 virtual void fixup(AxisData &data, qreal minExtent, qreal maxExtent);
539 virtual void flick(QDeclarativeFlickablePrivate::AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize,
540 QDeclarativeTimeLineCallback::Callback fixupCallback, qreal velocity);
541
542 QDeclarativeGuard<QDeclarativeVisualModel> model;
543 QVariant modelVariant;
544 QList<FxListItem*> visibleItems;
545 QHash<QDeclarativeItem*,int> unrequestedItems;
546 FxListItem *currentItem;
547 QDeclarativeListView::Orientation orient;
548 Qt::LayoutDirection layoutDirection;
549 qreal visiblePos;
550 int visibleIndex;
551 qreal averageSize;
552 int currentIndex;
553 int requestedIndex;
554 int itemCount;
555 qreal highlightRangeStart;
556 qreal highlightRangeEnd;
557 bool highlightRangeStartValid;
558 bool highlightRangeEndValid;
559 QDeclarativeComponent *highlightComponent;
560 FxListItem *highlight;
561 FxListItem *trackedItem;
562 enum MovementReason { Other, SetIndex, Mouse };
563 MovementReason moveReason;
564 int buffer;
565 QSmoothedAnimation *highlightPosAnimator;
566 QSmoothedAnimation *highlightSizeAnimator;
567 QDeclarativeViewSection *sectionCriteria;
568 QString currentSection;
569 static const int sectionCacheSize = 4;
570 QDeclarativeItem *sectionCache[sectionCacheSize];
571 qreal spacing;
572 qreal highlightMoveSpeed;
573 int highlightMoveDuration;
574 qreal highlightResizeSpeed;
575 int highlightResizeDuration;
576 QDeclarativeListView::HighlightRangeMode highlightRange;
577 QDeclarativeListView::SnapMode snapMode;
578 qreal overshootDist;
579 QDeclarativeComponent *footerComponent;
580 FxListItem *footer;
581 QDeclarativeComponent *headerComponent;
582 FxListItem *header;
583 enum BufferMode { NoBuffer = 0x00, BufferBefore = 0x01, BufferAfter = 0x02 };
584 int bufferMode;
585 mutable qreal minExtent;
586 mutable qreal maxExtent;
587
588 bool ownModel : 1;
589 bool wrap : 1;
590 bool autoHighlight : 1;
591 bool haveHighlightRange : 1;
592 bool correctFlick : 1;
593 bool inFlickCorrection : 1;
594 bool lazyRelease : 1;
595 bool deferredRelease : 1;
596 bool layoutScheduled : 1;
597 bool currentIndexCleared : 1;
598 bool inViewportMoved : 1;
599 mutable bool minExtentDirty : 1;
600 mutable bool maxExtentDirty : 1;
601 };
602
init()603 void QDeclarativeListViewPrivate::init()
604 {
605 Q_Q(QDeclarativeListView);
606 q->setFlag(QGraphicsItem::ItemIsFocusScope);
607 addItemChangeListener(this, Geometry);
608 QObject::connect(q, SIGNAL(movementEnded()), q, SLOT(animStopped()));
609 q->setFlickableDirection(QDeclarativeFlickable::VerticalFlick);
610 ::memset(sectionCache, 0, sizeof(QDeclarativeItem*) * sectionCacheSize);
611 }
612
clear()613 void QDeclarativeListViewPrivate::clear()
614 {
615 timeline.clear();
616 for (int i = 0; i < visibleItems.count(); ++i)
617 releaseItem(visibleItems.at(i));
618 visibleItems.clear();
619 for (int i = 0; i < sectionCacheSize; ++i) {
620 delete sectionCache[i];
621 sectionCache[i] = 0;
622 }
623 visiblePos = header ? header->size() : 0;
624 visibleIndex = 0;
625 releaseItem(currentItem);
626 currentItem = 0;
627 createHighlight();
628 trackedItem = 0;
629 minExtentDirty = true;
630 maxExtentDirty = true;
631 itemCount = 0;
632 }
633
createItem(int modelIndex)634 FxListItem *QDeclarativeListViewPrivate::createItem(int modelIndex)
635 {
636 Q_Q(QDeclarativeListView);
637 // create object
638 requestedIndex = modelIndex;
639 FxListItem *listItem = 0;
640 if (QDeclarativeItem *item = model->item(modelIndex, false)) {
641 listItem = new FxListItem(item, q);
642 listItem->index = modelIndex;
643 // initialise attached properties
644 if (sectionCriteria) {
645 QString propValue = model->stringValue(modelIndex, sectionCriteria->property());
646 listItem->attached->m_section = sectionCriteria->sectionString(propValue);
647 if (modelIndex > 0) {
648 if (FxListItem *item = itemBefore(modelIndex))
649 listItem->attached->m_prevSection = item->attached->section();
650 else
651 listItem->attached->m_prevSection = sectionAt(modelIndex-1);
652 }
653 if (modelIndex < model->count()-1) {
654 if (FxListItem *item = visibleItem(modelIndex+1))
655 listItem->attached->m_nextSection = item->attached->section();
656 else
657 listItem->attached->m_nextSection = sectionAt(modelIndex+1);
658 }
659 }
660 if (model->completePending()) {
661 // complete
662 listItem->item->setZValue(1);
663 listItem->item->setParentItem(q->contentItem());
664 model->completeItem();
665 } else {
666 listItem->item->setParentItem(q->contentItem());
667 }
668 QDeclarativeItemPrivate *itemPrivate = static_cast<QDeclarativeItemPrivate*>(QGraphicsItemPrivate::get(item));
669 itemPrivate->addItemChangeListener(this, QDeclarativeItemPrivate::Geometry);
670 if (sectionCriteria && sectionCriteria->delegate()) {
671 if (listItem->attached->m_prevSection != listItem->attached->m_section)
672 createSection(listItem);
673 }
674 unrequestedItems.remove(listItem->item);
675 }
676 requestedIndex = -1;
677
678 return listItem;
679 }
680
createComponentItem(QDeclarativeComponent * component)681 QDeclarativeItem *QDeclarativeListViewPrivate::createComponentItem(QDeclarativeComponent *component)
682 {
683 Q_Q(QDeclarativeListView);
684 QDeclarativeItem *item = 0;
685 QDeclarativeContext *creationContext = component->creationContext();
686 QDeclarativeContext *context = new QDeclarativeContext(
687 creationContext ? creationContext : qmlContext(q));
688 QObject *nobj = component->create(context);
689 if (nobj) {
690 QDeclarative_setParent_noEvent(context, nobj);
691 item = qobject_cast<QDeclarativeItem *>(nobj);
692 if (!item)
693 delete nobj;
694 } else {
695 delete context;
696 }
697
698 return item;
699 }
700
releaseItem(FxListItem * item)701 void QDeclarativeListViewPrivate::releaseItem(FxListItem *item)
702 {
703 Q_Q(QDeclarativeListView);
704 if (!item || !model)
705 return;
706 if (trackedItem == item)
707 trackedItem = 0;
708 QDeclarativeItemPrivate *itemPrivate = static_cast<QDeclarativeItemPrivate*>(QGraphicsItemPrivate::get(item->item));
709 itemPrivate->removeItemChangeListener(this, QDeclarativeItemPrivate::Geometry);
710 if (model->release(item->item) == 0) {
711 // item was not destroyed, and we no longer reference it.
712 unrequestedItems.insert(item->item, model->indexOf(item->item, q));
713 }
714 if (item->section) {
715 int i = 0;
716 do {
717 if (!sectionCache[i]) {
718 sectionCache[i] = item->section;
719 sectionCache[i]->setVisible(false);
720 item->section = 0;
721 break;
722 }
723 ++i;
724 } while (i < sectionCacheSize);
725 delete item->section;
726 }
727 delete item;
728 }
729
refill(qreal from,qreal to,bool doBuffer)730 void QDeclarativeListViewPrivate::refill(qreal from, qreal to, bool doBuffer)
731 {
732 Q_Q(QDeclarativeListView);
733 if (!isValid() || !q->isComponentComplete())
734 return;
735 itemCount = model->count();
736 qreal bufferFrom = from - buffer;
737 qreal bufferTo = to + buffer;
738 qreal fillFrom = from;
739 qreal fillTo = to;
740 if (doBuffer && (bufferMode & BufferAfter))
741 fillTo = bufferTo;
742 if (doBuffer && (bufferMode & BufferBefore))
743 fillFrom = bufferFrom;
744
745 bool haveValidItems = false;
746 int modelIndex = visibleIndex;
747 qreal itemEnd = visiblePos-1;
748 if (!visibleItems.isEmpty()) {
749 visiblePos = (*visibleItems.constBegin())->position();
750 itemEnd = (*(--visibleItems.constEnd()))->endPosition() + spacing;
751 int i = visibleItems.count() - 1;
752 while (i > 0 && visibleItems.at(i)->index == -1)
753 --i;
754 if (visibleItems.at(i)->index != -1) {
755 haveValidItems = true;
756 modelIndex = visibleItems.at(i)->index + 1;
757 }
758 }
759
760 if (haveValidItems && (fillFrom > itemEnd+averageSize+spacing
761 || fillTo < visiblePos - averageSize - spacing)) {
762 // We've jumped more than a page. Estimate which items are now
763 // visible and fill from there.
764 int count = (fillFrom - itemEnd) / (averageSize + spacing);
765 for (int i = 0; i < visibleItems.count(); ++i)
766 releaseItem(visibleItems.at(i));
767 visibleItems.clear();
768 modelIndex += count;
769 if (modelIndex >= model->count()) {
770 count -= modelIndex - model->count() + 1;
771 modelIndex = model->count() - 1;
772 } else if (modelIndex < 0) {
773 count -= modelIndex;
774 modelIndex = 0;
775 }
776 visibleIndex = modelIndex;
777 visiblePos = itemEnd + count * (averageSize + spacing) + 1;
778 itemEnd = visiblePos-1;
779 }
780
781 bool changed = false;
782 FxListItem *item = 0;
783 qreal pos = itemEnd + 1;
784 while (modelIndex < model->count() && pos <= fillTo) {
785 // qDebug() << "refill: append item" << modelIndex << "pos" << pos;
786 if (!(item = createItem(modelIndex)))
787 break;
788 item->setPosition(pos);
789 pos += item->size() + spacing;
790 visibleItems.append(item);
791 ++modelIndex;
792 changed = true;
793 if (doBuffer) // never buffer more than one item per frame
794 break;
795 }
796 while (visibleIndex > 0 && visibleIndex <= model->count() && visiblePos-1 >= fillFrom) {
797 // qDebug() << "refill: prepend item" << visibleIndex-1 << "current top pos" << visiblePos;
798 if (!(item = createItem(visibleIndex-1)))
799 break;
800 --visibleIndex;
801 visiblePos -= item->size() + spacing;
802 item->setPosition(visiblePos);
803 visibleItems.prepend(item);
804 changed = true;
805 if (doBuffer) // never buffer more than one item per frame
806 break;
807 }
808
809 if (!lazyRelease || !changed || deferredRelease) { // avoid destroying items in the same frame that we create
810 while (visibleItems.count() > 1 && (item = visibleItems.first()) && item->endPosition() < bufferFrom) {
811 if (item->attached->delayRemove())
812 break;
813 // qDebug() << "refill: remove first" << visibleIndex << "top end pos" << item->endPosition();
814 if (item->index != -1)
815 visibleIndex++;
816 visibleItems.removeFirst();
817 releaseItem(item);
818 changed = true;
819 }
820 while (visibleItems.count() > 1 && (item = visibleItems.last()) && item->position() > bufferTo) {
821 if (item->attached->delayRemove())
822 break;
823 // qDebug() << "refill: remove last" << visibleIndex+visibleItems.count()-1 << item->position();
824 visibleItems.removeLast();
825 releaseItem(item);
826 changed = true;
827 }
828 deferredRelease = false;
829 } else {
830 deferredRelease = true;
831 }
832 if (changed) {
833 minExtentDirty = true;
834 maxExtentDirty = true;
835 if (visibleItems.count())
836 visiblePos = (*visibleItems.constBegin())->position();
837 updateAverage();
838 if (currentIndex >= 0 && currentItem && !visibleItem(currentIndex)) {
839 currentItem->setPosition(positionAt(currentIndex));
840 updateHighlight();
841 }
842
843 if (sectionCriteria)
844 updateCurrentSection();
845 if (header)
846 updateHeader();
847 if (footer)
848 updateFooter();
849 updateViewport();
850 updateUnrequestedPositions();
851 } else if (!doBuffer && buffer && bufferMode != NoBuffer) {
852 refill(from, to, true);
853 }
854 lazyRelease = false;
855 }
856
scheduleLayout()857 void QDeclarativeListViewPrivate::scheduleLayout()
858 {
859 Q_Q(QDeclarativeListView);
860 if (!layoutScheduled) {
861 layoutScheduled = true;
862 QCoreApplication::postEvent(q, new QEvent(QEvent::User), Qt::HighEventPriority);
863 }
864 }
865
layout()866 void QDeclarativeListViewPrivate::layout()
867 {
868 Q_Q(QDeclarativeListView);
869 layoutScheduled = false;
870 if (!isValid() && !visibleItems.count()) {
871 clear();
872 setPosition(0);
873 return;
874 }
875 if (!visibleItems.isEmpty()) {
876 bool fixedCurrent = currentItem && visibleItems.first()->item == currentItem->item;
877 qreal sum = visibleItems.first()->size();
878 qreal pos = visibleItems.first()->position() + visibleItems.first()->size() + spacing;
879 for (int i=1; i < visibleItems.count(); ++i) {
880 FxListItem *item = visibleItems.at(i);
881 item->setPosition(pos);
882 pos += item->size() + spacing;
883 sum += item->size();
884 fixedCurrent = fixedCurrent || (currentItem && item->item == currentItem->item);
885 }
886 averageSize = qRound(sum / visibleItems.count());
887 // move current item if it is not a visible item.
888 if (currentIndex >= 0 && currentItem && !fixedCurrent)
889 currentItem->setPosition(positionAt(currentIndex));
890 }
891 q->refill();
892 minExtentDirty = true;
893 maxExtentDirty = true;
894 updateHighlight();
895 if (!q->isMoving() && !q->isFlicking()) {
896 fixupPosition();
897 q->refill();
898 }
899 if (sectionCriteria)
900 updateCurrentSection();
901 if (header)
902 updateHeader();
903 if (footer)
904 updateFooter();
905 updateViewport();
906 }
907
updateUnrequestedIndexes()908 void QDeclarativeListViewPrivate::updateUnrequestedIndexes()
909 {
910 Q_Q(QDeclarativeListView);
911 QHash<QDeclarativeItem*,int>::iterator it;
912 for (it = unrequestedItems.begin(); it != unrequestedItems.end(); ++it)
913 *it = model->indexOf(it.key(), q);
914 }
915
updateUnrequestedPositions()916 void QDeclarativeListViewPrivate::updateUnrequestedPositions()
917 {
918 Q_Q(QDeclarativeListView);
919 if (unrequestedItems.count()) {
920 qreal pos = position();
921 QHash<QDeclarativeItem*,int>::const_iterator it;
922 for (it = unrequestedItems.begin(); it != unrequestedItems.end(); ++it) {
923 QDeclarativeItem *item = it.key();
924 if (orient == QDeclarativeListView::Vertical) {
925 if (item->y() + item->height() > pos && item->y() < pos + q->height())
926 item->setY(positionAt(*it));
927 } else {
928 if (item->x() + item->width() > pos && item->x() < pos + q->width()) {
929 if (isRightToLeft())
930 item->setX(-positionAt(*it)-item->width());
931 else
932 item->setX(positionAt(*it));
933 }
934 }
935 }
936 }
937 }
938
updateTrackedItem()939 void QDeclarativeListViewPrivate::updateTrackedItem()
940 {
941 Q_Q(QDeclarativeListView);
942 FxListItem *item = currentItem;
943 if (highlight)
944 item = highlight;
945 trackedItem = item;
946 if (trackedItem)
947 q->trackedPositionChanged();
948 }
949
createHighlight()950 void QDeclarativeListViewPrivate::createHighlight()
951 {
952 Q_Q(QDeclarativeListView);
953 bool changed = false;
954 if (highlight) {
955 if (trackedItem == highlight)
956 trackedItem = 0;
957 if (highlight->item->scene())
958 highlight->item->scene()->removeItem(highlight->item);
959 highlight->item->deleteLater();
960 delete highlight;
961 highlight = 0;
962 delete highlightPosAnimator;
963 delete highlightSizeAnimator;
964 highlightPosAnimator = 0;
965 highlightSizeAnimator = 0;
966 changed = true;
967 }
968
969 if (currentItem) {
970 QDeclarativeItem *item = 0;
971 if (highlightComponent) {
972 item = createComponentItem(highlightComponent);
973 } else {
974 item = new QDeclarativeItem;
975 }
976 if (item) {
977 QDeclarative_setParent_noEvent(item, q->contentItem());
978 item->setParentItem(q->contentItem());
979 highlight = new FxListItem(item, q);
980 if (currentItem && autoHighlight) {
981 if (orient == QDeclarativeListView::Vertical) {
982 highlight->item->setHeight(currentItem->item->height());
983 } else {
984 highlight->item->setWidth(currentItem->item->width());
985 }
986 highlight->setPosition(currentItem->itemPosition());
987 }
988 QDeclarativeItemPrivate *itemPrivate = static_cast<QDeclarativeItemPrivate*>(QGraphicsItemPrivate::get(item));
989 itemPrivate->addItemChangeListener(this, QDeclarativeItemPrivate::Geometry);
990 const QLatin1String posProp(orient == QDeclarativeListView::Vertical ? "y" : "x");
991 highlightPosAnimator = new QSmoothedAnimation(q);
992 highlightPosAnimator->target = QDeclarativeProperty(highlight->item, posProp);
993 highlightPosAnimator->velocity = highlightMoveSpeed;
994 highlightPosAnimator->userDuration = highlightMoveDuration;
995 const QLatin1String sizeProp(orient == QDeclarativeListView::Vertical ? "height" : "width");
996 highlightSizeAnimator = new QSmoothedAnimation(q);
997 highlightSizeAnimator->velocity = highlightResizeSpeed;
998 highlightSizeAnimator->userDuration = highlightResizeDuration;
999 highlightSizeAnimator->target = QDeclarativeProperty(highlight->item, sizeProp);
1000 if (autoHighlight) {
1001 highlightPosAnimator->restart();
1002 highlightSizeAnimator->restart();
1003 }
1004 changed = true;
1005 }
1006 }
1007 if (changed)
1008 emit q->highlightItemChanged();
1009 }
1010
updateHighlight()1011 void QDeclarativeListViewPrivate::updateHighlight()
1012 {
1013 if ((!currentItem && highlight) || (currentItem && !highlight))
1014 createHighlight();
1015 if (currentItem && autoHighlight && highlight && !hData.moving && !vData.moving) {
1016 // auto-update highlight
1017 highlightPosAnimator->to = isRightToLeft()
1018 ? -currentItem->itemPosition()-currentItem->itemSize()
1019 : currentItem->itemPosition();
1020 highlightSizeAnimator->to = currentItem->itemSize();
1021 if (orient == QDeclarativeListView::Vertical) {
1022 if (highlight->item->width() == 0)
1023 highlight->item->setWidth(currentItem->item->width());
1024 } else {
1025 if (highlight->item->height() == 0)
1026 highlight->item->setHeight(currentItem->item->height());
1027 }
1028 highlightPosAnimator->restart();
1029 highlightSizeAnimator->restart();
1030 }
1031 updateTrackedItem();
1032 }
1033
createSection(FxListItem * listItem)1034 void QDeclarativeListViewPrivate::createSection(FxListItem *listItem)
1035 {
1036 Q_Q(QDeclarativeListView);
1037 if (!sectionCriteria || !sectionCriteria->delegate())
1038 return;
1039 if (listItem->attached->m_prevSection != listItem->attached->m_section) {
1040 if (!listItem->section) {
1041 qreal pos = listItem->position();
1042 int i = sectionCacheSize-1;
1043 while (i >= 0 && !sectionCache[i])
1044 --i;
1045 if (i >= 0) {
1046 listItem->section = sectionCache[i];
1047 sectionCache[i] = 0;
1048 listItem->section->setVisible(true);
1049 QDeclarativeContext *context = QDeclarativeEngine::contextForObject(listItem->section)->parentContext();
1050 context->setContextProperty(QLatin1String("section"), listItem->attached->m_section);
1051 } else {
1052 QDeclarativeContext *context = new QDeclarativeContext(qmlContext(q));
1053 context->setContextProperty(QLatin1String("section"), listItem->attached->m_section);
1054 QObject *nobj = sectionCriteria->delegate()->beginCreate(context);
1055 if (nobj) {
1056 QDeclarative_setParent_noEvent(context, nobj);
1057 listItem->section = qobject_cast<QDeclarativeItem *>(nobj);
1058 if (!listItem->section) {
1059 delete nobj;
1060 } else {
1061 listItem->section->setZValue(1);
1062 QDeclarative_setParent_noEvent(listItem->section, q->contentItem());
1063 listItem->section->setParentItem(q->contentItem());
1064 }
1065 } else {
1066 delete context;
1067 }
1068 sectionCriteria->delegate()->completeCreate();
1069 }
1070 listItem->setPosition(pos);
1071 } else {
1072 QDeclarativeContext *context = QDeclarativeEngine::contextForObject(listItem->section)->parentContext();
1073 context->setContextProperty(QLatin1String("section"), listItem->attached->m_section);
1074 }
1075 } else if (listItem->section) {
1076 qreal pos = listItem->position();
1077 int i = 0;
1078 do {
1079 if (!sectionCache[i]) {
1080 sectionCache[i] = listItem->section;
1081 sectionCache[i]->setVisible(false);
1082 listItem->section = 0;
1083 return;
1084 }
1085 ++i;
1086 } while (i < sectionCacheSize);
1087 delete listItem->section;
1088 listItem->section = 0;
1089 listItem->setPosition(pos);
1090 }
1091 }
1092
updateSections()1093 void QDeclarativeListViewPrivate::updateSections()
1094 {
1095 if (sectionCriteria && !visibleItems.isEmpty()) {
1096 QString prevSection;
1097 if (visibleIndex > 0)
1098 prevSection = sectionAt(visibleIndex-1);
1099 QDeclarativeListViewAttached *prevAtt = 0;
1100 int idx = -1;
1101 for (int i = 0; i < visibleItems.count(); ++i) {
1102 QDeclarativeListViewAttached *attached = visibleItems.at(i)->attached;
1103 attached->setPrevSection(prevSection);
1104 if (visibleItems.at(i)->index != -1) {
1105 QString propValue = model->stringValue(visibleItems.at(i)->index, sectionCriteria->property());
1106 attached->setSection(sectionCriteria->sectionString(propValue));
1107 idx = visibleItems.at(i)->index;
1108 }
1109 createSection(visibleItems.at(i));
1110 if (prevAtt)
1111 prevAtt->setNextSection(attached->section());
1112 prevSection = attached->section();
1113 prevAtt = attached;
1114 }
1115 if (prevAtt) {
1116 if (idx > 0 && idx < model->count()-1)
1117 prevAtt->setNextSection(sectionAt(idx+1));
1118 else
1119 prevAtt->setNextSection(QString());
1120 }
1121 }
1122 }
1123
updateCurrentSection()1124 void QDeclarativeListViewPrivate::updateCurrentSection()
1125 {
1126 Q_Q(QDeclarativeListView);
1127 if (!sectionCriteria || visibleItems.isEmpty()) {
1128 if (!currentSection.isEmpty()) {
1129 currentSection.clear();
1130 emit q->currentSectionChanged();
1131 }
1132 return;
1133 }
1134 int index = 0;
1135 while (index < visibleItems.count() && visibleItems.at(index)->endPosition() < position())
1136 ++index;
1137
1138 QString newSection = currentSection;
1139 if (index < visibleItems.count())
1140 newSection = visibleItems.at(index)->attached->section();
1141 else
1142 newSection = visibleItems.first()->attached->section();
1143 if (newSection != currentSection) {
1144 currentSection = newSection;
1145 emit q->currentSectionChanged();
1146 }
1147 }
1148
updateCurrent(int modelIndex)1149 void QDeclarativeListViewPrivate::updateCurrent(int modelIndex)
1150 {
1151 Q_Q(QDeclarativeListView);
1152 if (!q->isComponentComplete() || !isValid() || modelIndex < 0 || modelIndex >= model->count()) {
1153 if (currentItem) {
1154 currentItem->attached->setIsCurrentItem(false);
1155 releaseItem(currentItem);
1156 currentItem = 0;
1157 currentIndex = modelIndex;
1158 emit q->currentIndexChanged();
1159 updateHighlight();
1160 } else if (currentIndex != modelIndex) {
1161 currentIndex = modelIndex;
1162 emit q->currentIndexChanged();
1163 }
1164 return;
1165 }
1166
1167 if (currentItem && currentIndex == modelIndex) {
1168 updateHighlight();
1169 return;
1170 }
1171 FxListItem *oldCurrentItem = currentItem;
1172 currentIndex = modelIndex;
1173 currentItem = createItem(modelIndex);
1174 if (oldCurrentItem && (!currentItem || oldCurrentItem->item != currentItem->item))
1175 oldCurrentItem->attached->setIsCurrentItem(false);
1176 if (currentItem) {
1177 if (modelIndex == visibleIndex - 1 && visibleItems.count()) {
1178 // We can calculate exact postion in this case
1179 currentItem->setPosition(visibleItems.first()->position() - currentItem->size() - spacing);
1180 } else {
1181 // Create current item now and position as best we can.
1182 // Its position will be corrected when it becomes visible.
1183 currentItem->setPosition(positionAt(modelIndex));
1184 }
1185 currentItem->item->setFocus(true);
1186 currentItem->attached->setIsCurrentItem(true);
1187 // Avoid showing section delegate twice. We still need the section heading so that
1188 // currentItem positioning works correctly.
1189 // This is slightly sub-optimal, but section heading caching minimizes the impact.
1190 if (currentItem->section)
1191 currentItem->section->setVisible(false);
1192 if (visibleItems.isEmpty())
1193 averageSize = currentItem->size();
1194 }
1195 updateHighlight();
1196 emit q->currentIndexChanged();
1197 // Release the old current item
1198 releaseItem(oldCurrentItem);
1199 }
1200
updateAverage()1201 void QDeclarativeListViewPrivate::updateAverage()
1202 {
1203 if (!visibleItems.count())
1204 return;
1205 qreal sum = 0.0;
1206 for (int i = 0; i < visibleItems.count(); ++i)
1207 sum += visibleItems.at(i)->size();
1208 averageSize = qRound(sum / visibleItems.count());
1209 }
1210
updateFooter()1211 void QDeclarativeListViewPrivate::updateFooter()
1212 {
1213 Q_Q(QDeclarativeListView);
1214 if (!footer && footerComponent) {
1215 QDeclarativeItem *item = createComponentItem(footerComponent);
1216 if (item) {
1217 QDeclarative_setParent_noEvent(item, q->contentItem());
1218 item->setParentItem(q->contentItem());
1219 item->setZValue(1);
1220 QDeclarativeItemPrivate *itemPrivate = static_cast<QDeclarativeItemPrivate*>(QGraphicsItemPrivate::get(item));
1221 itemPrivate->addItemChangeListener(this, QDeclarativeItemPrivate::Geometry);
1222 footer = new FxListItem(item, q);
1223 }
1224 }
1225 if (footer) {
1226 if (visibleItems.count()) {
1227 qreal endPos = lastPosition() + 1;
1228 if (lastVisibleIndex() == model->count()-1) {
1229 footer->setPosition(endPos);
1230 } else {
1231 qreal visiblePos = position() + q->height();
1232 if (endPos <= visiblePos || footer->position() < endPos)
1233 footer->setPosition(endPos);
1234 }
1235 } else {
1236 footer->setPosition(visiblePos);
1237 }
1238 }
1239 }
1240
updateHeader()1241 void QDeclarativeListViewPrivate::updateHeader()
1242 {
1243 Q_Q(QDeclarativeListView);
1244 if (!header && headerComponent) {
1245 QDeclarativeItem *item = createComponentItem(headerComponent);
1246 if (item) {
1247 QDeclarative_setParent_noEvent(item, q->contentItem());
1248 item->setParentItem(q->contentItem());
1249 item->setZValue(1);
1250 QDeclarativeItemPrivate *itemPrivate = static_cast<QDeclarativeItemPrivate*>(QGraphicsItemPrivate::get(item));
1251 itemPrivate->addItemChangeListener(this, QDeclarativeItemPrivate::Geometry);
1252 header = new FxListItem(item, q);
1253 }
1254 }
1255 if (header) {
1256 if (visibleItems.count()) {
1257 qreal startPos = originPosition();
1258 if (visibleIndex == 0) {
1259 header->setPosition(startPos - header->size());
1260 } else {
1261 if (position() <= startPos || header->position() > startPos - header->size())
1262 header->setPosition(startPos - header->size());
1263 }
1264 } else {
1265 if (itemCount == 0)
1266 visiblePos = header->size();
1267 header->setPosition(0);
1268 }
1269 }
1270 }
1271
fixupPosition()1272 void QDeclarativeListViewPrivate::fixupPosition()
1273 {
1274 if ((haveHighlightRange && highlightRange == QDeclarativeListView::StrictlyEnforceRange)
1275 || snapMode != QDeclarativeListView::NoSnap)
1276 moveReason = Other;
1277 if (orient == QDeclarativeListView::Vertical)
1278 fixupY();
1279 else
1280 fixupX();
1281 }
1282
fixup(AxisData & data,qreal minExtent,qreal maxExtent)1283 void QDeclarativeListViewPrivate::fixup(AxisData &data, qreal minExtent, qreal maxExtent)
1284 {
1285 if ((orient == QDeclarativeListView::Horizontal && &data == &vData)
1286 || (orient == QDeclarativeListView::Vertical && &data == &hData))
1287 return;
1288
1289 correctFlick = false;
1290 fixupMode = moveReason == Mouse ? fixupMode : Immediate;
1291 bool strictHighlightRange = haveHighlightRange && highlightRange == QDeclarativeListView::StrictlyEnforceRange;
1292
1293 qreal highlightStart;
1294 qreal highlightEnd;
1295 qreal viewPos;
1296 if (isRightToLeft()) {
1297 // Handle Right-To-Left exceptions
1298 viewPos = -position()-size();
1299 highlightStart = highlightRangeStartValid ? size() - highlightRangeEnd : highlightRangeStart;
1300 highlightEnd = highlightRangeEndValid ? size() - highlightRangeStart : highlightRangeEnd;
1301 } else {
1302 viewPos = position();
1303 highlightStart = highlightRangeStart;
1304 highlightEnd = highlightRangeEnd;
1305 }
1306
1307 if (snapMode != QDeclarativeListView::NoSnap && moveReason != QDeclarativeListViewPrivate::SetIndex) {
1308 qreal tempPosition = isRightToLeft() ? -position()-size() : position();
1309 if (snapMode == QDeclarativeListView::SnapOneItem && moveReason == Mouse) {
1310 // if we've been dragged < averageSize/2 then bias towards the next item
1311 qreal dist = data.move.value() - (data.pressPos - data.dragStartOffset);
1312 qreal bias = 0;
1313 if (data.velocity > 0 && dist > QML_FLICK_SNAPONETHRESHOLD && dist < averageSize/2)
1314 bias = averageSize/2;
1315 else if (data.velocity < 0 && dist < -QML_FLICK_SNAPONETHRESHOLD && dist > -averageSize/2)
1316 bias = -averageSize/2;
1317 if (isRightToLeft())
1318 bias = -bias;
1319 tempPosition -= bias;
1320 }
1321 FxListItem *topItem = snapItemAt(tempPosition+highlightStart);
1322 if (!topItem && strictHighlightRange && currentItem) {
1323 // StrictlyEnforceRange always keeps an item in range
1324 updateHighlight();
1325 topItem = currentItem;
1326 }
1327 FxListItem *bottomItem = snapItemAt(tempPosition+highlightEnd);
1328 if (!bottomItem && strictHighlightRange && currentItem) {
1329 // StrictlyEnforceRange always keeps an item in range
1330 updateHighlight();
1331 bottomItem = currentItem;
1332 }
1333 qreal pos;
1334 bool isInBounds = -position() > maxExtent && -position() <= minExtent;
1335 if (topItem && (isInBounds || strictHighlightRange)) {
1336 if (topItem->index == 0 && header && tempPosition+highlightStart < header->position()+header->size()/2 && !strictHighlightRange) {
1337 pos = isRightToLeft() ? - header->position() + highlightStart - size() : header->position() - highlightStart;
1338 } else {
1339 if (isRightToLeft())
1340 pos = qMax(qMin(-topItem->position() + highlightStart - size(), -maxExtent), -minExtent);
1341 else
1342 pos = qMax(qMin(topItem->position() - highlightStart, -maxExtent), -minExtent);
1343 }
1344 } else if (bottomItem && isInBounds) {
1345 if (isRightToLeft())
1346 pos = qMax(qMin(-bottomItem->position() + highlightEnd - size(), -maxExtent), -minExtent);
1347 else
1348 pos = qMax(qMin(bottomItem->position() - highlightEnd, -maxExtent), -minExtent);
1349 } else {
1350 QDeclarativeFlickablePrivate::fixup(data, minExtent, maxExtent);
1351 return;
1352 }
1353
1354 qreal dist = qAbs(data.move + pos);
1355 if (dist > 0) {
1356 timeline.reset(data.move);
1357 if (fixupMode != Immediate) {
1358 timeline.move(data.move, -pos, QEasingCurve(QEasingCurve::InOutQuad), fixupDuration/2);
1359 data.fixingUp = true;
1360 } else {
1361 timeline.set(data.move, -pos);
1362 }
1363 vTime = timeline.time();
1364 }
1365 } else if (currentItem && strictHighlightRange
1366 && moveReason != QDeclarativeListViewPrivate::SetIndex) {
1367 updateHighlight();
1368 qreal pos = currentItem->itemPosition();
1369 if (viewPos < pos + currentItem->itemSize() - highlightEnd)
1370 viewPos = pos + currentItem->itemSize() - highlightEnd;
1371 if (viewPos > pos - highlightStart)
1372 viewPos = pos - highlightStart;
1373 if (isRightToLeft())
1374 viewPos = -viewPos-size();
1375
1376 timeline.reset(data.move);
1377 if (viewPos != position()) {
1378 if (fixupMode != Immediate) {
1379 timeline.move(data.move, -viewPos, QEasingCurve(QEasingCurve::InOutQuad), fixupDuration/2);
1380 data.fixingUp = true;
1381 } else {
1382 timeline.set(data.move, -viewPos);
1383 }
1384 }
1385 vTime = timeline.time();
1386 } else {
1387 QDeclarativeFlickablePrivate::fixup(data, minExtent, maxExtent);
1388 }
1389 data.inOvershoot = false;
1390 fixupMode = Normal;
1391 }
1392
flick(AxisData & data,qreal minExtent,qreal maxExtent,qreal vSize,QDeclarativeTimeLineCallback::Callback fixupCallback,qreal velocity)1393 void QDeclarativeListViewPrivate::flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize,
1394 QDeclarativeTimeLineCallback::Callback fixupCallback, qreal velocity)
1395 {
1396 Q_Q(QDeclarativeListView);
1397
1398 data.fixingUp = false;
1399 moveReason = Mouse;
1400 if ((!haveHighlightRange || highlightRange != QDeclarativeListView::StrictlyEnforceRange) && snapMode == QDeclarativeListView::NoSnap) {
1401 correctFlick = true;
1402 QDeclarativeFlickablePrivate::flick(data, minExtent, maxExtent, vSize, fixupCallback, velocity);
1403 return;
1404 }
1405 qreal maxDistance = 0;
1406 qreal dataValue = isRightToLeft() ? -data.move.value()+size() : data.move.value();
1407 qreal highlightStart = isRightToLeft() && highlightRangeStartValid ? size()-highlightRangeEnd : highlightRangeStart;
1408 // -ve velocity means list is moving up/left
1409 if (velocity > 0) {
1410 if (data.move.value() < minExtent) {
1411 if (snapMode == QDeclarativeListView::SnapOneItem && !hData.flicking && !vData.flicking) {
1412 // if we've been dragged < averageSize/2 then bias towards the next item
1413 qreal dist = data.move.value() - (data.pressPos - data.dragStartOffset);
1414 qreal bias = dist < averageSize/2 ? averageSize/2 : 0;
1415 if (isRightToLeft())
1416 bias = -bias;
1417 data.flickTarget = -snapPosAt(-(dataValue - highlightStart) - bias) + highlightStart;
1418 maxDistance = qAbs(data.flickTarget - data.move.value());
1419 velocity = maxVelocity;
1420 } else {
1421 maxDistance = qAbs(minExtent - data.move.value());
1422 }
1423 }
1424 if (snapMode == QDeclarativeListView::NoSnap && highlightRange != QDeclarativeListView::StrictlyEnforceRange)
1425 data.flickTarget = minExtent;
1426 } else {
1427 if (data.move.value() > maxExtent) {
1428 if (snapMode == QDeclarativeListView::SnapOneItem && !hData.flicking && !vData.flicking) {
1429 // if we've been dragged < averageSize/2 then bias towards the next item
1430 qreal dist = data.move.value() - (data.pressPos - data.dragStartOffset);
1431 qreal bias = -dist < averageSize/2 ? averageSize/2 : 0;
1432 if (isRightToLeft())
1433 bias = -bias;
1434 data.flickTarget = -snapPosAt(-(dataValue - highlightStart) + bias) + highlightStart;
1435 maxDistance = qAbs(data.flickTarget - data.move.value());
1436 velocity = -maxVelocity;
1437 } else {
1438 maxDistance = qAbs(maxExtent - data.move.value());
1439 }
1440 }
1441 if (snapMode == QDeclarativeListView::NoSnap && highlightRange != QDeclarativeListView::StrictlyEnforceRange)
1442 data.flickTarget = maxExtent;
1443 }
1444
1445 bool overShoot = boundsBehavior == QDeclarativeFlickable::DragAndOvershootBounds;
1446
1447 if (maxDistance > 0 || overShoot) {
1448 // These modes require the list to stop exactly on an item boundary.
1449 // The initial flick will estimate the boundary to stop on.
1450 // Since list items can have variable sizes, the boundary will be
1451 // reevaluated and adjusted as we approach the boundary.
1452 qreal v = velocity;
1453 if (maxVelocity != -1 && maxVelocity < qAbs(v)) {
1454 if (v < 0)
1455 v = -maxVelocity;
1456 else
1457 v = maxVelocity;
1458 }
1459 if (!hData.flicking && !vData.flicking) {
1460 // the initial flick - estimate boundary
1461 qreal accel = deceleration;
1462 qreal v2 = v * v;
1463 overshootDist = qreal(0.0);
1464 // + averageSize/4 to encourage moving at least one item in the flick direction
1465 qreal dist = v2 / (accel * qreal(2.0)) + averageSize/4;
1466 if (maxDistance > 0)
1467 dist = qMin(dist, maxDistance);
1468 if (v > 0)
1469 dist = -dist;
1470 if ((maxDistance > qreal(0.0) && v2 / (2.0f * maxDistance) < accel) || snapMode == QDeclarativeListView::SnapOneItem) {
1471 if (snapMode != QDeclarativeListView::SnapOneItem) {
1472 qreal distTemp = isRightToLeft() ? -dist : dist;
1473 data.flickTarget = -snapPosAt(-(dataValue - highlightStart) + distTemp) + highlightStart;
1474 }
1475 data.flickTarget = isRightToLeft() ? -data.flickTarget+size() : data.flickTarget;
1476 if (overShoot) {
1477 if (data.flickTarget >= minExtent) {
1478 overshootDist = overShootDistance(vSize);
1479 data.flickTarget += overshootDist;
1480 } else if (data.flickTarget <= maxExtent) {
1481 overshootDist = overShootDistance(vSize);
1482 data.flickTarget -= overshootDist;
1483 }
1484 }
1485 qreal adjDist = -data.flickTarget + data.move.value();
1486 if (qAbs(adjDist) > qAbs(dist)) {
1487 // Prevent painfully slow flicking - adjust velocity to suit flickDeceleration
1488 qreal adjv2 = accel * 2.0f * qAbs(adjDist);
1489 if (adjv2 > v2) {
1490 v2 = adjv2;
1491 v = qSqrt(v2);
1492 if (dist > 0)
1493 v = -v;
1494 }
1495 }
1496 dist = adjDist;
1497 accel = v2 / (2.0f * qAbs(dist));
1498 } else if (overShoot) {
1499 data.flickTarget = data.move.value() - dist;
1500 if (data.flickTarget >= minExtent) {
1501 overshootDist = overShootDistance(vSize);
1502 data.flickTarget += overshootDist;
1503 } else if (data.flickTarget <= maxExtent) {
1504 overshootDist = overShootDistance(vSize);
1505 data.flickTarget -= overshootDist;
1506 }
1507 }
1508
1509 timeline.reset(data.move);
1510 timeline.accel(data.move, v, accel, maxDistance + overshootDist);
1511 timeline.callback(QDeclarativeTimeLineCallback(&data.move, fixupCallback, this));
1512 if (!hData.flicking && q->xflick()) {
1513 hData.flicking = true;
1514 emit q->flickingChanged();
1515 emit q->flickingHorizontallyChanged();
1516 emit q->flickStarted();
1517 }
1518 if (!vData.flicking && q->yflick()) {
1519 vData.flicking = true;
1520 emit q->flickingChanged();
1521 emit q->flickingVerticallyChanged();
1522 emit q->flickStarted();
1523 }
1524 correctFlick = true;
1525 } else {
1526 // reevaluate the target boundary.
1527 qreal newtarget = data.flickTarget;
1528 if (snapMode != QDeclarativeListView::NoSnap || highlightRange == QDeclarativeListView::StrictlyEnforceRange) {
1529 qreal tempFlickTarget = isRightToLeft() ? -data.flickTarget+size() : data.flickTarget;
1530 newtarget = -snapPosAt(-(tempFlickTarget - highlightStart)) + highlightStart;
1531 newtarget = isRightToLeft() ? -newtarget+size() : newtarget;
1532 }
1533 if (velocity < 0 && newtarget <= maxExtent)
1534 newtarget = maxExtent - overshootDist;
1535 else if (velocity > 0 && newtarget >= minExtent)
1536 newtarget = minExtent + overshootDist;
1537 if (newtarget == data.flickTarget) { // boundary unchanged - nothing to do
1538 if (qAbs(velocity) < MinimumFlickVelocity)
1539 correctFlick = false;
1540 return;
1541 }
1542 data.flickTarget = newtarget;
1543 qreal dist = -newtarget + data.move.value();
1544 if ((v < 0 && dist < 0) || (v > 0 && dist > 0)) {
1545 correctFlick = false;
1546 timeline.reset(data.move);
1547 fixup(data, minExtent, maxExtent);
1548 return;
1549 }
1550
1551 timeline.reset(data.move);
1552 timeline.accelDistance(data.move, v, -dist);
1553 timeline.callback(QDeclarativeTimeLineCallback(&data.move, fixupCallback, this));
1554 }
1555 } else {
1556 correctFlick = false;
1557 timeline.reset(data.move);
1558 fixup(data, minExtent, maxExtent);
1559 }
1560 }
1561
1562 //----------------------------------------------------------------------------
1563
1564 /*!
1565 \qmlclass ListView QDeclarativeListView
1566 \ingroup qml-view-elements
1567 \since 4.7
1568 \inherits Flickable
1569 \brief The ListView item provides a list view of items provided by a model.
1570
1571 A ListView displays data from models created from built-in QML elements like ListModel
1572 and XmlListModel, or custom model classes defined in C++ that inherit from
1573 QAbstractListModel.
1574
1575 A ListView has a \l model, which defines the data to be displayed, and
1576 a \l delegate, which defines how the data should be displayed. Items in a
1577 ListView are laid out horizontally or vertically. List views are inherently
1578 flickable because ListView inherits from \l Flickable.
1579
1580 \section1 Example Usage
1581
1582 The following example shows the definition of a simple list model defined
1583 in a file called \c ContactModel.qml:
1584
1585 \snippet doc/src/snippets/declarative/listview/ContactModel.qml 0
1586
1587 Another component can display this model data in a ListView, like this:
1588
1589 \snippet doc/src/snippets/declarative/listview/listview.qml import
1590 \codeline
1591 \snippet doc/src/snippets/declarative/listview/listview.qml classdocs simple
1592
1593 \image listview-simple.png
1594
1595 Here, the ListView creates a \c ContactModel component for its model, and a \l Text element
1596 for its delegate. The view will create a new \l Text component for each item in the model. Notice
1597 the delegate is able to access the model's \c name and \c number data directly.
1598
1599 An improved list view is shown below. The delegate is visually improved and is moved
1600 into a separate \c contactDelegate component.
1601
1602 \snippet doc/src/snippets/declarative/listview/listview.qml classdocs advanced
1603 \image listview-highlight.png
1604
1605 The currently selected item is highlighted with a blue \l Rectangle using the \l highlight property,
1606 and \c focus is set to \c true to enable keyboard navigation for the list view.
1607 The list view itself is a focus scope (see \l{qmlfocus#Acquiring Focus and Focus Scopes}{the focus documentation page} for more details).
1608
1609 Delegates are instantiated as needed and may be destroyed at any time.
1610 State should \e never be stored in a delegate.
1611
1612 ListView attaches a number of properties to the root item of the delegate, for example
1613 \c {ListView.isCurrentItem}. In the following example, the root delegate item can access
1614 this attached property directly as \c ListView.isCurrentItem, while the child
1615 \c contactInfo object must refer to this property as \c wrapper.ListView.isCurrentItem.
1616
1617 \snippet doc/src/snippets/declarative/listview/listview.qml isCurrentItem
1618
1619 \note Views do not enable \e clip automatically. If the view
1620 is not clipped by another item or the screen, it will be necessary
1621 to set \e {clip: true} in order to have the out of view items clipped
1622 nicely.
1623
1624 \sa {QML Data Models}, GridView, {Models and Views: ListView Examples}{ListView examples}
1625 */
1626
QDeclarativeListView(QDeclarativeItem * parent)1627 QDeclarativeListView::QDeclarativeListView(QDeclarativeItem *parent)
1628 : QDeclarativeFlickable(*(new QDeclarativeListViewPrivate), parent)
1629 {
1630 Q_D(QDeclarativeListView);
1631 d->init();
1632 }
1633
~QDeclarativeListView()1634 QDeclarativeListView::~QDeclarativeListView()
1635 {
1636 Q_D(QDeclarativeListView);
1637 d->clear();
1638 if (d->ownModel)
1639 delete d->model;
1640 delete d->header;
1641 delete d->footer;
1642 }
1643
1644 /*!
1645 \qmlattachedproperty bool ListView::isCurrentItem
1646 This attached property is true if this delegate is the current item; otherwise false.
1647
1648 It is attached to each instance of the delegate.
1649
1650 This property may be used to adjust the appearance of the current item, for example:
1651
1652 \snippet doc/src/snippets/declarative/listview/listview.qml isCurrentItem
1653 */
1654
1655 /*!
1656 \qmlattachedproperty ListView ListView::view
1657 This attached property holds the view that manages this delegate instance.
1658
1659 It is attached to each instance of the delegate.
1660 */
1661
1662 /*!
1663 \qmlattachedproperty string ListView::previousSection
1664 This attached property holds the section of the previous element.
1665
1666 It is attached to each instance of the delegate.
1667
1668 The section is evaluated using the \l {ListView::section.property}{section} properties.
1669 */
1670
1671 /*!
1672 \qmlattachedproperty string ListView::nextSection
1673 This attached property holds the section of the next element.
1674
1675 It is attached to each instance of the delegate.
1676
1677 The section is evaluated using the \l {ListView::section.property}{section} properties.
1678 */
1679
1680 /*!
1681 \qmlattachedproperty string ListView::section
1682 This attached property holds the section of this element.
1683
1684 It is attached to each instance of the delegate.
1685
1686 The section is evaluated using the \l {ListView::section.property}{section} properties.
1687 */
1688
1689 /*!
1690 \qmlattachedproperty bool ListView::delayRemove
1691 This attached property holds whether the delegate may be destroyed.
1692
1693 It is attached to each instance of the delegate.
1694
1695 It is sometimes necessary to delay the destruction of an item
1696 until an animation completes.
1697
1698 The example delegate below ensures that the animation completes before
1699 the item is removed from the list.
1700
1701 \snippet doc/src/snippets/declarative/listview/listview.qml delayRemove
1702 */
1703
1704 /*!
1705 \qmlattachedsignal ListView::onAdd()
1706 This attached handler is called immediately after an item is added to the view.
1707 */
1708
1709 /*!
1710 \qmlattachedsignal ListView::onRemove()
1711 This attached handler is called immediately before an item is removed from the view.
1712 */
1713
1714 /*!
1715 \qmlproperty model ListView::model
1716 This property holds the model providing data for the list.
1717
1718 The model provides the set of data that is used to create the items
1719 in the view. Models can be created directly in QML using \l ListModel, \l XmlListModel
1720 or \l VisualItemModel, or provided by C++ model classes. If a C++ model class is
1721 used, it must be a subclass of \l QAbstractItemModel or a simple list.
1722
1723 \sa {qmlmodels}{Data Models}
1724 */
model() const1725 QVariant QDeclarativeListView::model() const
1726 {
1727 Q_D(const QDeclarativeListView);
1728 return d->modelVariant;
1729 }
1730
setModel(const QVariant & model)1731 void QDeclarativeListView::setModel(const QVariant &model)
1732 {
1733 Q_D(QDeclarativeListView);
1734 if (d->modelVariant == model)
1735 return;
1736 if (d->model) {
1737 disconnect(d->model, SIGNAL(itemsInserted(int,int)), this, SLOT(itemsInserted(int,int)));
1738 disconnect(d->model, SIGNAL(itemsRemoved(int,int)), this, SLOT(itemsRemoved(int,int)));
1739 disconnect(d->model, SIGNAL(itemsMoved(int,int,int)), this, SLOT(itemsMoved(int,int,int)));
1740 disconnect(d->model, SIGNAL(itemsChanged(int,int)), this, SLOT(itemsChanged(int,int)));
1741 disconnect(d->model, SIGNAL(modelReset()), this, SLOT(modelReset()));
1742 disconnect(d->model, SIGNAL(createdItem(int,QDeclarativeItem*)), this, SLOT(createdItem(int,QDeclarativeItem*)));
1743 disconnect(d->model, SIGNAL(destroyingItem(QDeclarativeItem*)), this, SLOT(destroyingItem(QDeclarativeItem*)));
1744 }
1745 d->clear();
1746 QDeclarativeVisualModel *oldModel = d->model;
1747 d->model = 0;
1748 d->setPosition(0);
1749 d->modelVariant = model;
1750 QObject *object = qvariant_cast<QObject*>(model);
1751 QDeclarativeVisualModel *vim = 0;
1752 if (object && (vim = qobject_cast<QDeclarativeVisualModel *>(object))) {
1753 if (d->ownModel) {
1754 delete oldModel;
1755 d->ownModel = false;
1756 }
1757 d->model = vim;
1758 } else {
1759 if (!d->ownModel) {
1760 d->model = new QDeclarativeVisualDataModel(qmlContext(this), this);
1761 d->ownModel = true;
1762 } else {
1763 d->model = oldModel;
1764 }
1765 if (QDeclarativeVisualDataModel *dataModel = qobject_cast<QDeclarativeVisualDataModel*>(d->model))
1766 dataModel->setModel(model);
1767 }
1768 if (d->model) {
1769 d->bufferMode = QDeclarativeListViewPrivate::BufferBefore | QDeclarativeListViewPrivate::BufferAfter;
1770 if (isComponentComplete()) {
1771 updateSections();
1772 refill();
1773 if ((d->currentIndex >= d->model->count() || d->currentIndex < 0) && !d->currentIndexCleared) {
1774 setCurrentIndex(0);
1775 } else {
1776 d->moveReason = QDeclarativeListViewPrivate::SetIndex;
1777 d->updateCurrent(d->currentIndex);
1778 if (d->highlight && d->currentItem) {
1779 if (d->autoHighlight)
1780 d->highlight->setPosition(d->currentItem->position());
1781 d->updateTrackedItem();
1782 }
1783 }
1784 d->updateViewport();
1785 }
1786 connect(d->model, SIGNAL(itemsInserted(int,int)), this, SLOT(itemsInserted(int,int)));
1787 connect(d->model, SIGNAL(itemsRemoved(int,int)), this, SLOT(itemsRemoved(int,int)));
1788 connect(d->model, SIGNAL(itemsMoved(int,int,int)), this, SLOT(itemsMoved(int,int,int)));
1789 connect(d->model, SIGNAL(itemsChanged(int,int)), this, SLOT(itemsChanged(int,int)));
1790 connect(d->model, SIGNAL(modelReset()), this, SLOT(modelReset()));
1791 connect(d->model, SIGNAL(createdItem(int,QDeclarativeItem*)), this, SLOT(createdItem(int,QDeclarativeItem*)));
1792 connect(d->model, SIGNAL(destroyingItem(QDeclarativeItem*)), this, SLOT(destroyingItem(QDeclarativeItem*)));
1793 emit countChanged();
1794 }
1795 emit modelChanged();
1796 }
1797
1798 /*!
1799 \qmlproperty Component ListView::delegate
1800
1801 The delegate provides a template defining each item instantiated by the view.
1802 The index is exposed as an accessible \c index property. Properties of the
1803 model are also available depending upon the type of \l {qmlmodels}{Data Model}.
1804
1805 The number of elements in the delegate has a direct effect on the
1806 flicking performance of the view. If at all possible, place functionality
1807 that is not needed for the normal display of the delegate in a \l Loader which
1808 can load additional elements when needed.
1809
1810 The ListView will lay out the items based on the size of the root item
1811 in the delegate.
1812
1813 It is recommended that the delagate's size be a whole number to avoid sub-pixel
1814 alignment of items.
1815
1816 \note Delegates are instantiated as needed and may be destroyed at any time.
1817 State should \e never be stored in a delegate.
1818 */
delegate() const1819 QDeclarativeComponent *QDeclarativeListView::delegate() const
1820 {
1821 Q_D(const QDeclarativeListView);
1822 if (d->model) {
1823 if (QDeclarativeVisualDataModel *dataModel = qobject_cast<QDeclarativeVisualDataModel*>(d->model))
1824 return dataModel->delegate();
1825 }
1826
1827 return 0;
1828 }
1829
setDelegate(QDeclarativeComponent * delegate)1830 void QDeclarativeListView::setDelegate(QDeclarativeComponent *delegate)
1831 {
1832 Q_D(QDeclarativeListView);
1833 if (delegate == this->delegate())
1834 return;
1835 if (!d->ownModel) {
1836 d->model = new QDeclarativeVisualDataModel(qmlContext(this));
1837 d->ownModel = true;
1838 }
1839 if (QDeclarativeVisualDataModel *dataModel = qobject_cast<QDeclarativeVisualDataModel*>(d->model)) {
1840 int oldCount = dataModel->count();
1841 dataModel->setDelegate(delegate);
1842 if (isComponentComplete()) {
1843 for (int i = 0; i < d->visibleItems.count(); ++i)
1844 d->releaseItem(d->visibleItems.at(i));
1845 d->visibleItems.clear();
1846 d->releaseItem(d->currentItem);
1847 d->currentItem = 0;
1848 updateSections();
1849 refill();
1850 d->moveReason = QDeclarativeListViewPrivate::SetIndex;
1851 d->updateCurrent(d->currentIndex);
1852 if (d->highlight && d->currentItem) {
1853 if (d->autoHighlight)
1854 d->highlight->setPosition(d->currentItem->position());
1855 d->updateTrackedItem();
1856 }
1857 d->updateViewport();
1858 }
1859 if (oldCount != dataModel->count())
1860 emit countChanged();
1861 }
1862 emit delegateChanged();
1863 }
1864
1865 /*!
1866 \qmlproperty int ListView::currentIndex
1867 \qmlproperty Item ListView::currentItem
1868
1869 The \c currentIndex property holds the index of the current item, and
1870 \c currentItem holds the current item. Setting the currentIndex to -1
1871 will clear the highlight and set currentItem to null.
1872
1873 If highlightFollowsCurrentItem is \c true, setting either of these
1874 properties will smoothly scroll the ListView so that the current
1875 item becomes visible.
1876
1877 Note that the position of the current item
1878 may only be approximate until it becomes visible in the view.
1879 */
currentIndex() const1880 int QDeclarativeListView::currentIndex() const
1881 {
1882 Q_D(const QDeclarativeListView);
1883 return d->currentIndex;
1884 }
1885
setCurrentIndex(int index)1886 void QDeclarativeListView::setCurrentIndex(int index)
1887 {
1888 Q_D(QDeclarativeListView);
1889 if (d->requestedIndex >= 0) // currently creating item
1890 return;
1891 d->currentIndexCleared = (index == -1);
1892 if (index == d->currentIndex)
1893 return;
1894 if (isComponentComplete() && d->isValid()) {
1895 if (d->layoutScheduled)
1896 d->layout();
1897 d->moveReason = QDeclarativeListViewPrivate::SetIndex;
1898 d->updateCurrent(index);
1899 } else if (d->currentIndex != index) {
1900 d->currentIndex = index;
1901 emit currentIndexChanged();
1902 }
1903 }
1904
currentItem()1905 QDeclarativeItem *QDeclarativeListView::currentItem()
1906 {
1907 Q_D(QDeclarativeListView);
1908 if (!d->currentItem)
1909 return 0;
1910 return d->currentItem->item;
1911 }
1912
1913 /*!
1914 \qmlproperty Item ListView::highlightItem
1915
1916 This holds the highlight item created from the \l highlight component.
1917
1918 The \c highlightItem is managed by the view unless
1919 \l highlightFollowsCurrentItem is set to false.
1920
1921 \sa highlight, highlightFollowsCurrentItem
1922 */
highlightItem()1923 QDeclarativeItem *QDeclarativeListView::highlightItem()
1924 {
1925 Q_D(QDeclarativeListView);
1926 if (!d->highlight)
1927 return 0;
1928 return d->highlight->item;
1929 }
1930
1931 /*!
1932 \qmlproperty int ListView::count
1933 This property holds the number of items in the view.
1934 */
count() const1935 int QDeclarativeListView::count() const
1936 {
1937 Q_D(const QDeclarativeListView);
1938 if (d->model)
1939 return d->model->count();
1940 return 0;
1941 }
1942
1943 /*!
1944 \qmlproperty Component ListView::highlight
1945 This property holds the component to use as the highlight.
1946
1947 An instance of the highlight component is created for each list.
1948 The geometry of the resulting component instance is managed by the list
1949 so as to stay with the current item, unless the highlightFollowsCurrentItem
1950 property is false.
1951
1952 \sa highlightItem, highlightFollowsCurrentItem, {Models and Views: ListView Examples}{ListView examples}
1953 */
highlight() const1954 QDeclarativeComponent *QDeclarativeListView::highlight() const
1955 {
1956 Q_D(const QDeclarativeListView);
1957 return d->highlightComponent;
1958 }
1959
setHighlight(QDeclarativeComponent * highlight)1960 void QDeclarativeListView::setHighlight(QDeclarativeComponent *highlight)
1961 {
1962 Q_D(QDeclarativeListView);
1963 if (highlight != d->highlightComponent) {
1964 d->highlightComponent = highlight;
1965 d->createHighlight();
1966 if (d->currentItem)
1967 d->updateHighlight();
1968 emit highlightChanged();
1969 }
1970 }
1971
1972 /*!
1973 \qmlproperty bool ListView::highlightFollowsCurrentItem
1974 This property holds whether the highlight is managed by the view.
1975
1976 If this property is true (the default value), the highlight is moved smoothly
1977 to follow the current item. Otherwise, the
1978 highlight is not moved by the view, and any movement must be implemented
1979 by the highlight.
1980
1981 Here is a highlight with its motion defined by a \l {SpringAnimation} item:
1982
1983 \snippet doc/src/snippets/declarative/listview/listview.qml highlightFollowsCurrentItem
1984
1985 Note that the highlight animation also affects the way that the view
1986 is scrolled. This is because the view moves to maintain the
1987 highlight within the preferred highlight range (or visible viewport).
1988
1989 \sa highlight, highlightMoveSpeed
1990 */
highlightFollowsCurrentItem() const1991 bool QDeclarativeListView::highlightFollowsCurrentItem() const
1992 {
1993 Q_D(const QDeclarativeListView);
1994 return d->autoHighlight;
1995 }
1996
setHighlightFollowsCurrentItem(bool autoHighlight)1997 void QDeclarativeListView::setHighlightFollowsCurrentItem(bool autoHighlight)
1998 {
1999 Q_D(QDeclarativeListView);
2000 if (d->autoHighlight != autoHighlight) {
2001 d->autoHighlight = autoHighlight;
2002 if (autoHighlight) {
2003 d->updateHighlight();
2004 } else {
2005 if (d->highlightPosAnimator)
2006 d->highlightPosAnimator->stop();
2007 if (d->highlightSizeAnimator)
2008 d->highlightSizeAnimator->stop();
2009 }
2010 emit highlightFollowsCurrentItemChanged();
2011 }
2012 }
2013
2014 //###Possibly rename these properties, since they are very useful even without a highlight?
2015 /*!
2016 \qmlproperty real ListView::preferredHighlightBegin
2017 \qmlproperty real ListView::preferredHighlightEnd
2018 \qmlproperty enumeration ListView::highlightRangeMode
2019
2020 These properties define the preferred range of the highlight (for the current item)
2021 within the view. The \c preferredHighlightBegin value must be less than the
2022 \c preferredHighlightEnd value.
2023
2024 These properties affect the position of the current item when the list is scrolled.
2025 For example, if the currently selected item should stay in the middle of the
2026 list when the view is scrolled, set the \c preferredHighlightBegin and
2027 \c preferredHighlightEnd values to the top and bottom coordinates of where the middle
2028 item would be. If the \c currentItem is changed programmatically, the list will
2029 automatically scroll so that the current item is in the middle of the view.
2030 Furthermore, the behavior of the current item index will occur whether or not a
2031 highlight exists.
2032
2033 Valid values for \c highlightRangeMode are:
2034
2035 \list
2036 \o ListView.ApplyRange - the view attempts to maintain the highlight within the range.
2037 However, the highlight can move outside of the range at the ends of the list or due
2038 to mouse interaction.
2039 \o ListView.StrictlyEnforceRange - the highlight never moves outside of the range.
2040 The current item changes if a keyboard or mouse action would cause the highlight to move
2041 outside of the range.
2042 \o ListView.NoHighlightRange - this is the default value.
2043 \endlist
2044 */
preferredHighlightBegin() const2045 qreal QDeclarativeListView::preferredHighlightBegin() const
2046 {
2047 Q_D(const QDeclarativeListView);
2048 return d->highlightRangeStart;
2049 }
2050
setPreferredHighlightBegin(qreal start)2051 void QDeclarativeListView::setPreferredHighlightBegin(qreal start)
2052 {
2053 Q_D(QDeclarativeListView);
2054 d->highlightRangeStartValid = true;
2055 if (d->highlightRangeStart == start)
2056 return;
2057 d->highlightRangeStart = start;
2058 d->haveHighlightRange = d->highlightRange != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd;
2059 emit preferredHighlightBeginChanged();
2060 }
2061
resetPreferredHighlightBegin()2062 void QDeclarativeListView::resetPreferredHighlightBegin()
2063 {
2064 Q_D(QDeclarativeListView);
2065 d->highlightRangeStartValid = false;
2066 if (d->highlightRangeStart == 0)
2067 return;
2068 d->highlightRangeStart = 0;
2069 emit preferredHighlightBeginChanged();
2070 }
2071
preferredHighlightEnd() const2072 qreal QDeclarativeListView::preferredHighlightEnd() const
2073 {
2074 Q_D(const QDeclarativeListView);
2075 return d->highlightRangeEnd;
2076 }
2077
setPreferredHighlightEnd(qreal end)2078 void QDeclarativeListView::setPreferredHighlightEnd(qreal end)
2079 {
2080 Q_D(QDeclarativeListView);
2081 d->highlightRangeEndValid = true;
2082 if (d->highlightRangeEnd == end)
2083 return;
2084 d->highlightRangeEnd = end;
2085 d->haveHighlightRange = d->highlightRange != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd;
2086 emit preferredHighlightEndChanged();
2087 }
2088
resetPreferredHighlightEnd()2089 void QDeclarativeListView::resetPreferredHighlightEnd()
2090 {
2091 Q_D(QDeclarativeListView);
2092 d->highlightRangeEndValid = false;
2093 if (d->highlightRangeEnd == 0)
2094 return;
2095 d->highlightRangeEnd = 0;
2096 emit preferredHighlightEndChanged();
2097 }
2098
highlightRangeMode() const2099 QDeclarativeListView::HighlightRangeMode QDeclarativeListView::highlightRangeMode() const
2100 {
2101 Q_D(const QDeclarativeListView);
2102 return d->highlightRange;
2103 }
2104
setHighlightRangeMode(HighlightRangeMode mode)2105 void QDeclarativeListView::setHighlightRangeMode(HighlightRangeMode mode)
2106 {
2107 Q_D(QDeclarativeListView);
2108 if (d->highlightRange == mode)
2109 return;
2110 d->highlightRange = mode;
2111 d->haveHighlightRange = d->highlightRange != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd;
2112 emit highlightRangeModeChanged();
2113 }
2114
2115 /*!
2116 \qmlproperty real ListView::spacing
2117
2118 This property holds the spacing between items.
2119
2120 The default value is 0.
2121 */
spacing() const2122 qreal QDeclarativeListView::spacing() const
2123 {
2124 Q_D(const QDeclarativeListView);
2125 return d->spacing;
2126 }
2127
setSpacing(qreal spacing)2128 void QDeclarativeListView::setSpacing(qreal spacing)
2129 {
2130 Q_D(QDeclarativeListView);
2131 if (spacing != d->spacing) {
2132 d->spacing = spacing;
2133 d->layout();
2134 emit spacingChanged();
2135 }
2136 }
2137
2138 /*!
2139 \qmlproperty enumeration ListView::orientation
2140 This property holds the orientation of the list.
2141
2142 Possible values:
2143
2144 \list
2145 \o ListView.Horizontal - Items are laid out horizontally
2146 \o ListView.Vertical (default) - Items are laid out vertically
2147 \endlist
2148
2149 \table
2150 \row
2151 \o Horizontal orientation:
2152 \image ListViewHorizontal.png
2153
2154 \row
2155 \o Vertical orientation:
2156 \image listview-highlight.png
2157 \endtable
2158 */
orientation() const2159 QDeclarativeListView::Orientation QDeclarativeListView::orientation() const
2160 {
2161 Q_D(const QDeclarativeListView);
2162 return d->orient;
2163 }
2164
setOrientation(QDeclarativeListView::Orientation orientation)2165 void QDeclarativeListView::setOrientation(QDeclarativeListView::Orientation orientation)
2166 {
2167 Q_D(QDeclarativeListView);
2168 if (d->orient != orientation) {
2169 d->orient = orientation;
2170 if (d->orient == QDeclarativeListView::Vertical) {
2171 setContentWidth(-1);
2172 setFlickableDirection(VerticalFlick);
2173 setContentX(0);
2174 } else {
2175 setContentHeight(-1);
2176 setFlickableDirection(HorizontalFlick);
2177 setContentY(0);
2178 }
2179 d->regenerate();
2180 emit orientationChanged();
2181 }
2182 }
2183
2184 /*!
2185 \qmlproperty enumeration ListView::layoutDirection
2186 This property holds the layout direction of the horizontal list.
2187
2188 Possible values:
2189
2190 \list
2191 \o Qt.LeftToRight (default) - Items will be laid out from left to right.
2192 \o Qt.RightToLeft - Items will be laid out from right to let.
2193 \endlist
2194
2195 When using the attached property \l {LayoutMirroring::enabled} for locale layouts,
2196 the layout direction of the horizontal list will be mirrored. However, the actual property
2197 \c layoutDirection will remain unchanged. You can use the property
2198 \l {LayoutMirroring::enabled} to determine whether the direction has been mirrored.
2199
2200 \sa {LayoutMirroring}{LayoutMirroring}
2201 */
2202
layoutDirection() const2203 Qt::LayoutDirection QDeclarativeListView::layoutDirection() const
2204 {
2205 Q_D(const QDeclarativeListView);
2206 return d->layoutDirection;
2207 }
2208
setLayoutDirection(Qt::LayoutDirection layoutDirection)2209 void QDeclarativeListView::setLayoutDirection(Qt::LayoutDirection layoutDirection)
2210 {
2211 Q_D(QDeclarativeListView);
2212 if (d->layoutDirection != layoutDirection) {
2213 d->layoutDirection = layoutDirection;
2214 d->regenerate();
2215 emit layoutDirectionChanged();
2216 }
2217 }
2218
effectiveLayoutDirection() const2219 Qt::LayoutDirection QDeclarativeListView::effectiveLayoutDirection() const
2220 {
2221 Q_D(const QDeclarativeListView);
2222 if (d->effectiveLayoutMirror)
2223 return d->layoutDirection == Qt::RightToLeft ? Qt::LeftToRight : Qt::RightToLeft;
2224 else
2225 return d->layoutDirection;
2226 }
2227
2228 /*!
2229 \qmlproperty bool ListView::keyNavigationWraps
2230 This property holds whether the list wraps key navigation.
2231
2232 If this is true, key navigation that would move the current item selection
2233 past the end of the list instead wraps around and moves the selection to
2234 the start of the list, and vice-versa.
2235
2236 By default, key navigation is not wrapped.
2237 */
isWrapEnabled() const2238 bool QDeclarativeListView::isWrapEnabled() const
2239 {
2240 Q_D(const QDeclarativeListView);
2241 return d->wrap;
2242 }
2243
setWrapEnabled(bool wrap)2244 void QDeclarativeListView::setWrapEnabled(bool wrap)
2245 {
2246 Q_D(QDeclarativeListView);
2247 if (d->wrap == wrap)
2248 return;
2249 d->wrap = wrap;
2250 emit keyNavigationWrapsChanged();
2251 }
2252
2253 /*!
2254 \qmlproperty int ListView::cacheBuffer
2255 This property determines whether delegates are retained outside the
2256 visible area of the view.
2257
2258 If this value is non-zero, the view keeps as many delegates
2259 instantiated as it can fit within the buffer specified. For example,
2260 if in a vertical view the delegate is 20 pixels high and \c cacheBuffer is
2261 set to 40, then up to 2 delegates above and 2 delegates below the visible
2262 area may be retained.
2263
2264 Note that cacheBuffer is not a pixel buffer - it only maintains additional
2265 instantiated delegates.
2266
2267 Setting this value can improve the smoothness of scrolling behavior at the expense
2268 of additional memory usage. It is not a substitute for creating efficient
2269 delegates; the fewer elements in a delegate, the faster a view can be
2270 scrolled.
2271 */
cacheBuffer() const2272 int QDeclarativeListView::cacheBuffer() const
2273 {
2274 Q_D(const QDeclarativeListView);
2275 return d->buffer;
2276 }
2277
setCacheBuffer(int b)2278 void QDeclarativeListView::setCacheBuffer(int b)
2279 {
2280 Q_D(QDeclarativeListView);
2281 if (d->buffer != b) {
2282 d->buffer = b;
2283 if (isComponentComplete()) {
2284 d->bufferMode = QDeclarativeListViewPrivate::BufferBefore | QDeclarativeListViewPrivate::BufferAfter;
2285 refill();
2286 }
2287 emit cacheBufferChanged();
2288 }
2289 }
2290
2291 /*!
2292 \qmlproperty string ListView::section.property
2293 \qmlproperty enumeration ListView::section.criteria
2294 \qmlproperty Component ListView::section.delegate
2295
2296 These properties hold the expression to be evaluated for the \l section attached property.
2297
2298 The \l section attached property enables a ListView to be visually
2299 separated into different parts. These properties determine how sections
2300 are created.
2301
2302 \c section.property holds the name of the property that is the basis
2303 of each section.
2304
2305 \c section.criteria holds the criteria for forming each section based on
2306 \c section.property. This value can be one of:
2307
2308 \list
2309 \o ViewSection.FullString (default) - sections are created based on the
2310 \c section.property value.
2311 \o ViewSection.FirstCharacter - sections are created based on the first
2312 character of the \c section.property value (for example, 'A', 'B', 'C'
2313 sections, etc. for an address book)
2314 \endlist
2315
2316 \c section.delegate holds the delegate component for each section.
2317
2318 Each item in the list has attached properties named \c ListView.section,
2319 \c ListView.previousSection and \c ListView.nextSection. These may be
2320 used to place a section header for related items.
2321
2322 For example, here is a ListView that displays a list of animals, separated
2323 into sections. Each item in the ListView is placed in a different section
2324 depending on the "size" property of the model item. The \c sectionHeading
2325 delegate component provides the light blue bar that marks the beginning of
2326 each section.
2327
2328
2329 \snippet examples/declarative/modelviews/listview/sections.qml 0
2330
2331 \image qml-listview-sections-example.png
2332
2333 \note Adding sections to a ListView does not automatically re-order the
2334 list items by the section criteria.
2335 If the model is not ordered by section, then it is possible that
2336 the sections created will not be unique; each boundary between
2337 differing sections will result in a section header being created
2338 even if that section exists elsewhere.
2339
2340 \sa {Models and Views: ListView Examples}{ListView examples}
2341 */
sectionCriteria()2342 QDeclarativeViewSection *QDeclarativeListView::sectionCriteria()
2343 {
2344 Q_D(QDeclarativeListView);
2345 if (!d->sectionCriteria) {
2346 d->sectionCriteria = new QDeclarativeViewSection(this);
2347 connect(d->sectionCriteria, SIGNAL(propertyChanged()), this, SLOT(updateSections()));
2348 }
2349 return d->sectionCriteria;
2350 }
2351
2352 /*!
2353 \qmlproperty string ListView::currentSection
2354 This property holds the section that is currently at the beginning of the view.
2355 */
currentSection() const2356 QString QDeclarativeListView::currentSection() const
2357 {
2358 Q_D(const QDeclarativeListView);
2359 return d->currentSection;
2360 }
2361
2362 /*!
2363 \qmlproperty real ListView::highlightMoveSpeed
2364 \qmlproperty int ListView::highlightMoveDuration
2365 \qmlproperty real ListView::highlightResizeSpeed
2366 \qmlproperty int ListView::highlightResizeDuration
2367
2368 These properties hold the move and resize animation speed of the highlight delegate.
2369
2370 \l highlightFollowsCurrentItem must be true for these properties
2371 to have effect.
2372
2373 The default value for the speed properties is 400 pixels/second.
2374 The default value for the duration properties is -1, i.e. the
2375 highlight will take as much time as necessary to move at the set speed.
2376
2377 These properties have the same characteristics as a SmoothedAnimation.
2378
2379 \sa highlightFollowsCurrentItem
2380 */
highlightMoveSpeed() const2381 qreal QDeclarativeListView::highlightMoveSpeed() const
2382 {
2383 Q_D(const QDeclarativeListView);\
2384 return d->highlightMoveSpeed;
2385 }
2386
setHighlightMoveSpeed(qreal speed)2387 void QDeclarativeListView::setHighlightMoveSpeed(qreal speed)
2388 {
2389 Q_D(QDeclarativeListView);\
2390 if (d->highlightMoveSpeed != speed) {
2391 d->highlightMoveSpeed = speed;
2392 if (d->highlightPosAnimator)
2393 d->highlightPosAnimator->velocity = d->highlightMoveSpeed;
2394 emit highlightMoveSpeedChanged();
2395 }
2396 }
2397
highlightMoveDuration() const2398 int QDeclarativeListView::highlightMoveDuration() const
2399 {
2400 Q_D(const QDeclarativeListView);
2401 return d->highlightMoveDuration;
2402 }
2403
setHighlightMoveDuration(int duration)2404 void QDeclarativeListView::setHighlightMoveDuration(int duration)
2405 {
2406 Q_D(QDeclarativeListView);\
2407 if (d->highlightMoveDuration != duration) {
2408 d->highlightMoveDuration = duration;
2409 if (d->highlightPosAnimator)
2410 d->highlightPosAnimator->userDuration = d->highlightMoveDuration;
2411 emit highlightMoveDurationChanged();
2412 }
2413 }
2414
highlightResizeSpeed() const2415 qreal QDeclarativeListView::highlightResizeSpeed() const
2416 {
2417 Q_D(const QDeclarativeListView);\
2418 return d->highlightResizeSpeed;
2419 }
2420
setHighlightResizeSpeed(qreal speed)2421 void QDeclarativeListView::setHighlightResizeSpeed(qreal speed)
2422 {
2423 Q_D(QDeclarativeListView);\
2424 if (d->highlightResizeSpeed != speed) {
2425 d->highlightResizeSpeed = speed;
2426 if (d->highlightSizeAnimator)
2427 d->highlightSizeAnimator->velocity = d->highlightResizeSpeed;
2428 emit highlightResizeSpeedChanged();
2429 }
2430 }
2431
highlightResizeDuration() const2432 int QDeclarativeListView::highlightResizeDuration() const
2433 {
2434 Q_D(const QDeclarativeListView);
2435 return d->highlightResizeDuration;
2436 }
2437
setHighlightResizeDuration(int duration)2438 void QDeclarativeListView::setHighlightResizeDuration(int duration)
2439 {
2440 Q_D(QDeclarativeListView);\
2441 if (d->highlightResizeDuration != duration) {
2442 d->highlightResizeDuration = duration;
2443 if (d->highlightSizeAnimator)
2444 d->highlightSizeAnimator->userDuration = d->highlightResizeDuration;
2445 emit highlightResizeDurationChanged();
2446 }
2447 }
2448
2449 /*!
2450 \qmlproperty enumeration ListView::snapMode
2451
2452 This property determines how the view scrolling will settle following a drag or flick.
2453 The possible values are:
2454
2455 \list
2456 \o ListView.NoSnap (default) - the view stops anywhere within the visible area.
2457 \o ListView.SnapToItem - the view settles with an item aligned with the start of
2458 the view.
2459 \o ListView.SnapOneItem - the view settles no more than one item away from the first
2460 visible item at the time the mouse button is released. This mode is particularly
2461 useful for moving one page at a time.
2462 \endlist
2463
2464 \c snapMode does not affect the \l currentIndex. To update the
2465 \l currentIndex as the list is moved, set \l highlightRangeMode
2466 to \c ListView.StrictlyEnforceRange.
2467
2468 \sa highlightRangeMode
2469 */
snapMode() const2470 QDeclarativeListView::SnapMode QDeclarativeListView::snapMode() const
2471 {
2472 Q_D(const QDeclarativeListView);
2473 return d->snapMode;
2474 }
2475
setSnapMode(SnapMode mode)2476 void QDeclarativeListView::setSnapMode(SnapMode mode)
2477 {
2478 Q_D(QDeclarativeListView);
2479 if (d->snapMode != mode) {
2480 d->snapMode = mode;
2481 emit snapModeChanged();
2482 }
2483 }
2484
2485 /*!
2486 \qmlproperty Component ListView::footer
2487 This property holds the component to use as the footer.
2488
2489 An instance of the footer component is created for each view. The
2490 footer is positioned at the end of the view, after any items.
2491
2492 \sa header
2493 */
footer() const2494 QDeclarativeComponent *QDeclarativeListView::footer() const
2495 {
2496 Q_D(const QDeclarativeListView);
2497 return d->footerComponent;
2498 }
2499
setFooter(QDeclarativeComponent * footer)2500 void QDeclarativeListView::setFooter(QDeclarativeComponent *footer)
2501 {
2502 Q_D(QDeclarativeListView);
2503 if (d->footerComponent != footer) {
2504 if (d->footer) {
2505 if (scene())
2506 scene()->removeItem(d->footer->item);
2507 d->footer->item->deleteLater();
2508 delete d->footer;
2509 d->footer = 0;
2510 }
2511 d->footerComponent = footer;
2512 d->minExtentDirty = true;
2513 d->maxExtentDirty = true;
2514 if (isComponentComplete()) {
2515 d->updateFooter();
2516 d->updateViewport();
2517 d->fixupPosition();
2518 }
2519 emit footerChanged();
2520 }
2521 }
2522
2523 /*!
2524 \qmlproperty Component ListView::header
2525 This property holds the component to use as the header.
2526
2527 An instance of the header component is created for each view. The
2528 header is positioned at the beginning of the view, before any items.
2529
2530 \sa footer
2531 */
header() const2532 QDeclarativeComponent *QDeclarativeListView::header() const
2533 {
2534 Q_D(const QDeclarativeListView);
2535 return d->headerComponent;
2536 }
2537
setHeader(QDeclarativeComponent * header)2538 void QDeclarativeListView::setHeader(QDeclarativeComponent *header)
2539 {
2540 Q_D(QDeclarativeListView);
2541 if (d->headerComponent != header) {
2542 if (d->header) {
2543 if (scene())
2544 scene()->removeItem(d->header->item);
2545 d->header->item->deleteLater();
2546 delete d->header;
2547 d->header = 0;
2548 }
2549 d->headerComponent = header;
2550 d->minExtentDirty = true;
2551 d->maxExtentDirty = true;
2552 if (isComponentComplete()) {
2553 d->updateHeader();
2554 d->updateFooter();
2555 d->updateViewport();
2556 d->fixupPosition();
2557 }
2558 emit headerChanged();
2559 }
2560 }
2561
setContentX(qreal pos)2562 void QDeclarativeListView::setContentX(qreal pos)
2563 {
2564 Q_D(QDeclarativeListView);
2565 // Positioning the view manually should override any current movement state
2566 d->moveReason = QDeclarativeListViewPrivate::Other;
2567 QDeclarativeFlickable::setContentX(pos);
2568 }
2569
setContentY(qreal pos)2570 void QDeclarativeListView::setContentY(qreal pos)
2571 {
2572 Q_D(QDeclarativeListView);
2573 // Positioning the view manually should override any current movement state
2574 d->moveReason = QDeclarativeListViewPrivate::Other;
2575 QDeclarativeFlickable::setContentY(pos);
2576 }
2577
event(QEvent * event)2578 bool QDeclarativeListView::event(QEvent *event)
2579 {
2580 Q_D(QDeclarativeListView);
2581 if (event->type() == QEvent::User) {
2582 if (d->layoutScheduled)
2583 d->layout();
2584 return true;
2585 }
2586
2587 return QDeclarativeFlickable::event(event);
2588 }
2589
viewportMoved()2590 void QDeclarativeListView::viewportMoved()
2591 {
2592 Q_D(QDeclarativeListView);
2593 QDeclarativeFlickable::viewportMoved();
2594 if (!d->itemCount)
2595 return;
2596 // Recursion can occur due to refill changing the content size.
2597 if (d->inViewportMoved)
2598 return;
2599 d->inViewportMoved = true;
2600 d->lazyRelease = true;
2601 refill();
2602 if (d->hData.flicking || d->vData.flicking || d->hData.moving || d->vData.moving)
2603 d->moveReason = QDeclarativeListViewPrivate::Mouse;
2604 if (d->moveReason != QDeclarativeListViewPrivate::SetIndex) {
2605 if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange && d->highlight) {
2606 // reposition highlight
2607 qreal pos = d->highlight->position();
2608 qreal viewPos;
2609 qreal highlightStart;
2610 qreal highlightEnd;
2611 if (d->isRightToLeft()) {
2612 // Handle Right-To-Left exceptions
2613 viewPos = -d->position()-d->size();
2614 highlightStart = d->highlightRangeStartValid ? d->size()-d->highlightRangeEnd : d->highlightRangeStart;
2615 highlightEnd = d->highlightRangeEndValid ? d->size()-d->highlightRangeStart : d->highlightRangeEnd;
2616 } else {
2617 viewPos = d->position();
2618 highlightStart = d->highlightRangeStart;
2619 highlightEnd = d->highlightRangeEnd;
2620 }
2621 if (pos > viewPos + highlightEnd - d->highlight->size())
2622 pos = viewPos + highlightEnd - d->highlight->size();
2623 if (pos < viewPos + highlightStart)
2624 pos = viewPos + highlightStart;
2625 d->highlightPosAnimator->stop();
2626 d->highlight->setPosition(qRound(pos));
2627
2628 // update current index
2629 if (FxListItem *snapItem = d->snapItemAt(d->highlight->position())) {
2630 if (snapItem->index >= 0 && snapItem->index != d->currentIndex)
2631 d->updateCurrent(snapItem->index);
2632 }
2633 }
2634 }
2635
2636 if ((d->hData.flicking || d->vData.flicking) && d->correctFlick && !d->inFlickCorrection) {
2637 d->inFlickCorrection = true;
2638 // Near an end and it seems that the extent has changed?
2639 // Recalculate the flick so that we don't end up in an odd position.
2640 if (yflick() && !d->vData.inOvershoot) {
2641 if (d->vData.velocity > 0) {
2642 const qreal minY = minYExtent();
2643 if ((minY - d->vData.move.value() < height()/2 || d->vData.flickTarget - d->vData.move.value() < height()/2)
2644 && minY != d->vData.flickTarget)
2645 d->flickY(-d->vData.smoothVelocity.value());
2646 d->bufferMode = QDeclarativeListViewPrivate::BufferBefore;
2647 } else if (d->vData.velocity < 0) {
2648 const qreal maxY = maxYExtent();
2649 if ((d->vData.move.value() - maxY < height()/2 || d->vData.move.value() - d->vData.flickTarget < height()/2)
2650 && maxY != d->vData.flickTarget)
2651 d->flickY(-d->vData.smoothVelocity.value());
2652 d->bufferMode = QDeclarativeListViewPrivate::BufferAfter;
2653 }
2654 }
2655
2656 if (xflick() && !d->hData.inOvershoot) {
2657 if (d->hData.velocity > 0) {
2658 const qreal minX = minXExtent();
2659 if ((minX - d->hData.move.value() < width()/2 || d->hData.flickTarget - d->hData.move.value() < width()/2)
2660 && minX != d->hData.flickTarget)
2661 d->flickX(-d->hData.smoothVelocity.value());
2662 d->bufferMode = d->isRightToLeft()
2663 ? QDeclarativeListViewPrivate::BufferAfter : QDeclarativeListViewPrivate::BufferBefore;
2664 } else if (d->hData.velocity < 0) {
2665 const qreal maxX = maxXExtent();
2666 if ((d->hData.move.value() - maxX < width()/2 || d->hData.move.value() - d->hData.flickTarget < width()/2)
2667 && maxX != d->hData.flickTarget)
2668 d->flickX(-d->hData.smoothVelocity.value());
2669 d->bufferMode = d->isRightToLeft()
2670 ? QDeclarativeListViewPrivate::BufferBefore : QDeclarativeListViewPrivate::BufferAfter;
2671 }
2672 }
2673 d->inFlickCorrection = false;
2674 }
2675 d->inViewportMoved = false;
2676 }
2677
minYExtent() const2678 qreal QDeclarativeListView::minYExtent() const
2679 {
2680 Q_D(const QDeclarativeListView);
2681 if (d->orient == QDeclarativeListView::Horizontal)
2682 return QDeclarativeFlickable::minYExtent();
2683 if (d->minExtentDirty) {
2684 d->minExtent = -d->startPosition();
2685 if (d->header && d->visibleItems.count())
2686 d->minExtent += d->header->size();
2687 if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange) {
2688 d->minExtent += d->highlightRangeStart;
2689 if (d->sectionCriteria) {
2690 if (d->visibleItem(0))
2691 d->minExtent -= d->visibleItem(0)->sectionSize();
2692 }
2693 d->minExtent = qMax(d->minExtent, -(d->endPositionAt(0) - d->highlightRangeEnd + 1));
2694 }
2695 d->minExtentDirty = false;
2696 }
2697
2698 return d->minExtent;
2699 }
2700
maxYExtent() const2701 qreal QDeclarativeListView::maxYExtent() const
2702 {
2703 Q_D(const QDeclarativeListView);
2704 if (d->orient == QDeclarativeListView::Horizontal)
2705 return height();
2706 if (d->maxExtentDirty) {
2707 if (!d->model || !d->model->count()) {
2708 d->maxExtent = d->header ? -d->header->size() : 0;
2709 d->maxExtent += height();
2710 } else if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange) {
2711 d->maxExtent = -(d->positionAt(d->model->count()-1) - d->highlightRangeStart);
2712 if (d->highlightRangeEnd != d->highlightRangeStart)
2713 d->maxExtent = qMin(d->maxExtent, -(d->endPosition() - d->highlightRangeEnd + 1));
2714 } else {
2715 d->maxExtent = -(d->endPosition() - height() + 1);
2716 }
2717 if (d->footer)
2718 d->maxExtent -= d->footer->size();
2719 qreal minY = minYExtent();
2720 if (d->maxExtent > minY)
2721 d->maxExtent = minY;
2722 d->maxExtentDirty = false;
2723 }
2724 return d->maxExtent;
2725 }
2726
minXExtent() const2727 qreal QDeclarativeListView::minXExtent() const
2728 {
2729 Q_D(const QDeclarativeListView);
2730 if (d->orient == QDeclarativeListView::Vertical)
2731 return QDeclarativeFlickable::minXExtent();
2732 if (d->minExtentDirty) {
2733 d->minExtent = -d->startPosition();
2734
2735 qreal highlightStart;
2736 qreal highlightEnd;
2737 qreal endPositionFirstItem = 0;
2738 if (d->isRightToLeft()) {
2739 if (d->model && d->model->count())
2740 endPositionFirstItem = d->positionAt(d->model->count()-1);
2741 else if (d->header)
2742 d->minExtent += d->header->size();
2743 highlightStart = d->highlightRangeStartValid
2744 ? d->highlightRangeStart - (d->lastPosition()-endPositionFirstItem)
2745 : d->size() - (d->lastPosition()-endPositionFirstItem);
2746 highlightEnd = d->highlightRangeEndValid ? d->highlightRangeEnd : d->size();
2747 if (d->footer)
2748 d->minExtent += d->footer->size();
2749 qreal maxX = maxXExtent();
2750 if (d->minExtent < maxX)
2751 d->minExtent = maxX;
2752 } else {
2753 endPositionFirstItem = d->endPositionAt(0);
2754 highlightStart = d->highlightRangeStart;
2755 highlightEnd = d->highlightRangeEnd;
2756 if (d->header && d->visibleItems.count())
2757 d->minExtent += d->header->size();
2758 }
2759 if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange) {
2760 d->minExtent += d->isRightToLeft() ? -highlightStart : highlightStart;
2761 d->minExtent = qMax(d->minExtent, -(endPositionFirstItem - highlightEnd + 1));
2762 }
2763 d->minExtentDirty = false;
2764 }
2765
2766 return d->minExtent;
2767 }
2768
maxXExtent() const2769 qreal QDeclarativeListView::maxXExtent() const
2770 {
2771 Q_D(const QDeclarativeListView);
2772 if (d->orient == QDeclarativeListView::Vertical)
2773 return width();
2774 if (d->maxExtentDirty) {
2775 qreal highlightStart;
2776 qreal highlightEnd;
2777 qreal lastItemPosition = 0;
2778 d->maxExtent = 0;
2779 if (d->isRightToLeft()) {
2780 highlightStart = d->highlightRangeStartValid ? d->highlightRangeEnd : d->size();
2781 highlightEnd = d->highlightRangeEndValid ? d->highlightRangeStart : d->size();
2782 lastItemPosition = d->endPosition();
2783 } else {
2784 highlightStart = d->highlightRangeStart;
2785 highlightEnd = d->highlightRangeEnd;
2786 if (d->model && d->model->count())
2787 lastItemPosition = d->positionAt(d->model->count()-1);
2788 }
2789 if (!d->model || !d->model->count()) {
2790 if (!d->isRightToLeft())
2791 d->maxExtent = d->header ? -d->header->size() : 0;
2792 d->maxExtent += width();
2793 } else if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange) {
2794 d->maxExtent = -(lastItemPosition - highlightStart);
2795 if (highlightEnd != highlightStart) {
2796 d->maxExtent = d->isRightToLeft()
2797 ? qMax(d->maxExtent, -(d->endPosition() - highlightEnd + 1))
2798 : qMin(d->maxExtent, -(d->endPosition() - highlightEnd + 1));
2799 }
2800 } else {
2801 d->maxExtent = -(d->endPosition() - width() + 1);
2802 }
2803 if (d->isRightToLeft()) {
2804 if (d->header && d->visibleItems.count())
2805 d->maxExtent -= d->header->size();
2806 } else {
2807 if (d->footer)
2808 d->maxExtent -= d->footer->size();
2809 qreal minX = minXExtent();
2810 if (d->maxExtent > minX)
2811 d->maxExtent = minX;
2812 }
2813 d->maxExtentDirty = false;
2814 }
2815 return d->maxExtent;
2816 }
2817
keyPressEvent(QKeyEvent * event)2818 void QDeclarativeListView::keyPressEvent(QKeyEvent *event)
2819 {
2820 Q_D(QDeclarativeListView);
2821 keyPressPreHandler(event);
2822 if (event->isAccepted())
2823 return;
2824
2825 if (d->model && d->model->count() && d->interactive) {
2826 if ((d->orient == QDeclarativeListView::Horizontal && !d->isRightToLeft() && event->key() == Qt::Key_Left)
2827 || (d->orient == QDeclarativeListView::Horizontal && d->isRightToLeft() && event->key() == Qt::Key_Right)
2828 || (d->orient == QDeclarativeListView::Vertical && event->key() == Qt::Key_Up)) {
2829 if (currentIndex() > 0 || (d->wrap && !event->isAutoRepeat())) {
2830 decrementCurrentIndex();
2831 event->accept();
2832 return;
2833 } else if (d->wrap) {
2834 event->accept();
2835 return;
2836 }
2837 } else if ((d->orient == QDeclarativeListView::Horizontal && !d->isRightToLeft() && event->key() == Qt::Key_Right)
2838 || (d->orient == QDeclarativeListView::Horizontal && d->isRightToLeft() && event->key() == Qt::Key_Left)
2839 || (d->orient == QDeclarativeListView::Vertical && event->key() == Qt::Key_Down)) {
2840 if (currentIndex() < d->model->count() - 1 || (d->wrap && !event->isAutoRepeat())) {
2841 incrementCurrentIndex();
2842 event->accept();
2843 return;
2844 } else if (d->wrap) {
2845 event->accept();
2846 return;
2847 }
2848 }
2849 }
2850 event->ignore();
2851 QDeclarativeFlickable::keyPressEvent(event);
2852 }
2853
geometryChanged(const QRectF & newGeometry,const QRectF & oldGeometry)2854 void QDeclarativeListView::geometryChanged(const QRectF &newGeometry,
2855 const QRectF &oldGeometry)
2856 {
2857 Q_D(QDeclarativeListView);
2858 d->maxExtentDirty = true;
2859 d->minExtentDirty = true;
2860 if (d->isRightToLeft() && d->orient == QDeclarativeListView::Horizontal) {
2861 // maintain position relative to the right edge
2862 int dx = newGeometry.width() - oldGeometry.width();
2863 setContentX(contentX() - dx);
2864 }
2865 QDeclarativeFlickable::geometryChanged(newGeometry, oldGeometry);
2866 }
2867
2868
2869 /*!
2870 \qmlmethod ListView::incrementCurrentIndex()
2871
2872 Increments the current index. The current index will wrap
2873 if keyNavigationWraps is true and it is currently at the end.
2874 This method has no effect if the \l count is zero.
2875
2876 \bold Note: methods should only be called after the Component has completed.
2877 */
incrementCurrentIndex()2878 void QDeclarativeListView::incrementCurrentIndex()
2879 {
2880 Q_D(QDeclarativeListView);
2881 int count = d->model ? d->model->count() : 0;
2882 if (count && (currentIndex() < count - 1 || d->wrap)) {
2883 d->moveReason = QDeclarativeListViewPrivate::SetIndex;
2884 int index = currentIndex()+1;
2885 setCurrentIndex((index >= 0 && index < count) ? index : 0);
2886 }
2887 }
2888
2889 /*!
2890 \qmlmethod ListView::decrementCurrentIndex()
2891
2892 Decrements the current index. The current index will wrap
2893 if keyNavigationWraps is true and it is currently at the beginning.
2894 This method has no effect if the \l count is zero.
2895
2896 \bold Note: methods should only be called after the Component has completed.
2897 */
decrementCurrentIndex()2898 void QDeclarativeListView::decrementCurrentIndex()
2899 {
2900 Q_D(QDeclarativeListView);
2901 int count = d->model ? d->model->count() : 0;
2902 if (count && (currentIndex() > 0 || d->wrap)) {
2903 d->moveReason = QDeclarativeListViewPrivate::SetIndex;
2904 int index = currentIndex()-1;
2905 setCurrentIndex((index >= 0 && index < count) ? index : count-1);
2906 }
2907 }
2908
positionViewAtIndex(int index,int mode)2909 void QDeclarativeListViewPrivate::positionViewAtIndex(int index, int mode)
2910 {
2911 Q_Q(QDeclarativeListView);
2912 if (!isValid())
2913 return;
2914 if (mode < QDeclarativeListView::Beginning || mode > QDeclarativeListView::Contain)
2915 return;
2916 int idx = qMax(qMin(index, model->count()-1), 0);
2917
2918 if (layoutScheduled)
2919 layout();
2920 qreal pos = isRightToLeft() ? -position() - size() : position();
2921 FxListItem *item = visibleItem(idx);
2922 qreal maxExtent;
2923 if (orient == QDeclarativeListView::Vertical)
2924 maxExtent = -q->maxYExtent();
2925 else
2926 maxExtent = isRightToLeft() ? q->minXExtent()-size(): -q->maxXExtent();
2927
2928 if (!item) {
2929 int itemPos = positionAt(idx);
2930 // save the currently visible items in case any of them end up visible again
2931 QList<FxListItem*> oldVisible = visibleItems;
2932 visibleItems.clear();
2933 visiblePos = itemPos;
2934 visibleIndex = idx;
2935 setPosition(qMin(qreal(itemPos), maxExtent));
2936 // now release the reference to all the old visible items.
2937 for (int i = 0; i < oldVisible.count(); ++i)
2938 releaseItem(oldVisible.at(i));
2939 item = visibleItem(idx);
2940 }
2941 if (item) {
2942 const qreal itemPos = item->position();
2943 switch (mode) {
2944 case QDeclarativeListView::Beginning:
2945 pos = itemPos;
2946 if (index < 0 && header)
2947 pos -= header->size();
2948 break;
2949 case QDeclarativeListView::Center:
2950 pos = itemPos - (size() - item->size())/2;
2951 break;
2952 case QDeclarativeListView::End:
2953 pos = itemPos - size() + item->size();
2954 if (index >= model->count() && footer)
2955 pos += footer->size();
2956 break;
2957 case QDeclarativeListView::Visible:
2958 if (itemPos > pos + size())
2959 pos = itemPos - size() + item->size();
2960 else if (item->endPosition() < pos)
2961 pos = itemPos;
2962 break;
2963 case QDeclarativeListView::Contain:
2964 if (item->endPosition() > pos + size())
2965 pos = itemPos - size() + item->size();
2966 if (itemPos < pos)
2967 pos = itemPos;
2968 }
2969 pos = qMin(pos, maxExtent);
2970 qreal minExtent;
2971 if (orient == QDeclarativeListView::Vertical) {
2972 minExtent = -q->minYExtent();
2973 } else {
2974 minExtent = isRightToLeft() ? q->maxXExtent()-size(): -q->minXExtent();
2975 }
2976 pos = qMax(pos, minExtent);
2977 moveReason = QDeclarativeListViewPrivate::Other;
2978 q->cancelFlick();
2979 setPosition(pos);
2980 if (highlight) {
2981 if (autoHighlight) {
2982 highlight->setPosition(currentItem->itemPosition());
2983 highlight->setSize(currentItem->itemSize());
2984 }
2985 updateHighlight();
2986 }
2987 }
2988 fixupPosition();
2989 }
2990
2991 /*!
2992 \qmlmethod ListView::positionViewAtIndex(int index, PositionMode mode)
2993
2994 Positions the view such that the \a index is at the position specified by
2995 \a mode:
2996
2997 \list
2998 \o ListView.Beginning - position item at the top (or left for horizontal orientation) of the view.
2999 \o ListView.Center - position item in the center of the view.
3000 \o ListView.End - position item at bottom (or right for horizontal orientation) of the view.
3001 \o ListView.Visible - if any part of the item is visible then take no action, otherwise
3002 bring the item into view.
3003 \o ListView.Contain - ensure the entire item is visible. If the item is larger than
3004 the view the item is positioned at the top (or left for horizontal orientation) of the view.
3005 \endlist
3006
3007 If positioning the view at \a index would cause empty space to be displayed at
3008 the beginning or end of the view, the view will be positioned at the boundary.
3009
3010 It is not recommended to use \l {Flickable::}{contentX} or \l {Flickable::}{contentY} to position the view
3011 at a particular index. This is unreliable since removing items from the start
3012 of the list does not cause all other items to be repositioned, and because
3013 the actual start of the view can vary based on the size of the delegates.
3014 The correct way to bring an item into view is with \c positionViewAtIndex.
3015
3016 \bold Note: methods should only be called after the Component has completed. To position
3017 the view at startup, this method should be called by Component.onCompleted. For
3018 example, to position the view at the end:
3019
3020 \code
3021 Component.onCompleted: positionViewAtIndex(count - 1, ListView.Beginning)
3022 \endcode
3023 */
positionViewAtIndex(int index,int mode)3024 void QDeclarativeListView::positionViewAtIndex(int index, int mode)
3025 {
3026 Q_D(QDeclarativeListView);
3027 if (!d->isValid() || index < 0 || index >= d->model->count())
3028 return;
3029 d->positionViewAtIndex(index, mode);
3030 }
3031
3032 /*!
3033 \qmlmethod ListView::positionViewAtBeginning()
3034 \qmlmethod ListView::positionViewAtEnd()
3035 \since QtQuick 1.1
3036
3037 Positions the view at the beginning or end, taking into account any header or footer.
3038
3039 It is not recommended to use \l {Flickable::}{contentX} or \l {Flickable::}{contentY} to position the view
3040 at a particular index. This is unreliable since removing items from the start
3041 of the list does not cause all other items to be repositioned, and because
3042 the actual start of the view can vary based on the size of the delegates.
3043
3044 \bold Note: methods should only be called after the Component has completed. To position
3045 the view at startup, this method should be called by Component.onCompleted. For
3046 example, to position the view at the end on startup:
3047
3048 \code
3049 Component.onCompleted: positionViewAtEnd()
3050 \endcode
3051 */
positionViewAtBeginning()3052 void QDeclarativeListView::positionViewAtBeginning()
3053 {
3054 Q_D(QDeclarativeListView);
3055 if (!d->isValid())
3056 return;
3057 d->positionViewAtIndex(-1, Beginning);
3058 }
3059
positionViewAtEnd()3060 void QDeclarativeListView::positionViewAtEnd()
3061 {
3062 Q_D(QDeclarativeListView);
3063 if (!d->isValid())
3064 return;
3065 d->positionViewAtIndex(d->model->count(), End);
3066 }
3067
3068 /*!
3069 \qmlmethod int ListView::indexAt(int x, int y)
3070
3071 Returns the index of the visible item containing the point \a x, \a y in content
3072 coordinates. If there is no item at the point specified, or the item is
3073 not visible -1 is returned.
3074
3075 If the item is outside the visible area, -1 is returned, regardless of
3076 whether an item will exist at that point when scrolled into view.
3077
3078 \bold Note: methods should only be called after the Component has completed.
3079 */
indexAt(qreal x,qreal y) const3080 int QDeclarativeListView::indexAt(qreal x, qreal y) const
3081 {
3082 Q_D(const QDeclarativeListView);
3083 for (int i = 0; i < d->visibleItems.count(); ++i) {
3084 const FxListItem *listItem = d->visibleItems.at(i);
3085 if(listItem->contains(x, y))
3086 return listItem->index;
3087 }
3088
3089 return -1;
3090 }
3091
componentComplete()3092 void QDeclarativeListView::componentComplete()
3093 {
3094 Q_D(QDeclarativeListView);
3095 QDeclarativeFlickable::componentComplete();
3096 updateSections();
3097 d->updateHeader();
3098 d->updateFooter();
3099 if (d->isValid()) {
3100 refill();
3101 d->moveReason = QDeclarativeListViewPrivate::SetIndex;
3102 if (d->currentIndex < 0 && !d->currentIndexCleared)
3103 d->updateCurrent(0);
3104 else
3105 d->updateCurrent(d->currentIndex);
3106 if (d->highlight && d->currentItem) {
3107 if (d->autoHighlight)
3108 d->highlight->setPosition(d->currentItem->position());
3109 d->updateTrackedItem();
3110 }
3111 d->moveReason = QDeclarativeListViewPrivate::Other;
3112 d->fixupPosition();
3113 }
3114 }
3115
updateSections()3116 void QDeclarativeListView::updateSections()
3117 {
3118 Q_D(QDeclarativeListView);
3119 if (isComponentComplete() && d->model) {
3120 QList<QByteArray> roles;
3121 if (d->sectionCriteria && !d->sectionCriteria->property().isEmpty())
3122 roles << d->sectionCriteria->property().toUtf8();
3123 d->model->setWatchedRoles(roles);
3124 d->updateSections();
3125 if (d->itemCount)
3126 d->layout();
3127 }
3128 }
3129
refill()3130 void QDeclarativeListView::refill()
3131 {
3132 Q_D(QDeclarativeListView);
3133 if (d->isRightToLeft())
3134 d->refill(-d->position()-d->size()+1, -d->position());
3135 else
3136 d->refill(d->position(), d->position()+d->size()-1);
3137 }
3138
trackedPositionChanged()3139 void QDeclarativeListView::trackedPositionChanged()
3140 {
3141 Q_D(QDeclarativeListView);
3142 if (!d->trackedItem || !d->currentItem)
3143 return;
3144 if (d->moveReason == QDeclarativeListViewPrivate::SetIndex) {
3145 qreal trackedPos = qCeil(d->trackedItem->position());
3146 qreal trackedSize = d->trackedItem->size();
3147 if (d->trackedItem != d->currentItem) {
3148 trackedPos -= d->currentItem->sectionSize();
3149 trackedSize += d->currentItem->sectionSize();
3150 }
3151 qreal viewPos;
3152 qreal highlightStart;
3153 qreal highlightEnd;
3154 if (d->isRightToLeft()) {
3155 viewPos = -d->position()-d->size();
3156 highlightStart = d->highlightRangeStartValid ? d->size()-d->highlightRangeEnd : d->highlightRangeStart;
3157 highlightEnd = d->highlightRangeEndValid ? d->size()-d->highlightRangeStart : d->highlightRangeEnd;
3158 } else {
3159 viewPos = d->position();
3160 highlightStart = d->highlightRangeStart;
3161 highlightEnd = d->highlightRangeEnd;
3162 }
3163 qreal pos = viewPos;
3164 if (d->haveHighlightRange) {
3165 if (d->highlightRange == StrictlyEnforceRange) {
3166 if (trackedPos > pos + highlightEnd - d->trackedItem->size())
3167 pos = trackedPos - highlightEnd + d->trackedItem->size();
3168 if (trackedPos < pos + highlightStart)
3169 pos = trackedPos - highlightStart;
3170 } else {
3171 if (trackedPos < d->startPosition() + highlightStart) {
3172 pos = d->startPosition();
3173 } else if (d->trackedItem->endPosition() > d->endPosition() - d->size() + highlightEnd) {
3174 pos = d->endPosition() - d->size() + 1;
3175 if (pos < d->startPosition())
3176 pos = d->startPosition();
3177 } else {
3178 if (trackedPos < viewPos + highlightStart) {
3179 pos = trackedPos - highlightStart;
3180 } else if (trackedPos > viewPos + highlightEnd - trackedSize) {
3181 pos = trackedPos - highlightEnd + trackedSize;
3182 }
3183 }
3184 }
3185 } else {
3186 if (trackedPos < viewPos && d->currentItem->position() < viewPos) {
3187 pos = d->currentItem->position() < trackedPos ? trackedPos : d->currentItem->position();
3188 } else if (d->trackedItem->endPosition() >= viewPos + d->size()
3189 && d->currentItem->endPosition() >= viewPos + d->size()) {
3190 if (d->trackedItem->endPosition() <= d->currentItem->endPosition()) {
3191 pos = d->trackedItem->endPosition() - d->size() + 1;
3192 if (trackedSize > d->size())
3193 pos = trackedPos;
3194 } else {
3195 pos = d->currentItem->endPosition() - d->size() + 1;
3196 if (d->currentItem->size() > d->size())
3197 pos = d->currentItem->position();
3198 }
3199 }
3200 }
3201 if (viewPos != pos) {
3202 cancelFlick();
3203 d->calcVelocity = true;
3204 d->setPosition(pos);
3205 d->calcVelocity = false;
3206 }
3207 }
3208 }
3209
itemsInserted(int modelIndex,int count)3210 void QDeclarativeListView::itemsInserted(int modelIndex, int count)
3211 {
3212 Q_D(QDeclarativeListView);
3213 if (!isComponentComplete())
3214 return;
3215 d->updateUnrequestedIndexes();
3216 d->moveReason = QDeclarativeListViewPrivate::Other;
3217
3218 qreal tempPos = d->isRightToLeft() ? -d->position()-d->size() : d->position();
3219 int index = d->visibleItems.count() ? d->mapFromModel(modelIndex) : 0;
3220
3221 if (index < 0) {
3222 int i = d->visibleItems.count() - 1;
3223 while (i > 0 && d->visibleItems.at(i)->index == -1)
3224 --i;
3225 if (i == 0 && d->visibleItems.first()->index == -1) {
3226 // there are no visible items except items marked for removal
3227 index = d->visibleItems.count();
3228 } else if (d->visibleItems.at(i)->index + 1 == modelIndex
3229 && d->visibleItems.at(i)->endPosition() < d->buffer+tempPos+d->size()-1) {
3230 // Special case of appending an item to the model.
3231 index = d->visibleItems.count();
3232 } else {
3233 if (modelIndex < d->visibleIndex) {
3234 // Insert before visible items
3235 d->visibleIndex += count;
3236 for (int i = 0; i < d->visibleItems.count(); ++i) {
3237 FxListItem *listItem = d->visibleItems.at(i);
3238 if (listItem->index != -1 && listItem->index >= modelIndex)
3239 listItem->index += count;
3240 }
3241 }
3242 if (d->currentIndex >= modelIndex) {
3243 // adjust current item index
3244 d->currentIndex += count;
3245 if (d->currentItem)
3246 d->currentItem->index = d->currentIndex;
3247 emit currentIndexChanged();
3248 }
3249 d->scheduleLayout();
3250 d->itemCount += count;
3251 emit countChanged();
3252 return;
3253 }
3254 }
3255
3256 // index can be the next item past the end of the visible items list (i.e. appended)
3257 int pos = 0;
3258 if (d->visibleItems.count()) {
3259 pos = index < d->visibleItems.count() ? d->visibleItems.at(index)->position()
3260 : d->visibleItems.last()->endPosition()+d->spacing+1;
3261 } else if (d->itemCount == 0 && d->header) {
3262 pos = d->header->size();
3263 }
3264
3265 int initialPos = pos;
3266 int diff = 0;
3267 QList<FxListItem*> added;
3268 bool addedVisible = false;
3269 FxListItem *firstVisible = d->firstVisibleItem();
3270 if (firstVisible && pos < firstVisible->position()) {
3271 // Insert items before the visible item.
3272 int insertionIdx = index;
3273 int i = 0;
3274 int from = tempPos - d->buffer;
3275 for (i = count-1; i >= 0 && pos > from; --i) {
3276 if (!addedVisible) {
3277 d->scheduleLayout();
3278 addedVisible = true;
3279 }
3280 FxListItem *item = d->createItem(modelIndex + i);
3281 if (!item) {
3282 // broken or no delegate
3283 d->clear();
3284 return;
3285 }
3286 d->visibleItems.insert(insertionIdx, item);
3287 pos -= item->size() + d->spacing;
3288 item->setPosition(pos);
3289 index++;
3290 }
3291 if (i >= 0) {
3292 // If we didn't insert all our new items - anything
3293 // before the current index is not visible - remove it.
3294 while (insertionIdx--) {
3295 FxListItem *item = d->visibleItems.takeFirst();
3296 if (item->index != -1)
3297 d->visibleIndex++;
3298 d->releaseItem(item);
3299 }
3300 } else {
3301 // adjust pos of items before inserted items.
3302 for (int i = insertionIdx-1; i >= 0; i--) {
3303 FxListItem *listItem = d->visibleItems.at(i);
3304 listItem->setPosition(listItem->position() - (initialPos - pos));
3305 }
3306 }
3307 } else {
3308 int i = 0;
3309 int to = d->buffer+tempPos+d->size()-1;
3310 for (i = 0; i < count && pos <= to; ++i) {
3311 if (!addedVisible) {
3312 d->scheduleLayout();
3313 addedVisible = true;
3314 }
3315 FxListItem *item = d->createItem(modelIndex + i);
3316 if (!item) {
3317 // broken or no delegate
3318 d->clear();
3319 return;
3320 }
3321 d->visibleItems.insert(index, item);
3322 item->setPosition(pos);
3323 added.append(item);
3324 pos += item->size() + d->spacing;
3325 ++index;
3326 }
3327 if (i != count) {
3328 // We didn't insert all our new items, which means anything
3329 // beyond the current index is not visible - remove it.
3330 while (d->visibleItems.count() > index)
3331 d->releaseItem(d->visibleItems.takeLast());
3332 }
3333 diff = pos - initialPos;
3334 }
3335 if (d->itemCount && d->currentIndex >= modelIndex) {
3336 // adjust current item index
3337 d->currentIndex += count;
3338 if (d->currentItem) {
3339 d->currentItem->index = d->currentIndex;
3340 d->currentItem->setPosition(d->currentItem->position() + diff);
3341 }
3342 emit currentIndexChanged();
3343 } else if (!d->itemCount && (!d->currentIndex || (d->currentIndex < 0 && !d->currentIndexCleared))) {
3344 d->updateCurrent(0);
3345 }
3346 // Update the indexes of the following visible items.
3347 for (; index < d->visibleItems.count(); ++index) {
3348 FxListItem *listItem = d->visibleItems.at(index);
3349 if (d->currentItem && listItem->item != d->currentItem->item)
3350 listItem->setPosition(listItem->position() + diff);
3351 if (listItem->index != -1)
3352 listItem->index += count;
3353 }
3354 // everything is in order now - emit add() signal
3355 for (int j = 0; j < added.count(); ++j)
3356 added.at(j)->attached->emitAdd();
3357
3358 d->updateSections();
3359 d->itemCount += count;
3360 emit countChanged();
3361 }
3362
itemsRemoved(int modelIndex,int count)3363 void QDeclarativeListView::itemsRemoved(int modelIndex, int count)
3364 {
3365 Q_D(QDeclarativeListView);
3366 if (!isComponentComplete())
3367 return;
3368 d->moveReason = QDeclarativeListViewPrivate::Other;
3369 d->updateUnrequestedIndexes();
3370 d->itemCount -= count;
3371
3372 FxListItem *firstVisible = d->firstVisibleItem();
3373 int preRemovedSize = 0;
3374 bool removedVisible = false;
3375 // Remove the items from the visible list, skipping anything already marked for removal
3376 QList<FxListItem*>::Iterator it = d->visibleItems.begin();
3377 while (it != d->visibleItems.end()) {
3378 FxListItem *item = *it;
3379 if (item->index == -1 || item->index < modelIndex) {
3380 // already removed, or before removed items
3381 ++it;
3382 } else if (item->index >= modelIndex + count) {
3383 // after removed items
3384 item->index -= count;
3385 ++it;
3386 } else {
3387 // removed item
3388 if (!removedVisible) {
3389 d->scheduleLayout();
3390 removedVisible = true;
3391 }
3392 item->attached->emitRemove();
3393 if (item->attached->delayRemove()) {
3394 item->index = -1;
3395 connect(item->attached, SIGNAL(delayRemoveChanged()), this, SLOT(destroyRemoved()), Qt::QueuedConnection);
3396 ++it;
3397 } else {
3398 if (item == firstVisible)
3399 firstVisible = 0;
3400 if (firstVisible && item->position() < firstVisible->position())
3401 preRemovedSize += item->size();
3402 it = d->visibleItems.erase(it);
3403 d->releaseItem(item);
3404 }
3405 }
3406 }
3407
3408 if (firstVisible && d->visibleItems.first() != firstVisible)
3409 d->visibleItems.first()->setPosition(d->visibleItems.first()->position() + preRemovedSize);
3410
3411 // fix current
3412 if (d->currentIndex >= modelIndex + count) {
3413 d->currentIndex -= count;
3414 if (d->currentItem)
3415 d->currentItem->index -= count;
3416 emit currentIndexChanged();
3417 } else if (d->currentIndex >= modelIndex && d->currentIndex < modelIndex + count) {
3418 // current item has been removed.
3419 d->currentItem->attached->setIsCurrentItem(false);
3420 d->releaseItem(d->currentItem);
3421 d->currentItem = 0;
3422 d->currentIndex = -1;
3423 if (d->itemCount)
3424 d->updateCurrent(qMin(modelIndex, d->itemCount-1));
3425 else
3426 emit currentIndexChanged();
3427 }
3428
3429 // update visibleIndex
3430 bool haveVisibleIndex = false;
3431 for (it = d->visibleItems.begin(); it != d->visibleItems.end(); ++it) {
3432 if ((*it)->index != -1) {
3433 d->visibleIndex = (*it)->index;
3434 haveVisibleIndex = true;
3435 break;
3436 }
3437 }
3438
3439 if (!haveVisibleIndex) {
3440 d->timeline.clear();
3441 if (removedVisible && d->itemCount == 0) {
3442 d->visibleIndex = 0;
3443 d->visiblePos = d->header ? d->header->size() : 0;
3444 d->setPosition(0);
3445 d->updateHeader();
3446 d->updateFooter();
3447 update();
3448 } else {
3449 if (modelIndex < d->visibleIndex)
3450 d->visibleIndex = modelIndex+1;
3451 d->visibleIndex = qMax(qMin(d->visibleIndex, d->itemCount-1), 0);
3452 }
3453 }
3454
3455 d->updateSections();
3456 emit countChanged();
3457 }
3458
destroyRemoved()3459 void QDeclarativeListView::destroyRemoved()
3460 {
3461 Q_D(QDeclarativeListView);
3462 for (QList<FxListItem*>::Iterator it = d->visibleItems.begin();
3463 it != d->visibleItems.end();) {
3464 FxListItem *listItem = *it;
3465 if (listItem->index == -1 && listItem->attached->delayRemove() == false) {
3466 d->releaseItem(listItem);
3467 it = d->visibleItems.erase(it);
3468 } else {
3469 ++it;
3470 }
3471 }
3472
3473 // Correct the positioning of the items
3474 d->updateSections();
3475 d->layout();
3476 }
3477
itemsMoved(int from,int to,int count)3478 void QDeclarativeListView::itemsMoved(int from, int to, int count)
3479 {
3480 Q_D(QDeclarativeListView);
3481 if (!isComponentComplete())
3482 return;
3483 d->updateUnrequestedIndexes();
3484
3485 if (d->visibleItems.isEmpty()) {
3486 refill();
3487 return;
3488 }
3489
3490 d->moveReason = QDeclarativeListViewPrivate::Other;
3491 FxListItem *firstVisible = d->firstVisibleItem();
3492 qreal firstItemPos = firstVisible->position();
3493 QHash<int,FxListItem*> moved;
3494 int moveBy = 0;
3495
3496 QList<FxListItem*>::Iterator it = d->visibleItems.begin();
3497 while (it != d->visibleItems.end()) {
3498 FxListItem *item = *it;
3499 if (item->index >= from && item->index < from + count) {
3500 // take the items that are moving
3501 item->index += (to-from);
3502 moved.insert(item->index, item);
3503 if (item->position() < firstItemPos)
3504 moveBy += item->size();
3505 it = d->visibleItems.erase(it);
3506 } else {
3507 // move everything after the moved items.
3508 if (item->index > from && item->index != -1)
3509 item->index -= count;
3510 ++it;
3511 }
3512 }
3513
3514 int remaining = count;
3515 int endIndex = d->visibleIndex;
3516 it = d->visibleItems.begin();
3517 while (it != d->visibleItems.end()) {
3518 FxListItem *item = *it;
3519 if (remaining && item->index >= to && item->index < to + count) {
3520 // place items in the target position, reusing any existing items
3521 FxListItem *movedItem = moved.take(item->index);
3522 if (!movedItem)
3523 movedItem = d->createItem(item->index);
3524 if (!movedItem) {
3525 // broken or no delegate
3526 d->clear();
3527 return;
3528 }
3529 if (item->index <= firstVisible->index)
3530 moveBy -= movedItem->size();
3531 it = d->visibleItems.insert(it, movedItem);
3532 ++it;
3533 --remaining;
3534 } else {
3535 if (item->index != -1) {
3536 if (item->index >= to) {
3537 // update everything after the moved items.
3538 item->index += count;
3539 }
3540 endIndex = item->index;
3541 }
3542 ++it;
3543 }
3544 }
3545
3546 // If we have moved items to the end of the visible items
3547 // then add any existing moved items that we have
3548 while (FxListItem *item = moved.take(endIndex+1)) {
3549 d->visibleItems.append(item);
3550 ++endIndex;
3551 }
3552
3553 // update visibleIndex
3554 for (it = d->visibleItems.begin(); it != d->visibleItems.end(); ++it) {
3555 if ((*it)->index != -1) {
3556 d->visibleIndex = (*it)->index;
3557 break;
3558 }
3559 }
3560
3561 // Fix current index
3562 if (d->currentIndex >= 0 && d->currentItem) {
3563 int oldCurrent = d->currentIndex;
3564 d->currentIndex = d->model->indexOf(d->currentItem->item, this);
3565 if (oldCurrent != d->currentIndex) {
3566 d->currentItem->index = d->currentIndex;
3567 emit currentIndexChanged();
3568 }
3569 }
3570
3571 // Whatever moved items remain are no longer visible items.
3572 while (moved.count()) {
3573 int idx = moved.begin().key();
3574 FxListItem *item = moved.take(idx);
3575 if (d->currentItem && item->item == d->currentItem->item)
3576 item->setPosition(d->positionAt(idx));
3577 d->releaseItem(item);
3578 }
3579
3580 // Ensure we don't cause an ugly list scroll.
3581 if (!d->visibleItems.isEmpty())
3582 d->visibleItems.first()->setPosition(d->visibleItems.first()->position() + moveBy);
3583
3584 d->updateSections();
3585 d->layout();
3586 }
3587
itemsChanged(int,int)3588 void QDeclarativeListView::itemsChanged(int, int)
3589 {
3590 Q_D(QDeclarativeListView);
3591 d->updateSections();
3592 d->layout();
3593 }
3594
modelReset()3595 void QDeclarativeListView::modelReset()
3596 {
3597 Q_D(QDeclarativeListView);
3598 d->moveReason = QDeclarativeListViewPrivate::SetIndex;
3599 d->regenerate();
3600 if (d->highlight && d->currentItem) {
3601 if (d->autoHighlight)
3602 d->highlight->setPosition(d->currentItem->position());
3603 d->updateTrackedItem();
3604 }
3605 d->moveReason = QDeclarativeListViewPrivate::Other;
3606 emit countChanged();
3607 }
3608
createdItem(int index,QDeclarativeItem * item)3609 void QDeclarativeListView::createdItem(int index, QDeclarativeItem *item)
3610 {
3611 Q_D(QDeclarativeListView);
3612 if (d->requestedIndex != index) {
3613 item->setParentItem(contentItem());
3614 d->unrequestedItems.insert(item, index);
3615 if (d->orient == QDeclarativeListView::Vertical) {
3616 item->setY(d->positionAt(index));
3617 } else {
3618 if (d->isRightToLeft())
3619 item->setX(-d->positionAt(index)-item->width());
3620 else
3621 item->setX(d->positionAt(index));
3622 }
3623 }
3624 }
3625
destroyingItem(QDeclarativeItem * item)3626 void QDeclarativeListView::destroyingItem(QDeclarativeItem *item)
3627 {
3628 Q_D(QDeclarativeListView);
3629 d->unrequestedItems.remove(item);
3630 }
3631
animStopped()3632 void QDeclarativeListView::animStopped()
3633 {
3634 Q_D(QDeclarativeListView);
3635 d->bufferMode = QDeclarativeListViewPrivate::NoBuffer;
3636 if (d->haveHighlightRange && d->highlightRange == QDeclarativeListView::StrictlyEnforceRange)
3637 d->updateHighlight();
3638 }
3639
qmlAttachedProperties(QObject * obj)3640 QDeclarativeListViewAttached *QDeclarativeListView::qmlAttachedProperties(QObject *obj)
3641 {
3642 return new QDeclarativeListViewAttached(obj);
3643 }
3644
3645 QT_END_NAMESPACE
3646