+ 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 : qAsConst(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& current, 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& current, 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&) {