+ // Update the indexes of all KItemListWidget instances that are located
+ // after the inserted items. It is important to adjust the indexes in the order
+ // from the highest index to the lowest index to prevent overlaps when setting the new index.
+ std::sort(itemsToMove.begin(), itemsToMove.end());
+ for (int i = itemsToMove.count() - 1; i >= 0; --i) {
+ KItemListWidget *widget = m_visibleItems.value(itemsToMove[i]);
+ Q_ASSERT(widget);
+ const int newIndex = widget->index() + count;
+ if (hasMultipleRanges) {
+ setWidgetIndex(widget, newIndex);
+ } else {
+ // Try to animate the moving of the item
+ moveWidgetToIndex(widget, newIndex);
+ }
+ }
+
+ if (m_model->count() == count && m_activeTransactions == 0) {
+ // Check whether a scrollbar is required to show the inserted items. In this case
+ // the size of the layouter will be decreased before calling doLayout(): This prevents
+ // an unnecessary temporary animation due to the geometry change of the inserted scrollbar.
+ const bool verticalScrollOrientation = (scrollOrientation() == Qt::Vertical);
+ const bool decreaseLayouterSize = (verticalScrollOrientation && maximumScrollOffset() > size().height())
+ || (!verticalScrollOrientation && maximumScrollOffset() > size().width());
+ if (decreaseLayouterSize) {
+ const int scrollBarExtent = style()->pixelMetric(QStyle::PM_ScrollBarExtent);
+
+ int scrollbarSpacing = 0;
+ if (style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) {
+ scrollbarSpacing = style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarSpacing);
+ }
+
+ QSizeF layouterSize = m_layouter->size();
+ if (verticalScrollOrientation) {
+ layouterSize.rwidth() -= scrollBarExtent + scrollbarSpacing;
+ } else {
+ layouterSize.rheight() -= scrollBarExtent + scrollbarSpacing;
+ }
+ m_layouter->setSize(layouterSize);
+ }
+ }
+
+ if (!hasMultipleRanges) {
+ doLayout(animateChangedItemCount(count) ? Animation : NoAnimation, index, count);
+ updateSiblingsInformation();
+ }
+ }
+
+ if (m_controller) {
+ m_controller->selectionManager()->itemsInserted(itemRanges);
+ }
+
+ if (hasMultipleRanges) {
+ m_endTransactionAnimationHint = NoAnimation;
+ endTransaction();
+
+ updateSiblingsInformation();
+ }
+
+ if (m_grouped && (hasMultipleRanges || itemRanges.first().count < m_model->count())) {
+ // In case if items of the same group have been inserted before an item that
+ // currently represents the first item of the group, the group header of
+ // this item must be removed.
+ updateVisibleGroupHeaders();
+ }
+
+ if (useAlternateBackgrounds()) {
+ updateAlternateBackgrounds();
+ }
+}
+
+void KItemListView::slotItemsRemoved(const KItemRangeList &itemRanges)
+{
+ if (m_itemSize.isEmpty()) {
+ // Don't pass the item-range: The preferred column-widths of
+ // all items must be adjusted when removing items.
+ updatePreferredColumnWidths();
+ }
+
+ const bool hasMultipleRanges = (itemRanges.count() > 1);
+ if (hasMultipleRanges) {
+ beginTransaction();
+ }
+
+ m_layouter->markAsDirty();
+
+ m_sizeHintResolver->itemsRemoved(itemRanges);
+
+ for (int i = itemRanges.count() - 1; i >= 0; --i) {
+ const KItemRange &range = itemRanges[i];
+ const int index = range.index;
+ const int count = range.count;
+ if (index < 0 || count <= 0) {
+ qCWarning(DolphinDebug) << "Invalid item range (index:" << index << ", count:" << count << ")";
+ continue;
+ }
+
+ const int firstRemovedIndex = index;
+ const int lastRemovedIndex = index + count - 1;
+
+ // Remember which items have to be moved because they are behind the removed range.
+ QVector<int> itemsToMove;
+
+ // Remove all KItemListWidget instances that got deleted
+ // Iterate over a const copy because the container is mutated within the loop
+ // directly and in `recycleWidget()` (https://bugs.kde.org/show_bug.cgi?id=428374)
+ const auto visibleItems = m_visibleItems;
+ for (KItemListWidget *widget : visibleItems) {
+ const int i = widget->index();
+ if (i < firstRemovedIndex) {
+ continue;
+ } else if (i > lastRemovedIndex) {
+ itemsToMove.append(i);
+ continue;
+ }
+
+ m_animation->stop(widget);
+ // Stopping the animation might lead to recycling the widget if
+ // it is invisible (see slotAnimationFinished()).
+ // Check again whether it is still visible:
+ if (!m_visibleItems.contains(i)) {
+ continue;
+ }
+
+ if (m_model->count() == 0 || hasMultipleRanges || !animateChangedItemCount(count)) {
+ // Remove the widget without animation
+ recycleWidget(widget);
+ } else {
+ // Animate the removing of the items. Special case: When removing an item there
+ // is no valid model index available anymore. For the
+ // remove-animation the item gets removed from m_visibleItems but the widget
+ // will stay alive until the animation has been finished and will
+ // be recycled (deleted) in KItemListView::slotAnimationFinished().
+ m_visibleItems.remove(i);
+ widget->setIndex(-1);
+ m_animation->start(widget, KItemListViewAnimation::DeleteAnimation);
+ }
+ }
+
+ // Update the indexes of all KItemListWidget instances that are located
+ // after the deleted items. It is important to update them in ascending
+ // order to prevent overlaps when setting the new index.
+ std::sort(itemsToMove.begin(), itemsToMove.end());
+ for (int i : std::as_const(itemsToMove)) {
+ KItemListWidget *widget = m_visibleItems.value(i);
+ Q_ASSERT(widget);
+ const int newIndex = i - count;
+ if (hasMultipleRanges) {
+ setWidgetIndex(widget, newIndex);
+ } else {
+ // Try to animate the moving of the item
+ moveWidgetToIndex(widget, newIndex);
+ }
+ }
+
+ if (!hasMultipleRanges) {
+ // The decrease-layout-size optimization in KItemListView::slotItemsInserted()
+ // assumes an updated geometry. If items are removed during an active transaction,
+ // the transaction will be temporary deactivated so that doLayout() triggers a
+ // geometry update if necessary.
+ const int activeTransactions = m_activeTransactions;
+ m_activeTransactions = 0;
+ doLayout(animateChangedItemCount(count) ? Animation : NoAnimation, index, -count);
+ m_activeTransactions = activeTransactions;
+ updateSiblingsInformation();
+ }
+ }
+
+ if (m_controller) {
+ m_controller->selectionManager()->itemsRemoved(itemRanges);
+ }
+
+ if (hasMultipleRanges) {
+ m_endTransactionAnimationHint = NoAnimation;
+ endTransaction();
+ updateSiblingsInformation();
+ }
+
+ if (m_grouped && (hasMultipleRanges || m_model->count() > 0)) {
+ // In case if the first item of a group has been removed, the group header
+ // must be applied to the next visible item.
+ updateVisibleGroupHeaders();
+ }
+
+ if (useAlternateBackgrounds()) {
+ updateAlternateBackgrounds();
+ }
+}
+
+void KItemListView::slotItemsMoved(const KItemRange &itemRange, const QList<int> &movedToIndexes)
+{
+ m_sizeHintResolver->itemsMoved(itemRange, movedToIndexes);
+ m_layouter->markAsDirty();
+
+ if (m_controller) {
+ m_controller->selectionManager()->itemsMoved(itemRange, movedToIndexes);
+ }
+
+ const int firstVisibleMovedIndex = qMax(firstVisibleIndex(), itemRange.index);
+ const int lastVisibleMovedIndex = qMin(lastVisibleIndex(), itemRange.index + itemRange.count - 1);
+
+ for (int index = firstVisibleMovedIndex; index <= lastVisibleMovedIndex; ++index) {
+ KItemListWidget *widget = m_visibleItems.value(index);
+ if (widget) {
+ updateWidgetProperties(widget, index);
+ initializeItemListWidget(widget);
+ }
+ }
+
+ doLayout(NoAnimation);
+ updateSiblingsInformation();
+}
+
+void KItemListView::slotItemsChanged(const KItemRangeList &itemRanges, const QSet<QByteArray> &roles)
+{
+ const bool updateSizeHints = itemSizeHintUpdateRequired(roles);
+ if (updateSizeHints && m_itemSize.isEmpty()) {
+ updatePreferredColumnWidths(itemRanges);
+ }
+
+ for (const KItemRange &itemRange : itemRanges) {
+ const int index = itemRange.index;
+ const int count = itemRange.count;
+
+ if (updateSizeHints) {
+ m_sizeHintResolver->itemsChanged(index, count, roles);
+ m_layouter->markAsDirty();
+ }
+
+ // Apply the changed roles to the visible item-widgets
+ const int lastIndex = index + count - 1;
+ for (int i = index; i <= lastIndex; ++i) {
+ KItemListWidget *widget = m_visibleItems.value(i);
+ if (widget) {
+ widget->setData(m_model->data(i), roles);
+ }
+ }
+
+ if (m_grouped && roles.contains(m_model->sortRole())) {
+ // The sort-role has been changed which might result
+ // in modified group headers
+ updateVisibleGroupHeaders();
+ doLayout(NoAnimation);
+ }
+
+ QAccessibleTableModelChangeEvent ev(this, QAccessibleTableModelChangeEvent::DataChanged);
+ ev.setFirstRow(itemRange.index);
+ ev.setLastRow(itemRange.index + itemRange.count);
+ QAccessible::updateAccessibility(&ev);
+ }
+
+ doLayout(NoAnimation);
+}
+
+void KItemListView::slotGroupsChanged()
+{
+ updateVisibleGroupHeaders();
+ doLayout(NoAnimation);
+ updateSiblingsInformation();
+}
+
+void KItemListView::slotGroupedSortingChanged(bool current)
+{
+ m_grouped = current;
+ m_layouter->markAsDirty();
+
+ if (m_grouped) {
+ updateGroupHeaderHeight();
+ } else {
+ // Clear all visible headers. Note that the QHashIterator takes a copy of
+ // m_visibleGroups. Therefore, it remains valid even if items are removed
+ // from m_visibleGroups in recycleGroupHeaderForWidget().
+ QHashIterator<KItemListWidget *, KItemListGroupHeader *> it(m_visibleGroups);
+ while (it.hasNext()) {
+ it.next();
+ recycleGroupHeaderForWidget(it.key());
+ }
+ Q_ASSERT(m_visibleGroups.isEmpty());
+ }
+
+ if (useAlternateBackgrounds()) {
+ // Changing the group mode requires to update the alternate backgrounds
+ // as with the enabled group mode the altering is done on base of the first
+ // group item.
+ updateAlternateBackgrounds();
+ }
+ updateSiblingsInformation();
+ doLayout(NoAnimation);
+}
+
+void KItemListView::slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous)
+{
+ Q_UNUSED(current)
+ Q_UNUSED(previous)
+ if (m_grouped) {
+ updateVisibleGroupHeaders();
+ doLayout(NoAnimation);
+ }
+}
+
+void KItemListView::slotSortRoleChanged(const QByteArray ¤t, const QByteArray &previous)
+{
+ Q_UNUSED(current)
+ Q_UNUSED(previous)
+ if (m_grouped) {
+ updateVisibleGroupHeaders();
+ doLayout(NoAnimation);
+ }
+}
+
+void KItemListView::slotCurrentChanged(int current, int previous)
+{
+ Q_UNUSED(previous)
+
+ // In SingleSelection mode (e.g., in the Places Panel), the current item is
+ // always the selected item. It is not necessary to highlight the current item then.
+ if (m_controller->selectionBehavior() != KItemListController::SingleSelection) {
+ KItemListWidget *previousWidget = m_visibleItems.value(previous, nullptr);
+ if (previousWidget) {
+ previousWidget->setCurrent(false);
+ }
+
+ KItemListWidget *currentWidget = m_visibleItems.value(current, nullptr);
+ if (currentWidget) {
+ currentWidget->setCurrent(true);
+ }
+ }
+
+ QAccessibleEvent ev(this, QAccessible::Focus);
+ ev.setChild(current);
+ QAccessible::updateAccessibility(&ev);
+}
+
+void KItemListView::slotSelectionChanged(const KItemSet ¤t, const KItemSet &previous)
+{
+ Q_UNUSED(previous)
+
+ QHashIterator<int, KItemListWidget *> it(m_visibleItems);
+ while (it.hasNext()) {
+ it.next();
+ const int index = it.key();
+ KItemListWidget *widget = it.value();
+ widget->setSelected(current.contains(index));
+ }
+}
+
+void KItemListView::slotAnimationFinished(QGraphicsWidget *widget, KItemListViewAnimation::AnimationType type)
+{
+ KItemListWidget *itemListWidget = qobject_cast<KItemListWidget *>(widget);
+ Q_ASSERT(itemListWidget);
+
+ if (type == KItemListViewAnimation::DeleteAnimation) {
+ // As we recycle the widget in this case it is important to assure that no
+ // other animation has been started. This is a convention in KItemListView and
+ // not a requirement defined by KItemListViewAnimation.
+ Q_ASSERT(!m_animation->isStarted(itemListWidget));
+
+ // All KItemListWidgets that are animated by the DeleteAnimation are not maintained
+ // by m_visibleWidgets and must be deleted manually after the animation has
+ // been finished.
+ recycleGroupHeaderForWidget(itemListWidget);
+ widgetCreator()->recycle(itemListWidget);
+ } else {
+ const int index = itemListWidget->index();
+ const bool invisible = (index < m_layouter->firstVisibleIndex()) || (index > m_layouter->lastVisibleIndex());
+ if (invisible && !m_animation->isStarted(itemListWidget)) {
+ recycleWidget(itemListWidget);
+ }
+ }
+}
+
+void KItemListView::slotRubberBandPosChanged()
+{
+ update();
+}
+
+void KItemListView::slotRubberBandActivationChanged(bool active)
+{
+ if (active) {
+ connect(m_rubberBand, &KItemListRubberBand::startPositionChanged, this, &KItemListView::slotRubberBandPosChanged);
+ connect(m_rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListView::slotRubberBandPosChanged);
+ m_skipAutoScrollForRubberBand = true;
+ } else {
+ QRectF rubberBandRect = QRectF(m_rubberBand->startPosition(), m_rubberBand->endPosition()).normalized();
+
+ auto animation = new QVariantAnimation(this);
+ animation->setStartValue(1.0);
+ animation->setEndValue(0.0);
+ animation->setDuration(RubberFadeSpeed);
+ animation->setProperty(RubberPropertyName, rubberBandRect);
+
+ QEasingCurve curve;
+ curve.setType(QEasingCurve::BezierSpline);
+ curve.addCubicBezierSegment(QPointF(0.4, 0.0), QPointF(1.0, 1.0), QPointF(1.0, 1.0));
+ animation->setEasingCurve(curve);
+
+ connect(animation, &QVariantAnimation::valueChanged, this, [=](const QVariant &) {
+ update();
+ });
+ connect(animation, &QVariantAnimation::finished, this, [=]() {
+ m_rubberBandAnimations.removeAll(animation);
+ delete animation;
+ });
+ animation->start();
+ m_rubberBandAnimations << animation;
+
+ disconnect(m_rubberBand, &KItemListRubberBand::startPositionChanged, this, &KItemListView::slotRubberBandPosChanged);
+ disconnect(m_rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListView::slotRubberBandPosChanged);
+ m_skipAutoScrollForRubberBand = false;
+ }
+
+ update();
+}
+
+void KItemListView::slotHeaderColumnWidthChanged(const QByteArray &role, qreal currentWidth, qreal previousWidth)
+{
+ Q_UNUSED(role)
+ Q_UNUSED(currentWidth)
+ Q_UNUSED(previousWidth)
+
+ m_headerWidget->setAutomaticColumnResizing(false);
+ applyColumnWidthsFromHeader();
+ doLayout(NoAnimation);
+}
+
+void KItemListView::slotSidePaddingChanged(qreal width)
+{
+ Q_UNUSED(width)
+ if (m_headerWidget->automaticColumnResizing()) {
+ applyAutomaticColumnWidths();
+ }
+ applyColumnWidthsFromHeader();
+ doLayout(NoAnimation);
+}
+
+void KItemListView::slotHeaderColumnMoved(const QByteArray &role, int currentIndex, int previousIndex)
+{
+ Q_ASSERT(m_visibleRoles[previousIndex] == role);
+
+ const QList<QByteArray> previous = m_visibleRoles;
+
+ QList<QByteArray> current = m_visibleRoles;
+ current.removeAt(previousIndex);
+ current.insert(currentIndex, role);
+
+ setVisibleRoles(current);
+
+ Q_EMIT visibleRolesChanged(current, previous);
+}
+
+void KItemListView::triggerAutoScrolling()
+{
+ if (!m_autoScrollTimer) {
+ return;
+ }
+
+ int pos = 0;
+ int visibleSize = 0;
+ if (scrollOrientation() == Qt::Vertical) {
+ pos = m_mousePos.y();
+ visibleSize = size().height();
+ } else {
+ pos = m_mousePos.x();
+ visibleSize = size().width();
+ }
+
+ if (m_autoScrollTimer->interval() == InitialAutoScrollDelay) {
+ m_autoScrollIncrement = 0;
+ }
+
+ m_autoScrollIncrement = calculateAutoScrollingIncrement(pos, visibleSize, m_autoScrollIncrement);
+ if (m_autoScrollIncrement == 0) {
+ // The mouse position is not above an autoscroll margin (the autoscroll timer
+ // will be restarted in mouseMoveEvent())
+ m_autoScrollTimer->stop();
+ return;
+ }
+
+ if (m_rubberBand->isActive() && m_skipAutoScrollForRubberBand) {
+ // If a rubberband selection is ongoing the autoscrolling may only get triggered
+ // if the direction of the rubberband is similar to the autoscroll direction. This
+ // prevents that starting to create a rubberband within the autoscroll margins starts
+ // an autoscrolling.
+
+ const qreal minDiff = 4; // Ignore any autoscrolling if the rubberband is very small
+ const qreal diff = (scrollOrientation() == Qt::Vertical) ? m_rubberBand->endPosition().y() - m_rubberBand->startPosition().y()
+ : m_rubberBand->endPosition().x() - m_rubberBand->startPosition().x();
+ if (qAbs(diff) < minDiff || (m_autoScrollIncrement < 0 && diff > 0) || (m_autoScrollIncrement > 0 && diff < 0)) {
+ // The rubberband direction is different from the scroll direction (e.g. the rubberband has
+ // been moved up although the autoscroll direction might be down)
+ m_autoScrollTimer->stop();
+ return;
+ }
+ }
+
+ // As soon as the autoscrolling has been triggered at least once despite having an active rubberband,
+ // the autoscrolling may not get skipped anymore until a new rubberband is created
+ m_skipAutoScrollForRubberBand = false;
+
+ const qreal maxVisibleOffset = qMax(qreal(0), maximumScrollOffset() - visibleSize);
+ const qreal newScrollOffset = qMin(scrollOffset() + m_autoScrollIncrement, maxVisibleOffset);
+ setScrollOffset(newScrollOffset);
+
+ // Trigger the autoscroll timer which will periodically call
+ // triggerAutoScrolling()
+ m_autoScrollTimer->start(RepeatingAutoScrollDelay);
+}
+
+void KItemListView::slotGeometryOfGroupHeaderParentChanged()
+{
+ KItemListWidget *widget = qobject_cast<KItemListWidget *>(sender());
+ Q_ASSERT(widget);
+ KItemListGroupHeader *groupHeader = m_visibleGroups.value(widget);
+ Q_ASSERT(groupHeader);
+ updateGroupHeaderLayout(widget);
+}
+
+void KItemListView::slotRoleEditingCanceled(int index, const QByteArray &role, const QVariant &value)
+{
+ disconnectRoleEditingSignals(index);
+
+ m_editingRole = false;
+ Q_EMIT roleEditingCanceled(index, role, value);
+}
+
+void KItemListView::slotRoleEditingFinished(int index, const QByteArray &role, const QVariant &value)
+{
+ disconnectRoleEditingSignals(index);
+
+ m_editingRole = false;
+ Q_EMIT roleEditingFinished(index, role, value);
+}
+
+void KItemListView::setController(KItemListController *controller)
+{
+ if (m_controller != controller) {
+ KItemListController *previous = m_controller;
+ if (previous) {
+ KItemListSelectionManager *selectionManager = previous->selectionManager();
+ disconnect(selectionManager, &KItemListSelectionManager::currentChanged, this, &KItemListView::slotCurrentChanged);
+ disconnect(selectionManager, &KItemListSelectionManager::selectionChanged, this, &KItemListView::slotSelectionChanged);
+ }
+
+ m_controller = controller;
+
+ if (controller) {
+ KItemListSelectionManager *selectionManager = controller->selectionManager();
+ connect(selectionManager, &KItemListSelectionManager::currentChanged, this, &KItemListView::slotCurrentChanged);
+ connect(selectionManager, &KItemListSelectionManager::selectionChanged, this, &KItemListView::slotSelectionChanged);
+ }
+
+ onControllerChanged(controller, previous);
+ }
+}
+
+void KItemListView::setModel(KItemModelBase *model)
+{
+ if (m_model == model) {
+ return;
+ }
+
+ KItemModelBase *previous = m_model;
+
+ if (m_model) {
+ disconnect(m_model, &KItemModelBase::itemsChanged, this, &KItemListView::slotItemsChanged);
+ disconnect(m_model, &KItemModelBase::itemsInserted, this, &KItemListView::slotItemsInserted);
+ disconnect(m_model, &KItemModelBase::itemsRemoved, this, &KItemListView::slotItemsRemoved);
+ disconnect(m_model, &KItemModelBase::itemsMoved, this, &KItemListView::slotItemsMoved);
+ disconnect(m_model, &KItemModelBase::groupsChanged, this, &KItemListView::slotGroupsChanged);
+ disconnect(m_model, &KItemModelBase::groupedSortingChanged, this, &KItemListView::slotGroupedSortingChanged);
+ disconnect(m_model, &KItemModelBase::sortOrderChanged, this, &KItemListView::slotSortOrderChanged);
+ disconnect(m_model, &KItemModelBase::sortRoleChanged, this, &KItemListView::slotSortRoleChanged);
+
+ m_sizeHintResolver->itemsRemoved(KItemRangeList() << KItemRange(0, m_model->count()));
+ }
+
+ m_model = model;
+ m_layouter->setModel(model);
+ m_grouped = model->groupedSorting();
+
+ if (m_model) {
+ connect(m_model, &KItemModelBase::itemsChanged, this, &KItemListView::slotItemsChanged);
+ connect(m_model, &KItemModelBase::itemsInserted, this, &KItemListView::slotItemsInserted);
+ connect(m_model, &KItemModelBase::itemsRemoved, this, &KItemListView::slotItemsRemoved);
+ connect(m_model, &KItemModelBase::itemsMoved, this, &KItemListView::slotItemsMoved);
+ connect(m_model, &KItemModelBase::groupsChanged, this, &KItemListView::slotGroupsChanged);
+ connect(m_model, &KItemModelBase::groupedSortingChanged, this, &KItemListView::slotGroupedSortingChanged);
+ connect(m_model, &KItemModelBase::sortOrderChanged, this, &KItemListView::slotSortOrderChanged);
+ connect(m_model, &KItemModelBase::sortRoleChanged, this, &KItemListView::slotSortRoleChanged);
+
+ const int itemCount = m_model->count();
+ if (itemCount > 0) {
+ slotItemsInserted(KItemRangeList() << KItemRange(0, itemCount));
+ }
+ }
+
+ onModelChanged(model, previous);
+}
+
+KItemListRubberBand *KItemListView::rubberBand() const
+{
+ return m_rubberBand;
+}
+
+void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int changedCount)
+{
+ if (m_activeTransactions > 0) {
+ if (hint == NoAnimation) {
+ // As soon as at least one property change should be done without animation,
+ // the whole transaction will be marked as not animated.
+ m_endTransactionAnimationHint = NoAnimation;
+ }
+ return;
+ }
+
+ if (!m_model || m_model->count() < 0) {
+ return;
+ }
+
+ int firstVisibleIndex = m_layouter->firstVisibleIndex();
+ if (firstVisibleIndex < 0) {
+ emitOffsetChanges();
+ return;
+ }
+
+ // Do a sanity check of the scroll-offset property: When properties of the itemlist-view have been changed
+ // it might be possible that the maximum offset got changed too. Assure that the full visible range
+ // is still shown if the maximum offset got decreased.
+ const qreal visibleOffsetRange = (scrollOrientation() == Qt::Horizontal) ? size().width() : size().height();
+ const qreal maxOffsetToShowFullRange = maximumScrollOffset() - visibleOffsetRange;
+ if (scrollOffset() > maxOffsetToShowFullRange) {
+ m_layouter->setScrollOffset(qMax(qreal(0), maxOffsetToShowFullRange));
+ firstVisibleIndex = m_layouter->firstVisibleIndex();
+ }
+
+ const int lastVisibleIndex = m_layouter->lastVisibleIndex();
+
+ int firstSibblingIndex = -1;
+ int lastSibblingIndex = -1;
+ const bool supportsExpanding = supportsItemExpanding();
+
+ QList<int> reusableItems = recycleInvisibleItems(firstVisibleIndex, lastVisibleIndex, hint);
+
+ // Assure that for each visible item a KItemListWidget is available. KItemListWidget
+ // instances from invisible items are reused. If no reusable items are
+ // found then new KItemListWidget instances get created.
+ const bool animate = (hint == Animation);
+ for (int i = firstVisibleIndex; i <= lastVisibleIndex; ++i) {
+ bool applyNewPos = true;
+
+ const QRectF itemBounds = m_layouter->itemRect(i);
+ const QPointF newPos = itemBounds.topLeft();
+ KItemListWidget *widget = m_visibleItems.value(i);
+ if (!widget) {
+ if (!reusableItems.isEmpty()) {
+ // Reuse a KItemListWidget instance from an invisible item
+ const int oldIndex = reusableItems.takeLast();
+ widget = m_visibleItems.value(oldIndex);
+ setWidgetIndex(widget, i);
+ updateWidgetProperties(widget, i);
+ initializeItemListWidget(widget);
+ } else {
+ // No reusable KItemListWidget instance is available, create a new one
+ widget = createWidget(i);
+ }
+ widget->resize(itemBounds.size());
+
+ if (animate && changedCount < 0) {
+ // Items have been deleted.
+ if (i >= changedIndex) {
+ // The item is located behind the removed range. Move the
+ // created item to the imaginary old position outside the
+ // view. It will get animated to the new position later.
+ const int previousIndex = i - changedCount;
+ const QRectF itemRect = m_layouter->itemRect(previousIndex);
+ if (itemRect.isEmpty()) {
+ const QPointF invisibleOldPos = (scrollOrientation() == Qt::Vertical) ? QPointF(0, size().height()) : QPointF(size().width(), 0);
+ widget->setPos(invisibleOldPos);
+ } else {
+ widget->setPos(itemRect.topLeft());
+ }
+ applyNewPos = false;
+ }
+ }
+
+ if (supportsExpanding && changedCount == 0) {
+ if (firstSibblingIndex < 0) {
+ firstSibblingIndex = i;
+ }
+ lastSibblingIndex = i;
+ }
+ }
+
+ if (animate) {
+ if (m_animation->isStarted(widget, KItemListViewAnimation::MovingAnimation)) {
+ m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos);
+ applyNewPos = false;
+ }
+
+ const bool itemsRemoved = (changedCount < 0);
+ const bool itemsInserted = (changedCount > 0);
+ if (itemsRemoved && (i >= changedIndex)) {
+ // The item is located after the removed items. Animate the moving of the position.
+ applyNewPos = !moveWidget(widget, newPos);
+ } else if (itemsInserted && i >= changedIndex) {
+ // The item is located after the first inserted item
+ if (i <= changedIndex + changedCount - 1) {
+ // The item is an inserted item. Animate the appearing of the item.
+ // For performance reasons no animation is done when changedCount is equal
+ // to all available items.
+ if (changedCount < m_model->count()) {
+ m_animation->start(widget, KItemListViewAnimation::CreateAnimation);
+ }
+ } else if (!m_animation->isStarted(widget, KItemListViewAnimation::CreateAnimation)) {
+ // The item was already there before, so animate the moving of the position.
+ // No moving animation is done if the item is animated by a create animation: This
+ // prevents a "move animation mess" when inserting several ranges in parallel.
+ applyNewPos = !moveWidget(widget, newPos);
+ }
+ }
+ } else {
+ m_animation->stop(widget);