+ 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;