From 6fbc4b7e7e5aed8739ca1143e0fc1e38b8c8e17a Mon Sep 17 00:00:00 2001 From: Bea Lam Date: Mon, 29 Aug 2011 15:33:36 +1000 Subject: [PATCH] Batch view changes instead of applying them immediately If there are multiple changes to be applied to a view before the next repaint, collate them using QDeclarativeChangeSet and apply the changes as a group on the next layout(). (Note that changes to the current index are applied in sequence as changes are received, since changing it out of order produces different results.) Previously, any itemsInserted(), itemsRemoved() and itemsMoved() changes were immediately applied and items were repositioned immediately. In this situation if the same indexes changed multiple times between repaints, this could lead to redundant changes and bugs arising from multiple changes to the same items. Functions that will execute differently depending on whether pending view changes have been applied (e.g. count(), currentIndex(), highlight()) now call applyPendingChanges() before proceeding to ensure they are executed on a view that is up to date. Also, itemsMoved() operations that moved item/s backwards will now properly move backwards instead of being adjusted to a forward movement (which was implemented recently with e2b5681b1adab83555c7307b05f508d796a1152b) since backwards movements can be implemented more easily with the batched changes which translates moves into insert/remove actions. Change-Id: If9b39755898e8d2ed7e4bc61288206d5f1d653fd Reviewed-on: http://codereview.qt.nokia.com/3697 Reviewed-by: Qt Sanity Bot Reviewed-by: Martin Jones --- src/declarative/items/qsggridview.cpp | 375 ++++-------------- src/declarative/items/qsggridview_p.h | 5 - src/declarative/items/qsgitemview.cpp | 344 +++++++++++++++-- src/declarative/items/qsgitemview_p.h | 6 +- src/declarative/items/qsgitemview_p_p.h | 36 ++- src/declarative/items/qsglistview.cpp | 411 ++++--------------- src/declarative/items/qsglistview_p.h | 5 - .../declarative/qsggridview/tst_qsggridview.cpp | 297 +++++++++++++-- .../declarative/qsglistview/tst_qsglistview.cpp | 328 +++++++++++++++-- 9 files changed, 1096 insertions(+), 711 deletions(-) diff --git a/src/declarative/items/qsggridview.cpp b/src/declarative/items/qsggridview.cpp index bc4954a..f095636 100644 --- a/src/declarative/items/qsggridview.cpp +++ b/src/declarative/items/qsggridview.cpp @@ -166,6 +166,9 @@ public: virtual FxViewItem *newViewItem(int index, QSGItem *item); virtual void repositionPackageItemAt(QSGItem *item, int index); + virtual void resetItemPosition(FxViewItem *item, FxViewItem *toItem); + virtual void resetFirstItemPosition(); + virtual void moveItemBy(FxViewItem *item, const QList &items, const QList &movedBackwards); virtual void createHighlight(); virtual void updateHighlight(); @@ -173,6 +176,7 @@ public: virtual void setPosition(qreal pos); virtual void layoutVisibleItems(); + bool applyInsertionChange(const QDeclarativeChangeSet::Insert &, QList *, QList*, FxViewItem *); virtual qreal headerSize() const; virtual qreal footerSize() const; @@ -560,6 +564,24 @@ void QSGGridViewPrivate::repositionPackageItemAt(QSGItem *item, int index) } } +void QSGGridViewPrivate::resetItemPosition(FxViewItem *item, FxViewItem *toItem) +{ + FxGridItemSG *toGridItem = static_cast(toItem); + static_cast(item)->setPosition(toGridItem->colPos(), toGridItem->rowPos()); +} + +void QSGGridViewPrivate::resetFirstItemPosition() +{ + FxGridItemSG *item = static_cast(visibleItems.first()); + item->setPosition(0, 0); +} + +void QSGGridViewPrivate::moveItemBy(FxViewItem *item, const QList &forwards, const QList &backwards) +{ + int moveCount = forwards.count() - backwards.count(); + FxGridItemSG *gridItem = static_cast(item); + gridItem->setPosition(gridItem->colPos(), gridItem->rowPos() + ((moveCount / columns) * rowSize())); +} void QSGGridViewPrivate::createHighlight() { @@ -602,6 +624,8 @@ void QSGGridViewPrivate::createHighlight() void QSGGridViewPrivate::updateHighlight() { + applyPendingChanges(); + if ((!currentItem && highlight) || (currentItem && !highlight)) createHighlight(); bool strictHighlight = haveHighlightRange && highlightRange == QSGGridView::StrictlyEnforceRange; @@ -1370,6 +1394,7 @@ void QSGGridView::setCellWidth(int cellWidth) d->cellWidth = qMax(1, cellWidth); d->updateViewport(); emit cellWidthChanged(); + d->forceLayout = true; d->layout(); } } @@ -1387,6 +1412,7 @@ void QSGGridView::setCellHeight(int cellHeight) d->cellHeight = qMax(1, cellHeight); d->updateViewport(); emit cellHeightChanged(); + d->forceLayout = true; d->layout(); } } @@ -1688,349 +1714,95 @@ void QSGGridView::moveCurrentIndexRight() } } - -void QSGGridView::itemsInserted(int modelIndex, int count) +bool QSGGridViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::Insert &change, QList *movedBackwards, QList *addedItems, FxViewItem *firstVisible) { - Q_D(QSGGridView); - if (!isComponentComplete() || !d->model || !d->model->isValid()) - return; + Q_Q(QSGGridView); + + int modelIndex = change.index; + int count = change.count; + + int index = visibleItems.count() ? mapFromModel(modelIndex) : 0; - int index = d->visibleItems.count() ? d->mapFromModel(modelIndex) : 0; if (index < 0) { - int i = d->visibleItems.count() - 1; - while (i > 0 && d->visibleItems.at(i)->index == -1) + int i = visibleItems.count() - 1; + while (i > 0 && visibleItems.at(i)->index == -1) --i; - if (d->visibleItems.at(i)->index + 1 == modelIndex) { + if (visibleItems.at(i)->index + 1 == modelIndex) { // Special case of appending an item to the model. - index = d->visibleIndex + d->visibleItems.count(); + index = visibleIndex + visibleItems.count(); } else { - if (modelIndex <= d->visibleIndex) { + if (modelIndex <= visibleIndex) { // Insert before visible items - d->visibleIndex += count; - for (int i = 0; i < d->visibleItems.count(); ++i) { - FxViewItem *item = d->visibleItems.at(i); + visibleIndex += count; + for (int i = 0; i < visibleItems.count(); ++i) { + FxViewItem *item = visibleItems.at(i); if (item->index != -1 && item->index >= modelIndex) item->index += count; } } - if (d->currentIndex >= modelIndex) { - // adjust current item index - d->currentIndex += count; - if (d->currentItem) - d->currentItem->index = d->currentIndex; - emit currentIndexChanged(); - } - d->scheduleLayout(); - d->itemCount += count; - emit countChanged(); - return; + return true; } } int insertCount = count; - if (index < d->visibleIndex && d->visibleItems.count()) { - insertCount -= d->visibleIndex - index; - index = d->visibleIndex; - modelIndex = d->visibleIndex; + if (index < visibleIndex && visibleItems.count()) { + insertCount -= visibleIndex - index; + index = visibleIndex; + modelIndex = visibleIndex; } - qreal tempPos = d->isRightToLeftTopToBottom() ? -d->position()-d->size()+width()+1 : d->position(); - int to = d->buffer+tempPos+d->size()-1; + qreal tempPos = isRightToLeftTopToBottom() ? -position()-size()+q->width()+1 : position(); + int to = buffer+tempPos+size()-1; int colPos = 0; int rowPos = 0; - if (d->visibleItems.count()) { - index -= d->visibleIndex; - if (index < d->visibleItems.count()) { - FxGridItemSG *gridItem = static_cast(d->visibleItems.at(index)); + if (visibleItems.count()) { + index -= visibleIndex; + if (index < visibleItems.count()) { + FxGridItemSG *gridItem = static_cast(visibleItems.at(index)); colPos = gridItem->colPos(); rowPos = gridItem->rowPos(); } else { // appending items to visible list - FxGridItemSG *gridItem = static_cast(d->visibleItems.at(index-1)); - colPos = gridItem->colPos() + d->colSize(); + FxGridItemSG *gridItem = static_cast(visibleItems.at(index-1)); + colPos = gridItem->colPos() + colSize(); rowPos = gridItem->rowPos(); - if (colPos > d->colSize() * (d->columns-1)) { + if (colPos > colSize() * (columns-1)) { colPos = 0; - rowPos += d->rowSize(); + rowPos += rowSize(); } } } // Update the indexes of the following visible items. - for (int i = 0; i < d->visibleItems.count(); ++i) { - FxViewItem *item = d->visibleItems.at(i); + for (int i = 0; i < visibleItems.count(); ++i) { + FxViewItem *item = visibleItems.at(i); if (item->index != -1 && item->index >= modelIndex) item->index += count; } - bool addedVisible = false; - QList added; int i = 0; - while (i < insertCount && rowPos <= to + d->rowSize()*(d->columns - (colPos/d->colSize()))/qreal(d->columns)) { - if (!addedVisible) { - d->scheduleLayout(); - addedVisible = true; + bool prevAddedCount = addedItems->count(); + while (i < insertCount && rowPos <= to + rowSize()*(columns - (colPos/colSize()))/qreal(columns)) { + FxViewItem *item = 0; + if (change.isMove() && (item = currentChanges.removedItems.take(change.moveKey(modelIndex + i)))) { + if (item->index > modelIndex + i) + movedBackwards->append(item); + item->index = modelIndex + i; } - FxGridItemSG *item = static_cast(d->createItem(modelIndex + i)); - d->visibleItems.insert(index, item); - item->setPosition(colPos, rowPos); - added.append(item); - colPos += d->colSize(); - if (colPos > d->colSize() * (d->columns-1)) { + if (!item) + item = createItem(modelIndex + i); + visibleItems.insert(index, item); + addedItems->append(item); + colPos += colSize(); + if (colPos > colSize() * (columns-1)) { colPos = 0; - rowPos += d->rowSize(); + rowPos += rowSize(); } ++index; ++i; } - if (i < insertCount) { - // We didn't insert all our new items, which means anything - // beyond the current index is not visible - remove it. - while (d->visibleItems.count() > index) { - d->releaseItem(d->visibleItems.takeLast()); - } - } - - // update visibleIndex - d->visibleIndex = 0; - for (QList::Iterator it = d->visibleItems.begin(); it != d->visibleItems.end(); ++it) { - if ((*it)->index != -1) { - d->visibleIndex = (*it)->index; - break; - } - } - - if (d->itemCount && d->currentIndex >= modelIndex) { - // adjust current item index - d->currentIndex += count; - if (d->currentItem) { - d->currentItem->index = d->currentIndex; - static_cast(d->currentItem)->setPosition(d->colPosAt(d->currentIndex), d->rowPosAt(d->currentIndex)); - } - emit currentIndexChanged(); - } else if (d->itemCount == 0 && (!d->currentIndex || (d->currentIndex < 0 && !d->currentIndexCleared))) { - setCurrentIndex(0); - } - - // everything is in order now - emit add() signal - for (int j = 0; j < added.count(); ++j) - added.at(j)->attached->emitAdd(); - - d->itemCount += count; - emit countChanged(); -} - -void QSGGridView::itemsRemoved(int modelIndex, int count) -{ - Q_D(QSGGridView); - if (!isComponentComplete() || !d->model || !d->model->isValid()) - return; - - d->itemCount -= count; - bool currentRemoved = d->currentIndex >= modelIndex && d->currentIndex < modelIndex + count; - bool removedVisible = false; - - // Remove the items from the visible list, skipping anything already marked for removal - QList::Iterator it = d->visibleItems.begin(); - while (it != d->visibleItems.end()) { - FxViewItem *item = *it; - if (item->index == -1 || item->index < modelIndex) { - // already removed, or before removed items - if (item->index < modelIndex && !removedVisible) { - d->scheduleLayout(); - removedVisible = true; - } - ++it; - } else if (item->index >= modelIndex + count) { - // after removed items - item->index -= count; - ++it; - } else { - // removed item - if (!removedVisible) { - d->scheduleLayout(); - removedVisible = true; - } - item->attached->emitRemove(); - if (item->attached->delayRemove()) { - item->index = -1; - connect(item->attached, SIGNAL(delayRemoveChanged()), this, SLOT(destroyRemoved()), Qt::QueuedConnection); - ++it; - } else { - it = d->visibleItems.erase(it); - d->releaseItem(item); - } - } - } - - // update visibleIndex - d->visibleIndex = 0; - for (it = d->visibleItems.begin(); it != d->visibleItems.end(); ++it) { - if ((*it)->index != -1) { - d->visibleIndex = (*it)->index; - break; - } - } - - // fix current - if (d->currentIndex >= modelIndex + count) { - d->currentIndex -= count; - if (d->currentItem) - d->currentItem->index -= count; - emit currentIndexChanged(); - } else if (currentRemoved) { - // current item has been removed. - d->releaseItem(d->currentItem); - d->currentItem = 0; - d->currentIndex = -1; - if (d->itemCount) - d->updateCurrent(qMin(modelIndex, d->itemCount-1)); - else - emit currentIndexChanged(); - } - - if (removedVisible && d->visibleItems.isEmpty()) { - d->timeline.clear(); - if (d->itemCount == 0) { - d->setPosition(d->contentStartPosition()); - d->updateHeader(); - d->updateFooter(); - } - } - - emit countChanged(); -} - - -void QSGGridView::itemsMoved(int from, int to, int count) -{ - Q_D(QSGGridView); - if (!isComponentComplete() || !d->isValid()) - return; - d->updateUnrequestedIndexes(); - - if (d->visibleItems.isEmpty()) { - d->refill(); - return; - } - - d->moveReason = QSGGridViewPrivate::Other; - - bool movingBackwards = from > to; - d->adjustMoveParameters(&from, &to, &count); - - QHash moved; - int moveByCount = 0; - FxGridItemSG *firstVisible = static_cast(d->firstVisibleItem()); - int firstItemIndex = firstVisible ? firstVisible->index : -1; - - // if visibleItems.first() is above the content start pos, and the items - // beneath it are moved, ensure this first item is later repositioned correctly - // (to above the next visible item) so that subsequent layout() is correct - bool repositionFirstItem = firstVisible - && d->visibleItems.first()->position() < firstVisible->position() - && from > d->visibleItems.first()->index; - - QList::Iterator it = d->visibleItems.begin(); - while (it != d->visibleItems.end()) { - FxViewItem *item = *it; - if (item->index >= from && item->index < from + count) { - // take the items that are moving - item->index += (to-from); - moved.insert(item->index, static_cast(item)); - if (repositionFirstItem) - moveByCount++; - it = d->visibleItems.erase(it); - } else { - if (item->index > from && item->index != -1) { - // move everything after the moved items. - item->index -= count; - if (item->index < d->visibleIndex) - d->visibleIndex = item->index; - } - ++it; - } - } - - int movedCount = 0; - int endIndex = d->visibleIndex; - it = d->visibleItems.begin(); - while (it != d->visibleItems.end()) { - FxViewItem *item = *it; - if (movedCount < count && item->index >= to && item->index < to + count) { - // place items in the target position, reusing any existing items - int targetIndex = item->index + movedCount; - FxGridItemSG *movedItem = moved.take(targetIndex); - if (!movedItem) - movedItem = static_cast(d->createItem(targetIndex)); - it = d->visibleItems.insert(it, movedItem); - ++it; - ++movedCount; - } else { - if (item->index != -1) { - if (item->index >= to) { - // update everything after the moved items. - item->index += count; - } - endIndex = item->index; - } - ++it; - } - } - - // If we have moved items to the end of the visible items - // then add any existing moved items that we have - while (FxGridItemSG *item = moved.take(endIndex+1)) { - d->visibleItems.append(item); - ++endIndex; - } - - // update visibleIndex - for (it = d->visibleItems.begin(); it != d->visibleItems.end(); ++it) { - if ((*it)->index != -1) { - d->visibleIndex = (*it)->index; - break; - } - } - // if first visible item is moving but another item is moving up to replace it, - // do this positioning now to avoid shifting all content forwards - if (movingBackwards && firstItemIndex >= 0) { - for (it = d->visibleItems.begin(); it != d->visibleItems.end(); ++it) { - if ((*it)->index == firstItemIndex) { - static_cast(*it)->setPosition(firstVisible->colPos(), - firstVisible->rowPos()); - break; - } - } - } - - // Fix current index - if (d->currentIndex >= 0 && d->currentItem) { - int oldCurrent = d->currentIndex; - d->currentIndex = d->model->indexOf(d->currentItem->item, this); - if (oldCurrent != d->currentIndex) { - d->currentItem->index = d->currentIndex; - emit currentIndexChanged(); - } - } - - // Whatever moved items remain are no longer visible items. - while (moved.count()) { - int idx = moved.begin().key(); - FxGridItemSG *item = moved.take(idx); - if (d->currentItem && item->item == d->currentItem->item) - item->setPosition(d->colPosAt(idx), d->rowPosAt(idx)); - d->releaseItem(item); - } - - // Ensure we don't cause an ugly list scroll. - if (d->visibleItems.count() && moveByCount > 0) { - FxGridItemSG *first = static_cast(d->visibleItems.first()); - first->setPosition(first->colPos(), first->rowPos() + ((moveByCount / d->columns) * d->rowSize())); - } - - d->layout(); + return addedItems->count() > prevAddedCount; } /*! @@ -2098,6 +1870,7 @@ void QSGGridView::itemsMoved(int from, int to, int count) \bold Note: methods should only be called after the Component has completed. */ + QSGGridViewAttached *QSGGridView::qmlAttachedProperties(QObject *obj) { return new QSGGridViewAttached(obj); diff --git a/src/declarative/items/qsggridview_p.h b/src/declarative/items/qsggridview_p.h index ae4ce34..1cdf81d 100644 --- a/src/declarative/items/qsggridview_p.h +++ b/src/declarative/items/qsggridview_p.h @@ -109,11 +109,6 @@ Q_SIGNALS: protected: virtual void viewportMoved(); virtual void keyPressEvent(QKeyEvent *); - -private Q_SLOTS: - void itemsInserted(int index, int count); - void itemsRemoved(int index, int count); - void itemsMoved(int from, int to, int count); }; class QSGGridViewAttached : public QSGItemViewAttached diff --git a/src/declarative/items/qsgitemview.cpp b/src/declarative/items/qsgitemview.cpp index 5543f12..c28fbb1 100644 --- a/src/declarative/items/qsgitemview.cpp +++ b/src/declarative/items/qsgitemview.cpp @@ -58,6 +58,102 @@ FxViewItem::~FxViewItem() } } + +QSGItemViewChangeSet::QSGItemViewChangeSet() + : active(false) +{ + reset(); +} + +bool QSGItemViewChangeSet::hasPendingChanges() const +{ + return !pendingChanges.isEmpty(); +} + +void QSGItemViewChangeSet::doInsert(int index, int count) +{ + pendingChanges.insert(index, count); + + if (newCurrentIndex >= index) { + // adjust current item index + newCurrentIndex += count; + currentChanged = true; + } else if (newCurrentIndex < 0) { + newCurrentIndex = 0; + currentChanged = true; + } + + itemCount += count; +} + +void QSGItemViewChangeSet::doRemove(int index, int count) +{ + itemCount -= count; + pendingChanges.remove(index, count); + + if (newCurrentIndex >= index + count) { + newCurrentIndex -= count; + currentChanged = true; + } else if (newCurrentIndex >= index && newCurrentIndex < index + count) { + // current item has been removed. + currentRemoved = true; + newCurrentIndex = -1; + if (itemCount) + newCurrentIndex = qMin(index, itemCount-1); + currentChanged = true; + } +} + +void QSGItemViewChangeSet::doMove(int from, int to, int count) +{ + pendingChanges.move(from, to, count); + + if (to > from) { + if (newCurrentIndex >= from) { + if (newCurrentIndex < from + count) + newCurrentIndex += (to-from); + else if (newCurrentIndex < to + count) + newCurrentIndex -= count; + } + currentChanged = true; + } else if (to < from) { + if (newCurrentIndex >= to) { + if (newCurrentIndex >= from && newCurrentIndex < from + count) + newCurrentIndex -= (from-to); + else if (newCurrentIndex < from) + newCurrentIndex += count; + } + currentChanged = true; + } +} + +void QSGItemViewChangeSet::QSGItemViewChangeSet::doChange(int index, int count) +{ + pendingChanges.change(index, count); +} + +void QSGItemViewChangeSet::prepare(int currentIndex, int count) +{ + if (active) + return; + reset(); + active = true; + itemCount = count; + newCurrentIndex = currentIndex; +} + +void QSGItemViewChangeSet::reset() +{ + itemCount = 0; + newCurrentIndex = -1; + pendingChanges.clear(); + removedItems.clear(); + active = false; + currentChanged = false; + currentRemoved = false; +} + + QSGItemView::QSGItemView(QSGFlickablePrivate &dd, QSGItem *parent) : QSGFlickable(dd, parent) { @@ -81,6 +177,7 @@ QSGItem *QSGItemView::currentItem() const Q_D(const QSGItemView); if (!d->currentItem) return 0; + const_cast(d)->applyPendingChanges(); return d->currentItem->item; } @@ -213,14 +310,16 @@ void QSGItemView::setDelegate(QDeclarativeComponent *delegate) int QSGItemView::count() const { Q_D(const QSGItemView); - if (d->model) - return d->model->count(); - return 0; + if (!d->model) + return 0; + const_cast(d)->applyPendingChanges(); + return d->model->count(); } int QSGItemView::currentIndex() const { Q_D(const QSGItemView); + const_cast(d)->applyPendingChanges(); return d->currentIndex; } @@ -230,6 +329,8 @@ void QSGItemView::setCurrentIndex(int index) if (d->requestedIndex >= 0) // currently creating item return; d->currentIndexCleared = (index == -1); + + d->applyPendingChanges(); if (index == d->currentIndex) return; if (isComponentComplete() && d->isValid()) { @@ -313,6 +414,7 @@ QDeclarativeComponent *QSGItemView::header() const QSGItem *QSGItemView::headerItem() const { Q_D(const QSGItemView); + const_cast(d)->applyPendingChanges(); return d->header ? d->header->item : 0; } @@ -320,6 +422,7 @@ void QSGItemView::setHeader(QDeclarativeComponent *headerComponent) { Q_D(QSGItemView); if (d->headerComponent != headerComponent) { + d->applyPendingChanges(); delete d->header; d->header = 0; d->headerComponent = headerComponent; @@ -348,6 +451,7 @@ QDeclarativeComponent *QSGItemView::footer() const QSGItem *QSGItemView::footerItem() const { Q_D(const QSGItemView); + const_cast(d)->applyPendingChanges(); return d->footer ? d->footer->item : 0; } @@ -355,6 +459,7 @@ void QSGItemView::setFooter(QDeclarativeComponent *footerComponent) { Q_D(QSGItemView); if (d->footerComponent != footerComponent) { + d->applyPendingChanges(); delete d->footer; d->footer = 0; d->footerComponent = footerComponent; @@ -373,6 +478,7 @@ void QSGItemView::setFooter(QDeclarativeComponent *footerComponent) QDeclarativeComponent *QSGItemView::highlight() const { Q_D(const QSGItemView); + const_cast(d)->applyPendingChanges(); return d->highlightComponent; } @@ -380,6 +486,7 @@ void QSGItemView::setHighlight(QDeclarativeComponent *highlightComponent) { Q_D(QSGItemView); if (highlightComponent != d->highlightComponent) { + d->applyPendingChanges(); d->highlightComponent = highlightComponent; d->createHighlight(); if (d->currentItem) @@ -391,9 +498,8 @@ void QSGItemView::setHighlight(QDeclarativeComponent *highlightComponent) QSGItem *QSGItemView::highlightItem() const { Q_D(const QSGItemView); - if (!d->highlight) - return 0; - return d->highlight->item; + const_cast(d)->applyPendingChanges(); + return d->highlight ? d->highlight->item : 0; } bool QSGItemView::highlightFollowsCurrentItem() const @@ -506,10 +612,10 @@ void QSGItemViewPrivate::positionViewAtIndex(int index, int mode) return; if (mode < QSGItemView::Beginning || mode > QSGItemView::Contain) return; + + applyPendingChanges(); int idx = qMax(qMin(index, model->count()-1), 0); - if (layoutScheduled) - layout(); qreal pos = isContentFlowReversed() ? -position() - size() : position(); FxViewItem *item = visibleItem(idx); qreal maxExtent; @@ -614,6 +720,12 @@ int QSGItemView::indexAt(qreal x, qreal y) const return -1; } +void QSGItemViewPrivate::applyPendingChanges() +{ + Q_Q(QSGItemView); + if (q->isComponentComplete() && currentChanges.hasPendingChanges()) + layout(); +} // for debugging only void QSGItemViewPrivate::checkVisible() const @@ -629,8 +741,6 @@ void QSGItemViewPrivate::checkVisible() const } } - - void QSGItemViewPrivate::itemGeometryChanged(QSGItem *item, const QRectF &newGeometry, const QRectF &oldGeometry) { Q_Q(QSGItemView); @@ -668,11 +778,51 @@ void QSGItemView::destroyRemoved() d->layout(); } -void QSGItemView::itemsChanged(int, int) +void QSGItemView::itemsInserted(int index, int count) { Q_D(QSGItemView); - d->updateSections(); - d->layout(); + if (!isComponentComplete() || !d->model || !d->model->isValid()) + return; + + d->currentChanges.prepare(d->currentIndex, d->itemCount); + d->currentChanges.doInsert(index, count); + d->scheduleLayout(); +} + +void QSGItemView::itemsRemoved(int index, int count) +{ + Q_D(QSGItemView); + if (!isComponentComplete() || !d->model || !d->model->isValid()) + return; + + d->currentChanges.prepare(d->currentIndex, d->itemCount); + d->currentChanges.doRemove(index, count); + d->scheduleLayout(); +} + +void QSGItemView::itemsMoved(int from, int to, int count) +{ + Q_D(QSGItemView); + if (!isComponentComplete() || !d->model || !d->model->isValid()) + return; + + if (from == to || count <= 0 || from < 0 || to < 0) + return; + + d->currentChanges.prepare(d->currentIndex, d->itemCount); + d->currentChanges.doMove(from, to, count); + d->scheduleLayout(); +} + +void QSGItemView::itemsChanged(int index, int count) +{ + Q_D(QSGItemView); + if (!isComponentComplete() || !d->model || !d->model->isValid()) + return; + + d->currentChanges.prepare(d->currentIndex, d->itemCount); + d->currentChanges.doChange(index, count); + d->scheduleLayout(); } void QSGItemView::modelReset() @@ -796,7 +946,6 @@ void QSGItemView::trackedPositionChanged() } } - void QSGItemView::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { Q_D(QSGItemView); @@ -1017,7 +1166,7 @@ QSGItemViewPrivate::QSGItemViewPrivate() , headerComponent(0), header(0), footerComponent(0), footer(0) , minExtent(0), maxExtent(0) , ownModel(false), wrap(false), lazyRelease(false), deferredRelease(false) - , layoutScheduled(false), inViewportMoved(false), currentIndexCleared(false) + , layoutScheduled(false), inApplyModelChanges(false), inViewportMoved(false), forceLayout(false), currentIndexCleared(false) , haveHighlightRange(false), autoHighlight(true), highlightRangeStartValid(false), highlightRangeEndValid(false) , minExtentDirty(true), maxExtentDirty(true) { @@ -1082,7 +1231,7 @@ FxViewItem *QSGItemViewPrivate::firstVisibleItem() const { const qreal pos = isContentFlowReversed() ? -position()-size() : position(); for (int i = 0; i < visibleItems.count(); ++i) { FxViewItem *item = visibleItems.at(i); - if (item->index != -1 && item->endPosition() >= pos) + if (item->index != -1 && item->endPosition() > pos) return item; } return visibleItems.count() ? visibleItems.first() : 0; @@ -1105,18 +1254,6 @@ int QSGItemViewPrivate::mapFromModel(int modelIndex) const return -1; // Not in visibleList } -void QSGItemViewPrivate::adjustMoveParameters(int *from, int *to, int *count) const -{ - if (*from > *to) { - // Only move forwards - flip if backwards moving - int tfrom = *from; - int tto = *to; - *from = tto; - *to = tto + *count; - *count = tfrom - tto; - } -} - void QSGItemViewPrivate::init() { Q_Q(QSGItemView); @@ -1130,6 +1267,8 @@ void QSGItemViewPrivate::init() void QSGItemViewPrivate::updateCurrent(int modelIndex) { Q_Q(QSGItemView); + applyPendingChanges(); + if (!q->isComponentComplete() || !isValid() || modelIndex < 0 || modelIndex >= model->count()) { if (currentItem) { currentItem->attached->setIsCurrentItem(false); @@ -1168,6 +1307,7 @@ void QSGItemViewPrivate::updateCurrent(int modelIndex) void QSGItemViewPrivate::clear() { + currentChanges.reset(); timeline.clear(); for (int i = 0; i < visibleItems.count(); ++i) @@ -1207,6 +1347,9 @@ void QSGItemViewPrivate::refill(qreal from, qreal to, bool doBuffer) if (!isValid() || !q->isComponentComplete()) return; + currentChanges.reset(); + + int prevCount = itemCount; itemCount = model->count(); qreal bufferFrom = from - buffer; qreal bufferTo = to + buffer; @@ -1240,12 +1383,15 @@ void QSGItemViewPrivate::refill(qreal from, qreal to, bool doBuffer) } lazyRelease = false; + if (prevCount != itemCount) + emit q->countChanged(); } void QSGItemViewPrivate::regenerate() { Q_Q(QSGItemView); if (q->isComponentComplete()) { + currentChanges.reset(); delete header; header = 0; delete footer; @@ -1290,6 +1436,10 @@ void QSGItemViewPrivate::layout() return; } + if (!applyModelChanges() && !forceLayout) + return; + forceLayout = false; + layoutVisibleItems(); refill(); @@ -1297,6 +1447,7 @@ void QSGItemViewPrivate::layout() maxExtentDirty = true; updateHighlight(); + if (!q->isMoving() && !q->isFlicking()) { fixupPosition(); refill(); @@ -1308,6 +1459,132 @@ void QSGItemViewPrivate::layout() updateUnrequestedPositions(); } +bool QSGItemViewPrivate::applyModelChanges() +{ + Q_Q(QSGItemView); + if (!q->isComponentComplete() || !currentChanges.hasPendingChanges() || inApplyModelChanges) + return false; + inApplyModelChanges = true; + + updateUnrequestedIndexes(); + moveReason = QSGItemViewPrivate::Other; + + int prevCount = itemCount; + bool removedVisible = false; + + FxViewItem *firstVisible = firstVisibleItem(); + FxViewItem *origVisibleItemsFirst = visibleItems.count() ? visibleItems.first() : 0; + int firstItemIndex = firstVisible ? firstVisible->index : -1; + QList removedBeforeFirstVisible; + + const QVector &removals = currentChanges.pendingChanges.removes(); + for (int i=0; i::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 (item->index < removals[i].index && !removedVisible) + removedVisible = true; + ++it; + } else if (item->index >= removals[i].index + removals[i].count) { + // after removed items + item->index -= removals[i].count; + ++it; + } else { + // removed item + removedVisible = true; + item->attached->emitRemove(); + if (item->attached->delayRemove()) { + 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()) + removedBeforeFirstVisible.append(item); + 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 (!removals.isEmpty()) + updateVisibleIndex(); + + const QVector &insertions = currentChanges.pendingChanges.inserts(); + bool addedVisible = false; + QList addedItems; + QList movedBackwards; + + for (int i=0; iattached->emitAdd(); + + // if first visible item is moving but another item is moving up to replace it, + // do this positioning now to avoid shifting all content forwards + if (firstVisible && firstItemIndex >= 0) { + for (int i=0; iindex == firstItemIndex) { + resetItemPosition(movedBackwards[i], firstVisible); + movedBackwards.removeAt(i); + break; + } + } + } + + // 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 (movedBackwards.count() && origVisibleItemsFirst) + resetItemPosition(visibleItems.first(), origVisibleItemsFirst); + moveItemBy(visibleItems.first(), removedBeforeFirstVisible, movedBackwards); + } + + // Whatever removed/moved items remain are no longer visible items. + for (QHash::Iterator it = currentChanges.removedItems.begin(); + it != currentChanges.removedItems.end(); ++it) { + releaseItem(it.value()); + } + currentChanges.removedItems.clear(); + + if (currentChanges.currentChanged) { + if (currentChanges.currentRemoved && currentItem) { + currentItem->attached->setIsCurrentItem(false); + releaseItem(currentItem); + currentItem = 0; + } + if (!currentIndexCleared) + updateCurrent(currentChanges.newCurrentIndex); + } + currentChanges.reset(); + + updateSections(); + if (prevCount != itemCount) + emit q->countChanged(); + + inApplyModelChanges = false; + return removedVisible || addedVisible || !currentChanges.pendingChanges.changes().isEmpty(); +} + FxViewItem *QSGItemViewPrivate::createItem(int modelIndex) { Q_Q(QSGItemView); @@ -1415,4 +1692,15 @@ void QSGItemViewPrivate::updateUnrequestedPositions() repositionPackageItemAt(it.key(), it.value()); } +void QSGItemViewPrivate::updateVisibleIndex() +{ + visibleIndex = 0; + for (QList::Iterator it = visibleItems.begin(); it != visibleItems.end(); ++it) { + if ((*it)->index != -1) { + visibleIndex = (*it)->index; + break; + } + } +} + QT_END_NAMESPACE diff --git a/src/declarative/items/qsgitemview_p.h b/src/declarative/items/qsgitemview_p.h index b784015..b1093ff 100644 --- a/src/declarative/items/qsgitemview_p.h +++ b/src/declarative/items/qsgitemview_p.h @@ -191,13 +191,17 @@ protected: protected slots: virtual void updateSections() {} void destroyRemoved(); - void itemsChanged(int index, int count); void createdItem(int index, QSGItem *item); void modelReset(); void destroyingItem(QSGItem *item); void animStopped(); void trackedPositionChanged(); + void itemsInserted(int index, int count); + void itemsRemoved(int index, int count); + void itemsMoved(int from, int to, int count); + void itemsChanged(int index, int count); + private: Q_DECLARE_PRIVATE(QSGItemView) }; diff --git a/src/declarative/items/qsgitemview_p_p.h b/src/declarative/items/qsgitemview_p_p.h index f20f4cc..e2e222b 100644 --- a/src/declarative/items/qsgitemview_p_p.h +++ b/src/declarative/items/qsgitemview_p_p.h @@ -45,6 +45,7 @@ #include "qsgitemview_p.h" #include "qsgflickable_p_p.h" #include "qsgvisualitemmodel_p.h" +#include QT_BEGIN_HEADER @@ -73,6 +74,30 @@ public: QSGItemViewAttached *attached; }; +class QSGItemViewChangeSet +{ +public: + QSGItemViewChangeSet(); + + bool hasPendingChanges() const; + void prepare(int currentIndex, int count); + void reset(); + + void doInsert(int index, int count); + void doRemove(int index, int count); + void doMove(int from, int to, int count); + void doChange(int index, int count); + + int itemCount; + int newCurrentIndex; + QDeclarativeChangeSet pendingChanges; + QHash removedItems; + + bool active : 1; + bool currentChanged : 1; + bool currentRemoved : 1; +}; + class QSGItemViewPrivate : public QSGFlickablePrivate { Q_DECLARE_PUBLIC(QSGItemView) @@ -92,7 +117,6 @@ public: FxViewItem *visibleItem(int modelIndex) const; FxViewItem *firstVisibleItem() const; int mapFromModel(int modelIndex) const; - void adjustMoveParameters(int *from, int *to, int *count) const; virtual void init(); virtual void clear(); @@ -115,7 +139,10 @@ public: void updateTrackedItem(); void updateUnrequestedIndexes(); void updateUnrequestedPositions(); + void updateVisibleIndex(); void positionViewAtIndex(int index, int mode); + void applyPendingChanges(); + bool applyModelChanges(); void checkVisible() const; @@ -135,6 +162,7 @@ public: FxViewItem *trackedItem; QHash unrequestedItems; int requestedIndex; + QSGItemViewChangeSet currentChanges; // XXX split into struct QDeclarativeComponent *highlightComponent; @@ -157,7 +185,9 @@ public: bool lazyRelease : 1; bool deferredRelease : 1; bool layoutScheduled : 1; + bool inApplyModelChanges : 1; bool inViewportMoved : 1; + bool forceLayout : 1; bool currentIndexCleared : 1; bool haveHighlightRange : 1; bool autoHighlight : 1; @@ -195,9 +225,13 @@ protected: virtual FxViewItem *newViewItem(int index, QSGItem *item) = 0; virtual void repositionPackageItemAt(QSGItem *item, int index) = 0; + virtual void resetItemPosition(FxViewItem *item, FxViewItem *toItem) = 0; + virtual void resetFirstItemPosition() = 0; + virtual void moveItemBy(FxViewItem *item, const QList &, const QList &) = 0; virtual void layoutVisibleItems() = 0; virtual void changedVisibleIndex(int newIndex) = 0; + virtual bool applyInsertionChange(const QDeclarativeChangeSet::Insert &, QList *, QList *, FxViewItem *) = 0; virtual void initializeViewItem(FxViewItem *) {} virtual void initializeCurrentItem() {} diff --git a/src/declarative/items/qsglistview.cpp b/src/declarative/items/qsglistview.cpp index 3be4a4b..82a2dd7 100644 --- a/src/declarative/items/qsglistview.cpp +++ b/src/declarative/items/qsglistview.cpp @@ -209,6 +209,9 @@ public: virtual void initializeViewItem(FxViewItem *item); virtual void releaseItem(FxViewItem *item); virtual void repositionPackageItemAt(QSGItem *item, int index); + virtual void resetItemPosition(FxViewItem *item, FxViewItem *toItem); + virtual void resetFirstItemPosition(); + virtual void moveItemBy(FxViewItem *item, const QList &items, const QList &movedBackwards); virtual void createHighlight(); virtual void updateHighlight(); @@ -216,6 +219,7 @@ public: virtual void setPosition(qreal pos); virtual void layoutVisibleItems(); + bool applyInsertionChange(const QDeclarativeChangeSet::Insert &, QList *, QList *, FxViewItem *firstVisible); virtual void updateSections(); void createSection(FxListItemSG *); @@ -684,6 +688,27 @@ void QSGListViewPrivate::repositionPackageItemAt(QSGItem *item, int index) } } +void QSGListViewPrivate::resetItemPosition(FxViewItem *item, FxViewItem *toItem) +{ + static_cast(item)->setPosition(toItem->position()); +} + +void QSGListViewPrivate::resetFirstItemPosition() +{ + FxListItemSG *item = static_cast(visibleItems.first()); + item->setPosition(0); +} + +void QSGListViewPrivate::moveItemBy(FxViewItem *item, const QList &forwards, const QList &backwards) +{ + qreal pos = 0; + for (int i=0; isize(); + for (int i=0; isize(); + static_cast(item)->setPosition(item->position() + pos); +} + void QSGListViewPrivate::createHighlight() { Q_Q(QSGListView); @@ -733,6 +758,8 @@ void QSGListViewPrivate::createHighlight() void QSGListViewPrivate::updateHighlight() { + applyPendingChanges(); + if ((!currentItem && highlight) || (currentItem && !highlight)) createHighlight(); bool strictHighlight = haveHighlightRange && highlightRange == QSGListView::StrictlyEnforceRange; @@ -1009,6 +1036,7 @@ void QSGListViewPrivate::itemGeometryChanged(QSGItem *item, const QRectF &newGeo if (item != contentItem && (!highlight || item != highlight->item)) { if ((orient == QSGListView::Vertical && newGeometry.height() != oldGeometry.height()) || (orient == QSGListView::Horizontal && newGeometry.width() != oldGeometry.width())) { + forceLayout = true; scheduleLayout(); } } @@ -1565,6 +1593,7 @@ void QSGListView::setSpacing(qreal spacing) Q_D(QSGListView); if (spacing != d->spacing) { d->spacing = spacing; + d->forceLayout = true; d->layout(); emit spacingChanged(); } @@ -2067,373 +2096,107 @@ void QSGListView::updateSections() roles << d->sectionCriteria->property().toUtf8(); d->model->setWatchedRoles(roles); d->updateSections(); - if (d->itemCount) + if (d->itemCount) { + d->forceLayout = true; d->layout(); + } } } -void QSGListView::itemsInserted(int modelIndex, int count) +bool QSGListViewPrivate::applyInsertionChange(const QDeclarativeChangeSet::Insert &change, QList *movedBackwards, QList *addedItems, FxViewItem *firstVisible) { - Q_D(QSGListView); - if (!isComponentComplete() || !d->model || !d->model->isValid()) - return; - d->updateUnrequestedIndexes(); - d->moveReason = QSGListViewPrivate::Other; + Q_Q(QSGListView); + + int modelIndex = change.index; + int count = change.count; + + + qreal tempPos = isRightToLeft() ? -position()-size() : position(); + int index = visibleItems.count() ? mapFromModel(modelIndex) : 0; - qreal tempPos = d->isRightToLeft() ? -d->position()-d->size() : d->position(); - int index = d->visibleItems.count() ? d->mapFromModel(modelIndex) : 0; if (index < 0) { - int i = d->visibleItems.count() - 1; - while (i > 0 && d->visibleItems.at(i)->index == -1) + int i = visibleItems.count() - 1; + while (i > 0 && visibleItems.at(i)->index == -1) --i; - if (i == 0 && d->visibleItems.first()->index == -1) { + if (i == 0 && visibleItems.first()->index == -1) { // there are no visible items except items marked for removal - index = d->visibleItems.count(); - } else if (d->visibleItems.at(i)->index + 1 == modelIndex - && d->visibleItems.at(i)->endPosition() <= d->buffer+tempPos+d->size()) { + index = visibleItems.count(); + } else if (visibleItems.at(i)->index + 1 == modelIndex + && visibleItems.at(i)->endPosition() <= buffer+tempPos+size()) { // Special case of appending an item to the model. - index = d->visibleItems.count(); + index = visibleItems.count(); } else { - if (modelIndex < d->visibleIndex) { + if (modelIndex < visibleIndex) { // Insert before visible items - d->visibleIndex += count; - for (int i = 0; i < d->visibleItems.count(); ++i) { - FxViewItem *item = d->visibleItems.at(i); + visibleIndex += count; + for (int i = 0; i < visibleItems.count(); ++i) { + FxViewItem *item = visibleItems.at(i); if (item->index != -1 && item->index >= modelIndex) item->index += count; } } - if (d->currentIndex >= modelIndex) { - // adjust current item index - d->currentIndex += count; - if (d->currentItem) - d->currentItem->index = d->currentIndex; - emit currentIndexChanged(); - } - d->scheduleLayout(); - d->itemCount += count; - emit countChanged(); - return; + return true; } } // index can be the next item past the end of the visible items list (i.e. appended) int pos = 0; - if (d->visibleItems.count()) { - pos = index < d->visibleItems.count() ? d->visibleItems.at(index)->position() - : d->visibleItems.last()->endPosition()+d->spacing; + if (visibleItems.count()) { + pos = index < visibleItems.count() ? visibleItems.at(index)->position() + : visibleItems.last()->endPosition()+spacing; } - int initialPos = pos; - int diff = 0; - QList added; - bool addedVisible = false; - FxViewItem *firstVisible = d->firstVisibleItem(); + int prevAddedCount = addedItems->count(); if (firstVisible && pos < firstVisible->position()) { // Insert items before the visible item. int insertionIdx = index; int i = 0; - int from = tempPos - d->buffer; + int from = tempPos - buffer; + for (i = count-1; i >= 0 && pos > from; --i) { - if (!addedVisible) { - d->scheduleLayout(); - addedVisible = true; + FxViewItem *item = 0; + if (change.isMove() && (item = currentChanges.removedItems.take(change.moveKey(modelIndex + i)))) { + if (item->index > modelIndex + i) + movedBackwards->append(item); + item->index = modelIndex + i; } - FxListItemSG *item = static_cast(d->createItem(modelIndex + i)); - d->visibleItems.insert(insertionIdx, item); - pos -= item->size() + d->spacing; - item->setPosition(pos); + if (!item) + item = createItem(modelIndex + i); + + visibleItems.insert(insertionIdx, item); + addedItems->append(item); + pos -= item->size() + spacing; index++; } - if (i >= 0) { - // If we didn't insert all our new items - anything - // before the current index is not visible - remove it. - while (insertionIdx--) { - FxListItemSG *item = static_cast(d->visibleItems.takeFirst()); - if (item->index != -1) - d->visibleIndex++; - d->releaseItem(item); - } - } else { - // adjust pos of items before inserted items. - for (int i = insertionIdx-1; i >= 0; i--) { - FxListItemSG *listItem = static_cast(d->visibleItems.at(i)); - listItem->setPosition(listItem->position() - (initialPos - pos)); - } - } } else { int i = 0; - int to = d->buffer+tempPos+d->size(); + int to = buffer+tempPos+size(); for (i = 0; i < count && pos <= to; ++i) { - if (!addedVisible) { - d->scheduleLayout(); - addedVisible = true; - } - FxListItemSG *item = static_cast(d->createItem(modelIndex + i)); - d->visibleItems.insert(index, item); - item->setPosition(pos); - added.append(item); - pos += item->size() + d->spacing; - ++index; - } - if (i != count) { - // We didn't insert all our new items, which means anything - // beyond the current index is not visible - remove it. - while (d->visibleItems.count() > index) - d->releaseItem(d->visibleItems.takeLast()); - } - diff = pos - initialPos; - } - if (d->itemCount && d->currentIndex >= modelIndex) { - // adjust current item index - d->currentIndex += count; - if (d->currentItem) { - d->currentItem->index = d->currentIndex; - static_cast(d->currentItem)->setPosition(static_cast(d->currentItem)->position() + diff); - } - emit currentIndexChanged(); - } else if (!d->itemCount && (!d->currentIndex || (d->currentIndex < 0 && !d->currentIndexCleared))) { - d->updateCurrent(0); - } - // Update the indexes of the following visible items. - for (; index < d->visibleItems.count(); ++index) { - FxViewItem *item = d->visibleItems.at(index); - if (d->currentItem && item->item != d->currentItem->item) - static_cast(item)->setPosition(item->position() + diff); - if (item->index != -1) - item->index += count; - } - // everything is in order now - emit add() signal - for (int j = 0; j < added.count(); ++j) - added.at(j)->attached->emitAdd(); - - d->updateSections(); - d->itemCount += count; - emit countChanged(); -} - -void QSGListView::itemsRemoved(int modelIndex, int count) -{ - Q_D(QSGListView); - if (!isComponentComplete() || !d->model || !d->model->isValid()) - return; - d->moveReason = QSGListViewPrivate::Other; - d->updateUnrequestedIndexes(); - d->itemCount -= count; - - FxViewItem *firstVisible = d->firstVisibleItem(); - int preRemovedSize = 0; - bool removedVisible = false; - // Remove the items from the visible list, skipping anything already marked for removal - QList::Iterator it = d->visibleItems.begin(); - while (it != d->visibleItems.end()) { - FxViewItem *item = *it; - if (item->index == -1 || item->index < modelIndex) { - // already removed, or before removed items - ++it; - } else if (item->index >= modelIndex + count) { - // after removed items - item->index -= count; - ++it; - } else { - // removed item - if (!removedVisible) { - d->scheduleLayout(); - removedVisible = true; - } - item->attached->emitRemove(); - if (item->attached->delayRemove()) { - item->index = -1; - connect(item->attached, SIGNAL(delayRemoveChanged()), this, SLOT(destroyRemoved()), Qt::QueuedConnection); - ++it; - } else { - if (item == firstVisible) - firstVisible = 0; - if (firstVisible && item->position() < firstVisible->position()) - preRemovedSize += item->size(); - it = d->visibleItems.erase(it); - d->releaseItem(item); + FxViewItem *item = 0; + if (change.isMove() && (item = currentChanges.removedItems.take(change.moveKey(modelIndex + i)))) { + if (item->index > modelIndex + i) + movedBackwards->append(item); + item->index = modelIndex + i; } - } - } + if (!item) + item = createItem(modelIndex + i); - if (firstVisible && d->visibleItems.first() != firstVisible) - static_cast(d->visibleItems.first())->setPosition(d->visibleItems.first()->position() + preRemovedSize); - - // update visibleIndex - bool haveVisibleIndex = false; - for (it = d->visibleItems.begin(); it != d->visibleItems.end(); ++it) { - if ((*it)->index != -1) { - d->visibleIndex = (*it)->index; - haveVisibleIndex = true; - break; - } - } - - // fix current - if (d->currentIndex >= modelIndex + count) { - d->currentIndex -= count; - if (d->currentItem) - d->currentItem->index -= count; - emit currentIndexChanged(); - } else if (d->currentIndex >= modelIndex && d->currentIndex < modelIndex + count) { - // current item has been removed. - if (d->currentItem) { - d->currentItem->attached->setIsCurrentItem(false); - d->releaseItem(d->currentItem); - d->currentItem = 0; + visibleItems.insert(index, item); + addedItems->append(item); + pos += item->size() + spacing; + ++index; } - d->currentIndex = -1; - if (d->itemCount) - d->updateCurrent(qMin(modelIndex, d->itemCount-1)); - else - emit currentIndexChanged(); } - if (!haveVisibleIndex) { - d->timeline.clear(); - if (removedVisible && d->itemCount == 0) { - d->visibleIndex = 0; - d->visiblePos = 0; - d->setPosition(d->contentStartPosition()); - d->updateHeader(); - d->updateFooter(); - } else { - if (modelIndex < d->visibleIndex) - d->visibleIndex = modelIndex+1; - d->visibleIndex = qMax(qMin(d->visibleIndex, d->itemCount-1), 0); - } + for (; index < visibleItems.count(); ++index) { + FxViewItem *item = visibleItems.at(index); + if (item->index != -1) + item->index += count; } - d->updateSections(); - emit countChanged(); + return addedItems->count() > prevAddedCount; } -void QSGListView::itemsMoved(int from, int to, int count) -{ - Q_D(QSGListView); - if (!isComponentComplete() || !d->isValid()) - return; - d->updateUnrequestedIndexes(); - - if (d->visibleItems.isEmpty()) { - d->refill(); - return; - } - - d->moveReason = QSGListViewPrivate::Other; - - bool movingBackwards = from > to; - d->adjustMoveParameters(&from, &to, &count); - - QHash moved; - int moveBy = 0; - FxViewItem *firstVisible = d->firstVisibleItem(); - int firstItemIndex = firstVisible ? firstVisible->index : -1; - - // if visibleItems.first() is above the content start pos, and the items - // beneath it are moved, ensure this first item is later repositioned correctly - // (to above the next visible item) so that subsequent layout() is correct - bool repositionFirstItem = firstVisible - && d->visibleItems.first()->position() < firstVisible->position() - && from > d->visibleItems.first()->index; - - QList::Iterator it = d->visibleItems.begin(); - while (it != d->visibleItems.end()) { - FxViewItem *item = *it; - if (item->index >= from && item->index < from + count) { - // take the items that are moving - item->index += (to-from); - moved.insert(item->index, item); - if (repositionFirstItem) - moveBy += item->size(); - it = d->visibleItems.erase(it); - } else { - // move everything after the moved items. - if (item->index > from && item->index != -1) - item->index -= count; - ++it; - } - } - - int movedCount = 0; - int endIndex = d->visibleIndex; - it = d->visibleItems.begin(); - while (it != d->visibleItems.end()) { - FxViewItem *item = *it; - if (movedCount < count && item->index >= to && item->index < to + count) { - // place items in the target position, reusing any existing items - int targetIndex = item->index + movedCount; - FxViewItem *movedItem = moved.take(targetIndex); - if (!movedItem) - movedItem = d->createItem(targetIndex); - it = d->visibleItems.insert(it, movedItem); - ++it; - ++movedCount; - } else { - if (item->index != -1) { - if (item->index >= to) { - // update everything after the moved items. - item->index += count; - } - endIndex = item->index; - } - ++it; - } - } - - // If we have moved items to the end of the visible items - // then add any existing moved items that we have - while (FxViewItem *item = moved.take(endIndex+1)) { - d->visibleItems.append(item); - ++endIndex; - } - - // update visibleIndex - for (it = d->visibleItems.begin(); it != d->visibleItems.end(); ++it) { - if ((*it)->index != -1) { - d->visibleIndex = (*it)->index; - break; - } - } - - // if first visible item is moving but another item is moving up to replace it, - // do this positioning now to avoid shifting all content forwards - if (movingBackwards && firstItemIndex >= 0) { - for (it = d->visibleItems.begin(); it != d->visibleItems.end(); ++it) { - if ((*it)->index == firstItemIndex) { - static_cast(*it)->setPosition(firstVisible->position()); - break; - } - } - } - - // Fix current index - if (d->currentIndex >= 0 && d->currentItem) { - int oldCurrent = d->currentIndex; - d->currentIndex = d->model->indexOf(d->currentItem->item, this); - if (oldCurrent != d->currentIndex) { - d->currentItem->index = d->currentIndex; - emit currentIndexChanged(); - } - } - - // Whatever moved items remain are no longer visible items. - while (moved.count()) { - int idx = moved.begin().key(); - FxViewItem *item = moved.take(idx); - if (d->currentItem && item->item == d->currentItem->item) - static_cast(item)->setPosition(d->positionAt(idx)); - d->releaseItem(item); - } - - // Ensure we don't cause an ugly list scroll. - if (d->visibleItems.count()) - static_cast(d->visibleItems.first())->setPosition(d->visibleItems.first()->position() + moveBy); - - d->updateSections(); - d->layout(); -} /*! \qmlmethod QtQuick2::ListView::positionViewAtIndex(int index, PositionMode mode) diff --git a/src/declarative/items/qsglistview_p.h b/src/declarative/items/qsglistview_p.h index e45e16b..8ff4b05 100644 --- a/src/declarative/items/qsglistview_p.h +++ b/src/declarative/items/qsglistview_p.h @@ -166,11 +166,6 @@ protected: protected Q_SLOTS: void updateSections(); - -private Q_SLOTS: - void itemsInserted(int index, int count); - void itemsRemoved(int index, int count); - void itemsMoved(int from, int to, int count); }; class QSGListViewAttached : public QSGItemViewAttached diff --git a/tests/auto/declarative/qsggridview/tst_qsggridview.cpp b/tests/auto/declarative/qsggridview/tst_qsggridview.cpp index 49c3080..ae4ca7d 100644 --- a/tests/auto/declarative/qsggridview/tst_qsggridview.cpp +++ b/tests/auto/declarative/qsggridview/tst_qsggridview.cpp @@ -78,6 +78,8 @@ private slots: void clear(); void moved(); void moved_data(); + void multipleChanges(); + void multipleChanges_data(); void swapWithFirstItem(); void changeFlow(); void currentIndex(); @@ -150,6 +152,8 @@ void tst_QSGGridView::cleanupTestCase() { } + + class TestModel : public QAbstractListModel { public: @@ -196,6 +200,13 @@ public: emit endInsertRows(); } + void insertItems(int index, const QList > &items) { + emit beginInsertRows(QModelIndex(), index, index + items.count() - 1); + for (int i=0; i(items[i].first, items[i].second)); + emit endInsertRows(); + } + void removeItem(int index) { emit beginRemoveRows(QModelIndex(), index, index); list.removeAt(index); @@ -355,8 +366,8 @@ void tst_QSGGridView::inserted() QTRY_VERIFY(contentItem != 0); model.insertItem(1, "Will", "9876"); - QCOMPARE(canvas->rootObject()->property("count").toInt(), model.count()); + QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count()); QTRY_COMPARE(contentItem->childItems().count(), model.count()+1); // assumes all are visible, +1 for the (default) highlight item QSGText *name = findItem(contentItem, "textName", 1); @@ -434,7 +445,7 @@ void tst_QSGGridView::removed() QTRY_VERIFY(contentItem != 0); model.removeItem(1); - QCOMPARE(canvas->rootObject()->property("count").toInt(), model.count()); + QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count()); QSGText *name = findItem(contentItem, "textName", 1); QTRY_VERIFY(name != 0); @@ -443,6 +454,7 @@ void tst_QSGGridView::removed() QTRY_VERIFY(number != 0); QTRY_COMPARE(number->text(), model.number(1)); + // Checks that onRemove is called QString removed = canvas->rootObject()->property("removed").toString(); QTRY_COMPARE(removed, QString("Item1")); @@ -452,13 +464,13 @@ void tst_QSGGridView::removed() for (int i = 0; i < model.count() && i < itemCount; ++i) { QSGItem *item = findItem(contentItem, "wrapper", i); if (!item) qWarning() << "Item" << i << "not found"; - QTRY_VERIFY(item); QTRY_VERIFY(item->x() == (i%3)*80); QTRY_VERIFY(item->y() == (i/3)*60); } // Remove first item (which is the current item); model.removeItem(0); + QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count()); name = findItem(contentItem, "textName", 0); QTRY_VERIFY(name != 0); @@ -467,25 +479,25 @@ void tst_QSGGridView::removed() QTRY_VERIFY(number != 0); QTRY_COMPARE(number->text(), model.number(0)); + // Confirm items positioned correctly itemCount = findItems(contentItem, "wrapper").count(); for (int i = 0; i < model.count() && i < itemCount; ++i) { QSGItem *item = findItem(contentItem, "wrapper", i); if (!item) qWarning() << "Item" << i << "not found"; - QTRY_VERIFY(item); QTRY_VERIFY(item->x() == (i%3)*80); QTRY_VERIFY(item->y() == (i/3)*60); } // Remove items not visible model.removeItem(25); + QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count()); // Confirm items positioned correctly itemCount = findItems(contentItem, "wrapper").count(); for (int i = 0; i < model.count() && i < itemCount; ++i) { QSGItem *item = findItem(contentItem, "wrapper", i); if (!item) qWarning() << "Item" << i << "not found"; - QTRY_VERIFY(item); QTRY_VERIFY(item->x() == (i%3)*80); QTRY_VERIFY(item->y() == (i/3)*60); } @@ -498,12 +510,12 @@ void tst_QSGGridView::removed() QTRY_COMPARE(gridview->contentY(), 120.0); model.removeItem(1); + QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count()); // Confirm items positioned correctly for (int i = 6; i < 18; ++i) { QSGItem *item = findItem(contentItem, "wrapper", i); if (!item) qWarning() << "Item" << i << "not found"; - QTRY_VERIFY(item); QTRY_VERIFY(item->x() == (i%3)*80); QTRY_VERIFY(item->y() == (i/3)*60); } @@ -511,20 +523,19 @@ void tst_QSGGridView::removed() // Remove currentIndex QSGItem *oldCurrent = gridview->currentItem(); model.removeItem(9); + QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count()); QTRY_COMPARE(gridview->currentIndex(), 9); QTRY_VERIFY(gridview->currentItem() != oldCurrent); gridview->setContentY(0); // let transitions settle. - QTest::qWait(100); + QTest::qWait(300); // Confirm items positioned correctly itemCount = findItems(contentItem, "wrapper").count(); for (int i = 0; i < model.count() && i < itemCount; ++i) { QSGItem *item = findItem(contentItem, "wrapper", i); - if (!item) qWarning() << "Item" << i << "not found"; - QTRY_VERIFY(item); QTRY_VERIFY(item->x() == (i%3)*80); QTRY_VERIFY(item->y() == (i/3)*60); } @@ -587,7 +598,7 @@ void tst_QSGGridView::clear() // confirm sanity when adding an item to cleared list model.addItem("New", "1"); - QVERIFY(gridview->count() == 1); + QTRY_COMPARE(gridview->count(), 1); QVERIFY(gridview->currentItem() != 0); QVERIFY(gridview->currentIndex() == 0); @@ -631,10 +642,12 @@ void tst_QSGGridView::moved() gridview->setContentY(contentY); model.moveItems(from, to, count); + // wait for items to move + QTest::qWait(300); + // Confirm items positioned correctly and indexes correct int firstVisibleIndex = qCeil(contentY / 60.0) * 3; int itemCount = findItems(contentItem, "wrapper").count(); - for (int i = firstVisibleIndex; i < model.count() && i < itemCount; ++i) { if (i >= firstVisibleIndex + 18) // index has moved out of view continue; @@ -678,12 +691,12 @@ void tst_QSGGridView::moved_data() QTest::newRow("move 1 forwards, from non-visible -> visible") << 120.0 // show 6-23 << 1 << 23 << 1 - << 0.0; + << 0.0; // only 1 item was removed from the 1st row, so it doesn't move down QTest::newRow("move 1 forwards, from non-visible -> visible (move first item)") << 120.0 // // show 6-23 << 0 << 6 << 1 - << 0.0; + << 0.0; // only 1 item was removed from the 1st row, so it doesn't move down QTest::newRow("move 1 forwards, from visible -> non-visible") << 0.0 @@ -701,6 +714,11 @@ void tst_QSGGridView::moved_data() << 10 << 5 << 1 << 0.0; + QTest::newRow("move 1 backwards, within visible items (to first index)") + << 0.0 + << 10 << 0 << 1 + << 0.0; + QTest::newRow("move 1 backwards, from non-visible -> visible") << 0.0 << 28 << 8 << 1 @@ -714,12 +732,12 @@ void tst_QSGGridView::moved_data() QTest::newRow("move 1 backwards, from visible -> non-visible") << 120.0 // show 6-23 << 7 << 1 << 1 - << 60.0 * 2; // this results in a forward movement that removes 6 items (2 rows) + << 0.0; // only 1 item moved back, so items shift accordingly and first row doesn't move QTest::newRow("move 1 backwards, from visible -> non-visible (move first item)") << 120.0 // show 6-23 << 7 << 0 << 1 - << 60.0 * 2; // first visible item moved, so all items shift 2 rows down with it + << 0.0; // only 1 item moved back, so items shift accordingly and first row doesn't move QTest::newRow("move multiple forwards, within visible items") @@ -730,12 +748,12 @@ void tst_QSGGridView::moved_data() QTest::newRow("move multiple forwards, from non-visible -> visible") << 120.0 // show 6-23 << 1 << 6 << 3 - << 60.0; // top row moved, all items should shift down by 1 row + << 60.0; // 1st row (it's above visible area) disappears, 0 drops down 1 row, first visible item (6) stays where it is QTest::newRow("move multiple forwards, from non-visible -> visible (move first item)") << 120.0 // show 6-23 << 0 << 6 << 3 - << 60.0; // top row moved, all items should shift down by 1 row + << 60.0; // top row moved and shifted to below 3rd row, all items should shift down by 1 row QTest::newRow("move multiple forwards, from visible -> non-visible") << 0.0 @@ -766,14 +784,248 @@ void tst_QSGGridView::moved_data() QTest::newRow("move multiple backwards, from visible -> non-visible") << 120.0 // show 6-23 << 16 << 1 << 3 - << 60.0 * 5; // this results in a forward movement that removes 15 items (5 rows) + << -60.0; // to minimize movement, items are added above visible area, all items move up by 1 row QTest::newRow("move multiple backwards, from visible -> non-visible (move first item)") << 120.0 // show 6-23 << 16 << 0 << 3 - << 60.0 * 5; // this results in a forward movement that removes 16 items (5 rows) + << -60.0; // 16,17,18 move to above item 0, all items move up by 1 row } +struct ListChange { + enum { Inserted, Removed, Moved, SetCurrent } type; + int index; + int count; + int to; // Move + + static ListChange insert(int index, int count = 1) { ListChange c = { Inserted, index, count, -1 }; return c; } + static ListChange remove(int index, int count = 1) { ListChange c = { Removed, index, count, -1 }; return c; } + static ListChange move(int index, int to, int count) { ListChange c = { Moved, index, count, to }; return c; } + static ListChange setCurrent(int index) { ListChange c = { SetCurrent, index, -1, -1 }; return c; } +}; +Q_DECLARE_METATYPE(QList) + +void tst_QSGGridView::multipleChanges() +{ + QFETCH(int, startCount); + QFETCH(QList, changes); + QFETCH(int, newCount); + QFETCH(int, newCurrentIndex); + + QSGView *canvas = createView(); + canvas->show(); + + TestModel model; + for (int i = 0; i < startCount; i++) + model.addItem("Item" + QString::number(i), ""); + + QDeclarativeContext *ctxt = canvas->rootContext(); + ctxt->setContextProperty("testModel", &model); + ctxt->setContextProperty("testRightToLeft", QVariant(false)); + ctxt->setContextProperty("testTopToBottom", QVariant(false)); + + canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/gridview1.qml")); + qApp->processEvents(); + + QSGGridView *gridview = findItem(canvas->rootObject(), "grid"); + QTRY_VERIFY(gridview != 0); + + for (int i=0; i > items; + for (int j=changes[i].index; jsetCurrentIndex(changes[i].index); + break; + } + } + + QTRY_COMPARE(gridview->count(), newCount); + QCOMPARE(gridview->count(), model.count()); + QTRY_COMPARE(gridview->currentIndex(), newCurrentIndex); + + QSGText *name; + QSGText *number; + QSGItem *contentItem = gridview->contentItem(); + QTRY_VERIFY(contentItem != 0); + int itemCount = findItems(contentItem, "wrapper").count(); + for (int i=0; i < model.count() && i < itemCount; ++i) { + QSGItem *item = findItem(contentItem, "wrapper", i); + QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i))); + name = findItem(contentItem, "textName", i); + QVERIFY(name != 0); + QTRY_COMPARE(name->text(), model.name(i)); + number = findItem(contentItem, "textNumber", i); + QVERIFY(number != 0); + QTRY_COMPARE(number->text(), model.number(i)); + } + + delete canvas; +} + +void tst_QSGGridView::multipleChanges_data() +{ + QTest::addColumn("startCount"); + QTest::addColumn >("changes"); + QTest::addColumn("newCount"); + QTest::addColumn("newCurrentIndex"); + + QList changes; + + for (int i=1; i<30; i++) + changes << ListChange::remove(0); + QTest::newRow("remove all but 1, first->last") << 30 << changes << 1 << 0; + + changes << ListChange::remove(0); + QTest::newRow("remove all") << 30 << changes << 0 << -1; + + changes.clear(); + changes << ListChange::setCurrent(29); + for (int i=29; i>0; i--) + changes << ListChange::remove(i); + QTest::newRow("remove last (current) -> first") << 30 << changes << 1 << 0; + + QTest::newRow("remove then insert at 0") << 10 << (QList() + << ListChange::remove(0, 1) + << ListChange::insert(0, 1) + ) << 10 << 1; + + QTest::newRow("remove then insert at non-zero index") << 10 << (QList() + << ListChange::setCurrent(2) + << ListChange::remove(2, 1) + << ListChange::insert(2, 1) + ) << 10 << 3; + + QTest::newRow("remove current then insert below it") << 10 << (QList() + << ListChange::setCurrent(1) + << ListChange::remove(1, 3) + << ListChange::insert(2, 2) + ) << 9 << 1; + + QTest::newRow("remove current index then move it down") << 10 << (QList() + << ListChange::setCurrent(2) + << ListChange::remove(1, 3) + << ListChange::move(1, 5, 1) + ) << 7 << 5; + + QTest::newRow("remove current index then move it up") << 10 << (QList() + << ListChange::setCurrent(5) + << ListChange::remove(4, 3) + << ListChange::move(4, 1, 1) + ) << 7 << 1; + + + QTest::newRow("insert multiple times") << 0 << (QList() + << ListChange::insert(0, 2) + << ListChange::insert(0, 4) + << ListChange::insert(0, 6) + ) << 12 << 10; + + QTest::newRow("insert multiple times with current index changes") << 0 << (QList() + << ListChange::insert(0, 2) + << ListChange::insert(0, 4) + << ListChange::insert(0, 6) + << ListChange::setCurrent(3) + << ListChange::insert(3, 2) + ) << 14 << 5; + + QTest::newRow("insert and remove all") << 0 << (QList() + << ListChange::insert(0, 30) + << ListChange::remove(0, 30) + ) << 0 << -1; + + QTest::newRow("insert and remove current") << 30 << (QList() + << ListChange::insert(1) + << ListChange::setCurrent(1) + << ListChange::remove(1) + ) << 30 << 1; + + QTest::newRow("insert before 0, then remove cross section of new and old items") << 10 << (QList() + << ListChange::insert(0, 10) + << ListChange::remove(5, 10) + ) << 10 << 5; + + QTest::newRow("insert multiple, then move new items to end") << 10 << (QList() + << ListChange::insert(0, 3) + << ListChange::move(0, 10, 3) + ) << 13 << 0; + + QTest::newRow("insert multiple, then move new and some old items to end") << 10 << (QList() + << ListChange::insert(0, 3) + << ListChange::move(0, 8, 5) + ) << 13 << 11; + + QTest::newRow("insert multiple at end, then move new and some old items to start") << 10 << (QList() + << ListChange::setCurrent(9) + << ListChange::insert(10, 3) + << ListChange::move(8, 0, 5) + ) << 13 << 1; + + + QTest::newRow("move back and forth to same index") << 10 << (QList() + << ListChange::setCurrent(1) + << ListChange::move(1, 2, 2) + << ListChange::move(2, 1, 2) + ) << 10 << 1; + + QTest::newRow("move forwards then back") << 10 << (QList() + << ListChange::setCurrent(2) + << ListChange::move(1, 2, 3) + << ListChange::move(3, 0, 5) + ) << 10 << 0; + + QTest::newRow("move current, then remove it") << 10 << (QList() + << ListChange::setCurrent(5) + << ListChange::move(5, 0, 1) + << ListChange::remove(0) + ) << 9 << 0; + + QTest::newRow("move current, then insert before it") << 10 << (QList() + << ListChange::setCurrent(5) + << ListChange::move(5, 0, 1) + << ListChange::insert(0) + ) << 11 << 1; + + QTest::newRow("move multiple, then remove them") << 10 << (QList() + << ListChange::setCurrent(1) + << ListChange::move(5, 1, 3) + << ListChange::remove(1, 3) + ) << 7 << 1; + + QTest::newRow("move multiple, then insert before them") << 10 << (QList() + << ListChange::setCurrent(5) + << ListChange::move(5, 1, 3) + << ListChange::insert(1, 5) + ) << 15 << 6; + + QTest::newRow("move multiple, then insert after them") << 10 << (QList() + << ListChange::setCurrent(3) + << ListChange::move(0, 1, 2) + << ListChange::insert(3, 5) + ) << 15 << 8; + + + QTest::newRow("clear current") << 0 << (QList() + << ListChange::insert(0, 5) + << ListChange::setCurrent(-1) + << ListChange::remove(0, 5) + << ListChange::insert(0, 5) + ) << 5 << -1; +} + + void tst_QSGGridView::swapWithFirstItem() { // QTBUG_9697 @@ -2375,10 +2627,11 @@ void tst_QSGGridView::onAdd() items << qMakePair(QString("value %1").arg(i), QString::number(i)); model.addItems(items); + QTRY_COMPARE(model.count(), qobject_cast(canvas->rootObject())->count()); qApp->processEvents(); QVariantList result = object->property("addedDelegates").toList(); - QCOMPARE(result.count(), items.count()); + QTRY_COMPARE(result.count(), items.count()); for (int i=0; isetSource(QUrl::fromLocalFile(SRCDIR "/data/attachedSignals.qml")); QObject *object = canvas->rootObject(); - qApp->processEvents(); - model.removeItems(indexToRemove, removeCount); - qApp->processEvents(); + QTRY_COMPARE(model.count(), qobject_cast(canvas->rootObject())->count()); QCOMPARE(object->property("removedDelegateCount"), QVariant(removeCount)); delete canvas; diff --git a/tests/auto/declarative/qsglistview/tst_qsglistview.cpp b/tests/auto/declarative/qsglistview/tst_qsglistview.cpp index 45a32da..df4c5bc 100644 --- a/tests/auto/declarative/qsglistview/tst_qsglistview.cpp +++ b/tests/auto/declarative/qsglistview/tst_qsglistview.cpp @@ -51,6 +51,7 @@ #include #include #include +#include #include "../../../shared/util.h" #include "incrementalmodel.h" #include @@ -90,6 +91,9 @@ private slots: void qAbstractItemModel_moved(); void qAbstractItemModel_moved_data(); + void multipleChanges(); + void multipleChanges_data(); + void qListModelInterface_clear(); void qAbstractItemModel_clear(); @@ -294,6 +298,12 @@ public: emit itemsInserted(index, 1); } + void insertItems(int index, const QList > &items) { + for (int i=0; i(items[i].first, items[i].second)); + emit itemsInserted(index, items.count()); + } + void removeItem(int index) { list.removeAt(index); emit itemsRemoved(index, 1); @@ -378,6 +388,13 @@ public: emit endInsertRows(); } + void insertItems(int index, const QList > &items) { + emit beginInsertRows(QModelIndex(), index, index+items.count()-1); + for (int i=0; i(items[i].first, items[i].second)); + emit endInsertRows(); + } + void removeItem(int index) { emit beginRemoveRows(QModelIndex(), index, index); list.removeAt(index); @@ -544,6 +561,7 @@ template void tst_QSGListView::inserted() { QSGView *canvas = createView(); + canvas->show(); T model; model.addItem("Fred", "12345"); @@ -567,6 +585,7 @@ void tst_QSGListView::inserted() model.insertItem(1, "Will", "9876"); + QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count()); QTRY_COMPARE(contentItem->childItems().count(), model.count()+1); // assumes all are visible, +1 for the (default) highlight item QSGText *name = findItem(contentItem, "textName", 1); @@ -584,7 +603,7 @@ void tst_QSGListView::inserted() model.insertItem(0, "Foo", "1111"); // zero index, and current item - QCOMPARE(canvas->rootObject()->property("count").toInt(), model.count()); + QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count()); QTRY_COMPARE(contentItem->childItems().count(), model.count()+1); // assumes all are visible, +1 for the (default) highlight item name = findItem(contentItem, "textName", 0); @@ -625,6 +644,8 @@ void tst_QSGListView::inserted() // QTBUG-19675 model.clear(); model.insertItem(0, "Hello", "1234"); + QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count()); + QSGItem *item = findItem(contentItem, "wrapper", 0); QVERIFY(item); QCOMPARE(item->y(), 0.); @@ -660,7 +681,7 @@ void tst_QSGListView::removed(bool animated) QTRY_VERIFY(contentItem != 0); model.removeItem(1); - QCOMPARE(canvas->rootObject()->property("count").toInt(), model.count()); + QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count()); QSGText *name = findItem(contentItem, "textName", 1); QTRY_VERIFY(name != 0); @@ -680,8 +701,7 @@ void tst_QSGListView::removed(bool animated) // Remove first item (which is the current item); model.removeItem(0); // post: top item starts at 20 - - QTest::qWait(300); + QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count()); name = findItem(contentItem, "textName", 0); QTRY_VERIFY(name != 0); @@ -701,7 +721,7 @@ void tst_QSGListView::removed(bool animated) // Remove items not visible model.removeItem(18); - qApp->processEvents(); + QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count()); // Confirm items positioned correctly itemCount = findItems(contentItem, "wrapper").count(); @@ -717,7 +737,7 @@ void tst_QSGListView::removed(bool animated) listview->setCurrentIndex(10); model.removeItem(1); // post: top item will be at 40 - qApp->processEvents(); + QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count()); // Confirm items positioned correctly for (int i = 2; i < 18; ++i) { @@ -769,7 +789,7 @@ void tst_QSGListView::removed(bool animated) // remove all visible items model.removeItems(1, 18); - QTest::qWait(300); + QTRY_COMPARE(listview->count() , model.count()); // Confirm items positioned correctly itemCount = findItems(contentItem, "wrapper").count(); @@ -781,10 +801,13 @@ void tst_QSGListView::removed(bool animated) } model.removeItems(1, 17); -// QTest::qWait(300); + QTRY_COMPARE(listview->count() , model.count()); model.removeItems(2, 1); + QTRY_COMPARE(listview->count() , model.count()); + model.addItem("New", "1"); + QTRY_COMPARE(listview->count() , model.count()); QTRY_VERIFY(name = findItem(contentItem, "textName", model.count()-1)); QCOMPARE(name->text(), QString("New")); @@ -889,12 +912,23 @@ void tst_QSGListView::moved() listview->setContentY(contentY); model.moveItems(from, to, count); - qApp->processEvents(); + + // wait for items to move + QTest::qWait(300); + + QList items = findItems(contentItem, "wrapper"); + int firstVisibleIndex = -1; + for (int i=0; iy() >= contentY) { + QDeclarativeExpression e(qmlContext(items[i]), items[i], "index"); + firstVisibleIndex = e.evaluate().toInt(); + break; + } + } + QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex)); // Confirm items positioned correctly and indexes correct - int firstVisibleIndex = qCeil(contentY / 20.0); int itemCount = findItems(contentItem, "wrapper").count(); - for (int i = firstVisibleIndex; i < model.count() && i < itemCount; ++i) { if (i >= firstVisibleIndex + 16) // index has moved out of view continue; @@ -937,12 +971,12 @@ void tst_QSGListView::moved_data() QTest::newRow("move 1 forwards, from non-visible -> visible") << 80.0 // show 4-19 << 1 << 18 << 1 - << 20.0; + << 20.0; // removed 1 item above the first visible, so item 0 should drop down by 1 to minimize movement QTest::newRow("move 1 forwards, from non-visible -> visible (move first item)") << 80.0 // show 4-19 << 0 << 4 << 1 - << 20.0; + << 20.0; // first item has moved to below item4, everything drops down by size of 1 item QTest::newRow("move 1 forwards, from visible -> non-visible") << 0.0 @@ -960,6 +994,11 @@ void tst_QSGListView::moved_data() << 4 << 1 << 1 << 0.0; + QTest::newRow("move 1 backwards, within visible items (to first index)") + << 0.0 + << 4 << 0 << 1 + << 0.0; + QTest::newRow("move 1 backwards, from non-visible -> visible") << 0.0 << 20 << 4 << 1 @@ -973,12 +1012,12 @@ void tst_QSGListView::moved_data() QTest::newRow("move 1 backwards, from visible -> non-visible") << 80.0 // show 4-19 << 16 << 1 << 1 - << 20.0 * 15; // this results in a forward movement that removes 15 items + << -20.0; // to minimize movement, item 0 moves to -20, and other items do not move QTest::newRow("move 1 backwards, from visible -> non-visible (move first item)") << 80.0 // show 4-19 << 16 << 0 << 1 - << 20.0 * 16; // everything should move to after item 16 + << -20.0; // to minimize movement, item 16 (now at 0) moves to -20, and other items do not move QTest::newRow("move multiple forwards, within visible items") @@ -1025,12 +1064,248 @@ void tst_QSGListView::moved_data() QTest::newRow("move multiple backwards, from visible -> non-visible") << 80.0 // show 4-19 << 16 << 1 << 3 - << 20.0 * 15; // this results in a forward movement that removes 15 items + << -20.0 * 3; // to minimize movement, 0 moves by -60, and other items do not move QTest::newRow("move multiple backwards, from visible -> non-visible (move first item)") << 80.0 // show 4-19 << 16 << 0 << 3 - << 20.0 * 16; + << -20.0 * 3; // to minimize movement, 16,17,18 move to above item 0, and other items do not move +} + + +struct ListChange { + enum { Inserted, Removed, Moved, SetCurrent } type; + int index; + int count; + int to; // Move + + static ListChange insert(int index, int count = 1) { ListChange c = { Inserted, index, count, -1 }; return c; } + static ListChange remove(int index, int count = 1) { ListChange c = { Removed, index, count, -1 }; return c; } + static ListChange move(int index, int to, int count) { ListChange c = { Moved, index, count, to }; return c; } + static ListChange setCurrent(int index) { ListChange c = { SetCurrent, index, -1, -1 }; return c; } +}; +Q_DECLARE_METATYPE(QList) + +void tst_QSGListView::multipleChanges() +{ + QFETCH(int, startCount); + QFETCH(QList, changes); + QFETCH(int, newCount); + QFETCH(int, newCurrentIndex); + + QSGView *canvas = createView(); + canvas->show(); + + TestModel model; + for (int i = 0; i < startCount; i++) + model.addItem("Item" + QString::number(i), ""); + + QDeclarativeContext *ctxt = canvas->rootContext(); + ctxt->setContextProperty("testModel", &model); + + TestObject *testObject = new TestObject; + ctxt->setContextProperty("testObject", testObject); + + canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listviewtest.qml")); + qApp->processEvents(); + + QSGListView *listview = findItem(canvas->rootObject(), "list"); + QTRY_VERIFY(listview != 0); + + for (int i=0; i > items; + for (int j=changes[i].index; jsetCurrentIndex(changes[i].index); + break; + } + } + + QTRY_COMPARE(listview->count(), newCount); + QCOMPARE(listview->count(), model.count()); + QTRY_COMPARE(listview->currentIndex(), newCurrentIndex); + + QSGText *name; + QSGText *number; + QSGItem *contentItem = listview->contentItem(); + QTRY_VERIFY(contentItem != 0); + int itemCount = findItems(contentItem, "wrapper").count(); + for (int i=0; i < model.count() && i < itemCount; ++i) { + QSGItem *item = findItem(contentItem, "wrapper", i); + QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i))); + name = findItem(contentItem, "textName", i); + QVERIFY(name != 0); + QTRY_COMPARE(name->text(), model.name(i)); + number = findItem(contentItem, "textNumber", i); + QVERIFY(number != 0); + QTRY_COMPARE(number->text(), model.number(i)); + } + + delete testObject; + delete canvas; +} + +void tst_QSGListView::multipleChanges_data() +{ + QTest::addColumn("startCount"); + QTest::addColumn >("changes"); + QTest::addColumn("newCount"); + QTest::addColumn("newCurrentIndex"); + + QList changes; + + for (int i=1; i<30; i++) + changes << ListChange::remove(0); + QTest::newRow("remove all but 1, first->last") << 30 << changes << 1 << 0; + + changes << ListChange::remove(0); + QTest::newRow("remove all") << 30 << changes << 0 << -1; + + changes.clear(); + changes << ListChange::setCurrent(29); + for (int i=29; i>0; i--) + changes << ListChange::remove(i); + QTest::newRow("remove last (current) -> first") << 30 << changes << 1 << 0; + + QTest::newRow("remove then insert at 0") << 10 << (QList() + << ListChange::remove(0, 1) + << ListChange::insert(0, 1) + ) << 10 << 1; + + QTest::newRow("remove then insert at non-zero index") << 10 << (QList() + << ListChange::setCurrent(2) + << ListChange::remove(2, 1) + << ListChange::insert(2, 1) + ) << 10 << 3; + + QTest::newRow("remove current then insert below it") << 10 << (QList() + << ListChange::setCurrent(1) + << ListChange::remove(1, 3) + << ListChange::insert(2, 2) + ) << 9 << 1; + + QTest::newRow("remove current index then move it down") << 10 << (QList() + << ListChange::setCurrent(2) + << ListChange::remove(1, 3) + << ListChange::move(1, 5, 1) + ) << 7 << 5; + + QTest::newRow("remove current index then move it up") << 10 << (QList() + << ListChange::setCurrent(5) + << ListChange::remove(4, 3) + << ListChange::move(4, 1, 1) + ) << 7 << 1; + + + QTest::newRow("insert multiple times") << 0 << (QList() + << ListChange::insert(0, 2) + << ListChange::insert(0, 4) + << ListChange::insert(0, 6) + ) << 12 << 10; + + QTest::newRow("insert multiple times with current index changes") << 0 << (QList() + << ListChange::insert(0, 2) + << ListChange::insert(0, 4) + << ListChange::insert(0, 6) + << ListChange::setCurrent(3) + << ListChange::insert(3, 2) + ) << 14 << 5; + + QTest::newRow("insert and remove all") << 0 << (QList() + << ListChange::insert(0, 30) + << ListChange::remove(0, 30) + ) << 0 << -1; + + QTest::newRow("insert and remove current") << 30 << (QList() + << ListChange::insert(1) + << ListChange::setCurrent(1) + << ListChange::remove(1) + ) << 30 << 1; + + QTest::newRow("insert before 0, then remove cross section of new and old items") << 10 << (QList() + << ListChange::insert(0, 10) + << ListChange::remove(5, 10) + ) << 10 << 5; + + QTest::newRow("insert multiple, then move new items to end") << 10 << (QList() + << ListChange::insert(0, 3) + << ListChange::move(0, 10, 3) + ) << 13 << 0; + + QTest::newRow("insert multiple, then move new and some old items to end") << 10 << (QList() + << ListChange::insert(0, 3) + << ListChange::move(0, 8, 5) + ) << 13 << 11; + + QTest::newRow("insert multiple at end, then move new and some old items to start") << 10 << (QList() + << ListChange::setCurrent(9) + << ListChange::insert(10, 3) + << ListChange::move(8, 0, 5) + ) << 13 << 1; + + + QTest::newRow("move back and forth to same index") << 10 << (QList() + << ListChange::setCurrent(1) + << ListChange::move(1, 2, 2) + << ListChange::move(2, 1, 2) + ) << 10 << 1; + + QTest::newRow("move forwards then back") << 10 << (QList() + << ListChange::setCurrent(2) + << ListChange::move(1, 2, 3) + << ListChange::move(3, 0, 5) + ) << 10 << 0; + + QTest::newRow("move current, then remove it") << 10 << (QList() + << ListChange::setCurrent(5) + << ListChange::move(5, 0, 1) + << ListChange::remove(0) + ) << 9 << 0; + + QTest::newRow("move current, then insert before it") << 10 << (QList() + << ListChange::setCurrent(5) + << ListChange::move(5, 0, 1) + << ListChange::insert(0) + ) << 11 << 1; + + QTest::newRow("move multiple, then remove them") << 10 << (QList() + << ListChange::setCurrent(1) + << ListChange::move(5, 1, 3) + << ListChange::remove(1, 3) + ) << 7 << 1; + + QTest::newRow("move multiple, then insert before them") << 10 << (QList() + << ListChange::setCurrent(5) + << ListChange::move(5, 1, 3) + << ListChange::insert(1, 5) + ) << 15 << 6; + + QTest::newRow("move multiple, then insert after them") << 10 << (QList() + << ListChange::setCurrent(3) + << ListChange::move(0, 1, 2) + << ListChange::insert(3, 5) + ) << 15 << 8; + + + QTest::newRow("clear current") << 0 << (QList() + << ListChange::insert(0, 5) + << ListChange::setCurrent(-1) + << ListChange::remove(0, 5) + << ListChange::insert(0, 5) + ) << 5 << -1; } void tst_QSGListView::swapWithFirstItem() @@ -1167,6 +1442,7 @@ void tst_QSGListView::enforceRange_withoutHighlight() void tst_QSGListView::spacing() { QSGView *canvas = createView(); + canvas->show(); TestModel model; for (int i = 0; i < 30; i++) @@ -1226,6 +1502,7 @@ void tst_QSGListView::spacing() void tst_QSGListView::sections() { QSGView *canvas = createView(); + canvas->show(); TestModel model; for (int i = 0; i < 30; i++) @@ -1257,6 +1534,7 @@ void tst_QSGListView::sections() // Remove section boundary model.removeItem(5); + QTRY_COMPARE(listview->count(), model.count()); // New section header created QSGItem *item = findItem(contentItem, "wrapper", 5); @@ -1264,6 +1542,7 @@ void tst_QSGListView::sections() QTRY_COMPARE(item->height(), 40.0); model.insertItem(3, "New Item", "0"); + QTRY_COMPARE(listview->count(), model.count()); // Section header moved item = findItem(contentItem, "wrapper", 5); @@ -1276,6 +1555,7 @@ void tst_QSGListView::sections() // insert item which will become a section header model.insertItem(6, "Replace header", "1"); + QTRY_COMPARE(listview->count(), model.count()); item = findItem(contentItem, "wrapper", 6); QTRY_VERIFY(item); @@ -1304,6 +1584,7 @@ void tst_QSGListView::sections() // check that headers change when item changes listview->setContentY(0); model.modifyItem(0, "changed", "2"); + QTest::qWait(300); item = findItem(contentItem, "wrapper", 1); QTRY_VERIFY(item); @@ -1315,6 +1596,7 @@ void tst_QSGListView::sections() void tst_QSGListView::sectionsDelegate() { QSGView *canvas = createView(); + canvas->show(); TestModel model; for (int i = 0; i < 30; i++) @@ -1353,6 +1635,7 @@ void tst_QSGListView::sectionsDelegate() model.modifyItem(2, "Three", "aaa"); model.modifyItem(3, "Four", "aaa"); model.modifyItem(4, "Five", "aaa"); + QTest::qWait(300); for (int i = 0; i < 3; ++i) { QSGItem *item = findItem(contentItem, @@ -1363,7 +1646,7 @@ void tst_QSGListView::sectionsDelegate() // remove section boundary model.removeItem(5); - qApp->processEvents(); + QTRY_COMPARE(listview->count(), model.count()); for (int i = 0; i < 3; ++i) { QSGItem *item = findItem(contentItem, "sect_" + (i == 0 ? QString("aaa") : QString::number(i))); @@ -2694,6 +2977,7 @@ void tst_QSGListView::resizeDelegate() listview->setCurrentIndex(25); listview->setContentY(0); + QTest::qWait(300); for (int i = 0; i < 16; ++i) { QSGItem *item = findItem(contentItem, "wrapper", i); @@ -2905,8 +3189,7 @@ void tst_QSGListView::onAdd() for (int i=0; iprocessEvents(); + QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count()); QVariantList result = object->property("addedDelegates").toList(); QCOMPARE(result.count(), items.count()); @@ -2952,10 +3235,9 @@ void tst_QSGListView::onRemove() canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/attachedSignals.qml")); QObject *object = canvas->rootObject(); - qApp->processEvents(); - model.removeItems(indexToRemove, removeCount); - qApp->processEvents(); + QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count()); + QCOMPARE(object->property("removedDelegateCount"), QVariant(removeCount)); delete canvas; -- 1.7.2.5