]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/kitemviews/kitemlistview.cpp
Merge remote-tracking branch 'fork/work/zakharafoniam/useful-groups'
[dolphin.git] / src / kitemviews / kitemlistview.cpp
index b7d4c247097577ea8759832e57138b1f26397d43..82325cb1978813f38747d2203f84189461932b66 100644 (file)
-/***************************************************************************
- *   Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com>             *
- *                                                                         *
- *   Based on the Itemviews NG project from Trolltech Labs:                *
- *   http://qt.gitorious.org/qt-labs/itemviews-ng                          *
- *                                                                         *
- *   This program is free software; you can redistribute it and/or modify  *
- *   it under the terms of the GNU General Public License as published by  *
- *   the Free Software Foundation; either version 2 of the License, or     *
- *   (at your option) any later version.                                   *
- *                                                                         *
- *   This program is distributed in the hope that it will be useful,       *
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
- *   GNU General Public License for more details.                          *
- *                                                                         *
- *   You should have received a copy of the GNU General Public License     *
- *   along with this program; if not, write to the                         *
- *   Free Software Foundation, Inc.,                                       *
- *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA            *
- ***************************************************************************/
+/*
+ * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com>
+ *
+ * Based on the Itemviews NG project from Trolltech Labs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
 
 #include "kitemlistview.h"
 
+#ifndef QT_NO_ACCESSIBILITY
+#include "accessibility/kitemlistcontaineraccessible.h"
+#include "accessibility/kitemlistdelegateaccessible.h"
+#include "accessibility/kitemlistviewaccessible.h"
+#endif
+#include "dolphindebug.h"
+#include "kitemlistcontainer.h"
 #include "kitemlistcontroller.h"
-#include "kitemlistheader_p.h"
-#include "kitemlistrubberband_p.h"
+#include "kitemlistheader.h"
 #include "kitemlistselectionmanager.h"
-#include "kitemlistsizehintresolver_p.h"
-#include "kitemlistviewlayouter_p.h"
-#include "kitemlistviewanimation_p.h"
-#include "kitemlistwidget.h"
+#include "kstandarditemlistwidget.h"
 
-#include <KDebug>
+#include "private/kitemlistheaderwidget.h"
+#include "private/kitemlistrubberband.h"
+#include "private/kitemlistsizehintresolver.h"
+#include "private/kitemlistviewlayouter.h"
 
-#include <QCursor>
+#include <optional>
+
+#include <QElapsedTimer>
 #include <QGraphicsSceneMouseEvent>
-#include <QPainter>
+#include <QGraphicsView>
 #include <QPropertyAnimation>
-#include <QStyle>
 #include <QStyleOptionRubberBand>
 #include <QTimer>
+#include <QVariantAnimation>
 
-namespace {
-    // Time in ms until reaching the autoscroll margin triggers
-    // an initial autoscrolling
-    const int InitialAutoScrollDelay = 700;
-
-    // Delay in ms for triggering the next autoscroll
-    const int RepeatingAutoScrollDelay = 1000 / 60;
-}
-
-KItemListView::KItemListView(QGraphicsWidget* parent) :
-    QGraphicsWidget(parent),
-    m_enabledSelectionToggles(false),
-    m_grouped(false),
-    m_activeTransactions(0),
-    m_endTransactionAnimationHint(Animation),
-    m_itemSize(),
-    m_controller(0),
-    m_model(0),
-    m_visibleRoles(),
-    m_visibleRolesSizes(),
-    m_stretchedVisibleRolesSizes(),
-    m_widgetCreator(0),
-    m_groupHeaderCreator(0),
-    m_styleOption(),
-    m_visibleItems(),
-    m_visibleGroups(),
-    m_sizeHintResolver(0),
-    m_layouter(0),
-    m_animation(0),
-    m_layoutTimer(0),
-    m_oldScrollOffset(0),
-    m_oldMaximumScrollOffset(0),
-    m_oldItemOffset(0),
-    m_oldMaximumItemOffset(0),
-    m_skipAutoScrollForRubberBand(false),
-    m_rubberBand(0),
-    m_mousePos(),
-    m_autoScrollIncrement(0),
-    m_autoScrollTimer(0),
-    m_header(0),
-    m_useHeaderWidths(false)
+namespace
 {
-    setAcceptHoverEvents(true);
+// Time in ms until reaching the autoscroll margin triggers
+// an initial autoscrolling
+const int InitialAutoScrollDelay = 700;
 
-    m_sizeHintResolver = new KItemListSizeHintResolver(this);
+// Delay in ms for triggering the next autoscroll
+const int RepeatingAutoScrollDelay = 1000 / 60;
 
-    m_layouter = new KItemListViewLayouter(this);
-    m_layouter->setSizeHintResolver(m_sizeHintResolver);
+// Copied from the Kirigami.Units.shortDuration
+const int RubberFadeSpeed = 150;
 
-    m_animation = new KItemListViewAnimation(this);
-    connect(m_animation, SIGNAL(finished(QGraphicsWidget*,KItemListViewAnimation::AnimationType)),
-            this, SLOT(slotAnimationFinished(QGraphicsWidget*,KItemListViewAnimation::AnimationType)));
-
-    m_layoutTimer = new QTimer(this);
-    m_layoutTimer->setInterval(300);
-    m_layoutTimer->setSingleShot(true);
-    connect(m_layoutTimer, SIGNAL(timeout()), this, SLOT(slotLayoutTimerFinished()));
-
-    m_rubberBand = new KItemListRubberBand(this);
-    connect(m_rubberBand, SIGNAL(activationChanged(bool)), this, SLOT(slotRubberBandActivationChanged(bool)));
+const char *RubberPropertyName = "_kitemviews_rubberBandPosition";
 }
 
-KItemListView::~KItemListView()
+#ifndef QT_NO_ACCESSIBILITY
+QAccessibleInterface *accessibleInterfaceFactory(const QString &key, QObject *object)
 {
-    delete m_sizeHintResolver;
-    m_sizeHintResolver = 0;
-}
+    Q_UNUSED(key)
 
-void KItemListView::setScrollOrientation(Qt::Orientation orientation)
+    if (KItemListContainer *container = qobject_cast<KItemListContainer *>(object)) {
+        if (auto controller = container->controller(); controller) {
+            if (KItemListView *view = controller->view(); view && view->accessibleParent()) {
+                return view->accessibleParent();
+            }
+        }
+        return new KItemListContainerAccessible(container);
+    } else if (KItemListView *view = qobject_cast<KItemListView *>(object)) {
+        return new KItemListViewAccessible(view, view->accessibleParent());
+    }
+
+    return nullptr;
+}
+#endif
+
+KItemListView::KItemListView(QGraphicsWidget *parent)
+    : QGraphicsWidget(parent)
+    , m_enabledSelectionToggles(false)
+    , m_grouped(false)
+    , m_highlightEntireRow(false)
+    , m_alternateBackgrounds(false)
+    , m_supportsItemExpanding(false)
+    , m_editingRole(false)
+    , m_activeTransactions(0)
+    , m_endTransactionAnimationHint(Animation)
+    , m_itemSize()
+    , m_controller(nullptr)
+    , m_model(nullptr)
+    , m_visibleRoles()
+    , m_widgetCreator(nullptr)
+    , m_groupHeaderCreator(nullptr)
+    , m_styleOption()
+    , m_visibleItems()
+    , m_visibleGroups()
+    , m_visibleCells()
+    , m_scrollBarExtent(0)
+    , m_layouter(nullptr)
+    , m_animation(nullptr)
+    , m_oldScrollOffset(0)
+    , m_oldMaximumScrollOffset(0)
+    , m_oldItemOffset(0)
+    , m_oldMaximumItemOffset(0)
+    , m_skipAutoScrollForRubberBand(false)
+    , m_rubberBand(nullptr)
+    , m_tapAndHoldIndicator(nullptr)
+    , m_mousePos()
+    , m_autoScrollIncrement(0)
+    , m_autoScrollTimer(nullptr)
+    , m_header(nullptr)
+    , m_headerWidget(nullptr)
+    , m_indicatorAnimation(nullptr)
+    , m_statusBarOffset(0)
+    , m_dropIndicator()
+    , m_sizeHintResolver(nullptr)
 {
-    const Qt::Orientation previousOrientation = m_layouter->scrollOrientation();
-    if (orientation == previousOrientation) {
-        return;
-    }
+    setAcceptHoverEvents(true);
+    setAcceptTouchEvents(true);
 
-    m_layouter->setScrollOrientation(orientation);
-    m_animation->setScrollOrientation(orientation);
-    m_sizeHintResolver->clearCache();
+    m_sizeHintResolver = new KItemListSizeHintResolver(this);
 
-    if (m_grouped) {
-        QMutableHashIterator<KItemListWidget*, KItemListGroupHeader*> it (m_visibleGroups);
-        while (it.hasNext()) {
-            it.next();
-            it.value()->setScrollOrientation(orientation);
-        }
-        updateGroupHeaderHeight();
+    m_layouter = new KItemListViewLayouter(m_sizeHintResolver, this);
 
-    }
+    m_animation = new KItemListViewAnimation(this);
+    connect(m_animation, &KItemListViewAnimation::finished, this, &KItemListView::slotAnimationFinished);
 
-    doLayout(NoAnimation);
+    m_rubberBand = new KItemListRubberBand(this);
+    connect(m_rubberBand, &KItemListRubberBand::activationChanged, this, &KItemListView::slotRubberBandActivationChanged);
+
+    m_tapAndHoldIndicator = new KItemListRubberBand(this);
+    m_indicatorAnimation = new QPropertyAnimation(m_tapAndHoldIndicator, "endPosition", this);
+    connect(m_tapAndHoldIndicator, &KItemListRubberBand::activationChanged, this, [this](bool active) {
+        if (active) {
+            m_indicatorAnimation->setDuration(150);
+            m_indicatorAnimation->setStartValue(QPointF(1, 1));
+            m_indicatorAnimation->setEndValue(QPointF(40, 40));
+            m_indicatorAnimation->start();
+        }
+        update();
+    });
+    connect(m_tapAndHoldIndicator, &KItemListRubberBand::endPositionChanged, this, [this]() {
+        if (m_tapAndHoldIndicator->isActive()) {
+            update();
+        }
+    });
 
-    onScrollOrientationChanged(orientation, previousOrientation);
-    emit scrollOrientationChanged(orientation, previousOrientation);
-}
+    m_headerWidget = new KItemListHeaderWidget(this);
+    m_headerWidget->setVisible(false);
 
-Qt::Orientation KItemListView::scrollOrientation() const
-{
-    return m_layouter->scrollOrientation();
+    m_header = new KItemListHeader(this);
+
+#ifndef QT_NO_ACCESSIBILITY
+    QAccessible::installFactory(accessibleInterfaceFactory);
+#endif
 }
 
-void KItemListView::setItemSize(const QSizeF& itemSize)
+KItemListView::~KItemListView()
 {
-    const QSizeF previousSize = m_itemSize;
-    if (itemSize == previousSize) {
-        return;
-    }
+    // The group headers are children of the widgets created by
+    // widgetCreator(). So it is mandatory to delete the group headers
+    // first.
+    delete m_groupHeaderCreator;
+    m_groupHeaderCreator = nullptr;
 
-    // Skip animations when the number of rows or columns
-    // are changed in the grid layout. Although the animation
-    // engine can handle this usecase, it looks obtrusive.
-    const bool animate = !changesItemGridLayout(m_layouter->size(),
-                                                itemSize,
-                                                m_layouter->itemMargin());
-    
-    m_itemSize = itemSize;
-
-    if (itemSize.isEmpty()) {
-        updateVisibleRolesSizes();
-    } else {
-        m_layouter->setItemSize(itemSize);
-    }
-
-    m_sizeHintResolver->clearCache();
-    doLayout(animate ? Animation : NoAnimation);
-    onItemSizeChanged(itemSize, previousSize);
-}
+    delete m_widgetCreator;
+    m_widgetCreator = nullptr;
 
-QSizeF KItemListView::itemSize() const
-{
-    return m_itemSize;
+    delete m_sizeHintResolver;
+    m_sizeHintResolver = nullptr;
 }
 
 void KItemListView::setScrollOffset(qreal offset)
@@ -201,7 +193,7 @@ qreal KItemListView::scrollOffset() const
 
 qreal KItemListView::maximumScrollOffset() const
 {
-    return m_layouter->maximumScrollOffset();
+    return m_layouter->maximumScrollOffset() + m_statusBarOffset;
 }
 
 void KItemListView::setItemOffset(qreal offset)
@@ -211,8 +203,8 @@ void KItemListView::setItemOffset(qreal offset)
     }
 
     m_layouter->setItemOffset(offset);
-    if (m_header) {
-        m_header->setPos(-offset, 0);
+    if (m_headerWidget->isVisible()) {
+        m_headerWidget->setOffset(offset);
     }
 
     // Don't check whether the m_layoutTimer is active: Changing the
@@ -231,32 +223,51 @@ qreal KItemListView::maximumItemOffset() const
     return m_layouter->maximumItemOffset();
 }
 
-void KItemListView::setVisibleRoles(const QList<QByteArray>& roles)
+int KItemListView::maximumVisibleItems() const
+{
+    return m_layouter->maximumVisibleItems();
+}
+
+void KItemListView::setVisibleRoles(const QList<QByteArray> &roles)
 {
     const QList<QByteArray> previousRoles = m_visibleRoles;
     m_visibleRoles = roles;
-
-    QHashIterator<int, KItemListWidget*> it(m_visibleItems);
-    while (it.hasNext()) {
-        it.next();
-        KItemListWidget* widget = it.value();
-        widget->setVisibleRoles(roles);
-        widget->setVisibleRolesSizes(m_stretchedVisibleRolesSizes);
-    }
+    onVisibleRolesChanged(roles, previousRoles);
 
     m_sizeHintResolver->clearCache();
     m_layouter->markAsDirty();
 
-    if (m_header) {
-        m_header->setVisibleRoles(roles);
-        m_header->setVisibleRolesWidths(headerRolesWidths());
-        m_useHeaderWidths = false;
+    if (m_itemSize.isEmpty()) {
+        m_headerWidget->setColumns(roles);
+        updatePreferredColumnWidths();
+        if (!m_headerWidget->automaticColumnResizing()) {
+            // The column-width of new roles are still 0. Apply the preferred
+            // column-width as default with.
+            for (const QByteArray &role : std::as_const(m_visibleRoles)) {
+                if (m_headerWidget->columnWidth(role) == 0) {
+                    const qreal width = m_headerWidget->preferredColumnWidth(role);
+                    m_headerWidget->setColumnWidth(role, width);
+                }
+            }
+
+            applyColumnWidthsFromHeader();
+        }
     }
 
-    updateVisibleRolesSizes();
-    doLayout(NoAnimation);
+    const bool alternateBackgroundsChanged =
+        m_itemSize.isEmpty() && ((roles.count() > 1 && previousRoles.count() <= 1) || (roles.count() <= 1 && previousRoles.count() > 1));
 
-    onVisibleRolesChanged(roles, previousRoles);
+    QHashIterator<int, KItemListWidget *> it(m_visibleItems);
+    while (it.hasNext()) {
+        it.next();
+        KItemListWidget *widget = it.value();
+        widget->setVisibleRoles(roles);
+        if (alternateBackgroundsChanged) {
+            updateAlternateBackgroundForWidget(widget);
+        }
+    }
+
+    doLayout(NoAnimation);
 }
 
 QList<QByteArray> KItemListView::visibleRoles() const
@@ -269,18 +280,17 @@ void KItemListView::setAutoScroll(bool enabled)
     if (enabled && !m_autoScrollTimer) {
         m_autoScrollTimer = new QTimer(this);
         m_autoScrollTimer->setSingleShot(true);
-        connect(m_autoScrollTimer, SIGNAL(timeout()), this, SLOT(triggerAutoScrolling()));
+        connect(m_autoScrollTimer, &QTimer::timeout, this, &KItemListView::triggerAutoScrolling);
         m_autoScrollTimer->start(InitialAutoScrollDelay);
     } else if (!enabled && m_autoScrollTimer) {
         delete m_autoScrollTimer;
-        m_autoScrollTimer = 0;
+        m_autoScrollTimer = nullptr;
     }
-
 }
 
 bool KItemListView::autoScroll() const
 {
-    return m_autoScrollTimer != 0;
+    return m_autoScrollTimer != nullptr;
 }
 
 void KItemListView::setEnabledSelectionToggles(bool enabled)
@@ -288,7 +298,7 @@ void KItemListView::setEnabledSelectionToggles(bool enabled)
     if (m_enabledSelectionToggles != enabled) {
         m_enabledSelectionToggles = enabled;
 
-        QHashIterator<int, KItemListWidget*> it(m_visibleItems);
+        QHashIterator<int, KItemListWidget *> it(m_visibleItems);
         while (it.hasNext()) {
             it.next();
             it.value()->setEnabledSelectionToggle(enabled);
@@ -301,75 +311,68 @@ bool KItemListView::enabledSelectionToggles() const
     return m_enabledSelectionToggles;
 }
 
-KItemListControllerKItemListView::controller() const
+KItemListController *KItemListView::controller() const
 {
     return m_controller;
 }
 
-KItemModelBaseKItemListView::model() const
+KItemModelBase *KItemListView::model() const
 {
     return m_model;
 }
 
-void KItemListView::setWidgetCreator(KItemListWidgetCreatorBasewidgetCreator)
+void KItemListView::setWidgetCreator(KItemListWidgetCreatorBase *widgetCreator)
 {
+    delete m_widgetCreator;
     m_widgetCreator = widgetCreator;
 }
 
-KItemListWidgetCreatorBaseKItemListView::widgetCreator() const
+KItemListWidgetCreatorBase *KItemListView::widgetCreator() const
 {
+    if (!m_widgetCreator) {
+        m_widgetCreator = defaultWidgetCreator();
+    }
     return m_widgetCreator;
 }
 
-void KItemListView::setGroupHeaderCreator(KItemListGroupHeaderCreatorBasegroupHeaderCreator)
+void KItemListView::setGroupHeaderCreator(KItemListGroupHeaderCreatorBase *groupHeaderCreator)
 {
+    delete m_groupHeaderCreator;
     m_groupHeaderCreator = groupHeaderCreator;
 }
 
-KItemListGroupHeaderCreatorBaseKItemListView::groupHeaderCreator() const
+KItemListGroupHeaderCreatorBase *KItemListView::groupHeaderCreator() const
 {
+    if (!m_groupHeaderCreator) {
+        m_groupHeaderCreator = defaultGroupHeaderCreator();
+    }
     return m_groupHeaderCreator;
 }
 
-void KItemListView::setStyleOption(const KItemListStyleOption& option)
+#ifndef QT_NO_ACCESSIBILITY
+void KItemListView::setAccessibleParentsObject(KItemListContainer *accessibleParentsObject)
 {
-    const KItemListStyleOption previousOption = m_styleOption;
-    m_styleOption = option;
-
-    bool animate = true;    
-    const QSizeF margin(option.horizontalMargin, option.verticalMargin);
-    if (margin != m_layouter->itemMargin()) {
-        // Skip animations when the number of rows or columns
-        // are changed in the grid layout. Although the animation
-        // engine can handle this usecase, it looks obtrusive.
-        animate = !changesItemGridLayout(m_layouter->size(),
-                                         m_layouter->itemSize(),
-                                         margin);
-        m_layouter->setItemMargin(margin);
-    }
-
-    if (m_grouped) {
-        updateGroupHeaderHeight();
-    }
-
-    QHashIterator<int, KItemListWidget*> it(m_visibleItems);
-    while (it.hasNext()) {
-        it.next();
-        it.value()->setStyleOption(option);
-    }
-
-    m_sizeHintResolver->clearCache();   
-    doLayout(animate ? Animation : NoAnimation);
+    Q_ASSERT(!m_accessibleParent);
+    m_accessibleParent = new KItemListContainerAccessible(accessibleParentsObject);
+}
+KItemListContainerAccessible *KItemListView::accessibleParent()
+{
+    Q_CHECK_PTR(m_accessibleParent); // We always want the accessibility tree/hierarchy to be complete.
+    return m_accessibleParent;
+}
+#endif
 
-    onStyleOptionChanged(option, previousOption);
+QSizeF KItemListView::itemSize() const
+{
+    return m_itemSize;
 }
 
-const KItemListStyleOptionKItemListView::styleOption() const
+const KItemListStyleOption &KItemListView::styleOption() const
 {
     return m_styleOption;
 }
 
-void KItemListView::setGeometry(const QRectFrect)
+void KItemListView::setGeometry(const QRectF &rect)
 {
     QGraphicsWidget::setGeometry(rect);
 
@@ -379,82 +382,64 @@ void KItemListView::setGeometry(const QRectF& rect)
 
     const QSizeF newSize = rect.size();
     if (m_itemSize.isEmpty()) {
-        // The item size is dynamic:
-        // Changing the geometry does not require to do an expensive
-        // update of the visible-roles sizes, only the stretched sizes
-        // need to be adjusted to the new size.
-        updateStretchedVisibleRolesSizes();
-        
-        if (m_useHeaderWidths) {
-            QSizeF dynamicItemSize = m_layouter->itemSize();
-    
-            if (m_itemSize.width() < 0) {
-                const qreal requiredWidth = visibleRolesSizesWidthSum();
-                if (newSize.width() > requiredWidth) {
-                    dynamicItemSize.setWidth(newSize.width());
-                }
-                const qreal headerWidth = qMax(newSize.width(), requiredWidth);
-                m_header->resize(headerWidth, m_header->size().height());
-            }
-    
-            if (m_itemSize.height() < 0) {
-                const qreal requiredHeight = visibleRolesSizesHeightSum();
-                if (newSize.height() > requiredHeight) {
-                    dynamicItemSize.setHeight(newSize.height());
-                }
-                // TODO: KItemListHeader is not prepared for vertical alignment
-            }
-    
-            m_layouter->setItemSize(dynamicItemSize);
-        }
-        
-        // Triggering a synchronous layout is fine from a performance point of view,
-        // as with dynamic item sizes no moving animation must be done.
-        m_layouter->setSize(newSize);
-        doLayout(Animation);
-    } else {
-        const bool animate = !changesItemGridLayout(newSize,
-                                                    m_layouter->itemSize(),
-                                                    m_layouter->itemMargin());        
-        m_layouter->setSize(newSize);
-        
-        if (animate) {
-            // Trigger an asynchronous relayout with m_layoutTimer to prevent
-            // performance bottlenecks. If the timer is exceeded, an animated layout
-            // will be triggered.
-            if (!m_layoutTimer->isActive()) {
-                m_layoutTimer->start();
-            }
+        m_headerWidget->resize(rect.width(), m_headerWidget->size().height());
+        if (m_headerWidget->automaticColumnResizing()) {
+            applyAutomaticColumnWidths();
         } else {
-            m_layoutTimer->stop();
-            doLayout(NoAnimation);
+            const qreal requiredWidth = m_headerWidget->leftPadding() + columnWidthsSum() + m_headerWidget->rightPadding();
+            const QSizeF dynamicItemSize(qMax(newSize.width(), requiredWidth), m_itemSize.height());
+            m_layouter->setItemSize(dynamicItemSize);
         }
     }
+
+    m_layouter->setSize(newSize);
+    // We don't animate the moving of the items here because
+    // it would look like the items are slow to find their position.
+    doLayout(NoAnimation);
+}
+
+qreal KItemListView::scrollSingleStep() const
+{
+    const QFontMetrics metrics(font());
+    return metrics.height();
+}
+
+qreal KItemListView::verticalPageStep() const
+{
+    qreal headerHeight = 0;
+    if (m_headerWidget->isVisible()) {
+        headerHeight = m_headerWidget->size().height();
+    }
+    return size().height() - headerHeight;
 }
 
-int KItemListView::itemAt(const QPointF& pos) const
+std::optional<int> KItemListView::itemAt(const QPointF &pos) const
 {
-    QHashIterator<int, KItemListWidget*> it(m_visibleItems);
+    if (headerBoundaries().contains(pos)) {
+        return std::nullopt;
+    }
+
+    QHashIterator<int, KItemListWidget *> it(m_visibleItems);
     while (it.hasNext()) {
         it.next();
 
-        const KItemListWidgetwidget = it.value();
+        const KItemListWidget *widget = it.value();
         const QPointF mappedPos = widget->mapFromItem(this, pos);
         if (widget->contains(mappedPos)) {
             return it.key();
         }
     }
 
-    return -1;
+    return std::nullopt;
 }
 
-bool KItemListView::isAboveSelectionToggle(int index, const QPointFpos) const
+bool KItemListView::isAboveSelectionToggle(int index, const QPointF &pos) const
 {
     if (!m_enabledSelectionToggles) {
         return false;
     }
 
-    const KItemListWidgetwidget = m_visibleItems.value(index);
+    const KItemListWidget *widget = m_visibleItems.value(index);
     if (widget) {
         const QRectF selectionToggleRect = widget->selectionToggleRect();
         if (!selectionToggleRect.isEmpty()) {
@@ -465,9 +450,9 @@ bool KItemListView::isAboveSelectionToggle(int index, const QPointF& pos) const
     return false;
 }
 
-bool KItemListView::isAboveExpansionToggle(int index, const QPointFpos) const
+bool KItemListView::isAboveExpansionToggle(int index, const QPointF &pos) const
 {
-    const KItemListWidgetwidget = m_visibleItems.value(index);
+    const KItemListWidget *widget = m_visibleItems.value(index);
     if (widget) {
         const QRectF expansionToggleRect = widget->expansionToggleRect();
         if (!expansionToggleRect.isEmpty()) {
@@ -478,6 +463,19 @@ bool KItemListView::isAboveExpansionToggle(int index, const QPointF& pos) const
     return false;
 }
 
+bool KItemListView::isAboveText(int index, const QPointF &pos) const
+{
+    const KItemListWidget *widget = m_visibleItems.value(index);
+    if (widget) {
+        const QRectF &textRect = widget->textRect();
+        if (!textRect.isEmpty()) {
+            const QPointF mappedPos = widget->mapFromItem(this, pos);
+            return textRect.contains(mappedPos);
+        }
+    }
+    return false;
+}
+
 int KItemListView::firstVisibleIndex() const
 {
     return m_layouter->firstVisibleIndex();
@@ -488,21 +486,49 @@ int KItemListView::lastVisibleIndex() const
     return m_layouter->lastVisibleIndex();
 }
 
-QSizeF KItemListView::itemSizeHint(int index) const
+void KItemListView::calculateItemSizeHints(QVector<std::pair<qreal, bool>> &logicalHeightHints, qreal &logicalWidthHint) const
 {
-    Q_UNUSED(index);
-    return itemSize();
+    widgetCreator()->calculateItemSizeHints(logicalHeightHints, logicalWidthHint, this);
 }
 
-QHash<QByteArray, QSizeF> KItemListView::visibleRolesSizes(const KItemRangeList& itemRanges) const
+void KItemListView::setSupportsItemExpanding(bool supportsExpanding)
 {
-    Q_UNUSED(itemRanges);
-    return QHash<QByteArray, QSizeF>();
+    if (m_supportsItemExpanding != supportsExpanding) {
+        m_supportsItemExpanding = supportsExpanding;
+        updateSiblingsInformation();
+        onSupportsItemExpandingChanged(supportsExpanding);
+    }
 }
 
 bool KItemListView::supportsItemExpanding() const
 {
-    return false;
+    return m_supportsItemExpanding;
+}
+
+void KItemListView::setHighlightEntireRow(bool highlightEntireRow)
+{
+    if (m_highlightEntireRow != highlightEntireRow) {
+        m_highlightEntireRow = highlightEntireRow;
+        onHighlightEntireRowChanged(highlightEntireRow);
+    }
+}
+
+bool KItemListView::highlightEntireRow() const
+{
+    return m_highlightEntireRow;
+}
+
+void KItemListView::setAlternateBackgrounds(bool alternate)
+{
+    if (m_alternateBackgrounds != alternate) {
+        m_alternateBackgrounds = alternate;
+        updateAlternateBackgrounds();
+    }
+}
+
+bool KItemListView::alternateBackgrounds() const
+{
+    return m_alternateBackgrounds;
 }
 
 QRectF KItemListView::itemRect(int index) const
@@ -514,44 +540,118 @@ QRectF KItemListView::itemContextRect(int index) const
 {
     QRectF contextRect;
 
-    const KItemListWidgetwidget = m_visibleItems.value(index);
+    const KItemListWidget *widget = m_visibleItems.value(index);
     if (widget) {
-        contextRect = widget->iconRect() | widget->textRect();
+        contextRect = widget->selectionRectCore();
         contextRect.translate(itemRect(index).topLeft());
     }
 
     return contextRect;
 }
 
-void KItemListView::scrollToItem(int index)
+bool KItemListView::isElided(int index) const
+{
+    return m_sizeHintResolver->isElided(index);
+}
+
+void KItemListView::scrollToItem(int index, ViewItemPosition viewItemPosition)
 {
     QRectF viewGeometry = geometry();
-    if (m_header) {
-        const qreal headerHeight = m_header->size().height();
+    if (m_headerWidget->isVisible()) {
+        const qreal headerHeight = m_headerWidget->size().height();
         viewGeometry.adjust(0, headerHeight, 0, 0);
     }
-    const QRectF currentRect = itemRect(index);
+    if (m_statusBarOffset != 0) {
+        viewGeometry.adjust(0, 0, 0, -m_statusBarOffset);
+    }
+    QRectF currentRect = itemRect(index);
 
-    if (!viewGeometry.contains(currentRect)) {
-        qreal newOffset = scrollOffset();
-        if (scrollOrientation() == Qt::Vertical) {
-            if (currentRect.top() < viewGeometry.top()) {
-                newOffset += currentRect.top() - viewGeometry.top();
-            } else if (currentRect.bottom() > viewGeometry.bottom()) {
-                newOffset += currentRect.bottom() - viewGeometry.bottom();
+    if (layoutDirection() == Qt::RightToLeft && scrollOrientation() == Qt::Horizontal) {
+        currentRect.moveLeft(m_layouter->size().width() - currentRect.right());
+    }
+
+    // Fix for Bug 311099 - View the underscore when using Ctrl + PageDown
+    currentRect.adjust(-m_styleOption.horizontalMargin, -m_styleOption.verticalMargin, m_styleOption.horizontalMargin, m_styleOption.verticalMargin);
+
+    qreal offset = 0;
+    switch (scrollOrientation()) {
+    case Qt::Vertical:
+        if (currentRect.top() < viewGeometry.top() || currentRect.bottom() > viewGeometry.bottom()) {
+            switch (viewItemPosition) {
+            case Beginning:
+                offset = currentRect.top() - viewGeometry.top();
+                break;
+            case Middle:
+                offset = 0.5 * (currentRect.top() + currentRect.bottom() - (viewGeometry.top() + viewGeometry.bottom()));
+                break;
+            case End:
+                offset = currentRect.bottom() - viewGeometry.bottom();
+                break;
+            case Nearest:
+                if (currentRect.top() < viewGeometry.top()) {
+                    offset = currentRect.top() - viewGeometry.top();
+                }
+                if (currentRect.bottom() > viewGeometry.bottom() + offset) {
+                    offset += currentRect.bottom() - viewGeometry.bottom() - offset;
+                }
+                break;
+            default:
+                Q_UNREACHABLE();
             }
-        } else {
-            if (currentRect.left() < viewGeometry.left()) {
-                newOffset += currentRect.left() - viewGeometry.left();
-            } else if (currentRect.right() > viewGeometry.right()) {
-                newOffset += currentRect.right() - viewGeometry.right();
+        }
+        break;
+    case Qt::Horizontal:
+        if (currentRect.left() < viewGeometry.left() || currentRect.right() > viewGeometry.right()) {
+            switch (viewItemPosition) {
+            case Beginning:
+                if (layoutDirection() == Qt::RightToLeft) {
+                    offset = currentRect.right() - viewGeometry.right();
+                } else {
+                    offset = currentRect.left() - viewGeometry.left();
+                }
+                break;
+            case Middle:
+                offset = 0.5 * (currentRect.left() + currentRect.right() - (viewGeometry.left() + viewGeometry.right()));
+                break;
+            case End:
+                if (layoutDirection() == Qt::RightToLeft) {
+                    offset = currentRect.left() - viewGeometry.left();
+                } else {
+                    offset = currentRect.right() - viewGeometry.right();
+                }
+                break;
+            case Nearest:
+                if (layoutDirection() == Qt::RightToLeft) {
+                    if (currentRect.left() < viewGeometry.left()) {
+                        offset = currentRect.left() - viewGeometry.left();
+                    }
+                    if (currentRect.right() > viewGeometry.right() + offset) {
+                        offset += currentRect.right() - viewGeometry.right() - offset;
+                    }
+                } else {
+                    if (currentRect.right() > viewGeometry.right()) {
+                        offset = currentRect.right() - viewGeometry.right();
+                    }
+                    if (currentRect.left() < viewGeometry.left() + offset) {
+                        offset += currentRect.left() - viewGeometry.left() - offset;
+                    }
+                }
+                break;
+            default:
+                Q_UNREACHABLE();
             }
         }
+        break;
+    default:
+        Q_UNREACHABLE();
+    }
 
-        if (newOffset != scrollOffset()) {
-            emit scrollTo(newOffset);
-        }
+    if (!qFuzzyIsNull(offset)) {
+        Q_EMIT scrollTo(scrollOffset() + offset);
+        return;
     }
+
+    Q_EMIT scrollingStopped();
 }
 
 void KItemListView::beginTransaction()
@@ -567,7 +667,7 @@ void KItemListView::endTransaction()
     --m_activeTransactions;
     if (m_activeTransactions < 0) {
         m_activeTransactions = 0;
-        kWarning() << "Mismatch between beginTransaction()/endTransaction()";
+        qCWarning(DolphinDebug) << "Mismatch between beginTransaction()/endTransaction()";
     }
 
     if (m_activeTransactions == 0) {
@@ -582,53 +682,122 @@ bool KItemListView::isTransactionActive() const
     return m_activeTransactions > 0;
 }
 
-void KItemListView::setHeaderShown(bool show)
-{
-
-    if (show && !m_header) {
-        m_header = new KItemListHeader(this);
-        m_header->setPos(0, 0);
-        m_header->setModel(m_model);
-        m_header->setVisibleRoles(m_visibleRoles);
-        m_header->setVisibleRolesWidths(headerRolesWidths());
-        m_header->setZValue(1);
-
-        connect(m_header, SIGNAL(visibleRoleWidthChanged(QByteArray,qreal,qreal)),
-                this, SLOT(slotVisibleRoleWidthChanged(QByteArray,qreal,qreal)));
-        connect(m_header, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)),
-                this, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)));
-        connect(m_header, SIGNAL(sortRoleChanged(QByteArray,QByteArray)),
-                this, SIGNAL(sortRoleChanged(QByteArray,QByteArray)));
+void KItemListView::setHeaderVisible(bool visible)
+{
+    if (visible && !m_headerWidget->isVisible()) {
+        QStyleOptionHeader option;
+        const QSize headerSize = style()->sizeFromContents(QStyle::CT_HeaderSection, &option, QSize());
+
+        m_headerWidget->setPos(0, 0);
+        m_headerWidget->resize(size().width(), headerSize.height());
+        m_headerWidget->setModel(m_model);
+        m_headerWidget->setColumns(m_visibleRoles);
+        m_headerWidget->setZValue(1);
+
+        connect(m_headerWidget, &KItemListHeaderWidget::columnWidthChanged, this, &KItemListView::slotHeaderColumnWidthChanged);
+        connect(m_headerWidget, &KItemListHeaderWidget::sidePaddingChanged, this, &KItemListView::slotSidePaddingChanged);
+        connect(m_headerWidget, &KItemListHeaderWidget::columnMoved, this, &KItemListView::slotHeaderColumnMoved);
+        connect(m_headerWidget, &KItemListHeaderWidget::sortOrderChanged, this, &KItemListView::sortOrderChanged);
+        connect(m_headerWidget, &KItemListHeaderWidget::sortRoleChanged, this, &KItemListView::sortRoleChanged);
+        connect(m_headerWidget, &KItemListHeaderWidget::columnHovered, this, &KItemListView::columnHovered);
+        connect(m_headerWidget, &KItemListHeaderWidget::columnUnHovered, this, &KItemListView::columnUnHovered);
+
+        m_layouter->setHeaderHeight(headerSize.height());
+        m_headerWidget->setVisible(true);
+    } else if (!visible && m_headerWidget->isVisible()) {
+        disconnect(m_headerWidget, &KItemListHeaderWidget::columnWidthChanged, this, &KItemListView::slotHeaderColumnWidthChanged);
+        disconnect(m_headerWidget, &KItemListHeaderWidget::sidePaddingChanged, this, &KItemListView::slotSidePaddingChanged);
+        disconnect(m_headerWidget, &KItemListHeaderWidget::columnMoved, this, &KItemListView::slotHeaderColumnMoved);
+        disconnect(m_headerWidget, &KItemListHeaderWidget::sortOrderChanged, this, &KItemListView::sortOrderChanged);
+        disconnect(m_headerWidget, &KItemListHeaderWidget::sortRoleChanged, this, &KItemListView::sortRoleChanged);
+        disconnect(m_headerWidget, &KItemListHeaderWidget::columnHovered, this, &KItemListView::columnHovered);
+        disconnect(m_headerWidget, &KItemListHeaderWidget::columnUnHovered, this, &KItemListView::columnUnHovered);
 
-        m_useHeaderWidths = false;
-
-        m_layouter->setHeaderHeight(m_header->size().height());
-    } else if (!show && m_header) {
-        delete m_header;
-        m_header = 0;
-        m_useHeaderWidths = false;
         m_layouter->setHeaderHeight(0);
+        m_headerWidget->setVisible(false);
     }
 }
 
-bool KItemListView::isHeaderShown() const
+bool KItemListView::isHeaderVisible() const
 {
-    return m_header != 0;
+    return m_headerWidget->isVisible();
 }
 
-QPixmap KItemListView::createDragPixmap(const QSet<int>& indexes) const
+KItemListHeader *KItemListView::header() const
 {
-    Q_UNUSED(indexes);
-    return QPixmap();
+    return m_header;
 }
 
-void KItemListView::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
+QPixmap KItemListView::createDragPixmap(const KItemSet &indexes) const
+{
+    QPixmap pixmap;
+
+    if (indexes.count() == 1) {
+        KItemListWidget *item = m_visibleItems.value(indexes.first());
+        QGraphicsView *graphicsView = scene()->views()[0];
+        if (item && graphicsView) {
+            pixmap = item->createDragPixmap(nullptr, graphicsView);
+        }
+    } else {
+        // TODO: Not implemented yet. Probably extend the interface
+        // from KItemListWidget::createDragPixmap() to return a pixmap
+        // that can be used for multiple indexes.
+    }
+
+    return pixmap;
+}
+
+void KItemListView::editRole(int index, const QByteArray &role)
+{
+    KStandardItemListWidget *widget = qobject_cast<KStandardItemListWidget *>(m_visibleItems.value(index));
+    if (!widget) {
+        return;
+    }
+    if (m_editingRole || m_animation->isStarted(widget)) {
+        Q_EMIT widget->roleEditingCanceled(index, role, QVariant());
+        return;
+    }
+
+    m_editingRole = true;
+    m_controller->selectionManager()->setCurrentItem(index);
+    widget->setEditedRole(role);
+
+    connect(widget, &KItemListWidget::roleEditingCanceled, this, &KItemListView::slotRoleEditingCanceled);
+    connect(widget, &KItemListWidget::roleEditingFinished, this, &KItemListView::slotRoleEditingFinished);
+
+    connect(this, &KItemListView::scrollOffsetChanged, widget, &KStandardItemListWidget::finishRoleEditing);
+}
+
+void KItemListView::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
 {
     QGraphicsWidget::paint(painter, option, widget);
 
+    for (auto animation : std::as_const(m_rubberBandAnimations)) {
+        QRectF rubberBandRect = animation->property(RubberPropertyName).toRectF();
+
+        const QPointF topLeft = rubberBandRect.topLeft();
+        if (scrollOrientation() == Qt::Vertical) {
+            rubberBandRect.moveTo(topLeft.x(), topLeft.y() - scrollOffset());
+        } else {
+            rubberBandRect.moveTo(topLeft.x() - scrollOffset(), topLeft.y());
+        }
+
+        QStyleOptionRubberBand opt;
+        initStyleOption(&opt);
+        opt.shape = QRubberBand::Rectangle;
+        opt.opaque = false;
+        opt.rect = rubberBandRect.toRect();
+
+        painter->save();
+
+        painter->setOpacity(animation->currentValue().toReal());
+        style()->drawControl(QStyle::CE_RubberBand, &opt, painter);
+
+        painter->restore();
+    }
+
     if (m_rubberBand->isActive()) {
-        QRectF rubberBandRect = QRectF(m_rubberBand->startPosition(),
-                                       m_rubberBand->endPosition()).normalized();
+        QRectF rubberBandRect = QRectF(m_rubberBand->startPosition(), m_rubberBand->endPosition()).normalized();
 
         const QPointF topLeft = rubberBandRect.topLeft();
         if (scrollOrientation() == Qt::Vertical) {
@@ -638,65 +807,252 @@ void KItemListView::paint(QPainter* painter, const QStyleOptionGraphicsItem* opt
         }
 
         QStyleOptionRubberBand opt;
-        opt.initFrom(widget);
+        initStyleOption(&opt);
         opt.shape = QRubberBand::Rectangle;
         opt.opaque = false;
         opt.rect = rubberBandRect.toRect();
         style()->drawControl(QStyle::CE_RubberBand, &opt, painter);
     }
+
+    if (m_tapAndHoldIndicator->isActive()) {
+        const QPointF indicatorSize = m_tapAndHoldIndicator->endPosition();
+        const QRectF rubberBandRect =
+            QRectF(m_tapAndHoldIndicator->startPosition() - indicatorSize, (m_tapAndHoldIndicator->startPosition()) + indicatorSize).normalized();
+        QStyleOptionRubberBand opt;
+        initStyleOption(&opt);
+        opt.shape = QRubberBand::Rectangle;
+        opt.opaque = false;
+        opt.rect = rubberBandRect.toRect();
+        style()->drawControl(QStyle::CE_RubberBand, &opt, painter);
+    }
+
+    if (!m_dropIndicator.isEmpty()) {
+        const QRectF r = m_dropIndicator.toRect();
+
+        QColor color = palette().brush(QPalette::Normal, QPalette::Text).color();
+        painter->setPen(color);
+
+        // TODO: The following implementation works only for a vertical scroll-orientation
+        // and assumes a height of the m_draggingInsertIndicator of 1.
+        Q_ASSERT(r.height() == 1);
+        painter->drawLine(r.left() + 1, r.top(), r.right() - 1, r.top());
+
+        color.setAlpha(128);
+        painter->setPen(color);
+        painter->drawRect(r.left(), r.top() - 1, r.width() - 1, 2);
+    }
 }
 
-void KItemListView::initializeItemListWidget(KItemListWidget* item)
+void KItemListView::setStatusBarOffset(int offset)
 {
-    Q_UNUSED(item);
+    if (m_statusBarOffset != offset) {
+        m_statusBarOffset = offset;
+        if (m_layouter) {
+            m_layouter->setStatusBarOffset(offset);
+        }
+    }
+}
+
+QVariant KItemListView::itemChange(GraphicsItemChange change, const QVariant &value)
+{
+    if (change == QGraphicsItem::ItemSceneHasChanged && scene()) {
+        if (!scene()->views().isEmpty()) {
+            m_styleOption.palette = scene()->views().at(0)->palette();
+        }
+    }
+    return QGraphicsItem::itemChange(change, value);
+}
+
+void KItemListView::setItemSize(const QSizeF &size)
+{
+    const QSizeF previousSize = m_itemSize;
+    if (size == previousSize) {
+        return;
+    }
+
+    // Skip animations when the number of rows or columns
+    // are changed in the grid layout. Although the animation
+    // engine can handle this usecase, it looks obtrusive.
+    const bool animate = !changesItemGridLayout(m_layouter->size(), size, m_layouter->itemMargin());
+
+    const bool alternateBackgroundsChanged = m_alternateBackgrounds && ((m_itemSize.isEmpty() && !size.isEmpty()) || (!m_itemSize.isEmpty() && size.isEmpty()));
+
+    m_itemSize = size;
+
+    if (alternateBackgroundsChanged) {
+        // For an empty item size alternate backgrounds are drawn if more than
+        // one role is shown. Assure that the backgrounds for visible items are
+        // updated when changing the size in this context.
+        updateAlternateBackgrounds();
+    }
+
+    if (size.isEmpty()) {
+        if (m_headerWidget->automaticColumnResizing()) {
+            updatePreferredColumnWidths();
+        } else {
+            // Only apply the changed height and respect the header widths
+            // set by the user
+            const qreal currentWidth = m_layouter->itemSize().width();
+            const QSizeF newSize(currentWidth, size.height());
+            m_layouter->setItemSize(newSize);
+        }
+    } else {
+        m_layouter->setItemSize(size);
+    }
+
+    m_sizeHintResolver->clearCache();
+    doLayout(animate ? Animation : NoAnimation);
+    onItemSizeChanged(size, previousSize);
 }
 
-bool KItemListView::itemSizeHintUpdateRequired(const QSet<QByteArray>& changedRoles) const
+void KItemListView::setStyleOption(const KItemListStyleOption &option)
 {
-    Q_UNUSED(changedRoles);
+    if (m_styleOption == option) {
+        return;
+    }
+
+    const KItemListStyleOption previousOption = m_styleOption;
+    m_styleOption = option;
+
+    bool animate = true;
+    const QSizeF margin(option.horizontalMargin, option.verticalMargin);
+    if (margin != m_layouter->itemMargin()) {
+        // Skip animations when the number of rows or columns
+        // are changed in the grid layout. Although the animation
+        // engine can handle this usecase, it looks obtrusive.
+        animate = !changesItemGridLayout(m_layouter->size(), m_layouter->itemSize(), margin);
+        m_layouter->setItemMargin(margin);
+    }
+
+    if (m_grouped) {
+        updateGroupHeaderHeight();
+    }
+
+    if (animate && (previousOption.maxTextLines != option.maxTextLines || previousOption.maxTextWidth != option.maxTextWidth)) {
+        // Animating a change of the maximum text size just results in expensive
+        // temporary eliding and clipping operations and does not look good visually.
+        animate = false;
+    }
+
+    QHashIterator<int, KItemListWidget *> it(m_visibleItems);
+    while (it.hasNext()) {
+        it.next();
+        it.value()->setStyleOption(option);
+    }
+
+    m_sizeHintResolver->clearCache();
+    m_layouter->markAsDirty();
+    doLayout(animate ? Animation : NoAnimation);
+
+    if (m_itemSize.isEmpty()) {
+        updatePreferredColumnWidths();
+    }
+
+    onStyleOptionChanged(option, previousOption);
+}
+
+void KItemListView::setScrollOrientation(Qt::Orientation orientation)
+{
+    const Qt::Orientation previousOrientation = m_layouter->scrollOrientation();
+    if (orientation == previousOrientation) {
+        return;
+    }
+
+    m_layouter->setScrollOrientation(orientation);
+    m_animation->setScrollOrientation(orientation);
+    m_sizeHintResolver->clearCache();
+
+    if (m_grouped) {
+        QMutableHashIterator<KItemListWidget *, KItemListGroupHeader *> it(m_visibleGroups);
+        while (it.hasNext()) {
+            it.next();
+            it.value()->setScrollOrientation(orientation);
+        }
+        updateGroupHeaderHeight();
+    }
+
+    doLayout(NoAnimation);
+
+    onScrollOrientationChanged(orientation, previousOrientation);
+    Q_EMIT scrollOrientationChanged(orientation, previousOrientation);
+}
+
+Qt::Orientation KItemListView::scrollOrientation() const
+{
+    return m_layouter->scrollOrientation();
+}
+
+KItemListWidgetCreatorBase *KItemListView::defaultWidgetCreator() const
+{
+    return nullptr;
+}
+
+KItemListGroupHeaderCreatorBase *KItemListView::defaultGroupHeaderCreator() const
+{
+    return nullptr;
+}
+
+void KItemListView::initializeItemListWidget(KItemListWidget *item)
+{
+    Q_UNUSED(item)
+}
+
+bool KItemListView::itemSizeHintUpdateRequired(const QSet<QByteArray> &changedRoles) const
+{
+    Q_UNUSED(changedRoles)
     return true;
 }
 
-void KItemListView::onControllerChanged(KItemListController* current, KItemListController* previous)
+void KItemListView::onControllerChanged(KItemListController *current, KItemListController *previous)
 {
-    Q_UNUSED(current);
-    Q_UNUSED(previous);
+    Q_UNUSED(current)
+    Q_UNUSED(previous)
 }
 
-void KItemListView::onModelChanged(KItemModelBase* current, KItemModelBase* previous)
+void KItemListView::onModelChanged(KItemModelBase *current, KItemModelBase *previous)
 {
-    Q_UNUSED(current);
-    Q_UNUSED(previous);
+    Q_UNUSED(current)
+    Q_UNUSED(previous)
 }
 
 void KItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous)
 {
-    Q_UNUSED(current);
-    Q_UNUSED(previous);
+    Q_UNUSED(current)
+    Q_UNUSED(previous)
 }
 
-void KItemListView::onItemSizeChanged(const QSizeF& current, const QSizeF& previous)
+void KItemListView::onItemSizeChanged(const QSizeF &current, const QSizeF &previous)
 {
-    Q_UNUSED(current);
-    Q_UNUSED(previous);
+    Q_UNUSED(current)
+    Q_UNUSED(previous)
 }
 
 void KItemListView::onScrollOffsetChanged(qreal current, qreal previous)
 {
-    Q_UNUSED(current);
-    Q_UNUSED(previous);
+    Q_UNUSED(current)
+    Q_UNUSED(previous)
+}
+
+void KItemListView::onVisibleRolesChanged(const QList<QByteArray> &current, const QList<QByteArray> &previous)
+{
+    Q_UNUSED(current)
+    Q_UNUSED(previous)
 }
 
-void KItemListView::onVisibleRolesChanged(const QList<QByteArray>& current, const QList<QByteArray>& previous)
+void KItemListView::onStyleOptionChanged(const KItemListStyleOption &current, const KItemListStyleOption &previous)
 {
-    Q_UNUSED(current);
-    Q_UNUSED(previous);
+    Q_UNUSED(current)
+    Q_UNUSED(previous)
 }
 
-void KItemListView::onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous)
+void KItemListView::onHighlightEntireRowChanged(bool highlightEntireRow)
 {
-    Q_UNUSED(current);
-    Q_UNUSED(previous);
+    Q_UNUSED(highlightEntireRow)
+}
+
+void KItemListView::onSupportsItemExpandingChanged(bool supportsExpanding)
+{
+    Q_UNUSED(supportsExpanding)
 }
 
 void KItemListView::onTransactionBegin()
@@ -707,23 +1063,47 @@ void KItemListView::onTransactionEnd()
 {
 }
 
-bool KItemListView::event(QEventevent)
+bool KItemListView::event(QEvent *event)
 {
-    // Forward all events to the controller and handle them there
-    if (m_controller && m_controller->processEvent(event, transform())) {
+    switch (event->type()) {
+    case QEvent::PaletteChange:
+        updatePalette();
+        break;
+
+    case QEvent::FontChange:
+        updateFont();
+        break;
+
+    case QEvent::FocusIn:
+        focusInEvent(static_cast<QFocusEvent *>(event));
+        event->accept();
+        return true;
+        break;
+
+    case QEvent::FocusOut:
+        focusOutEvent(static_cast<QFocusEvent *>(event));
         event->accept();
         return true;
+        break;
+
+    default:
+        // Forward all other events to the controller and handle them there
+        if (!m_editingRole && m_controller && m_controller->processEvent(event, transform())) {
+            event->accept();
+            return true;
+        }
     }
+
     return QGraphicsWidget::event(event);
 }
 
-void KItemListView::mousePressEvent(QGraphicsSceneMouseEventevent)
+void KItemListView::mousePressEvent(QGraphicsSceneMouseEvent *event)
 {
     m_mousePos = transform().map(event->pos());
     event->accept();
 }
 
-void KItemListView::mouseMoveEvent(QGraphicsSceneMouseEventevent)
+void KItemListView::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
 {
     QGraphicsWidget::mouseMoveEvent(event);
 
@@ -733,7 +1113,7 @@ void KItemListView::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
     }
 }
 
-void KItemListView::dragEnterEvent(QGraphicsSceneDragDropEventevent)
+void KItemListView::dragEnterEvent(QGraphicsSceneDragDropEvent *event)
 {
     event->setAccepted(true);
     setAutoScroll(true);
@@ -755,44 +1135,66 @@ void KItemListView::dragLeaveEvent(QGraphicsSceneDragDropEvent *event)
     setAutoScroll(false);
 }
 
-void KItemListView::dropEvent(QGraphicsSceneDragDropEventevent)
+void KItemListView::dropEvent(QGraphicsSceneDragDropEvent *event)
 {
     QGraphicsWidget::dropEvent(event);
     setAutoScroll(false);
 }
 
-QList<KItemListWidget*> KItemListView::visibleItemListWidgets() const
+QList<KItemListWidget *> KItemListView::visibleItemListWidgets() const
 {
     return m_visibleItems.values();
 }
 
-void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges)
+void KItemListView::updateFont()
 {
-    updateVisibleRolesSizes(itemRanges);
+    if (scene() && !scene()->views().isEmpty()) {
+        KItemListStyleOption option = styleOption();
+        option.font = scene()->views().first()->font();
+        option.fontMetrics = QFontMetrics(option.font);
+
+        setStyleOption(option);
+    }
+}
+
+void KItemListView::updatePalette()
+{
+    KItemListStyleOption option = styleOption();
+    option.palette = palette();
+    setStyleOption(option);
+}
+
+void KItemListView::slotItemsInserted(const KItemRangeList &itemRanges)
+{
+    if (m_itemSize.isEmpty()) {
+        updatePreferredColumnWidths(itemRanges);
+    }
 
     const bool hasMultipleRanges = (itemRanges.count() > 1);
     if (hasMultipleRanges) {
         beginTransaction();
     }
 
+    m_layouter->markAsDirty();
+
+    m_sizeHintResolver->itemsInserted(itemRanges);
+
     int previouslyInsertedCount = 0;
-    foreach (const KItemRange& range, itemRanges) {
+    for (const KItemRange &range : itemRanges) {
         // range.index is related to the model before anything has been inserted.
         // As in each loop the current item-range gets inserted the index must
         // be increased by the already previously inserted items.
         const int index = range.index + previouslyInsertedCount;
         const int count = range.count;
         if (index < 0 || count <= 0) {
-            kWarning() << "Invalid item range (index:" << index << ", count:" << count << ")";
+            qCWarning(DolphinDebug) << "Invalid item range (index:" << index << ", count:" << count << ")";
             continue;
         }
         previouslyInsertedCount += count;
 
-        m_sizeHintResolver->itemsInserted(index, count);
-
         // Determine which visible items must be moved
         QList<int> itemsToMove;
-        QHashIterator<int, KItemListWidget*> it(m_visibleItems);
+        QHashIterator<int, KItemListWidget *> it(m_visibleItems);
         while (it.hasNext()) {
             it.next();
             const int visibleItemIndex = it.key();
@@ -804,28 +1206,39 @@ void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges)
         // 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.
-        qSort(itemsToMove);
+        std::sort(itemsToMove.begin(), itemsToMove.end());
         for (int i = itemsToMove.count() - 1; i >= 0; --i) {
-            KItemListWidgetwidget = m_visibleItems.value(itemsToMove[i]);
+            KItemListWidget *widget = m_visibleItems.value(itemsToMove[i]);
             Q_ASSERT(widget);
-            setWidgetIndex(widget, widget->index() + count);
+            const int newIndex = widget->index() + count;
+            if (hasMultipleRanges) {
+                setWidgetIndex(widget, newIndex);
+            } else {
+                // Try to animate the moving of the item
+                moveWidgetToIndex(widget, newIndex);
+            }
         }
 
-        m_layouter->markAsDirty();
         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());
+            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;
+                    layouterSize.rwidth() -= scrollBarExtent + scrollbarSpacing;
                 } else {
-                    layouterSize.rheight() -= scrollBarExtent;
+                    layouterSize.rheight() -= scrollBarExtent + scrollbarSpacing;
                 }
                 m_layouter->setSize(layouterSize);
             }
@@ -844,38 +1257,64 @@ void KItemListView::slotItemsInserted(const KItemRangeList& 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 KItemRangeListitemRanges)
+void KItemListView::slotItemsRemoved(const KItemRangeList &itemRanges)
 {
-    updateVisibleRolesSizes();
+    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.at(i);
+        const KItemRange &range = itemRanges[i];
         const int index = range.index;
         const int count = range.count;
         if (index < 0 || count <= 0) {
-            kWarning() << "Invalid item range (index:" << index << ", count:" << count << ")";
+            qCWarning(DolphinDebug) << "Invalid item range (index:" << index << ", count:" << count << ")";
             continue;
         }
 
-        m_sizeHintResolver->itemsRemoved(index, count);
-
         const int firstRemovedIndex = index;
         const int lastRemovedIndex = index + count - 1;
-        const int lastIndex = m_model->count() + 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
-        for (int i = firstRemovedIndex; i <= lastRemovedIndex; ++i) {
-            KItemListWidget* widget = m_visibleItems.value(i);
-            if (!widget) {
+        // 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;
             }
 
@@ -903,16 +1342,21 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges)
         }
 
         // Update the indexes of all KItemListWidget instances that are located
-        // after the deleted items
-        for (int i = lastRemovedIndex + 1; i <= lastIndex; ++i) {
-            KItemListWidget* widget = m_visibleItems.value(i);
-            if (widget) {
-                const int newIndex = i - count;
+        // 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);
             }
         }
 
-        m_layouter->markAsDirty();
         if (!hasMultipleRanges) {
             // The decrease-layout-size optimization in KItemListView::slotItemsInserted()
             // assumes an updated geometry. If items are removed during an active transaction,
@@ -935,11 +1379,21 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges)
         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)
+void KItemListView::slotItemsMoved(const KItemRange &itemRange, const QList<int> &movedToIndexes)
 {
-    m_sizeHintResolver->itemsMoved(itemRange.index, itemRange.count);
+    m_sizeHintResolver->itemsMoved(itemRange, movedToIndexes);
     m_layouter->markAsDirty();
 
     if (m_controller) {
@@ -949,46 +1403,53 @@ void KItemListView::slotItemsMoved(const KItemRange& itemRange, const QList<int>
     const int firstVisibleMovedIndex = qMax(firstVisibleIndex(), itemRange.index);
     const int lastVisibleMovedIndex = qMin(lastVisibleIndex(), itemRange.index + itemRange.count - 1);
 
+    /// Represents an item that was moved while being edited.
+    struct MovedEditedItem {
+        int movedToIndex;
+        QByteArray editedRole;
+    };
+    std::optional<MovedEditedItem> movedEditedItem;
     for (int index = firstVisibleMovedIndex; index <= lastVisibleMovedIndex; ++index) {
-        KItemListWidgetwidget = m_visibleItems.value(index);
+        KItemListWidget *widget = m_visibleItems.value(index);
         if (widget) {
-            updateWidgetProperties(widget, index);
-            if (m_grouped) {
-                updateGroupHeaderForWidget(widget);
+            if (m_editingRole && !widget->editedRole().isEmpty()) {
+                movedEditedItem = {movedToIndexes[index - itemRange.index], widget->editedRole()};
+                disconnectRoleEditingSignals(index);
+                m_editingRole = false;
             }
+            updateWidgetProperties(widget, index);
             initializeItemListWidget(widget);
         }
     }
 
     doLayout(NoAnimation);
     updateSiblingsInformation();
+
+    if (movedEditedItem) {
+        editRole(movedEditedItem->movedToIndex, movedEditedItem->editedRole);
+    }
 }
 
-void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges,
-                                     const QSet<QByteArray>& roles)
+void KItemListView::slotItemsChanged(const KItemRangeList &itemRanges, const QSet<QByteArray> &roles)
 {
     const bool updateSizeHints = itemSizeHintUpdateRequired(roles);
-    if (updateSizeHints) {
-        updateVisibleRolesSizes(itemRanges);
+    if (updateSizeHints && m_itemSize.isEmpty()) {
+        updatePreferredColumnWidths(itemRanges);
     }
 
-    foreach (const KItemRange& itemRange, 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();
-
-            if (!m_layoutTimer->isActive()) {
-                m_layoutTimer->start();
-            }
         }
 
         // Apply the changed roles to the visible item-widgets
         const int lastIndex = index + count - 1;
         for (int i = index; i <= lastIndex; ++i) {
-            KItemListWidgetwidget = m_visibleItems.value(i);
+            KItemListWidget *widget = m_visibleItems.value(i);
             if (widget) {
                 widget->setData(m_model->data(i), roles);
             }
@@ -1001,6 +1462,14 @@ void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges,
             doLayout(NoAnimation);
         }
     }
+    doLayout(NoAnimation);
+}
+
+void KItemListView::slotGroupsChanged()
+{
+    updateVisibleGroupHeaders();
+    doLayout(NoAnimation);
+    updateSiblingsInformation();
 }
 
 void KItemListView::slotGroupedSortingChanged(bool current)
@@ -1011,8 +1480,10 @@ void KItemListView::slotGroupedSortingChanged(bool current)
     if (m_grouped) {
         updateGroupHeaderHeight();
     } else {
-        // Clear all visible headers
-        QMutableHashIterator<KItemListWidget*, KItemListGroupHeader*> it (m_visibleGroups);
+        // 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());
@@ -1020,67 +1491,112 @@ void KItemListView::slotGroupedSortingChanged(bool current)
         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);
+    Q_UNUSED(current)
+    Q_UNUSED(previous)
     if (m_grouped) {
         updateVisibleGroupHeaders();
         doLayout(NoAnimation);
     }
 }
 
-void KItemListView::slotSortRoleChanged(const QByteArray& current, const QByteArray& previous)
+void KItemListView::slotSortRoleChanged(const QByteArray &current, const QByteArray &previous)
 {
-    Q_UNUSED(current);
-    Q_UNUSED(previous);
+    Q_UNUSED(current)
+    Q_UNUSED(previous)
     if (m_grouped) {
         updateVisibleGroupHeaders();
         doLayout(NoAnimation);
     }
 }
 
-void KItemListView::slotCurrentChanged(int current, int previous)
+void KItemListView::slotGroupOrderChanged(Qt::SortOrder current, Qt::SortOrder previous)
 {
-    Q_UNUSED(previous);
-
-    KItemListWidget* previousWidget = m_visibleItems.value(previous, 0);
-    if (previousWidget) {
-        Q_ASSERT(previousWidget->isCurrent());
-        previousWidget->setCurrent(false);
+    Q_UNUSED(current)
+    Q_UNUSED(previous)
+    if (m_grouped) {
+        updateVisibleGroupHeaders();
+        doLayout(NoAnimation);
     }
+}
 
-    KItemListWidget* currentWidget = m_visibleItems.value(current, 0);
-    if (currentWidget) {
-        Q_ASSERT(!currentWidget->isCurrent());
-        currentWidget->setCurrent(true);
+void KItemListView::slotGroupRoleChanged(const QByteArray &current, const QByteArray &previous)
+{
+    Q_UNUSED(current)
+    Q_UNUSED(previous)
+    if (m_grouped) {
+        updateVisibleGroupHeaders();
+        doLayout(NoAnimation);
     }
 }
 
-void KItemListView::slotSelectionChanged(const QSet<int>& current, const QSet<int>& previous)
+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);
+        }
+    }
+#ifndef QT_NO_ACCESSIBILITY
+    if (current != previous && QAccessible::isActive()) {
+        static_cast<KItemListViewAccessible *>(QAccessible::queryAccessibleInterface(this))->announceCurrentItem();
+    }
+#endif
+}
 
-    QHashIterator<int, KItemListWidget*> it(m_visibleItems);
+void KItemListView::slotSelectionChanged(const KItemSet &current, const KItemSet &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));
+        KItemListWidget *widget = it.value();
+        const bool isSelected(current.contains(index));
+        widget->setSelected(isSelected);
+
+#ifndef QT_NO_ACCESSIBILITY
+        if (!QAccessible::isActive()) {
+            continue;
+        }
+        // Let the screen reader announce "selected" or "not selected" for the active item.
+        const bool wasSelected(previous.contains(index));
+        if (isSelected != wasSelected) {
+            QAccessibleEvent accessibleSelectionChangedEvent(this, QAccessible::SelectionAdd);
+            accessibleSelectionChangedEvent.setChild(index);
+            QAccessible::updateAccessibility(&accessibleSelectionChangedEvent);
+        }
+    }
+#else
     }
+    Q_UNUSED(previous)
+#endif
 }
 
-void KItemListView::slotAnimationFinished(QGraphicsWidget* widget,
-                                          KItemListViewAnimation::AnimationType type)
+void KItemListView::slotAnimationFinished(QGraphicsWidget *widget, KItemListViewAnimation::AnimationType type)
 {
-    KItemListWidget* itemListWidget = qobject_cast<KItemListWidget*>(widget);
+    KItemListWidget *itemListWidget = qobject_cast<KItemListWidget *>(widget);
     Q_ASSERT(itemListWidget);
 
-    switch (type) {
-    case KItemListViewAnimation::DeleteAnimation: {
+    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.
@@ -1090,32 +1606,16 @@ void KItemListView::slotAnimationFinished(QGraphicsWidget* widget,
         // by m_visibleWidgets and must be deleted manually after the animation has
         // been finished.
         recycleGroupHeaderForWidget(itemListWidget);
-        m_widgetCreator->recycle(itemListWidget);
-        break;
-    }
-
-    case KItemListViewAnimation::CreateAnimation:
-    case KItemListViewAnimation::MovingAnimation:
-    case KItemListViewAnimation::ResizeAnimation: {
+        widgetCreator()->recycle(itemListWidget);
+    } else {
         const int index = itemListWidget->index();
-        const bool invisible = (index < m_layouter->firstVisibleIndex()) ||
-                               (index > m_layouter->lastVisibleIndex());
+        const bool invisible = (index < m_layouter->firstVisibleIndex()) || (index > m_layouter->lastVisibleIndex());
         if (invisible && !m_animation->isStarted(itemListWidget)) {
             recycleWidget(itemListWidget);
         }
-        break;
-    }
-
-    default: break;
     }
 }
 
-void KItemListView::slotLayoutTimerFinished()
-{
-    m_layouter->setSize(geometry().size());
-    doLayout(Animation);
-}
-
 void KItemListView::slotRubberBandPosChanged()
 {
     update();
@@ -1124,52 +1624,75 @@ void KItemListView::slotRubberBandPosChanged()
 void KItemListView::slotRubberBandActivationChanged(bool active)
 {
     if (active) {
-        connect(m_rubberBand, SIGNAL(startPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandPosChanged()));
-        connect(m_rubberBand, SIGNAL(endPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandPosChanged()));
+        connect(m_rubberBand, &KItemListRubberBand::startPositionChanged, this, &KItemListView::slotRubberBandPosChanged);
+        connect(m_rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListView::slotRubberBandPosChanged);
         m_skipAutoScrollForRubberBand = true;
     } else {
-        disconnect(m_rubberBand, SIGNAL(startPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandPosChanged()));
-        disconnect(m_rubberBand, SIGNAL(endPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandPosChanged()));
+        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, [=, this](const QVariant &) {
+            update();
+        });
+        connect(animation, &QVariantAnimation::finished, this, [=, 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::slotVisibleRoleWidthChanged(const QByteArray& role,
-                                                qreal currentWidth,
-                                                qreal previousWidth)
+void KItemListView::slotHeaderColumnWidthChanged(const QByteArray &role, qreal currentWidth, qreal previousWidth)
 {
-    Q_UNUSED(previousWidth);
+    Q_UNUSED(role)
+    Q_UNUSED(currentWidth)
+    Q_UNUSED(previousWidth)
 
-    m_useHeaderWidths = true;
+    m_headerWidget->setAutomaticColumnResizing(false);
+    applyColumnWidthsFromHeader();
+    doLayout(NoAnimation);
+}
+
+void KItemListView::slotSidePaddingChanged(qreal width)
+{
+    Q_UNUSED(width)
+    if (m_headerWidget->automaticColumnResizing()) {
+        applyAutomaticColumnWidths();
+    }
+    applyColumnWidthsFromHeader();
+    doLayout(NoAnimation);
+}
 
-    if (m_visibleRolesSizes.contains(role)) {
-        QSizeF roleSize = m_visibleRolesSizes.value(role);
-        roleSize.setWidth(currentWidth);
-        m_visibleRolesSizes.insert(role, roleSize);
-        m_stretchedVisibleRolesSizes.insert(role, roleSize);
+void KItemListView::slotHeaderColumnMoved(const QByteArray &role, int currentIndex, int previousIndex)
+{
+    Q_ASSERT(m_visibleRoles[previousIndex] == role);
 
-        // Apply the new size to the layouter
-        QSizeF dynamicItemSize = m_itemSize;
-        if (dynamicItemSize.width() < 0) {
-            const qreal requiredWidth = visibleRolesSizesWidthSum();
-            dynamicItemSize.setWidth(qMax(size().width(), requiredWidth));
-        }
-        if (dynamicItemSize.height() < 0) {
-            const qreal requiredHeight = visibleRolesSizesHeightSum();
-            dynamicItemSize.setHeight(qMax(size().height(), requiredHeight));
-        }
+    const QList<QByteArray> previous = m_visibleRoles;
 
-        m_layouter->setItemSize(dynamicItemSize);
+    QList<QByteArray> current = m_visibleRoles;
+    current.removeAt(previousIndex);
+    current.insert(currentIndex, role);
 
-        // Update the role sizes for all visible widgets
-        foreach (KItemListWidget* widget, visibleItemListWidgets()) {
-            widget->setVisibleRolesSizes(m_stretchedVisibleRolesSizes);
-        }
+    setVisibleRoles(current);
 
-        doLayout(NoAnimation);
-    }
+    Q_EMIT visibleRolesChanged(current, previous);
 }
 
 void KItemListView::triggerAutoScrolling()
@@ -1207,9 +1730,8 @@ void KItemListView::triggerAutoScrolling()
         // 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();
+        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)
@@ -1226,102 +1748,111 @@ void KItemListView::triggerAutoScrolling()
     const qreal newScrollOffset = qMin(scrollOffset() + m_autoScrollIncrement, maxVisibleOffset);
     setScrollOffset(newScrollOffset);
 
-   // Trigger the autoscroll timer which will periodically call
-   // triggerAutoScrolling()
-   m_autoScrollTimer->start(RepeatingAutoScrollDelay);
+    // Trigger the autoscroll timer which will periodically call
+    // triggerAutoScrolling()
+    m_autoScrollTimer->start(RepeatingAutoScrollDelay);
 }
 
 void KItemListView::slotGeometryOfGroupHeaderParentChanged()
 {
-    KItemListWidget* widget = qobject_cast<KItemListWidget*>(sender());
+    KItemListWidget *widget = qobject_cast<KItemListWidget *>(sender());
     Q_ASSERT(widget);
-    KItemListGroupHeadergroupHeader = m_visibleGroups.value(widget);
+    KItemListGroupHeader *groupHeader = m_visibleGroups.value(widget);
     Q_ASSERT(groupHeader);
     updateGroupHeaderLayout(widget);
 }
 
-void KItemListView::setController(KItemListController* controller)
+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) {
-        KItemListControllerprevious = m_controller;
+        KItemListController *previous = m_controller;
         if (previous) {
-            KItemListSelectionManagerselectionManager = previous->selectionManager();
-            disconnect(selectionManager, SIGNAL(currentChanged(int,int)), this, SLOT(slotCurrentChanged(int,int)));
-            disconnect(selectionManager, SIGNAL(selectionChanged(QSet<int>,QSet<int>)), this, SLOT(slotSelectionChanged(QSet<int>,QSet<int>)));
+            KItemListSelectionManager *selectionManager = previous->selectionManager();
+            disconnect(selectionManager, &KItemListSelectionManager::currentChanged, this, &KItemListView::slotCurrentChanged);
+            disconnect(selectionManager, &KItemListSelectionManager::selectionChanged, this, &KItemListView::slotSelectionChanged);
         }
 
         m_controller = controller;
 
         if (controller) {
-            KItemListSelectionManagerselectionManager = controller->selectionManager();
-            connect(selectionManager, SIGNAL(currentChanged(int,int)), this, SLOT(slotCurrentChanged(int,int)));
-            connect(selectionManager, SIGNAL(selectionChanged(QSet<int>,QSet<int>)), this, SLOT(slotSelectionChanged(QSet<int>,QSet<int>)));
+            KItemListSelectionManager *selectionManager = controller->selectionManager();
+            connect(selectionManager, &KItemListSelectionManager::currentChanged, this, &KItemListView::slotCurrentChanged);
+            connect(selectionManager, &KItemListSelectionManager::selectionChanged, this, &KItemListView::slotSelectionChanged);
         }
 
         onControllerChanged(controller, previous);
     }
 }
 
-void KItemListView::setModel(KItemModelBasemodel)
+void KItemListView::setModel(KItemModelBase *model)
 {
     if (m_model == model) {
         return;
     }
 
-    KItemModelBaseprevious = m_model;
+    KItemModelBase *previous = m_model;
 
     if (m_model) {
-        disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
-                   this,    SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
-        disconnect(m_model, SIGNAL(itemsInserted(KItemRangeList)),
-                   this,    SLOT(slotItemsInserted(KItemRangeList)));
-        disconnect(m_model, SIGNAL(itemsRemoved(KItemRangeList)),
-                   this,    SLOT(slotItemsRemoved(KItemRangeList)));
-        disconnect(m_model, SIGNAL(itemsMoved(KItemRange,QList<int>)),
-                   this,    SLOT(slotItemsMoved(KItemRange,QList<int>)));
-        disconnect(m_model, SIGNAL(groupedSortingChanged(bool)),
-                   this,    SLOT(slotGroupedSortingChanged(bool)));
-        disconnect(m_model, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)),
-                   this,    SLOT(slotSortOrderChanged(Qt::SortOrder,Qt::SortOrder)));
-        disconnect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)),
-                   this,    SLOT(slotSortRoleChanged(QByteArray,QByteArray)));
+        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);
+        disconnect(m_model, &KItemModelBase::groupOrderChanged, this, &KItemListView::slotGroupOrderChanged);
+        disconnect(m_model, &KItemModelBase::groupRoleChanged, this, &KItemListView::slotGroupRoleChanged);
+
+        m_sizeHintResolver->itemsRemoved(KItemRangeList() << KItemRange(0, m_model->count()));
     }
 
     m_model = model;
-    m_layouter->setModel(model);   
+    m_layouter->setModel(model);
     m_grouped = model->groupedSorting();
 
     if (m_model) {
-        connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
-                this,    SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
-        connect(m_model, SIGNAL(itemsInserted(KItemRangeList)),
-                this,    SLOT(slotItemsInserted(KItemRangeList)));
-        connect(m_model, SIGNAL(itemsRemoved(KItemRangeList)),
-                this,    SLOT(slotItemsRemoved(KItemRangeList)));
-        connect(m_model, SIGNAL(itemsMoved(KItemRange,QList<int>)),
-                this,    SLOT(slotItemsMoved(KItemRange,QList<int>)));
-        connect(m_model, SIGNAL(groupedSortingChanged(bool)),
-                this,    SLOT(slotGroupedSortingChanged(bool)));
-        connect(m_model, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)),
-                this,    SLOT(slotSortOrderChanged(Qt::SortOrder,Qt::SortOrder)));
-        connect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)),
-                this,    SLOT(slotSortRoleChanged(QByteArray,QByteArray)));
+        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);
 }
 
-KItemListRubberBandKItemListView::rubberBand() const
+KItemListRubberBand *KItemListView::rubberBand() const
 {
     return m_rubberBand;
 }
 
 void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int changedCount)
 {
-    if (m_layoutTimer->isActive()) {
-        m_layoutTimer->stop();
-    }
-
     if (m_activeTransactions > 0) {
         if (hint == NoAnimation) {
             // As soon as at least one property change should be done without animation,
@@ -1365,22 +1896,18 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha
     const bool animate = (hint == Animation);
     for (int i = firstVisibleIndex; i <= lastVisibleIndex; ++i) {
         bool applyNewPos = true;
-        bool wasHidden = false;
 
         const QRectF itemBounds = m_layouter->itemRect(i);
         const QPointF newPos = itemBounds.topLeft();
-        KItemListWidgetwidget = m_visibleItems.value(i);
+        KItemListWidget *widget = m_visibleItems.value(i);
         if (!widget) {
-            wasHidden = true;
             if (!reusableItems.isEmpty()) {
                 // Reuse a KItemListWidget instance from an invisible item
                 const int oldIndex = reusableItems.takeLast();
                 widget = m_visibleItems.value(oldIndex);
                 setWidgetIndex(widget, i);
-
-                if (m_grouped) {
-                    updateGroupHeaderForWidget(widget);
-                }
+                updateWidgetProperties(widget, i);
+                initializeItemListWidget(widget);
             } else {
                 // No reusable KItemListWidget instance is available, create a new one
                 widget = createWidget(i);
@@ -1388,18 +1915,21 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha
             widget->resize(itemBounds.size());
 
             if (animate && changedCount < 0) {
-                // Items have been deleted, move the created item to the
-                // imaginary old position. They will get animated to the new position
-                // later.
-                const QRectF itemRect = m_layouter->itemRect(i - changedCount);
-                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());
+                // 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;
                 }
-                applyNewPos = false;
             }
 
             if (supportsExpanding && changedCount == 0) {
@@ -1411,11 +1941,19 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha
         }
 
         if (animate) {
+            if (m_animation->isStarted(widget, KItemListViewAnimation::MovingAnimation)) {
+                if (m_editingRole) {
+                    Q_EMIT widget->roleEditingCanceled(widget->index(), QByteArray(), QVariant());
+                }
+                m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos);
+                applyNewPos = false;
+            }
+
             const bool itemsRemoved = (changedCount < 0);
             const bool itemsInserted = (changedCount > 0);
-            if (itemsRemoved && (i >= changedIndex + changedCount + 1)) {
+            if (itemsRemoved && (i >= changedIndex)) {
                 // The item is located after the removed items. Animate the moving of the position.
-                applyNewPos = !moveWidget(widget, itemBounds);
+                applyNewPos = !moveWidget(widget, newPos);
             } else if (itemsInserted && i >= changedIndex) {
                 // The item is located after the first inserted item
                 if (i <= changedIndex + changedCount - 1) {
@@ -1429,11 +1967,8 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha
                     // 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, itemBounds);
+                    applyNewPos = !moveWidget(widget, newPos);
                 }
-            } else if (!itemsRemoved && !itemsInserted && !wasHidden) {
-                // The size of the view might have been changed. Animate the moving of the position.
-                applyNewPos = !moveWidget(widget, itemBounds);
             }
         } else {
             m_animation->stop(widget);
@@ -1446,6 +1981,8 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha
         Q_ASSERT(widget->index() == i);
         widget->setVisible(true);
 
+        bool animateIconResizing = animate;
+
         if (widget->size() != itemBounds.size()) {
             // Resize the widget for the item to the changed size.
             if (animate) {
@@ -1462,11 +1999,27 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha
             } else {
                 widget->resize(itemBounds.size());
             }
+        } else {
+            animateIconResizing = false;
+        }
+
+        const int newIconSize = widget->styleOption().iconSize;
+        if (widget->iconSize() != newIconSize) {
+            if (animateIconResizing) {
+                m_animation->start(widget, KItemListViewAnimation::IconResizeAnimation, newIconSize);
+            } else {
+                widget->setIconSize(newIconSize);
+            }
         }
+
+        // Updating the cell-information must be done as last step: The decision whether the
+        // moving-animation should be started at all is based on the previous cell-information.
+        const Cell cell(m_layouter->itemColumn(i), m_layouter->itemRow(i));
+        m_visibleCells.insert(i, cell);
     }
 
     // Delete invisible KItemListWidget instances that have not been reused
-    foreach (int index, reusableItems) {
+    for (int index : std::as_const(reusableItems)) {
         recycleWidget(m_visibleItems.value(index));
     }
 
@@ -1477,7 +2030,7 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha
 
     if (m_grouped) {
         // Update the layout of all visible group headers
-        QHashIterator<KItemListWidget*, KItemListGroupHeader*> it(m_visibleGroups);
+        QHashIterator<KItemListWidget *, KItemListGroupHeader *> it(m_visibleGroups);
         while (it.hasNext()) {
             it.next();
             updateGroupHeaderLayout(it.key());
@@ -1487,9 +2040,7 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha
     emitOffsetChanges();
 }
 
-QList<int> KItemListView::recycleInvisibleItems(int firstVisibleIndex,
-                                                int lastVisibleIndex,
-                                                LayoutAnimationHint hint)
+QList<int> KItemListView::recycleInvisibleItems(int firstVisibleIndex, int lastVisibleIndex, LayoutAnimationHint hint)
 {
     // Determine all items that are completely invisible and might be
     // reused for items that just got (at least partly) visible. If the
@@ -1499,11 +2050,11 @@ QList<int> KItemListView::recycleInvisibleItems(int firstVisibleIndex,
 
     QList<int> items;
 
-    QHashIterator<int, KItemListWidget*> it(m_visibleItems);
+    QHashIterator<int, KItemListWidget *> it(m_visibleItems);
     while (it.hasNext()) {
         it.next();
 
-        KItemListWidgetwidget = it.value();
+        KItemListWidget *widget = it.value();
         const int index = widget->index();
         const bool invisible = (index < firstVisibleIndex) || (index > lastVisibleIndex);
 
@@ -1517,7 +2068,7 @@ QList<int> KItemListView::recycleInvisibleItems(int firstVisibleIndex,
             } else {
                 widget->setVisible(false);
                 items.append(index);
-    
+
                 if (m_grouped) {
                     recycleGroupHeaderForWidget(widget);
                 }
@@ -1528,29 +2079,30 @@ QList<int> KItemListView::recycleInvisibleItems(int firstVisibleIndex,
     return items;
 }
 
-bool KItemListView::moveWidget(KItemListWidget* widget,const QRectF& itemBounds)
+bool KItemListView::moveWidget(KItemListWidget *widget, const QPointF &newPos)
 {
-    const QPointF oldPos = widget->pos();
-    const QPointF newPos = itemBounds.topLeft();
-    if (oldPos == newPos) {
+    if (widget->pos() == newPos) {
         return false;
     }
-    
-    bool startMovingAnim = m_itemSize.isEmpty() || widget->size() != itemBounds.size();
-    if (!startMovingAnim) {
+
+    bool startMovingAnim = false;
+
+    if (m_itemSize.isEmpty()) {
+        // The items are not aligned in a grid but either as columns or rows.
+        startMovingAnim = true;
+    } else {
         // When having a grid the moving-animation should only be started, if it is done within
         // one row in the vertical scroll-orientation or one column in the horizontal scroll-orientation.
         // Otherwise instead of a moving-animation a create-animation on the new position will be used
         // instead. This is done to prevent overlapping (and confusing) moving-animations.
-        const QSizeF itemMargin = m_layouter->itemMargin();
-        const qreal xMax = m_itemSize.width() + itemMargin.width();
-        const qreal yMax = m_itemSize.height() + itemMargin.height();
-        qreal xDiff = qAbs(oldPos.x() - newPos.x());
-        qreal yDiff = qAbs(oldPos.y() - newPos.y());
-        if (scrollOrientation() == Qt::Vertical) {
-            startMovingAnim = (xDiff > yDiff && yDiff < yMax);
-        } else {
-            startMovingAnim = (yDiff > xDiff && xDiff < xMax);
+        const int index = widget->index();
+        const Cell cell = m_visibleCells.value(index);
+        if (cell.column >= 0 && cell.row >= 0) {
+            if (scrollOrientation() == Qt::Vertical) {
+                startMovingAnim = (cell.row == m_layouter->itemRow(index));
+            } else {
+                startMovingAnim = (cell.column == m_layouter->itemColumn(index));
+            }
         }
     }
 
@@ -1568,92 +2120,122 @@ void KItemListView::emitOffsetChanges()
 {
     const qreal newScrollOffset = m_layouter->scrollOffset();
     if (m_oldScrollOffset != newScrollOffset) {
-        emit scrollOffsetChanged(newScrollOffset, m_oldScrollOffset);
+        Q_EMIT scrollOffsetChanged(newScrollOffset, m_oldScrollOffset);
         m_oldScrollOffset = newScrollOffset;
     }
 
     const qreal newMaximumScrollOffset = m_layouter->maximumScrollOffset();
     if (m_oldMaximumScrollOffset != newMaximumScrollOffset) {
-        emit maximumScrollOffsetChanged(newMaximumScrollOffset, m_oldMaximumScrollOffset);
+        Q_EMIT maximumScrollOffsetChanged(newMaximumScrollOffset, m_oldMaximumScrollOffset);
         m_oldMaximumScrollOffset = newMaximumScrollOffset;
     }
 
     const qreal newItemOffset = m_layouter->itemOffset();
     if (m_oldItemOffset != newItemOffset) {
-        emit itemOffsetChanged(newItemOffset, m_oldItemOffset);
+        Q_EMIT itemOffsetChanged(newItemOffset, m_oldItemOffset);
         m_oldItemOffset = newItemOffset;
     }
 
     const qreal newMaximumItemOffset = m_layouter->maximumItemOffset();
     if (m_oldMaximumItemOffset != newMaximumItemOffset) {
-        emit maximumItemOffsetChanged(newMaximumItemOffset, m_oldMaximumItemOffset);
+        Q_EMIT maximumItemOffsetChanged(newMaximumItemOffset, m_oldMaximumItemOffset);
         m_oldMaximumItemOffset = newMaximumItemOffset;
     }
 }
 
-KItemListWidgetKItemListView::createWidget(int index)
+KItemListWidget *KItemListView::createWidget(int index)
 {
-    KItemListWidget* widget = m_widgetCreator->create(this);
+    KItemListWidget *widget = widgetCreator()->create(this);
     widget->setFlag(QGraphicsItem::ItemStacksBehindParent);
 
-    updateWidgetProperties(widget, index);
     m_visibleItems.insert(index, widget);
-
-    if (m_grouped) {
-        updateGroupHeaderForWidget(widget);
-    }
-
+    m_visibleCells.insert(index, Cell());
+    updateWidgetProperties(widget, index);
     initializeItemListWidget(widget);
     return widget;
 }
 
-void KItemListView::recycleWidget(KItemListWidgetwidget)
+void KItemListView::recycleWidget(KItemListWidget *widget)
 {
     if (m_grouped) {
         recycleGroupHeaderForWidget(widget);
     }
 
-    m_visibleItems.remove(widget->index());
-    m_widgetCreator->recycle(widget);
+    const int index = widget->index();
+    m_visibleItems.remove(index);
+    m_visibleCells.remove(index);
+
+    widgetCreator()->recycle(widget);
 }
 
-void KItemListView::setWidgetIndex(KItemListWidgetwidget, int index)
+void KItemListView::setWidgetIndex(KItemListWidget *widget, int index)
 {
     const int oldIndex = widget->index();
     m_visibleItems.remove(oldIndex);
-    updateWidgetProperties(widget, index);
+    m_visibleCells.remove(oldIndex);
+
     m_visibleItems.insert(index, widget);
+    m_visibleCells.insert(index, Cell());
 
-    initializeItemListWidget(widget);
+    widget->setIndex(index);
+}
+
+void KItemListView::moveWidgetToIndex(KItemListWidget *widget, int index)
+{
+    const int oldIndex = widget->index();
+    const Cell oldCell = m_visibleCells.value(oldIndex);
+
+    setWidgetIndex(widget, index);
+
+    const Cell newCell(m_layouter->itemColumn(index), m_layouter->itemRow(index));
+    const bool vertical = (scrollOrientation() == Qt::Vertical);
+    const bool updateCell = (vertical && oldCell.row == newCell.row) || (!vertical && oldCell.column == newCell.column);
+    if (updateCell) {
+        m_visibleCells.insert(index, newCell);
+    }
 }
 
-void KItemListView::setLayouterSize(const QSizeFsize, SizeType sizeType)
+void KItemListView::setLayouterSize(const QSizeF &size, SizeType sizeType)
 {
     switch (sizeType) {
-    case LayouterSize: m_layouter->setSize(size); break;
-    case ItemSize: m_layouter->setItemSize(size); break;
-    default: break;
+    case LayouterSize:
+        m_layouter->setSize(size);
+        break;
+    case ItemSize:
+        m_layouter->setItemSize(size);
+        break;
+    default:
+        break;
     }
 }
 
-void KItemListView::updateWidgetProperties(KItemListWidgetwidget, int index)
+void KItemListView::updateWidgetProperties(KItemListWidget *widget, int index)
 {
     widget->setVisibleRoles(m_visibleRoles);
-    widget->setVisibleRolesSizes(m_stretchedVisibleRolesSizes);
+    updateWidgetColumnWidths(widget);
     widget->setStyleOption(m_styleOption);
 
-    const KItemListSelectionManager* selectionManager = m_controller->selectionManager();
-    widget->setCurrent(index == selectionManager->currentItem());
+    const KItemListSelectionManager *selectionManager = m_controller->selectionManager();
+
+    // 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) {
+        widget->setCurrent(index == selectionManager->currentItem());
+    }
     widget->setSelected(selectionManager->isSelected(index));
     widget->setHovered(false);
-    widget->setAlternatingBackgroundColors(false);
     widget->setEnabledSelectionToggle(enabledSelectionToggles());
     widget->setIndex(index);
     widget->setData(m_model->data(index));
     widget->setSiblingsInformation(QBitArray());
+    updateAlternateBackgroundForWidget(widget);
+
+    if (m_grouped) {
+        updateGroupHeaderForWidget(widget);
+    }
 }
 
-void KItemListView::updateGroupHeaderForWidget(KItemListWidgetwidget)
+void KItemListView::updateGroupHeaderForWidget(KItemListWidget *widget)
 {
     Q_ASSERT(m_grouped);
 
@@ -1665,36 +2247,24 @@ void KItemListView::updateGroupHeaderForWidget(KItemListWidget* widget)
         return;
     }
 
-    const QList<QPair<int, QVariant> > groups = model()->groups();
-    if (groups.isEmpty()) {
+    const QList<QPair<int, QVariant>> groups = model()->groups();
+    if (groups.isEmpty() || !groupHeaderCreator()) {
         return;
     }
 
-    KItemListGroupHeadergroupHeader = m_visibleGroups.value(widget);
+    KItemListGroupHeader *groupHeader = m_visibleGroups.value(widget);
     if (!groupHeader) {
-        groupHeader = m_groupHeaderCreator->create(this);
+        groupHeader = groupHeaderCreator()->create(this);
         groupHeader->setParentItem(widget);
         m_visibleGroups.insert(widget, groupHeader);
-        connect(widget, SIGNAL(geometryChanged()), this, SLOT(slotGeometryOfGroupHeaderParentChanged()));
+        connect(widget, &KItemListWidget::geometryChanged, this, &KItemListView::slotGeometryOfGroupHeaderParentChanged);
     }
     Q_ASSERT(groupHeader->parentItem() == widget);
 
-    // Determine the shown data for the header by doing a binary
-    // search in the groups-list
-    int min = 0;
-    int max = groups.count() - 1;
-    int mid = 0;
-    do {
-        mid = (min + max) / 2;
-        if (index > groups.at(mid).first) {
-            min = mid + 1;
-        } else {
-            max = mid - 1;
-        }
-    } while (groups.at(mid).first != index && min <= max);
-
-    groupHeader->setData(groups.at(mid).second);
-    groupHeader->setRole(model()->sortRole());
+    const int groupIndex = groupIndexForItem(index);
+    Q_ASSERT(groupIndex >= 0);
+    groupHeader->setData(groups.at(groupIndex).second);
+    groupHeader->setRole(model()->groupRole());
     groupHeader->setStyleOption(m_styleOption);
     groupHeader->setScrollOrientation(scrollOrientation());
     groupHeader->setItemIndex(index);
@@ -1702,9 +2272,9 @@ void KItemListView::updateGroupHeaderForWidget(KItemListWidget* widget)
     groupHeader->show();
 }
 
-void KItemListView::updateGroupHeaderLayout(KItemListWidgetwidget)
+void KItemListView::updateGroupHeaderLayout(KItemListWidget *widget)
 {
-    KItemListGroupHeadergroupHeader = m_visibleGroups.value(widget);
+    KItemListGroupHeader *groupHeader = m_visibleGroups.value(widget);
     Q_ASSERT(groupHeader);
 
     const int index = widget->index();
@@ -1717,22 +2287,24 @@ void KItemListView::updateGroupHeaderLayout(KItemListWidget* widget)
         // In the vertical scroll orientation the group header should always span
         // the whole width no matter which temporary position the parent widget
         // has. In this case the x-position and width will be adjusted manually.
-        groupHeader->setPos(-widget->x(), -groupHeaderRect.height());
-        groupHeader->resize(size().width(), groupHeaderRect.size().height());
+        const qreal x = -widget->x() - itemOffset();
+        const qreal width = maximumItemOffset();
+        groupHeader->setPos(x, -groupHeaderRect.height());
+        groupHeader->resize(width, groupHeaderRect.size().height());
     } else {
         groupHeader->setPos(groupHeaderRect.x() - itemRect.x(), -widget->y());
         groupHeader->resize(groupHeaderRect.size());
     }
 }
 
-void KItemListView::recycleGroupHeaderForWidget(KItemListWidgetwidget)
+void KItemListView::recycleGroupHeaderForWidget(KItemListWidget *widget)
 {
-    KItemListGroupHeaderheader = m_visibleGroups.value(widget);
+    KItemListGroupHeader *header = m_visibleGroups.value(widget);
     if (header) {
-        header->setParentItem(0);
-        m_groupHeaderCreator->recycle(header);
+        header->setParentItem(nullptr);
+        groupHeaderCreator()->recycle(header);
         m_visibleGroups.remove(widget);
-        disconnect(widget, SIGNAL(geometryChanged()), this, SLOT(slotGeometryOfGroupHeaderParentChanged()));
+        disconnect(widget, &KItemListWidget::geometryChanged, this, &KItemListView::slotGeometryOfGroupHeaderParentChanged);
     }
 }
 
@@ -1741,228 +2313,325 @@ void KItemListView::updateVisibleGroupHeaders()
     Q_ASSERT(m_grouped);
     m_layouter->markAsDirty();
 
-    QHashIterator<int, KItemListWidget*> it(m_visibleItems);
+    QHashIterator<int, KItemListWidget *> it(m_visibleItems);
     while (it.hasNext()) {
         it.next();
         updateGroupHeaderForWidget(it.value());
     }
 }
 
-QHash<QByteArray, qreal> KItemListView::headerRolesWidths() const
+int KItemListView::groupIndexForItem(int index) const
 {
-    QHash<QByteArray, qreal> rolesWidths;
+    Q_ASSERT(m_grouped);
+
+    const QList<QPair<int, QVariant>> groups = model()->groups();
+    if (groups.isEmpty()) {
+        return -1;
+    }
+
+    int min = 0;
+    int max = groups.count() - 1;
+    int mid = 0;
+    do {
+        mid = (min + max) / 2;
+        if (index > groups[mid].first) {
+            min = mid + 1;
+        } else {
+            max = mid - 1;
+        }
+    } while (groups[mid].first != index && min <= max);
+
+    if (min > max) {
+        while (groups[mid].first > index && mid > 0) {
+            --mid;
+        }
+    }
+
+    return mid;
+}
 
-    QHashIterator<QByteArray, QSizeF> it(m_stretchedVisibleRolesSizes);
+void KItemListView::updateAlternateBackgrounds()
+{
+    QHashIterator<int, KItemListWidget *> it(m_visibleItems);
     while (it.hasNext()) {
         it.next();
-        rolesWidths.insert(it.key(), it.value().width());
+        updateAlternateBackgroundForWidget(it.value());
+    }
+}
+
+void KItemListView::updateAlternateBackgroundForWidget(KItemListWidget *widget)
+{
+    bool enabled = useAlternateBackgrounds();
+    if (enabled) {
+        const int index = widget->index();
+        enabled = (index & 0x1) > 0;
+        if (m_grouped) {
+            const int groupIndex = groupIndexForItem(index);
+            if (groupIndex >= 0) {
+                const QList<QPair<int, QVariant>> groups = model()->groups();
+                const int indexOfFirstGroupItem = groups[groupIndex].first;
+                const int relativeIndex = index - indexOfFirstGroupItem;
+                enabled = (relativeIndex & 0x1) > 0;
+            }
+        }
     }
+    widget->setAlternateBackground(enabled);
+}
 
-    return rolesWidths;
+bool KItemListView::useAlternateBackgrounds() const
+{
+    return m_alternateBackgrounds && m_itemSize.isEmpty();
 }
 
-void KItemListView::updateVisibleRolesSizes(const KItemRangeList& itemRanges)
+QHash<QByteArray, qreal> KItemListView::preferredColumnWidths(const KItemRangeList &itemRanges) const
 {
-    if (!m_itemSize.isEmpty() || m_useHeaderWidths) {
-        return;
+    QElapsedTimer timer;
+    timer.start();
+
+    QHash<QByteArray, qreal> widths;
+
+    // Calculate the minimum width for each column that is required
+    // to show the headline unclipped.
+    const QFontMetricsF fontMetrics(m_headerWidget->font());
+    const int gripMargin = m_headerWidget->style()->pixelMetric(QStyle::PM_HeaderGripMargin);
+    const int headerMargin = m_headerWidget->style()->pixelMetric(QStyle::PM_HeaderMargin);
+    for (const QByteArray &visibleRole : std::as_const(m_visibleRoles)) {
+        const QString headerText = m_model->roleDescription(visibleRole);
+        const qreal headerWidth = fontMetrics.horizontalAdvance(headerText) + gripMargin + headerMargin * 2;
+        widths.insert(visibleRole, headerWidth);
+    }
+
+    // Calculate the preferred column widths for each item and ignore values
+    // smaller than the width for showing the headline unclipped.
+    const KItemListWidgetCreatorBase *creator = widgetCreator();
+    int calculatedItemCount = 0;
+    bool maxTimeExceeded = false;
+    for (const KItemRange &itemRange : itemRanges) {
+        const int startIndex = itemRange.index;
+        const int endIndex = startIndex + itemRange.count - 1;
+
+        for (int i = startIndex; i <= endIndex; ++i) {
+            for (const QByteArray &visibleRole : std::as_const(m_visibleRoles)) {
+                qreal maxWidth = widths.value(visibleRole, 0);
+                const qreal width = creator->preferredRoleColumnWidth(visibleRole, i, this);
+                maxWidth = qMax(width, maxWidth);
+                widths.insert(visibleRole, maxWidth);
+            }
+
+            if (calculatedItemCount > 100 && timer.elapsed() > 200) {
+                // When having several thousands of items calculating the sizes can get
+                // very expensive. We accept a possibly too small role-size in favour
+                // of having no blocking user interface.
+                maxTimeExceeded = true;
+                break;
+            }
+            ++calculatedItemCount;
+        }
+        if (maxTimeExceeded) {
+            break;
+        }
+    }
+
+    return widths;
+}
+
+void KItemListView::applyColumnWidthsFromHeader()
+{
+    // Apply the new size to the layouter
+    const qreal requiredWidth = m_headerWidget->leftPadding() + columnWidthsSum() + m_headerWidget->rightPadding();
+    const QSizeF dynamicItemSize(qMax(size().width(), requiredWidth), m_itemSize.height());
+    m_layouter->setItemSize(dynamicItemSize);
+
+    // Update the role sizes for all visible widgets
+    QHashIterator<int, KItemListWidget *> it(m_visibleItems);
+    while (it.hasNext()) {
+        it.next();
+        updateWidgetColumnWidths(it.value());
+    }
+}
+
+void KItemListView::updateWidgetColumnWidths(KItemListWidget *widget)
+{
+    for (const QByteArray &role : std::as_const(m_visibleRoles)) {
+        widget->setColumnWidth(role, m_headerWidget->columnWidth(role));
     }
+    widget->setSidePadding(m_headerWidget->leftPadding(), m_headerWidget->rightPadding());
+}
 
+void KItemListView::updatePreferredColumnWidths(const KItemRangeList &itemRanges)
+{
+    Q_ASSERT(m_itemSize.isEmpty());
     const int itemCount = m_model->count();
     int rangesItemCount = 0;
-    foreach (const KItemRange& range, itemRanges) {
+    for (const KItemRange &range : itemRanges) {
         rangesItemCount += range.count;
     }
 
     if (itemCount == rangesItemCount) {
-        m_visibleRolesSizes = visibleRolesSizes(itemRanges);
-        if (m_header) {
-            // Assure the the sizes are not smaller than the minimum defined by the header
-            // TODO: Currently only implemented for a top-aligned header
-            const qreal minHeaderRoleWidth = m_header->minimumRoleWidth();
-            QMutableHashIterator<QByteArray, QSizeF> it (m_visibleRolesSizes);
-            while (it.hasNext()) {
-                it.next();
-                const QSizeF& size = it.value();
-                if (size.width() < minHeaderRoleWidth) {
-                    const QSizeF newSize(minHeaderRoleWidth, size.height());
-                    m_visibleRolesSizes.insert(it.key(), newSize);
-                }
-            }
+        const QHash<QByteArray, qreal> preferredWidths = preferredColumnWidths(itemRanges);
+        for (const QByteArray &role : std::as_const(m_visibleRoles)) {
+            m_headerWidget->setPreferredColumnWidth(role, preferredWidths.value(role));
         }
     } else {
         // Only a sub range of the roles need to be determined.
-        // The chances are good that the sizes of the sub ranges
-        // already fit into the available sizes and hence no
+        // The chances are good that the widths of the sub ranges
+        // already fit into the available widths and hence no
         // expensive update might be required.
-        bool updateRequired = false;
+        bool changed = false;
 
-        const QHash<QByteArray, QSizeF> updatedSizes = visibleRolesSizes(itemRanges);
-        QHashIterator<QByteArray, QSizeF> it(updatedSizes);
+        const QHash<QByteArray, qreal> updatedWidths = preferredColumnWidths(itemRanges);
+        QHashIterator<QByteArray, qreal> it(updatedWidths);
         while (it.hasNext()) {
             it.next();
-            const QByteArrayrole = it.key();
-            const QSizeF& updatedSize = it.value();
-            const QSizeF currentSize = m_visibleRolesSizes.value(role);
-            if (updatedSize.width() > currentSize.width() || updatedSize.height() > currentSize.height()) {
-                m_visibleRolesSizes.insert(role, updatedSize);
-                updateRequired = true;
+            const QByteArray &role = it.key();
+            const qreal updatedWidth = it.value();
+            const qreal currentWidth = m_headerWidget->preferredColumnWidth(role);
+            if (updatedWidth > currentWidth) {
+                m_headerWidget->setPreferredColumnWidth(role, updatedWidth);
+                changed = true;
             }
         }
 
-        if (!updateRequired) {
+        if (!changed) {
             // All the updated sizes are smaller than the current sizes and no change
             // of the stretched roles-widths is required
             return;
         }
     }
 
-    updateStretchedVisibleRolesSizes();
+    if (m_headerWidget->automaticColumnResizing()) {
+        applyAutomaticColumnWidths();
+    }
 }
 
-void KItemListView::updateVisibleRolesSizes()
+void KItemListView::updatePreferredColumnWidths()
 {
-    if (!m_model) {
-        return;
-    }
-
-    const int itemCount = m_model->count();
-    if (itemCount > 0) {
-        updateVisibleRolesSizes(KItemRangeList() << KItemRange(0, itemCount));
+    if (m_model) {
+        updatePreferredColumnWidths(KItemRangeList() << KItemRange(0, m_model->count()));
     }
 }
 
-void KItemListView::updateStretchedVisibleRolesSizes()
+void KItemListView::applyAutomaticColumnWidths()
 {
-    if (!m_itemSize.isEmpty() || m_useHeaderWidths || m_visibleRoles.isEmpty()) {
+    Q_ASSERT(m_itemSize.isEmpty());
+    Q_ASSERT(m_headerWidget->automaticColumnResizing());
+    if (m_visibleRoles.isEmpty()) {
         return;
     }
 
     // Calculate the maximum size of an item by considering the
     // visible role sizes and apply them to the layouter. If the
-    // size does not use the available view-size it the size of the
+    // size does not use the available view-size the size of the
     // first role will get stretched.
-    m_stretchedVisibleRolesSizes = m_visibleRolesSizes;
-    const QByteArray role = m_visibleRoles.first();
-    QSizeF firstRoleSize = m_stretchedVisibleRolesSizes.value(role);
 
+    for (const QByteArray &role : std::as_const(m_visibleRoles)) {
+        const qreal preferredWidth = m_headerWidget->preferredColumnWidth(role);
+        m_headerWidget->setColumnWidth(role, preferredWidth);
+    }
+
+    const QByteArray firstRole = m_visibleRoles.first();
+    qreal firstColumnWidth = m_headerWidget->columnWidth(firstRole);
     QSizeF dynamicItemSize = m_itemSize;
 
-    if (dynamicItemSize.width() <= 0) {
-        const qreal requiredWidth = visibleRolesSizesWidthSum();
-        const qreal availableWidth = size().width();
-        if (requiredWidth < availableWidth) {
-            // Stretch the first role to use the whole width for the item
-            firstRoleSize.rwidth() += availableWidth - requiredWidth;
-            m_stretchedVisibleRolesSizes.insert(role, firstRoleSize);
+    qreal requiredWidth = m_headerWidget->leftPadding() + columnWidthsSum() + m_headerWidget->rightPadding();
+    // By default we want the same padding symmetrically on both sides of the view. This improves UX, looks better and increases the chances of users figuring
+    // out that the padding area can be used for deselecting and dropping files.
+    const qreal availableWidth = size().width();
+    if (requiredWidth < availableWidth) {
+        // Stretch the first column to use the whole remaining width
+        firstColumnWidth += availableWidth - requiredWidth;
+        m_headerWidget->setColumnWidth(firstRole, firstColumnWidth);
+    } else if (requiredWidth > availableWidth && m_visibleRoles.count() > 1) {
+        // Shrink the first column to be able to show as much other
+        // columns as possible
+        qreal shrinkedFirstColumnWidth = firstColumnWidth - requiredWidth + availableWidth;
+
+        // TODO: A proper calculation of the minimum width depends on the implementation
+        // of KItemListWidget. Probably a kind of minimum size-hint should be introduced
+        // later.
+        const qreal minWidth = qMin(firstColumnWidth, qreal(m_styleOption.iconSize * 2 + 200));
+        if (shrinkedFirstColumnWidth < minWidth) {
+            shrinkedFirstColumnWidth = minWidth;
         }
-        dynamicItemSize.setWidth(qMax(requiredWidth, availableWidth));
-    }
 
-    if (dynamicItemSize.height() <= 0) {
-        const qreal requiredHeight = visibleRolesSizesHeightSum();
-        const qreal availableHeight = size().height();
-        if (requiredHeight < availableHeight) {
-            // Stretch the first role to use the whole height for the item
-            firstRoleSize.rheight() += availableHeight - requiredHeight;
-            m_stretchedVisibleRolesSizes.insert(role, firstRoleSize);
-        }
-        dynamicItemSize.setHeight(qMax(requiredHeight, availableHeight));
+        m_headerWidget->setColumnWidth(firstRole, shrinkedFirstColumnWidth);
+        requiredWidth -= firstColumnWidth - shrinkedFirstColumnWidth;
     }
 
-    m_layouter->setItemSize(dynamicItemSize);
+    dynamicItemSize.rwidth() = qMax(requiredWidth, availableWidth);
 
-    if (m_header) {
-        m_header->setVisibleRolesWidths(headerRolesWidths());
-        m_header->resize(dynamicItemSize.width(), m_header->size().height());
-    }
+    m_layouter->setItemSize(dynamicItemSize);
 
     // Update the role sizes for all visible widgets
-    foreach (KItemListWidget* widget, visibleItemListWidgets()) {
-        widget->setVisibleRolesSizes(m_stretchedVisibleRolesSizes);
-    }
-}
-
-qreal KItemListView::visibleRolesSizesWidthSum() const
-{
-    qreal widthSum = 0;
-    QHashIterator<QByteArray, QSizeF> it(m_visibleRolesSizes);
+    QHashIterator<int, KItemListWidget *> it(m_visibleItems);
     while (it.hasNext()) {
         it.next();
-        widthSum += it.value().width();
+        updateWidgetColumnWidths(it.value());
     }
-    return widthSum;
 }
 
-qreal KItemListView::visibleRolesSizesHeightSum() const
+qreal KItemListView::columnWidthsSum() const
 {
-    qreal heightSum = 0;
-    QHashIterator<QByteArray, QSizeF> it(m_visibleRolesSizes);
-    while (it.hasNext()) {
-        it.next();
-        heightSum += it.value().height();
+    qreal widthsSum = 0;
+    for (const QByteArray &role : std::as_const(m_visibleRoles)) {
+        widthsSum += m_headerWidget->columnWidth(role);
     }
-    return heightSum;
+    return widthsSum;
 }
 
 QRectF KItemListView::headerBoundaries() const
 {
-    return m_header ? m_header->geometry() : QRectF();
+    return m_headerWidget->isVisible() ? m_headerWidget->geometry() : QRectF();
 }
 
-bool KItemListView::changesItemGridLayout(const QSizeF& newGridSize,
-                                          const QSizeF& newItemSize,
-                                          const QSizeF& newItemMargin) const
+bool KItemListView::changesItemGridLayout(const QSizeF &newGridSize, const QSizeF &newItemSize, const QSizeF &newItemMargin) const
 {
     if (newItemSize.isEmpty() || newGridSize.isEmpty()) {
         return false;
     }
-    
+
     if (m_layouter->scrollOrientation() == Qt::Vertical) {
         const qreal itemWidth = m_layouter->itemSize().width();
         if (itemWidth > 0) {
-            const int newColumnCount = itemsPerSize(newGridSize.width(),
-                                                    newItemSize.width(),
-                                                    newItemMargin.width());
+            const int newColumnCount = itemsPerSize(newGridSize.width(), newItemSize.width(), newItemMargin.width());
             if (m_model->count() > newColumnCount) {
-                const int oldColumnCount = itemsPerSize(m_layouter->size().width(),
-                                                        itemWidth,
-                                                        m_layouter->itemMargin().width());
+                const int oldColumnCount = itemsPerSize(m_layouter->size().width(), itemWidth, m_layouter->itemMargin().width());
                 return oldColumnCount != newColumnCount;
             }
-        }       
+        }
     } else {
         const qreal itemHeight = m_layouter->itemSize().height();
         if (itemHeight > 0) {
-            const int newRowCount = itemsPerSize(newGridSize.height(),
-                                                 newItemSize.height(),
-                                                 newItemMargin.height());
+            const int newRowCount = itemsPerSize(newGridSize.height(), newItemSize.height(), newItemMargin.height());
             if (m_model->count() > newRowCount) {
-                const int oldRowCount = itemsPerSize(m_layouter->size().height(),
-                                                     itemHeight,
-                                                     m_layouter->itemMargin().height());
+                const int oldRowCount = itemsPerSize(m_layouter->size().height(), itemHeight, m_layouter->itemMargin().height());
                 return oldRowCount != newRowCount;
             }
-        }               
+        }
     }
-    
+
     return false;
 }
 
 bool KItemListView::animateChangedItemCount(int changedItemCount) const
 {
+    if (m_itemSize.isEmpty()) {
+        // We have only columns or only rows, but no grid: An animation is usually
+        // welcome when inserting or removing items.
+        return !supportsItemExpanding();
+    }
+
     if (m_layouter->size().isEmpty() || m_layouter->itemSize().isEmpty()) {
         return false;
     }
-    
-    const int maximum = (scrollOrientation() == Qt::Vertical)
-                        ? m_layouter->size().width()  / m_layouter->itemSize().width()
-                        : m_layouter->size().height() / m_layouter->itemSize().height();
+
+    const int maximum = (scrollOrientation() == Qt::Vertical) ? m_layouter->size().width() / m_layouter->itemSize().width()
+                                                              : m_layouter->size().height() / m_layouter->itemSize().height();
     // Only animate if up to 2/3 of a row or column are inserted or removed
     return changedItemCount <= maximum * 2 / 3;
 }
 
-
-bool KItemListView::scrollBarRequired(const QSizeF& size) const
+bool KItemListView::scrollBarRequired(const QSizeF &size) const
 {
     const QSizeF oldSize = m_layouter->size();
 
@@ -1970,26 +2639,72 @@ bool KItemListView::scrollBarRequired(const QSizeF& size) const
     const qreal maxOffset = m_layouter->maximumScrollOffset();
     m_layouter->setSize(oldSize);
 
-    return m_layouter->scrollOrientation() == Qt::Vertical ? maxOffset > size.height()
-                                                           : maxOffset > size.width();
+    return m_layouter->scrollOrientation() == Qt::Vertical ? maxOffset > size.height() : maxOffset > size.width();
+}
+
+int KItemListView::showDropIndicator(const QPointF &pos)
+{
+    QHashIterator<int, KItemListWidget *> it(m_visibleItems);
+    while (it.hasNext()) {
+        it.next();
+        const KItemListWidget *widget = it.value();
+
+        const QPointF mappedPos = widget->mapFromItem(this, pos);
+        const QRectF rect = itemRect(widget->index());
+        if (mappedPos.y() >= 0 && mappedPos.y() <= rect.height()) {
+            if (m_model->supportsDropping(widget->index())) {
+                // Keep 30% of the rectangle as the gap instead of always having a fixed gap
+                const int gap = qMax(qreal(4.0), qreal(0.3) * rect.height());
+                if (mappedPos.y() >= gap && mappedPos.y() <= rect.height() - gap) {
+                    return -1;
+                }
+            }
+
+            const bool isAboveItem = (mappedPos.y() < rect.height() / 2);
+            const qreal y = isAboveItem ? rect.top() : rect.bottom();
+
+            const QRectF draggingInsertIndicator(rect.left(), y, rect.width(), 1);
+            if (m_dropIndicator != draggingInsertIndicator) {
+                m_dropIndicator = draggingInsertIndicator;
+                update();
+            }
+
+            int index = widget->index();
+            if (!isAboveItem) {
+                ++index;
+            }
+            return index;
+        }
+    }
+
+    const QRectF firstItemRect = itemRect(firstVisibleIndex());
+    return (pos.y() <= firstItemRect.top()) ? 0 : -1;
+}
+
+void KItemListView::hideDropIndicator()
+{
+    if (!m_dropIndicator.isNull()) {
+        m_dropIndicator = QRectF();
+        update();
+    }
 }
 
 void KItemListView::updateGroupHeaderHeight()
 {
     qreal groupHeaderHeight = m_styleOption.fontMetrics.height();
     qreal groupHeaderMargin = 0;
-    
+
     if (scrollOrientation() == Qt::Horizontal) {
         // The vertical margin above and below the header should be
         // equal to the horizontal margin, not the vertical margin
         // from m_styleOption.
         groupHeaderHeight += 2 * m_styleOption.horizontalMargin;
         groupHeaderMargin = m_styleOption.horizontalMargin;
-    } else if (m_itemSize.isEmpty()){
-        groupHeaderHeight += 2 * m_styleOption.padding;
+    } else if (m_itemSize.isEmpty()) {
+        groupHeaderHeight += 4 * m_styleOption.padding;
         groupHeaderMargin = m_styleOption.iconSize / 2;
     } else {
-        groupHeaderHeight += 2 * m_styleOption.padding;
+        groupHeaderHeight += 2 * m_styleOption.padding + m_styleOption.verticalMargin;
         groupHeaderMargin = m_styleOption.iconSize / 4;
     }
     m_layouter->setGroupHeaderHeight(groupHeaderHeight);
@@ -2000,16 +2715,15 @@ void KItemListView::updateGroupHeaderHeight()
 
 void KItemListView::updateSiblingsInformation(int firstIndex, int lastIndex)
 {
-    if (!supportsItemExpanding()) {
+    if (!supportsItemExpanding() || !m_model) {
         return;
     }
 
     if (firstIndex < 0 || lastIndex < 0) {
         firstIndex = m_layouter->firstVisibleIndex();
-        lastIndex  = m_layouter->lastVisibleIndex();
+        lastIndex = m_layouter->lastVisibleIndex();
     } else {
-        const bool isRangeVisible = (firstIndex <= m_layouter->lastVisibleIndex() &&
-                                     lastIndex  >= m_layouter->firstVisibleIndex());
+        const bool isRangeVisible = (firstIndex <= m_layouter->lastVisibleIndex() && lastIndex >= m_layouter->firstVisibleIndex());
         if (!isRangeVisible) {
             return;
         }
@@ -2025,7 +2739,7 @@ void KItemListView::updateSiblingsInformation(int firstIndex, int lastIndex)
     // contain a siblings information which can be used as base.
     int rootIndex = firstIndex;
 
-    KItemListWidgetwidget = m_visibleItems.value(firstIndex - 1);
+    KItemListWidget *widget = m_visibleItems.value(firstIndex - 1);
     if (!widget) {
         // There is no visible widget before the range, check whether there
         // is one after the range:
@@ -2036,7 +2750,7 @@ void KItemListView::updateSiblingsInformation(int firstIndex, int lastIndex)
             const int parents = m_model->expandedParentsCount(lastIndex + 1);
             for (int i = lastIndex; i >= firstIndex; --i) {
                 if (m_model->expandedParentsCount(i) != parents) {
-                    widget = 0;
+                    widget = nullptr;
                     break;
                 }
             }
@@ -2078,7 +2792,7 @@ void KItemListView::updateSiblingsInformation(int firstIndex, int lastIndex)
         if (i >= firstIndex) {
             // The index represents a visible item. Apply the parent-siblings
             // and update the sibling of the current item.
-            KItemListWidgetwidget = m_visibleItems.value(i);
+            KItemListWidget *widget = m_visibleItems.value(i);
             if (!widget) {
                 continue;
             }
@@ -2125,6 +2839,18 @@ bool KItemListView::hasSiblingSuccessor(int index) const
     return hasSuccessor;
 }
 
+void KItemListView::disconnectRoleEditingSignals(int index)
+{
+    KStandardItemListWidget *widget = qobject_cast<KStandardItemListWidget *>(m_visibleItems.value(index));
+    if (!widget) {
+        return;
+    }
+
+    disconnect(widget, &KItemListWidget::roleEditingCanceled, this, nullptr);
+    disconnect(widget, &KItemListWidget::roleEditingFinished, this, nullptr);
+    disconnect(this, &KItemListView::scrollOffsetChanged, widget, nullptr);
+}
+
 int KItemListView::calculateAutoScrollingIncrement(int pos, int range, int oldInc)
 {
     int inc = 0;
@@ -2158,20 +2884,18 @@ int KItemListView::itemsPerSize(qreal size, qreal itemSize, qreal itemMargin)
     return count;
 }
 
-
-
 KItemListCreatorBase::~KItemListCreatorBase()
 {
     qDeleteAll(m_recycleableWidgets);
     qDeleteAll(m_createdWidgets);
 }
 
-void KItemListCreatorBase::addCreatedWidget(QGraphicsWidgetwidget)
+void KItemListCreatorBase::addCreatedWidget(QGraphicsWidget *widget)
 {
     m_createdWidgets.insert(widget);
 }
 
-void KItemListCreatorBase::pushRecycleableWidget(QGraphicsWidgetwidget)
+void KItemListCreatorBase::pushRecycleableWidget(QGraphicsWidget *widget)
 {
     Q_ASSERT(m_createdWidgets.contains(widget));
     m_createdWidgets.remove(widget);
@@ -2184,13 +2908,13 @@ void KItemListCreatorBase::pushRecycleableWidget(QGraphicsWidget* widget)
     }
 }
 
-QGraphicsWidgetKItemListCreatorBase::popRecycleableWidget()
+QGraphicsWidget *KItemListCreatorBase::popRecycleableWidget()
 {
     if (m_recycleableWidgets.isEmpty()) {
-        return 0;
+        return nullptr;
     }
 
-    QGraphicsWidgetwidget = m_recycleableWidgets.takeLast();
+    QGraphicsWidget *widget = m_recycleableWidgets.takeLast();
     m_createdWidgets.insert(widget);
     return widget;
 }
@@ -2199,9 +2923,9 @@ KItemListWidgetCreatorBase::~KItemListWidgetCreatorBase()
 {
 }
 
-void KItemListWidgetCreatorBase::recycle(KItemListWidgetwidget)
+void KItemListWidgetCreatorBase::recycle(KItemListWidget *widget)
 {
-    widget->setParentItem(0);
+    widget->setParentItem(nullptr);
     widget->setOpacity(1.0);
     pushRecycleableWidget(widget);
 }
@@ -2210,10 +2934,10 @@ KItemListGroupHeaderCreatorBase::~KItemListGroupHeaderCreatorBase()
 {
 }
 
-void KItemListGroupHeaderCreatorBase::recycle(KItemListGroupHeaderheader)
+void KItemListGroupHeaderCreatorBase::recycle(KItemListGroupHeader *header)
 {
     header->setOpacity(1.0);
     pushRecycleableWidget(header);
 }
 
-#include "kitemlistview.moc"
+#include "moc_kitemlistview.cpp"