Remove "All rights reserved" line from license headers.
[profile/ivi/qtdeclarative.git] / src / quick / items / qquickitemview.cpp
index 340206b..6577617 100644 (file)
@@ -1,8 +1,7 @@
 /****************************************************************************
 **
-** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
-** All rights reserved.
-** Contact: Nokia Corporation (qt-info@nokia.com)
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
 **
 ** This file is part of the QtDeclarative module of the Qt Toolkit.
 **
@@ -35,6 +34,7 @@
 **
 **
 **
+**
 ** $QT_END_LICENSE$
 **
 ****************************************************************************/
@@ -75,7 +75,7 @@ void QQuickItemViewChangeSet::applyChanges(const QDeclarativeChangeSet &changeSe
     pendingChanges.apply(changeSet);
 
     int moveId = -1;
-    int moveOffset;
+    int moveOffset = 0;
 
     foreach (const QDeclarativeChangeSet::Remove &r, changeSet.removes()) {
         itemCount -= r.count;
@@ -700,6 +700,18 @@ int QQuickItemView::indexAt(qreal x, qreal y) const
     return -1;
 }
 
+QQuickItem *QQuickItemView::itemAt(qreal x, qreal y) const
+{
+    Q_D(const QQuickItemView);
+    for (int i = 0; i < d->visibleItems.count(); ++i) {
+        const FxViewItem *item = d->visibleItems.at(i);
+        if (item->contains(x, y))
+            return item->item;
+    }
+
+    return 0;
+}
+
 void QQuickItemViewPrivate::applyPendingChanges()
 {
     Q_Q(QQuickItemView);
@@ -763,7 +775,7 @@ void QQuickItemView::destroyRemoved()
     // Correct the positioning of the items
     d->updateSections();
     d->forceLayout = true;
-    d->layout();
+    polish();
 }
 
 void QQuickItemView::modelUpdated(const QDeclarativeChangeSet &changeSet, bool reset)
@@ -866,6 +878,10 @@ void QQuickItemView::geometryChanged(const QRectF &newGeometry, const QRectF &ol
 {
     Q_D(QQuickItemView);
     d->markExtentsDirty();
+    if (isComponentComplete() && d->isValid()) {
+        d->forceLayout = true;
+        polish();
+    }
     QQuickFlickable::geometryChanged(newGeometry, oldGeometry);
 }
 
@@ -1098,7 +1114,7 @@ QQuickItemViewPrivate::QQuickItemViewPrivate()
     , highlightMoveDuration(150)
     , headerComponent(0), header(0), footerComponent(0), footer(0)
     , minExtent(0), maxExtent(0)
-    , ownModel(false), wrap(false), deferredRelease(false)
+    , ownModel(false), wrap(false)
     , inApplyModelChanges(false), inViewportMoved(false), forceLayout(false), currentIndexCleared(false)
     , haveHighlightRange(false), autoHighlight(true), highlightRangeStartValid(false), highlightRangeEndValid(false)
     , fillCacheBuffer(false), inRequest(false), requestedAsync(false)
@@ -1200,7 +1216,6 @@ void QQuickItemViewPrivate::init()
     Q_Q(QQuickItemView);
     QQuickItemPrivate::get(contentItem)->childrenDoNotOverlap = true;
     q->setFlag(QQuickItem::ItemIsFocusScope);
-    addItemChangeListener(this, Geometry);
     QObject::connect(q, SIGNAL(movementEnded()), q, SLOT(animStopped()));
     q->setFlickableDirection(QQuickFlickable::VerticalFlick);
 }
@@ -1209,7 +1224,6 @@ void QQuickItemViewPrivate::updateCurrent(int modelIndex)
 {
     Q_Q(QQuickItemView);
     applyPendingChanges();
-
     if (!q->isComponentComplete() || !isValid() || modelIndex < 0 || modelIndex >= model->count()) {
         if (currentItem) {
             currentItem->attached->setIsCurrentItem(false);
@@ -1280,13 +1294,14 @@ void QQuickItemViewPrivate::mirrorChange()
 
 void QQuickItemViewPrivate::refill()
 {
+    qreal s = qMax(size(), qreal(0.));
     if (isContentFlowReversed())
-        refill(-position()-size(), -position());
+        refill(-position()-s, -position());
     else
-        refill(position(), position()+size());
+        refill(position(), position()+s);
 }
 
-void QQuickItemViewPrivate::refill(qreal from, qreal to, bool doBuffer)
+void QQuickItemViewPrivate::refill(qreal from, qreal to)
 {
     Q_Q(QQuickItemView);
     if (!isValid() || !q->isComponentComplete())
@@ -1300,35 +1315,22 @@ void QQuickItemViewPrivate::refill(qreal from, qreal to, bool doBuffer)
     qreal bufferTo = to + buffer;
     qreal fillFrom = from;
     qreal fillTo = to;
-    if (doBuffer && (bufferMode & BufferAfter))
-        fillTo = bufferTo;
-    if (doBuffer && (bufferMode & BufferBefore))
-        fillFrom = bufferFrom;
 
-    // Item creation and release is staggered in order to avoid
-    // creating/releasing multiple items in one frame
-    // while flicking (as much as possible).
+    bool added = addVisibleItems(fillFrom, fillTo, false);
+    bool removed = removeNonVisibleItems(bufferFrom, bufferTo);
 
-    bool changed = addVisibleItems(fillFrom, fillTo, doBuffer);
-
-    if (!changed || deferredRelease) { // avoid destroying items in the same frame that we create
-        if (removeNonVisibleItems(bufferFrom, bufferTo))
-            changed = true;
-        deferredRelease = false;
-    } else {
-        deferredRelease = true;
+    if (buffer && bufferMode != NoBuffer) {
+        if (bufferMode & BufferAfter)
+            fillTo = bufferTo;
+        if (bufferMode & BufferBefore)
+            fillFrom = bufferFrom;
+        added |= addVisibleItems(fillFrom, fillTo, true);
     }
 
-    if (changed) {
+    if (added || removed) {
         markExtentsDirty();
+        updateBeginningEnd();
         visibleItemsChanged();
-    } else if (!doBuffer && buffer && bufferMode != NoBuffer) {
-        refill(from, to, true);
-    }
-
-    if (!q->isMoving() && changed) {
-        fillCacheBuffer = true;
-        q->polish();
     }
 
     if (prevCount != itemCount)
@@ -1378,8 +1380,10 @@ void QQuickItemViewPrivate::layout()
     }
 
     if (!applyModelChanges() && !forceLayout) {
-        if (fillCacheBuffer)
+        if (fillCacheBuffer) {
+            fillCacheBuffer = false;
             refill();
+        }
         return;
     }
     forceLayout = false;
@@ -1413,119 +1417,66 @@ bool QQuickItemViewPrivate::applyModelChanges()
     updateUnrequestedIndexes();
     moveReason = QQuickItemViewPrivate::Other;
 
-    int prevCount = itemCount;
+    FxViewItem *prevVisibleItemsFirst = visibleItems.count() ? visibleItems.first() : 0;
+    int prevItemCount = itemCount;
+    int prevVisibleItemsCount = visibleItems.count();
     bool visibleAffected = false;
     bool viewportChanged = !currentChanges.pendingChanges.removes().isEmpty()
             || !currentChanges.pendingChanges.inserts().isEmpty();
 
-    FxViewItem *firstVisible = firstVisibleItem();
-    FxViewItem *origVisibleItemsFirst = visibleItems.count() ? visibleItems.first() : 0;
-    int firstItemIndex = firstVisible ? firstVisible->index : -1;
-    qreal removedBeforeFirstVisibleBy = 0;
+    FxViewItem *prevFirstVisible = firstVisibleItem();
+    QDeclarativeNullableValue<qreal> prevViewPos;
+    if (prevFirstVisible)
+        prevViewPos = prevFirstVisible->position();
+    qreal prevVisibleItemsFirstPos = visibleItems.count() ? visibleItems.first()->position() : 0.0;
 
     const QVector<QDeclarativeChangeSet::Remove> &removals = currentChanges.pendingChanges.removes();
+    const QVector<QDeclarativeChangeSet::Insert> &insertions = currentChanges.pendingChanges.inserts();
+    ChangeResult removalResult(prevViewPos);
+    ChangeResult insertionResult(prevViewPos);
+
+    int removedCount = 0;
     for (int i=0; i<removals.count(); i++) {
         itemCount -= removals[i].count;
-
-        // Remove the items from the visible list, skipping anything already marked for removal
-        QList<FxViewItem*>::Iterator it = visibleItems.begin();
-        while (it != visibleItems.end()) {
-            FxViewItem *item = *it;
-            if (item->index == -1 || item->index < removals[i].index) {
-                // already removed, or before removed items
-                if (!visibleAffected && item->index < removals[i].index)
-                    visibleAffected = true;
-                ++it;
-            } else if (item->index >= removals[i].index + removals[i].count) {
-                // after removed items
-                item->index -= removals[i].count;
-                ++it;
-            } else {
-                // removed item
-                visibleAffected = true;
-                if (!removals[i].isMove())
-                    item->attached->emitRemove();
-
-                if (item->attached->delayRemove() && !removals[i].isMove()) {
-                    item->index = -1;
-                    QObject::connect(item->attached, SIGNAL(delayRemoveChanged()), q, SLOT(destroyRemoved()), Qt::QueuedConnection);
-                    ++it;
-                } else {
-                    if (firstVisible && item->position() < firstVisible->position() && item != visibleItems.first())
-                        removedBeforeFirstVisibleBy += item->size();
-                    if (removals[i].isMove()) {
-                        currentChanges.removedItems.insert(removals[i].moveKey(item->index), item);
-                    } else {
-                        if (item == firstVisible)
-                            firstVisible = 0;
-                        currentChanges.removedItems.insertMulti(QDeclarativeChangeSet::MoveKey(), item);
-                    }
-                    it = visibleItems.erase(it);
-                }
-            }
-        }
+        if (applyRemovalChange(removals[i], &removalResult, &removedCount))
+            visibleAffected = true;
         if (!visibleAffected && needsRefillForAddedOrRemovedIndex(removals[i].index))
             visibleAffected = true;
     }
-    if (!removals.isEmpty())
+    if (!removals.isEmpty()) {
         updateVisibleIndex();
 
-    const QVector<QDeclarativeChangeSet::Insert> &insertions = currentChanges.pendingChanges.inserts();
-    InsertionsResult insertResult;
-    bool allInsertionsBeforeVisible = true;
+        // set positions correctly for the next insertion
+        if (!insertions.isEmpty()) {
+            repositionFirstItem(prevVisibleItemsFirst, prevVisibleItemsFirstPos, prevFirstVisible, &insertionResult, &removalResult);
+            layoutVisibleItems(removals.first().index);
+        }
+    }
 
+    QList<FxViewItem *> newItems;
     for (int i=0; i<insertions.count(); i++) {
         bool wasEmpty = visibleItems.isEmpty();
-        if (applyInsertionChange(insertions[i], firstVisible, &insertResult))
+        if (applyInsertionChange(insertions[i], &insertionResult, &newItems))
             visibleAffected = true;
         if (!visibleAffected && needsRefillForAddedOrRemovedIndex(insertions[i].index))
             visibleAffected = true;
-        if (insertions[i].index >= visibleIndex)
-            allInsertionsBeforeVisible = false;
         if (wasEmpty && !visibleItems.isEmpty())
             resetFirstItemPosition();
-        itemCount += insertions[i].count;
-    }
-    for (int i=0; i<insertResult.addedItems.count(); ++i)
-        insertResult.addedItems.at(i)->attached->emitAdd();
-
-    // if the first visible item has moved, ensure another one takes its place
-    // so that we avoid shifting all content forwards
-    // (if an item is removed from before the first visible, the first visible should not move upwards)
-    bool movedBackToFirstVisible = false;
-    if (firstVisible && firstItemIndex >= 0) {
-        for (int i=0; i<insertResult.movedBackwards.count(); i++) {
-            if (insertResult.movedBackwards[i]->index == firstItemIndex) {
-                // an item has moved backwards up to the first visible's position
-                resetItemPosition(insertResult.movedBackwards[i], firstVisible);
-                insertResult.movedBackwards.removeAt(i);
-                movedBackToFirstVisible = true;
-                break;
-            }
-        }
-        if (!movedBackToFirstVisible && !allInsertionsBeforeVisible) {
-            // first visible item has moved forward, another visible item takes its place
-            FxViewItem *item = visibleItem(firstItemIndex);
-            if (item)
-                resetItemPosition(item, firstVisible);
-        }
-    }
 
-    // Ensure we don't cause an ugly list scroll
-    if (firstVisible && visibleItems.count() && visibleItems.first() != firstVisible) {
-        // ensure first item is placed at correct postion if moving backward
-        // since it will be used to position all subsequent items
-        if (insertResult.movedBackwards.count() && origVisibleItemsFirst)
-            resetItemPosition(visibleItems.first(), origVisibleItemsFirst);
-
-        // correct the first item position (unless it has already been fixed)
-        if (!movedBackToFirstVisible) {
-            qreal moveBackwardsBy = insertResult.sizeAddedBeforeVisible;
-            for (int i=0; i<insertResult.movedBackwards.count(); i++)
-                moveBackwardsBy += insertResult.movedBackwards[i]->size();
-            moveItemBy(visibleItems.first(), removedBeforeFirstVisibleBy, moveBackwardsBy);
+        // set positions correctly for the next insertion
+        if (i < insertions.count() - 1) {
+            repositionFirstItem(prevVisibleItemsFirst, prevVisibleItemsFirstPos, prevFirstVisible, &insertionResult, &removalResult);
+            layoutVisibleItems(insertions[i].index);
         }
+
+        itemCount += insertions[i].count;
     }
+    for (int i=0; i<newItems.count(); i++)
+        newItems.at(i)->attached->emitAdd();
+
+    // reposition visibleItems.first() correctly so that the content y doesn't jump
+    if (removedCount != prevVisibleItemsCount)
+        repositionFirstItem(prevVisibleItemsFirst, prevVisibleItemsFirstPos, prevFirstVisible, &insertionResult, &removalResult);
 
     // Whatever removed/moved items remain are no longer visible items.
     for (QHash<QDeclarativeChangeSet::MoveKey, FxViewItem *>::Iterator it = currentChanges.removedItems.begin();
@@ -1546,7 +1497,7 @@ bool QQuickItemViewPrivate::applyModelChanges()
     currentChanges.reset();
 
     updateSections();
-    if (prevCount != itemCount)
+    if (prevItemCount != itemCount)
         emit q->countChanged();
 
     if (!visibleAffected)
@@ -1558,6 +1509,99 @@ bool QQuickItemViewPrivate::applyModelChanges()
     return visibleAffected;
 }
 
+bool QQuickItemViewPrivate::applyRemovalChange(const QDeclarativeChangeSet::Remove &removal, ChangeResult *removeResult, int *removedCount)
+{
+    Q_Q(QQuickItemView);
+    bool visibleAffected = false;
+
+    QList<FxViewItem*>::Iterator it = visibleItems.begin();
+    while (it != visibleItems.end()) {
+        FxViewItem *item = *it;
+        if (item->index == -1 || item->index < removal.index) {
+            // already removed, or before removed items
+            if (!visibleAffected && item->index < removal.index)
+                visibleAffected = true;
+            ++it;
+        } else if (item->index >= removal.index + removal.count) {
+            // after removed items
+            item->index -= removal.count;
+            ++it;
+        } else {
+            // removed item
+            visibleAffected = true;
+            if (!removal.isMove())
+                item->attached->emitRemove();
+
+            if (item->attached->delayRemove() && !removal.isMove()) {
+                item->index = -1;
+                QObject::connect(item->attached, SIGNAL(delayRemoveChanged()), q, SLOT(destroyRemoved()), Qt::QueuedConnection);
+                ++it;
+            } else {
+                if (removeResult->visiblePos.isValid()) {
+                    if (item->position() < removeResult->visiblePos)
+                        removeResult->sizeChangesBeforeVisiblePos += item->size();
+                    else
+                        removeResult->sizeChangesAfterVisiblePos += item->size();
+                }
+                if (removal.isMove()) {
+                    currentChanges.removedItems.insert(removal.moveKey(item->index), item);
+                } else {
+                    // track item so it is released later
+                    currentChanges.removedItems.insertMulti(QDeclarativeChangeSet::MoveKey(), item);
+                    (*removedCount)++;
+                }
+                if (!removeResult->changedFirstItem && item == visibleItems.first())
+                    removeResult->changedFirstItem = true;
+                it = visibleItems.erase(it);
+            }
+        }
+    }
+
+    if (removal.index + removal.count < visibleIndex)
+        removeResult->changeBeforeVisible -= removal.count;
+
+    return visibleAffected;
+}
+
+void QQuickItemViewPrivate::repositionFirstItem(FxViewItem *prevVisibleItemsFirst,
+                                                   qreal prevVisibleItemsFirstPos,
+                                                   FxViewItem *prevFirstVisible,
+                                                   ChangeResult *insertionResult,
+                                                   ChangeResult *removalResult)
+{
+    const QDeclarativeNullableValue<qreal> prevViewPos = insertionResult->visiblePos;
+
+    // reposition visibleItems.first() correctly so that the content y doesn't jump
+    if (visibleItems.count()) {
+        if (prevVisibleItemsFirst && insertionResult->changedFirstItem)
+            resetFirstItemPosition(prevVisibleItemsFirstPos);
+
+        if (prevFirstVisible && prevVisibleItemsFirst == prevFirstVisible
+                && prevFirstVisible != *visibleItems.constBegin()) {
+            // the previous visibleItems.first() was also the first visible item, and it has been
+            // moved/removed, so move the new visibleItems.first() to the pos of the previous one
+            if (!insertionResult->changedFirstItem)
+                resetFirstItemPosition(prevVisibleItemsFirstPos);
+
+        } else if (prevViewPos.isValid()) {
+            qreal moveForwardsBy = 0;
+            qreal moveBackwardsBy = 0;
+
+            // shift visibleItems.first() relative to the number of added/removed items
+            if (visibleItems.first()->position() > prevViewPos) {
+                moveForwardsBy = insertionResult->sizeChangesAfterVisiblePos;
+                moveBackwardsBy = removalResult->sizeChangesAfterVisiblePos;
+            } else if (visibleItems.first()->position() < prevViewPos) {
+                moveForwardsBy = removalResult->sizeChangesBeforeVisiblePos;
+                moveBackwardsBy = insertionResult->sizeChangesBeforeVisiblePos;
+            }
+            adjustFirstItem(moveForwardsBy, moveBackwardsBy, insertionResult->changeBeforeVisible + removalResult->changeBeforeVisible);
+        }
+        insertionResult->reset();
+        removalResult->reset();
+    }
+}
+
 /*
   This may return 0 if the item is being created asynchronously.
   When the item becomes available, refill() will be called and the item
@@ -1570,6 +1614,8 @@ FxViewItem *QQuickItemViewPrivate::createItem(int modelIndex, bool asynchronous)
         return 0;
 
     if (requestedIndex != -1 && requestedIndex != modelIndex) {
+        if (requestedItem && requestedItem->item)
+            requestedItem->item->setParentItem(0);
         delete requestedItem;
         requestedItem = 0;
     }
@@ -1582,7 +1628,6 @@ FxViewItem *QQuickItemViewPrivate::createItem(int modelIndex, bool asynchronous)
         item->setParentItem(q->contentItem());
         QDeclarative_setParent_noEvent(item, q->contentItem());
         requestedIndex = -1;
-        fillCacheBuffer = false;
         FxViewItem *viewItem = requestedItem;
         if (!viewItem)
             viewItem = newViewItem(modelIndex, item); // already in cache, so viewItem not initialized in initItem()
@@ -1616,9 +1661,6 @@ void QQuickItemView::createdItem(int index, QQuickItem *item)
             if (index == d->currentIndex)
                 d->updateCurrent(index);
             d->refill();
-        } else {
-            d->fillCacheBuffer = true;
-            polish();
         }
     }
 }