]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/kitemviews/kfileitemmodel.cpp
Reverted resortAllItems() in favor of a group comparator for lessThan. Minor bug...
[dolphin.git] / src / kitemviews / kfileitemmodel.cpp
index 052a71cbd4863020469da79c89adba18fe952a99..34a113eded441ec22753792b6471f9287f667833 100644 (file)
@@ -8,51 +8,50 @@
 
 #include "kfileitemmodel.h"
 
+#include "dolphin_contentdisplaysettings.h"
 #include "dolphin_generalsettings.h"
-#include "dolphin_detailsmodesettings.h"
 #include "dolphindebug.h"
 #include "private/kfileitemmodelsortalgorithm.h"
+#include "views/draganddrophelper.h"
 
 #include <KDirLister>
 #include <KIO/Job>
-#include <kio_version.h>
+#include <KIO/ListJob>
 #include <KLocalizedString>
-#include <KLazyLocalizedString>
 #include <KUrlMimeData>
 
 #include <QElapsedTimer>
+#include <QIcon>
 #include <QMimeData>
 #include <QMimeDatabase>
+#include <QRecursiveMutex>
 #include <QTimer>
 #include <QWidget>
-#include <QRecursiveMutex>
-#include <QIcon>
-#include <algorithm>
 #include <klazylocalizedstring.h>
 
 Q_GLOBAL_STATIC(QRecursiveMutex, s_collatorMutex)
 
 // #define KFILEITEMMODEL_DEBUG
 
-KFileItemModel::KFileItemModel(QObject* parent) :
-    KItemModelBase("text", parent),
-    m_dirLister(nullptr),
-    m_sortDirsFirst(true),
-    m_sortHiddenLast(false),
-    m_sortRole(NameRole),
-    m_sortingProgressPercent(-1),
-    m_roles(),
-    m_itemData(),
-    m_items(),
-    m_filter(),
-    m_filteredItems(),
-    m_requestRole(),
-    m_maximumUpdateIntervalTimer(nullptr),
-    m_resortAllItemsTimer(nullptr),
-    m_pendingItemsToInsert(),
-    m_groups(),
-    m_expandedDirs(),
-    m_urlsToExpand()
+KFileItemModel::KFileItemModel(QObject *parent)
+    : KItemModelBase("text", "text", parent)
+    , m_dirLister(nullptr)
+    , m_sortDirsFirst(true)
+    , m_sortHiddenLast(false)
+    , m_sortRole(NameRole)
+    , m_sortingProgressPercent(-1)
+    , m_roles()
+    , m_itemData()
+    , m_items()
+    , m_filter()
+    , m_filteredItems()
+    , m_requestRole()
+    , m_maximumUpdateIntervalTimer(nullptr)
+    , m_resortAllItemsTimer(nullptr)
+    , m_pendingItemsToInsert()
+    , m_groups()
+    , m_expandedDirs()
+    m_urlsToExpand()
 {
     m_collator.setNumericMode(true);
 
@@ -62,7 +61,7 @@ KFileItemModel::KFileItemModel(QObject* parent) :
     m_dirLister->setAutoErrorHandlingEnabled(false);
     m_dirLister->setDelayedMimeTypes(true);
 
-    const QWidget* parentWidget = qobject_cast<QWidget*>(parent);
+    const QWidget *parentWidget = qobject_cast<QWidget *>(parent);
     if (parentWidget) {
         m_dirLister->setMainWindow(parentWidget->window());
     }
@@ -101,11 +100,14 @@ KFileItemModel::KFileItemModel(QObject* parent) :
     // for a lot of items within a quite small timeslot. To prevent expensive resortings the
     // resorting is postponed until the timer has been exceeded.
     m_resortAllItemsTimer = new QTimer(this);
-    m_resortAllItemsTimer->setInterval(500);
+    m_resortAllItemsTimer->setInterval(100); // 100 is a middle ground between sorting too frequently which makes the view unreadable
+                                             // and sorting too infrequently which leads to users seeing an outdated sort order.
     m_resortAllItemsTimer->setSingleShot(true);
     connect(m_resortAllItemsTimer, &QTimer::timeout, this, &KFileItemModel::resortAllItems);
 
     connect(GeneralSettings::self(), &GeneralSettings::sortingChoiceChanged, this, &KFileItemModel::slotSortingChoiceChanged);
+
+    setShowTrashMime(m_dirLister->showHiddenFiles() || !GeneralSettings::hideXTrashFile());
 }
 
 KFileItemModel::~KFileItemModel()
@@ -130,6 +132,8 @@ void KFileItemModel::refreshDirectory(const QUrl &url)
     }
 
     m_dirLister->openUrl(url, KDirLister::Reload);
+
+    Q_EMIT directoryRefreshing();
 }
 
 QUrl KFileItemModel::directory() const
@@ -150,7 +154,7 @@ int KFileItemModel::count() const
 QHash<QByteArray, QVariant> KFileItemModel::data(int index) const
 {
     if (index >= 0 && index < count()) {
-        ItemDatadata = m_itemData.at(index);
+        ItemData *data = m_itemData.at(index);
         if (data->values.isEmpty()) {
             data->values = retrieveData(data->item, data->parent);
         } else if (data->values.count() <= 2 && data->values.value("isExpanded").toBool()) {
@@ -172,7 +176,7 @@ QHash<QByteArray, QVariant> KFileItemModel::data(int index) const
     return QHash<QByteArray, QVariant>();
 }
 
-bool KFileItemModel::setData(int index, const QHash<QByteArray, QVariant>values)
+bool KFileItemModel::setData(int index, const QHash<QByteArray, QVariant> &values)
 {
     if (index < 0 || index >= count()) {
         return false;
@@ -237,9 +241,31 @@ bool KFileItemModel::sortHiddenLast() const
     return m_sortHiddenLast;
 }
 
+void KFileItemModel::setShowTrashMime(bool showTrashMime)
+{
+    const auto trashMime = QStringLiteral("application/x-trash");
+    QStringList excludeFilter = m_filter.excludeMimeTypes();
+
+    if (showTrashMime) {
+        excludeFilter.removeAll(trashMime);
+    } else if (!excludeFilter.contains(trashMime)) {
+        excludeFilter.append(trashMime);
+    }
+
+    setExcludeMimeTypeFilter(excludeFilter);
+}
+
+void KFileItemModel::scheduleResortAllItems()
+{
+    if (!m_resortAllItemsTimer->isActive()) {
+        m_resortAllItemsTimer->start();
+    }
+}
+
 void KFileItemModel::setShowHiddenFiles(bool show)
 {
-    m_dirLister->setShowingDotFiles(show);
+    m_dirLister->setShowHiddenFiles(show);
+    setShowTrashMime(show || !GeneralSettings::hideXTrashFile());
     m_dirLister->emitChanges();
     if (show) {
         dispatchPendingItemsToInsert();
@@ -248,7 +274,7 @@ void KFileItemModel::setShowHiddenFiles(bool show)
 
 bool KFileItemModel::showHiddenFiles() const
 {
-    return m_dirLister->showingDotFiles();
+    return m_dirLister->showHiddenFiles();
 }
 
 void KFileItemModel::setShowDirectoriesOnly(bool enabled)
@@ -261,20 +287,20 @@ bool KFileItemModel::showDirectoriesOnly() const
     return m_dirLister->dirOnlyMode();
 }
 
-QMimeData* KFileItemModel::createMimeData(const KItemSet& indexes) const
+QMimeData *KFileItemModel::createMimeData(const KItemSet &indexes) const
 {
-    QMimeDatadata = new QMimeData();
+    QMimeData *data = new QMimeData();
 
     // The following code has been taken from KDirModel::mimeData()
     // (kdelibs/kio/kio/kdirmodel.cpp)
     // SPDX-FileCopyrightText: 2006 David Faure <faure@kde.org>
     QList<QUrl> urls;
     QList<QUrl> mostLocalUrls;
-    const ItemDatalastAddedItem = nullptr;
+    const ItemData *lastAddedItem = nullptr;
 
     for (int index : indexes) {
-        const ItemDataitemData = m_itemData.at(index);
-        const ItemDataparent = itemData->parent;
+        const ItemData *itemData = m_itemData.at(index);
+        const ItemData *parent = itemData->parent;
 
         while (parent && parent != lastAddedItem) {
             parent = parent->parent;
@@ -286,7 +312,7 @@ QMimeData* KFileItemModel::createMimeData(const KItemSet& indexes) const
         }
 
         lastAddedItem = itemData;
-        const KFileItemitem = itemData->item;
+        const KFileItem &item = itemData->item;
         if (!item.isNull()) {
             urls << item.url();
 
@@ -299,7 +325,7 @@ QMimeData* KFileItemModel::createMimeData(const KItemSet& indexes) const
     return data;
 }
 
-int KFileItemModel::indexForKeyboardSearch(const QStringtext, int startFromIndex) const
+int KFileItemModel::indexForKeyboardSearch(const QString &text, int startFromIndex) const
 {
     startFromIndex = qMax(0, startFromIndex);
     for (int i = startFromIndex; i < count(); ++i) {
@@ -317,16 +343,32 @@ int KFileItemModel::indexForKeyboardSearch(const QString& text, int startFromInd
 
 bool KFileItemModel::supportsDropping(int index) const
 {
-    const KFileItem item = fileItem(index);
+    KFileItem item;
+    if (index == -1) {
+        item = rootItem();
+    } else {
+        item = fileItem(index);
+    }
+    return !item.isNull() && DragAndDropHelper::supportsDropping(item);
+}
+
+bool KFileItemModel::canEnterOnHover(int index) const
+{
+    KFileItem item;
+    if (index == -1) {
+        item = rootItem();
+    } else {
+        item = fileItem(index);
+    }
     return !item.isNull() && (item.isDir() || item.isDesktopFile());
 }
 
-QString KFileItemModel::roleDescription(const QByteArrayrole) const
+QString KFileItemModel::roleDescription(const QByteArray &role) const
 {
     static QHash<QByteArray, QString> description;
     if (description.isEmpty()) {
         int count = 0;
-        const RoleInfoMapmap = rolesInfoMap(count);
+        const RoleInfoMap *map = rolesInfoMap(count);
         for (int i = 0; i < count; ++i) {
             if (map[i].roleTranslation.isEmpty()) {
                 continue;
@@ -338,16 +380,23 @@ QString KFileItemModel::roleDescription(const QByteArray& role) const
     return description.value(role);
 }
 
-QList<QPair<int, QVariant> > KFileItemModel::groups() const
+QList<QPair<int, QVariant>> KFileItemModel::groups() const
 {
     if (!m_itemData.isEmpty() && m_groups.isEmpty()) {
 #ifdef KFILEITEMMODEL_DEBUG
         QElapsedTimer timer;
         timer.start();
 #endif
-        switch (typeForRole(sortRole())) {
-        case NameRole:        m_groups = nameRoleGroups(); break;
-        case SizeRole:        m_groups = sizeRoleGroups(); break;
+        switch (typeForRole(groupRole())) {
+        case NoRole:
+            m_groups.clear();
+            break;
+        case NameRole:
+            m_groups = nameRoleGroups();
+            break;
+        case SizeRole:
+            m_groups = sizeRoleGroups();
+            break;
         case ModificationTimeRole:
             m_groups = timeRoleGroups([](const ItemData *item) {
                 return item->item.time(KFileItem::ModificationTime);
@@ -368,9 +417,15 @@ QList<QPair<int, QVariant> > KFileItemModel::groups() const
                 return item->values.value("deletiontime").toDateTime();
             });
             break;
-        case PermissionsRole: m_groups = permissionRoleGroups(); break;
-        case RatingRole:      m_groups = ratingRoleGroups(); break;
-        default:              m_groups = genericStringRoleGroups(sortRole()); break;
+        case PermissionsRole:
+            m_groups = permissionRoleGroups();
+            break;
+        case RatingRole:
+            m_groups = ratingRoleGroups();
+            break;
+        default:
+            m_groups = genericStringRoleGroups(groupRole());
+            break;
         }
 
 #ifdef KFILEITEMMODEL_DEBUG
@@ -399,12 +454,12 @@ KFileItem KFileItemModel::fileItem(const QUrl &url) const
     return KFileItem();
 }
 
-int KFileItemModel::index(const KFileItemitem) const
+int KFileItemModel::index(const KFileItem &item) const
 {
     return index(item.url());
 }
 
-int KFileItemModel::index(const QUrlurl) const
+int KFileItemModel::index(const QUrl &url) const
 {
     const QUrl urlToFind = url.adjusted(QUrl::StripTrailingSlash);
 
@@ -454,13 +509,13 @@ int KFileItemModel::index(const QUrl& url) const
             }
 
             const auto uniqueKeys = indexesForUrl.uniqueKeys();
-            for (const QUrlurl : uniqueKeys) {
+            for (const QUrl &url : uniqueKeys) {
                 if (indexesForUrl.count(url) > 1) {
                     qCWarning(DolphinDebug) << "Multiple items found with the URL" << url;
 
                     auto it = indexesForUrl.find(url);
                     while (it != indexesForUrl.end() && it.key() == url) {
-                        const ItemDatadata = m_itemData.at(it.value());
+                        const ItemData *data = m_itemData.at(it.value());
                         qCWarning(DolphinDebug) << "index" << it.value() << ":" << data->item;
                         if (data->parent) {
                             qCWarning(DolphinDebug) << "parent" << data->parent->item;
@@ -485,7 +540,7 @@ void KFileItemModel::clear()
     slotClear();
 }
 
-void KFileItemModel::setRoles(const QSet<QByteArray>roles)
+void KFileItemModel::setRoles(const QSet<QByteArray> &roles)
 {
     if (m_roles == roles) {
         return;
@@ -509,7 +564,7 @@ void KFileItemModel::setRoles(const QSet<QByteArray>& roles)
 
     QSetIterator<QByteArray> it(roles);
     while (it.hasNext()) {
-        const QByteArrayrole = it.next();
+        const QByteArray &role = it.next();
         m_requestRole[typeForRole(role)] = true;
     }
 
@@ -525,8 +580,8 @@ void KFileItemModel::setRoles(const QSet<QByteArray>& roles)
 
     // Clear the 'values' of all filtered items. They will be re-populated with the
     // correct roles the next time 'values' will be accessed via data(int).
-    QHash<KFileItem, ItemData*>::iterator filteredIt = m_filteredItems.begin();
-    const QHash<KFileItem, ItemData*>::iterator filteredEnd = m_filteredItems.end();
+    QHash<KFileItem, ItemData *>::iterator filteredIt = m_filteredItems.begin();
+    const QHash<KFileItem, ItemData *>::iterator filteredEnd = m_filteredItems.end();
     while (filteredIt != filteredEnd) {
         (*filteredIt)->values.clear();
         ++filteredIt;
@@ -558,7 +613,7 @@ bool KFileItemModel::setExpanded(int index, bool expanded)
         m_dirLister->openUrl(url, KDirLister::Keep);
 
         const QVariantList previouslyExpandedChildren = m_itemData.at(index)->values.value("previouslyExpandedChildren").value<QVariantList>();
-        for (const QVariantvar : previouslyExpandedChildren) {
+        for (const QVariant &var : previouslyExpandedChildren) {
             m_urlsToExpand.insert(var.toUrl());
         }
     } else {
@@ -576,9 +631,7 @@ bool KFileItemModel::setExpanded(int index, bool expanded)
 
         m_expandedDirs.remove(targetUrl);
         m_dirLister->stop(url);
-#if KIO_VERSION >= QT_VERSION_CHECK(5, 92, 0)
         m_dirLister->forgetDirs(url);
-#endif
 
         const int parentLevel = expandedParentsCount(index);
         const int itemCount = m_itemData.count();
@@ -588,15 +641,13 @@ bool KFileItemModel::setExpanded(int index, bool expanded)
 
         int childIndex = firstChildIndex;
         while (childIndex < itemCount && expandedParentsCount(childIndex) > parentLevel) {
-            ItemDataitemData = m_itemData.at(childIndex);
+            ItemData *itemData = m_itemData.at(childIndex);
             if (itemData->values.value("isExpanded").toBool()) {
                 const QUrl targetUrl = itemData->item.targetUrl();
                 const QUrl url = itemData->item.url();
                 m_expandedDirs.remove(targetUrl);
-                m_dirLister->stop(url);     // TODO: try to unit-test this, see https://bugs.kde.org/show_bug.cgi?id=332102#c11
-#if KIO_VERSION >= QT_VERSION_CHECK(5, 92, 0)
+                m_dirLister->stop(url); // TODO: try to unit-test this, see https://bugs.kde.org/show_bug.cgi?id=332102#c11
                 m_dirLister->forgetDirs(url);
-#endif
                 expandedChildren.append(targetUrl);
             }
             ++childIndex;
@@ -655,7 +706,6 @@ void KFileItemModel::restoreExpandedDirectories(const QSet<QUrl> &urls)
 
 void KFileItemModel::expandParentDirectories(const QUrl &url)
 {
-
     // Assure that each sub-path of the URL that should be
     // expanded is added to m_urlsToExpand. KDirLister
     // does not care whether the parent-URL has already been
@@ -689,7 +739,7 @@ void KFileItemModel::expandParentDirectories(const QUrl &url)
     }
 }
 
-void KFileItemModel::setNameFilter(const QStringnameFilter)
+void KFileItemModel::setNameFilter(const QString &nameFilter)
 {
     if (m_filter.pattern() != nameFilter) {
         dispatchPendingItemsToInsert();
@@ -703,7 +753,7 @@ QString KFileItemModel::nameFilter() const
     return m_filter.pattern();
 }
 
-void KFileItemModel::setMimeTypeFilters(const QStringListfilters)
+void KFileItemModel::setMimeTypeFilters(const QStringList &filters)
 {
     if (m_filter.mimeTypes() != filters) {
         dispatchPendingItemsToInsert();
@@ -717,6 +767,20 @@ QStringList KFileItemModel::mimeTypeFilters() const
     return m_filter.mimeTypes();
 }
 
+void KFileItemModel::setExcludeMimeTypeFilter(const QStringList &filters)
+{
+    if (m_filter.excludeMimeTypes() != filters) {
+        dispatchPendingItemsToInsert();
+        m_filter.setExcludeMimeTypes(filters);
+        applyFilters();
+    }
+}
+
+QStringList KFileItemModel::excludeMimeTypeFilter() const
+{
+    return m_filter.excludeMimeTypes();
+}
+
 void KFileItemModel::applyFilters()
 {
     // ===STEP 1===
@@ -733,8 +797,7 @@ void KFileItemModel::applyFilters()
     for (int index = m_itemData.count() - 1; index >= 0; --index) {
         ItemData *itemData = m_itemData.at(index);
 
-        if (m_filter.matches(itemData->item)
-            || (itemShownBelow && itemShownBelow->parent == itemData)) {
+        if (m_filter.matches(itemData->item) || (itemShownBelow && itemShownBelow->parent == itemData)) {
             // We could've entered here for two reasons:
             // 1. This item passes the filter itself
             // 2. This is an expanded folder that doesn't pass the filter but sees a filter-passing child just below
@@ -769,9 +832,8 @@ void KFileItemModel::applyFilters()
 
             // If this is a child of an expanded folder, we must make sure that its whole parental chain will also be shown.
             // We will go up through its parental chain until we either:
-            // 1 - reach the "root item" of the current view, i.e the currently opened folder on Dolphin. Their children have their ItemData::parent set to nullptr.
-            // or
-            // 2 - we reach an unfiltered parent or a previously discovered ancestor.
+            // 1 - reach the "root item" of the current view, i.e the currently opened folder on Dolphin. Their children have their ItemData::parent set to
+            // nullptr. or 2 - we reach an unfiltered parent or a previously discovered ancestor.
             for (ItemData *parent = it.value()->parent; parent && !ancestorsOfNewVisibleItems.contains(parent->item) && m_filteredItems.contains(parent->item);
                  parent = parent->parent) {
                 // We wish we could remove this parent from m_filteredItems right now, but we are iterating over it
@@ -801,7 +863,7 @@ void KFileItemModel::applyFilters()
     insertItems(newVisibleItems);
 }
 
-void KFileItemModel::removeFilteredChildren(const KItemRangeListitemRanges)
+void KFileItemModel::removeFilteredChildren(const KItemRangeList &itemRanges)
 {
     if (m_filteredItems.isEmpty() || !m_requestRole[ExpandedParentsCountRole]) {
         // There are either no filtered items, or it is not possible to expand
@@ -809,14 +871,14 @@ void KFileItemModel::removeFilteredChildren(const KItemRangeList& itemRanges)
         return;
     }
 
-    QSet<ItemData*> parents;
-    for (const KItemRangerange : itemRanges) {
+    QSet<ItemData *> parents;
+    for (const KItemRange &range : itemRanges) {
         for (int index = range.index; index < range.index + range.count; ++index) {
             parents.insert(m_itemData.at(index));
         }
     }
 
-    QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.begin();
+    QHash<KFileItem, ItemData *>::iterator it = m_filteredItems.begin();
     while (it != m_filteredItems.end()) {
         if (parents.contains(it.value()->parent)) {
             delete it.value();
@@ -827,27 +889,48 @@ void KFileItemModel::removeFilteredChildren(const KItemRangeList& itemRanges)
     }
 }
 
+KFileItemModel::RoleInfo KFileItemModel::roleInformation(const QByteArray &role)
+{
+    static QHash<QByteArray, RoleInfo> information;
+    if (information.isEmpty()) {
+        int count = 0;
+        const RoleInfoMap *map = rolesInfoMap(count);
+        for (int i = 0; i < count; ++i) {
+            RoleInfo info;
+            info.role = map[i].role;
+            info.translation = map[i].roleTranslation.toString();
+            if (!map[i].groupTranslation.isEmpty()) {
+                info.group = map[i].groupTranslation.toString();
+            } else {
+                // For top level roles, groupTranslation is 0. We must make sure that
+                // info.group is an empty string then because the code that generates
+                // menus tries to put the actions into sub menus otherwise.
+                info.group = QString();
+            }
+            info.requiresBaloo = map[i].requiresBaloo;
+            info.requiresIndexer = map[i].requiresIndexer;
+            if (!map[i].tooltipTranslation.isEmpty()) {
+                info.tooltip = map[i].tooltipTranslation.toString();
+            } else {
+                info.tooltip = QString();
+            }
+
+            information.insert(map[i].role, info);
+        }
+    }
+
+    return information.value(role);
+}
+
 QList<KFileItemModel::RoleInfo> KFileItemModel::rolesInformation()
 {
     static QList<RoleInfo> rolesInfo;
     if (rolesInfo.isEmpty()) {
         int count = 0;
-        const RoleInfoMapmap = rolesInfoMap(count);
+        const RoleInfoMap *map = rolesInfoMap(count);
         for (int i = 0; i < count; ++i) {
             if (map[i].roleType != NoRole) {
-                RoleInfo info;
-                info.role = map[i].role;
-                info.translation = map[i].roleTranslation.toString();
-                if (!map[i].groupTranslation.isEmpty()) {
-                    info.group = map[i].groupTranslation.toString();
-                } else {
-                    // For top level roles, groupTranslation is 0. We must make sure that
-                    // info.group is an empty string then because the code that generates
-                    // menus tries to put the actions into sub menus otherwise.
-                    info.group = QString();
-                }
-                info.requiresBaloo = map[i].requiresBaloo;
-                info.requiresIndexer = map[i].requiresIndexer;
+                RoleInfo info = roleInformation(map[i].role);
                 rolesInfo.append(info);
             }
         }
@@ -862,7 +945,7 @@ void KFileItemModel::onGroupedSortingChanged(bool current)
     m_groups.clear();
 }
 
-void KFileItemModel::onSortRoleChanged(const QByteArray& current, const QByteArray& previous, bool resortItems)
+void KFileItemModel::onSortRoleChanged(const QByteArray &current, const QByteArray &previous, bool resortItems)
 {
     Q_UNUSED(previous)
     m_sortRole = typeForRole(current);
@@ -878,11 +961,40 @@ void KFileItemModel::onSortRoleChanged(const QByteArray& current, const QByteArr
     }
 }
 
-void KFileItemModel::onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous)
+void KFileItemModel::onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous, bool resortItems)
 {
     Q_UNUSED(current)
     Q_UNUSED(previous)
-    resortAllItems();
+
+    if (resortItems) {
+        resortAllItems();
+    }
+}
+
+void KFileItemModel::onGroupRoleChanged(const QByteArray &current, const QByteArray &previous, bool resortItems)
+{
+    Q_UNUSED(previous)
+    m_groupRole = typeForRole(current);
+
+    if (!m_requestRole[m_sortRole]) {
+        QSet<QByteArray> newRoles = m_roles;
+        newRoles << current;
+        setRoles(newRoles);
+    }
+
+    if (resortItems) {
+        resortAllItems();
+    }
+}
+
+void KFileItemModel::onGroupOrderChanged(Qt::SortOrder current, Qt::SortOrder previous, bool resortItems)
+{
+    Q_UNUSED(current)
+    Q_UNUSED(previous)
+
+    if (resortItems) {
+        resortAllItems();
+    }
 }
 
 void KFileItemModel::loadSortingSettings()
@@ -930,7 +1042,7 @@ void KFileItemModel::resortAllItems()
     // been moved because of the resorting.
     QList<QUrl> oldUrls;
     oldUrls.reserve(itemCount);
-    for (const ItemData* itemData : qAsConst(m_itemData)) {
+    for (const ItemData *itemData : std::as_const(m_itemData)) {
         oldUrls.append(itemData->item.url());
     }
 
@@ -945,8 +1057,7 @@ void KFileItemModel::resortAllItems()
 
     // Determine the first index that has been moved.
     int firstMovedIndex = 0;
-    while (firstMovedIndex < itemCount
-           && firstMovedIndex == m_items.value(oldUrls.at(firstMovedIndex))) {
+    while (firstMovedIndex < itemCount && firstMovedIndex == m_items.value(oldUrls.at(firstMovedIndex))) {
         ++firstMovedIndex;
     }
 
@@ -955,8 +1066,7 @@ void KFileItemModel::resortAllItems()
         m_groups.clear();
 
         int lastMovedIndex = itemCount - 1;
-        while (lastMovedIndex > firstMovedIndex
-               && lastMovedIndex == m_items.value(oldUrls.at(lastMovedIndex))) {
+        while (lastMovedIndex > firstMovedIndex && lastMovedIndex == m_items.value(oldUrls.at(lastMovedIndex))) {
             --lastMovedIndex;
         }
 
@@ -976,7 +1086,7 @@ void KFileItemModel::resortAllItems()
         Q_EMIT itemsMoved(KItemRange(firstMovedIndex, movedItemsCount), movedToIndexes);
     } else if (groupedSorting()) {
         // The groups might have changed even if the order of the items has not.
-        const QList<QPair<int, QVariant> > oldGroups = m_groups;
+        const QList<QPair<int, QVariant>> oldGroups = m_groups;
         m_groups.clear();
         if (groups() != oldGroups) {
             Q_EMIT groupsChanged();
@@ -1000,7 +1110,7 @@ void KFileItemModel::slotCompleted()
         // -> we expand the first visible URL we find in m_restoredExpandedUrls.
         // Iterate over a const copy because items are deleted and inserted within the loop
         const auto urlsToExpand = m_urlsToExpand;
-        for(const QUrl &url : urlsToExpand) {
+        for (const QUrl &url : urlsToExpand) {
             const int indexForUrl = index(url);
             if (indexForUrl >= 0) {
                 m_urlsToExpand.remove(url);
@@ -1028,7 +1138,7 @@ void KFileItemModel::slotCanceled()
     Q_EMIT directoryLoadingCanceled();
 }
 
-void KFileItemModel::slotItemsAdded(const QUrl &directoryUrl, const KFileItemListitems)
+void KFileItemModel::slotItemsAdded(const QUrl &directoryUrl, const KFileItemList &items)
 {
     Q_ASSERT(!items.isEmpty());
 
@@ -1061,7 +1171,7 @@ void KFileItemModel::slotItemsAdded(const QUrl &directoryUrl, const KFileItemLis
         }
     }
 
-    const QList<ItemData*> itemDataList = createItemDataList(parentUrl, items);
+    const QList<ItemData *> itemDataList = createItemDataList(parentUrl, items);
 
     if (!m_filter.hasSetFilters()) {
         m_pendingItemsToInsert.append(itemDataList);
@@ -1071,7 +1181,7 @@ void KFileItemModel::slotItemsAdded(const QUrl &directoryUrl, const KFileItemLis
         // The name or type filter is active. Hide filtered items
         // before inserting them into the model and remember
         // the filtered items in m_filteredItems.
-        for (ItemDataitemData : itemDataList) {
+        for (ItemData *itemData : itemDataList) {
             if (m_filter.matches(itemData->item)) {
                 m_pendingItemsToInsert.append(itemData);
                 if (itemData->parent) {
@@ -1135,7 +1245,7 @@ int KFileItemModel::filterChildlessParents(KItemRangeList &removedItemRanges, co
     return filteredParentsCount;
 }
 
-void KFileItemModel::slotItemsDeleted(const KFileItemListitems)
+void KFileItemModel::slotItemsDeleted(const KFileItemList &items)
 {
     dispatchPendingItemsToInsert();
 
@@ -1143,13 +1253,20 @@ void KFileItemModel::slotItemsDeleted(const KFileItemList& items)
     indexesToRemove.reserve(items.count());
     KFileItemList dirsChanged;
 
-    for (const KFileItem& item : items) {
+    const auto currentDir = directory();
+
+    for (const KFileItem &item : items) {
+        if (item.url() == currentDir) {
+            Q_EMIT currentDirectoryRemoved();
+            return;
+        }
+
         const int indexForItem = index(item);
         if (indexForItem >= 0) {
             indexesToRemove.append(indexForItem);
         } else {
             // Probably the item has been filtered.
-            QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.find(item);
+            QHash<KFileItem, ItemData *>::iterator it = m_filteredItems.find(item);
             if (it != m_filteredItems.end()) {
                 delete it.value();
                 m_filteredItems.erase(it);
@@ -1170,7 +1287,7 @@ void KFileItemModel::slotItemsDeleted(const KFileItemList& items)
         indexesToRemoveWithChildren.reserve(m_itemData.count());
 
         const int itemCount = m_itemData.count();
-        for (int index : qAsConst(indexesToRemove)) {
+        for (int index : std::as_const(indexesToRemove)) {
             indexesToRemoveWithChildren.append(index);
 
             const int parentLevel = expandedParentsCount(index);
@@ -1199,7 +1316,7 @@ void KFileItemModel::slotItemsDeleted(const KFileItemList& items)
     Q_EMIT fileItemsChanged(dirsChanged);
 }
 
-void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >& items)
+void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem>> &items)
 {
     Q_ASSERT(!items.isEmpty());
 #ifdef KFILEITEMMODEL_DEBUG
@@ -1219,14 +1336,14 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
 
     // Contains currently hidden items that should
     // get visible and hence removed from m_filteredItems
-    QList<ItemData*> newVisibleItems;
+    QList<ItemData *> newVisibleItems;
 
-    QListIterator<QPair<KFileItem, KFileItem> > it(items);
+    QListIterator<QPair<KFileItem, KFileItem>> it(items);
 
     while (it.hasNext()) {
-        const QPair<KFileItem, KFileItem>itemPair = it.next();
-        const KFileItemoldItem = itemPair.first;
-        const KFileItemnewItem = itemPair.second;
+        const QPair<KFileItem, KFileItem> &itemPair = it.next();
+        const KFileItem &oldItem = itemPair.first;
+        const KFileItem &newItem = itemPair.second;
         const int indexForItem = index(oldItem);
         const bool newItemMatchesFilter = m_filter.matches(newItem);
         if (indexForItem >= 0) {
@@ -1234,11 +1351,11 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
 
             // Keep old values as long as possible if they could not retrieved synchronously yet.
             // The update of the values will be done asynchronously by KFileItemModelRolesUpdater.
-            ItemData * const itemData = m_itemData.at(indexForItem);
+            ItemData *const itemData = m_itemData.at(indexForItem);
             QHashIterator<QByteArray, QVariant> it(retrieveData(newItem, itemData->parent));
             while (it.hasNext()) {
                 it.next();
-                const QByteArrayrole = it.key();
+                const QByteArray &role = it.key();
                 if (itemData->values.value(role) != it.value()) {
                     itemData->values.insert(role, it.value());
                     changedRoles.insert(role);
@@ -1262,7 +1379,7 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
             }
         } else {
             // Check if 'oldItem' is one of the filtered items.
-            QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.find(oldItem);
+            QHash<KFileItem, ItemData *>::iterator it = m_filteredItems.find(oldItem);
             if (it != m_filteredItems.end()) {
                 ItemData *const itemData = it.value();
                 itemData->item = newItem;
@@ -1399,7 +1516,7 @@ void KFileItemModel::dispatchPendingItemsToInsert()
     }
 }
 
-void KFileItemModel::insertItems(QList<ItemData*>& newItems)
+void KFileItemModel::insertItems(QList<ItemData *> &newItems)
 {
     if (newItems.isEmpty()) {
         return;
@@ -1422,8 +1539,7 @@ void KFileItemModel::insertItems(QList<ItemData*>& newItems)
         if (m_sortRole == NameRole) {
             parallelMergeSort(newItems.begin(), newItems.end(), nameLessThan, QThread::idealThreadCount());
         } else if (isRoleValueNatural(m_sortRole)) {
-            auto lambdaLessThan = [&] (const KFileItemModel::ItemData* a, const KFileItemModel::ItemData* b)
-            {
+            auto lambdaLessThan = [&](const KFileItemModel::ItemData *a, const KFileItemModel::ItemData *b) {
                 const QByteArray role = roleForType(m_sortRole);
                 return a->values.value(role).toString() < b->values.value(role).toString();
             };
@@ -1462,7 +1578,7 @@ void KFileItemModel::insertItems(QList<ItemData*>& newItems)
         int rangeCount = 0;
 
         while (sourceIndexNewItems >= 0) {
-            ItemDatanewItem = newItems.at(sourceIndexNewItems);
+            ItemData *newItem = newItems.at(sourceIndexNewItems);
             if (sourceIndexExistingItems >= 0 && lessThan(newItem, m_itemData.at(sourceIndexExistingItems), m_collator)) {
                 // Move an existing item to its new position. If any new items
                 // are behind it, push the item range to itemRanges.
@@ -1491,6 +1607,8 @@ void KFileItemModel::insertItems(QList<ItemData*>& newItems)
         std::reverse(itemRanges.begin(), itemRanges.end());
     }
 
+    // N
+    // resortAllItems();
     // The indexes in m_items are not correct anymore. Therefore, we clear m_items.
     // It will be re-populated with the updated indices if index(const QUrl&) is called.
     m_items.clear();
@@ -1502,7 +1620,7 @@ void KFileItemModel::insertItems(QList<ItemData*>& newItems)
 #endif
 }
 
-void KFileItemModel::removeItems(const KItemRangeListitemRanges, RemoveItemsBehavior behavior)
+void KFileItemModel::removeItems(const KItemRangeList &itemRanges, RemoveItemsBehavior behavior)
 {
     if (itemRanges.isEmpty()) {
         return;
@@ -1512,7 +1630,7 @@ void KFileItemModel::removeItems(const KItemRangeList& itemRanges, RemoveItemsBe
 
     // Step 1: Remove the items from m_itemData, and free the ItemData.
     int removedItemsCount = 0;
-    for (const KItemRangerange : itemRanges) {
+    for (const KItemRange &range : itemRanges) {
         removedItemsCount += range.count;
 
         for (int index = range.index; index < range.index + range.count; ++index) {
@@ -1551,7 +1669,7 @@ void KFileItemModel::removeItems(const KItemRangeList& itemRanges, RemoveItemsBe
     Q_EMIT itemsRemoved(itemRanges);
 }
 
-QList<KFileItemModel::ItemData*> KFileItemModel::createItemDataList(const QUrl& parentUrl, const KFileItemList& items) const
+QList<KFileItemModel::ItemData *> KFileItemModel::createItemDataList(const QUrl &parentUrl, const KFileItemList &items) const
 {
     if (m_sortRole == TypeRole) {
         // Try to resolve the MIME-types synchronously to prevent a reordering of
@@ -1564,11 +1682,11 @@ QList<KFileItemModel::ItemData*> KFileItemModel::createItemDataList(const QUrl&
     const int parentIndex = index(parentUrl);
     ItemData *parentItem = parentIndex < 0 ? m_filteredItems.value(KFileItem(parentUrl), nullptr) : m_itemData.at(parentIndex);
 
-    QList<ItemData*> itemDataList;
+    QList<ItemData *> itemDataList;
     itemDataList.reserve(items.count());
 
-    for (const KFileItemitem : items) {
-        ItemDataitemData = new ItemData();
+    for (const KFileItem &item : items) {
+        ItemData *itemData = new ItemData();
         itemData->item = item;
         itemData->parent = parentItem;
         itemDataList.append(itemData);
@@ -1577,9 +1695,10 @@ QList<KFileItemModel::ItemData*> KFileItemModel::createItemDataList(const QUrl&
     return itemDataList;
 }
 
-void KFileItemModel::prepareItemsForSorting(QList<ItemData*>& itemDataList)
+void KFileItemModel::prepareItemsForSorting(QList<ItemData *> &itemDataList)
 {
     switch (m_sortRole) {
+    case ExtensionRole:
     case PermissionsRole:
     case OwnerRole:
     case GroupRole:
@@ -1588,7 +1707,7 @@ void KFileItemModel::prepareItemsForSorting(QList<ItemData*>& itemDataList)
     case DeletionTimeRole:
         // These roles can be determined with retrieveData, and they have to be stored
         // in the QHash "values" for the sorting.
-        for (ItemData* itemData : qAsConst(itemDataList)) {
+        for (ItemData *itemData : std::as_const(itemDataList)) {
             if (itemData->values.isEmpty()) {
                 itemData->values = retrieveData(itemData->item, itemData->parent);
             }
@@ -1597,7 +1716,7 @@ void KFileItemModel::prepareItemsForSorting(QList<ItemData*>& itemDataList)
 
     case TypeRole:
         // At least store the data including the file type for items with known MIME type.
-        for (ItemData* itemData : qAsConst(itemDataList)) {
+        for (ItemData *itemData : std::as_const(itemDataList)) {
             if (itemData->values.isEmpty()) {
                 const KFileItem item = itemData->item;
                 if (item.isDir() || item.isMimeTypeKnown()) {
@@ -1617,11 +1736,11 @@ void KFileItemModel::prepareItemsForSorting(QList<ItemData*>& itemDataList)
     }
 }
 
-int KFileItemModel::expandedParentsCount(const ItemDatadata)
+int KFileItemModel::expandedParentsCount(const ItemData *data)
 {
     // The hash 'values' is only guaranteed to contain the key "expandedParentsCount"
     // if the corresponding item is expanded, and it is not a top-level item.
-    const ItemDataparent = data->parent;
+    const ItemData *parent = data->parent;
     if (parent) {
         if (parent->parent) {
             Q_ASSERT(parent->values.contains("expandedParentsCount"));
@@ -1640,7 +1759,7 @@ void KFileItemModel::removeExpandedItems()
 
     const int maxIndex = m_itemData.count() - 1;
     for (int i = 0; i <= maxIndex; ++i) {
-        const ItemDataitemData = m_itemData.at(i);
+        const ItemData *itemData = m_itemData.at(i);
         if (itemData->parent) {
             indexesToRemove.append(i);
         }
@@ -1650,8 +1769,8 @@ void KFileItemModel::removeExpandedItems()
     m_expandedDirs.clear();
 
     // Also remove all filtered items which have a parent.
-    QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.begin();
-    const QHash<KFileItem, ItemData*>::iterator end = m_filteredItems.end();
+    QHash<KFileItem, ItemData *>::iterator it = m_filteredItems.begin();
+    const QHash<KFileItem, ItemData *>::iterator end = m_filteredItems.end();
 
     while (it != end) {
         if (it.value()->parent) {
@@ -1663,14 +1782,15 @@ void KFileItemModel::removeExpandedItems()
     }
 }
 
-void KFileItemModel::emitItemsChangedAndTriggerResorting(const KItemRangeList& itemRanges, const QSet<QByteArray>& changedRoles)
+void KFileItemModel::emitItemsChangedAndTriggerResorting(const KItemRangeList &itemRanges, const QSet<QByteArray> &changedRoles)
 {
     Q_EMIT itemsChanged(itemRanges, changedRoles);
 
     // Trigger a resorting if necessary. Note that this can happen even if the sort
     // role has not changed at all because the file name can be used as a fallback.
-    if (changedRoles.contains(sortRole()) || changedRoles.contains(roleForType(NameRole))) {
-        for (const KItemRange& range : itemRanges) {
+    if (changedRoles.contains(sortRole()) || changedRoles.contains(roleForType(NameRole))
+        || (changedRoles.contains("count") && sortRole() == "size")) { // "count" is used in the "size" sort role, so this might require a resorting.
+        for (const KItemRange &range : itemRanges) {
             bool needsResorting = false;
 
             const int first = range.index;
@@ -1680,11 +1800,9 @@ void KFileItemModel::emitItemsChangedAndTriggerResorting(const KItemRangeList& i
             // (a)  The first item in the range is "lessThan" its predecessor,
             // (b)  the successor of the last item is "lessThan" the last item, or
             // (c)  the internal order of the items in the range is incorrect.
-            if (first > 0
-                && lessThan(m_itemData.at(first), m_itemData.at(first - 1), m_collator)) {
+            if (first > 0 && lessThan(m_itemData.at(first), m_itemData.at(first - 1), m_collator)) {
                 needsResorting = true;
-            } else if (last < count() - 1
-                && lessThan(m_itemData.at(last + 1), m_itemData.at(last), m_collator)) {
+            } else if (last < count() - 1 && lessThan(m_itemData.at(last + 1), m_itemData.at(last), m_collator)) {
                 needsResorting = true;
             } else {
                 for (int index = first; index < last; ++index) {
@@ -1696,7 +1814,7 @@ void KFileItemModel::emitItemsChangedAndTriggerResorting(const KItemRangeList& i
             }
 
             if (needsResorting) {
-                m_resortAllItemsTimer->start();
+                scheduleResortAllItems();
                 return;
             }
         }
@@ -1722,14 +1840,14 @@ void KFileItemModel::resetRoles()
     }
 }
 
-KFileItemModel::RoleType KFileItemModel::typeForRole(const QByteArrayrole) const
+KFileItemModel::RoleType KFileItemModel::typeForRole(const QByteArray &role) const
 {
     static QHash<QByteArray, RoleType> roles;
     if (roles.isEmpty()) {
         // Insert user visible roles that can be accessed with
         // KFileItemModel::roleInformation()
         int count = 0;
-        const RoleInfoMapmap = rolesInfoMap(count);
+        const RoleInfoMap *map = rolesInfoMap(count);
         for (int i = 0; i < count; ++i) {
             roles.insert(map[i].role, map[i].roleType);
         }
@@ -1756,7 +1874,7 @@ QByteArray KFileItemModel::roleForType(RoleType roleType) const
         // Insert user visible roles that can be accessed with
         // KFileItemModel::roleInformation()
         int count = 0;
-        const RoleInfoMapmap = rolesInfoMap(count);
+        const RoleInfoMap *map = rolesInfoMap(count);
         for (int i = 0; i < count; ++i) {
             roles.insert(map[i].roleType, map[i].role);
         }
@@ -1776,7 +1894,7 @@ QByteArray KFileItemModel::roleForType(RoleType roleType) const
     return roles.value(roleType);
 }
 
-QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item, const ItemData* parent) const
+QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem &item, const ItemData *parent) const
 {
     // It is important to insert only roles that are fast to retrieve. E.g.
     // KFileItem::iconName() can be very expensive if the MIME-type is unknown
@@ -1794,13 +1912,18 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item,
     }
 
     if (m_requestRole[IsHiddenRole]) {
-        data.insert(sharedValue("isHidden"), item.isHidden());
+        data.insert(sharedValue("isHidden"), item.isHidden() || item.mimetype() == QStringLiteral("application/x-trash"));
     }
 
     if (m_requestRole[NameRole]) {
         data.insert(sharedValue("text"), item.text());
     }
 
+    if (m_requestRole[ExtensionRole] && !isDir) {
+        // TODO KF6 use KFileItem::suffix 464722
+        data.insert(sharedValue("extension"), QFileInfo(item.name()).suffix());
+    }
+
     if (m_requestRole[SizeRole] && !isDir) {
         data.insert(sharedValue("size"), item.size());
     }
@@ -1830,7 +1953,7 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item,
     }
 
     if (m_requestRole[PermissionsRole]) {
-        data.insert(sharedValue("permissions"), item.permissionsString());
+        data.insert(sharedValue("permissions"), QVariantList() << item.permissionsString() << item.permissions());
     }
 
     if (m_requestRole[OwnerRole]) {
@@ -1911,7 +2034,7 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item,
     return data;
 }
 
-bool KFileItemModel::lessThan(const ItemData* a, const ItemData* b, const QCollator& collator) const
+bool KFileItemModel::lessThan(const ItemData *a, const ItemData *b, const QCollator &collator) const
 {
     int result = 0;
 
@@ -1946,37 +2069,39 @@ bool KFileItemModel::lessThan(const ItemData* a, const ItemData* b, const QColla
         }
     }
 
-    // Show hidden files and folders last
-    if (m_sortHiddenLast) {
-        const bool isHiddenA = a->item.isHidden();
-        const bool isHiddenB = b->item.isHidden();
-        if (isHiddenA && !isHiddenB) {
-            return false;
-        } else if (!isHiddenA && isHiddenB) {
-            return true;
+    result = groupRoleCompare(a, b, collator);
+    if (result == 0) {
+        // Show hidden files and folders last
+        if (m_sortHiddenLast) {
+            const bool isHiddenA = a->item.isHidden();
+            const bool isHiddenB = b->item.isHidden();
+            if (isHiddenA && !isHiddenB) {
+                return false;
+            } else if (!isHiddenA && isHiddenB) {
+                return true;
+            }
         }
-    }
-
-    if (m_sortDirsFirst || (DetailsModeSettings::directorySizeCount() && m_sortRole == SizeRole)) {
-        const bool isDirA = a->item.isDir();
-        const bool isDirB = b->item.isDir();
-        if (isDirA && !isDirB) {
-            return true;
-        } else if (!isDirA && isDirB) {
-            return false;
+        if (m_sortDirsFirst
+            || (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount && m_sortRole == SizeRole)) {
+            const bool isDirA = a->item.isDir();
+            const bool isDirB = b->item.isDir();
+            if (isDirA && !isDirB) {
+                return true;
+            } else if (!isDirA && isDirB) {
+                return false;
+            }
         }
+        result = sortRoleCompare(a, b, collator);
+        result = (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0;
+    } else {
+        result = (groupOrder() == Qt::AscendingOrder) ? result < 0 : result > 0;
     }
-
-    result = sortRoleCompare(a, b, collator);
-
-    return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0;
+    return result;
 }
 
-void KFileItemModel::sort(const QList<KFileItemModel::ItemData*>::iterator &begin,
-                          const QList<KFileItemModel::ItemData*>::iterator &end) const
+void KFileItemModel::sort(const QList<KFileItemModel::ItemData *>::iterator &begin, const QList<KFileItemModel::ItemData *>::iterator &end) const
 {
-    auto lambdaLessThan = [&] (const KFileItemModel::ItemData* a, const KFileItemModel::ItemData* b)
-    {
+    auto lambdaLessThan = [&](const KFileItemModel::ItemData *a, const KFileItemModel::ItemData *b) {
         return lessThan(a, b, m_collator);
     };
 
@@ -1993,15 +2118,15 @@ void KFileItemModel::sort(const QList<KFileItemModel::ItemData*>::iterator &begi
     }
 }
 
-int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const QCollator& collator) const
+int KFileItemModel::sortRoleCompare(const ItemData *a, const ItemData *b, const QCollator &collator) const
 {
     // This function must never return 0, because that would break stable
     // sorting, which leads to all kinds of bugs.
     // See: https://bugs.kde.org/show_bug.cgi?id=433247
     // If two items have equal sort values, let the fallbacks at the bottom of
     // the function handle it.
-    const KFileItemitemA = a->item;
-    const KFileItemitemB = b->item;
+    const KFileItem &itemA = a->item;
+    const KFileItem &itemB = b->item;
 
     int result = 0;
 
@@ -2011,7 +2136,7 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const
         break;
 
     case SizeRole: {
-        if (DetailsModeSettings::directorySizeCount() && itemA.isDir()) {
+        if (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount && itemA.isDir()) {
             // folders first then
             // items A and B are folders thanks to lessThan checks
             auto valueA = a->values.value("count");
@@ -2063,6 +2188,17 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const
         break;
     }
 
+    case AccessTimeRole: {
+        const long long dateTimeA = itemA.entry().numberValue(KIO::UDSEntry::UDS_ACCESS_TIME, -1);
+        const long long dateTimeB = itemB.entry().numberValue(KIO::UDSEntry::UDS_ACCESS_TIME, -1);
+        if (dateTimeA < dateTimeB) {
+            return -1;
+        } else if (dateTimeA > dateTimeB) {
+            return +1;
+        }
+        break;
+    }
+
     case CreationTimeRole: {
         const long long dateTimeA = itemA.entry().numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1);
         const long long dateTimeB = itemB.entry().numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1);
@@ -2088,6 +2224,8 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const
     case RatingRole:
     case WidthRole:
     case HeightRole:
+    case PublisherRole:
+    case PageCountRole:
     case WordCountRole:
     case LineCountRole:
     case TrackRole:
@@ -2096,7 +2234,7 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const
         break;
     }
 
-   case DimensionsRole: {
+    case DimensionsRole: {
         const QByteArray role = roleForType(m_sortRole);
         const QSize dimensionsA = a->values.value(role).toSize();
         const QSize dimensionsB = b->values.value(role).toSize();
@@ -2124,7 +2262,6 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const
         }
         break;
     }
-
     }
 
     if (result != 0) {
@@ -2150,7 +2287,142 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const
     return QString::compare(itemA.url().url(), itemB.url().url(), Qt::CaseSensitive);
 }
 
-int KFileItemModel::stringCompare(const QString& a, const QString& b, const QCollator& collator) const
+int KFileItemModel::groupRoleCompare(const ItemData *a, const ItemData *b, const QCollator &collator) const
+{
+    // Unlike sortRoleCompare, this function can and often will return 0.
+    const KFileItem &itemA = a->item;
+    const KFileItem &itemB = b->item;
+
+    int result = 0;
+
+    switch (m_groupRole) {
+    case NoRole:
+        break;
+    case NameRole: {
+        QChar groupA = getNameRoleGroup(a, false).toChar();
+        QChar groupB = getNameRoleGroup(b, false).toChar();
+        if (groupA < groupB) {
+            result = -1;
+        } else if (groupA > groupB) {
+            result = 1;
+        }
+        break;
+    }
+    case SizeRole: {
+        int groupA = getSizeRoleGroup(a, false).toInt();
+        int groupB = getSizeRoleGroup(b, false).toInt();
+        if (groupA < groupB) {
+            result = -1;
+        } else if (groupA > groupB) {
+            result = 1;
+        }
+        break;
+    }
+    case ModificationTimeRole: {
+        int groupA = getTimeRoleGroup(
+                         [](const ItemData *item) {
+                             return item->item.time(KFileItem::ModificationTime);
+                         },
+                         a,
+                         false)
+                         .toInt();
+        int groupB = getTimeRoleGroup(
+                         [](const ItemData *item) {
+                             return item->item.time(KFileItem::ModificationTime);
+                         },
+                         b,
+                         false)
+                         .toInt();
+        if (groupA < groupB) {
+            result = -1;
+        } else if (groupA > groupB) {
+            result = 1;
+        }
+        break;
+    }
+    case CreationTimeRole: {
+        int groupA = getTimeRoleGroup(
+                         [](const ItemData *item) {
+                             return item->item.time(KFileItem::CreationTime);
+                         },
+                         a,
+                         false)
+                         .toInt();
+        int groupB = getTimeRoleGroup(
+                         [](const ItemData *item) {
+                             return item->item.time(KFileItem::CreationTime);
+                         },
+                         b,
+                         false)
+                         .toInt();
+        if (groupA < groupB) {
+            result = -1;
+        } else if (groupA > groupB) {
+            result = 1;
+        }
+        break;
+    }
+    case AccessTimeRole: {
+        int groupA = getTimeRoleGroup(
+                         [](const ItemData *item) {
+                             return item->item.time(KFileItem::AccessTime);
+                         },
+                         a,
+                         false)
+                         .toInt();
+        int groupB = getTimeRoleGroup(
+                         [](const ItemData *item) {
+                             return item->item.time(KFileItem::AccessTime);
+                         },
+                         b,
+                         false)
+                         .toInt();
+        if (groupA < groupB) {
+            result = -1;
+        } else if (groupA > groupB) {
+            result = 1;
+        }
+        break;
+    }
+    case DeletionTimeRole: {
+        int groupA = getTimeRoleGroup(
+                         [](const ItemData *item) {
+                             return item->values.value("deletiontime").toDateTime();
+                         },
+                         a,
+                         false)
+                         .toInt();
+        int groupB = getTimeRoleGroup(
+                         [](const ItemData *item) {
+                             return item->values.value("deletiontime").toDateTime();
+                         },
+                         b,
+                         false)
+                         .toInt();
+        if (groupA < groupB) {
+            result = -1;
+        } else if (groupA > groupB) {
+            result = 1;
+        }
+        break;
+    }
+    // case PermissionsRole:
+    //  case RatingRole:
+    default: {
+        QString groupA = getGenericStringRoleGroup(groupRole(), a);
+        QString groupB = getGenericStringRoleGroup(groupRole(), b);
+        if (groupA < groupB) {
+            result = -1;
+        } else if (groupA > groupB) {
+            result = 1;
+        }
+        break;
+    }
+    }
+    return result;
+}
+
+int KFileItemModel::stringCompare(const QString &a, const QString &b, const QCollator &collator) const
 {
     QMutexLocker collatorLock(s_collatorMutex());
 
@@ -2169,279 +2441,360 @@ int KFileItemModel::stringCompare(const QString& a, const QString& b, const QCol
     return QString::compare(a, b, Qt::CaseSensitive);
 }
 
-QList<QPair<int, QVariant> > KFileItemModel::nameRoleGroups() const
+QVariant KFileItemModel::getNameRoleGroup(const ItemData *itemData, bool asString) const
 {
-    Q_ASSERT(!m_itemData.isEmpty());
-
-    const int maxIndex = count() - 1;
-    QList<QPair<int, QVariant> > groups;
-
-    QString groupValue;
-    QChar firstChar;
-    for (int i = 0; i <= maxIndex; ++i) {
-        if (isChildItem(i)) {
-            continue;
-        }
-
-        const QString name = m_itemData.at(i)->item.text();
-
-        // Use the first character of the name as group indication
-        QChar newFirstChar = name.at(0).toUpper();
-        if (newFirstChar == QLatin1Char('~') && name.length() > 1) {
-            newFirstChar = name.at(1).toUpper();
-        }
-
-        if (firstChar != newFirstChar) {
-            QString newGroupValue;
-            if (newFirstChar.isLetter()) {
-
-                if (m_collator.compare(newFirstChar, QChar(QLatin1Char('A'))) >= 0 && m_collator.compare(newFirstChar, QChar(QLatin1Char('Z'))) <= 0) {
-                    // WARNING! Symbols based on latin 'Z' like 'Z' with acute are treated wrong as non Latin and put in a new group.
-
-                    // Try to find a matching group in the range 'A' to 'Z'.
-                    static std::vector<QChar> lettersAtoZ;
-                    lettersAtoZ.reserve('Z' - 'A' + 1);
-                    if (lettersAtoZ.empty()) {
-                        for (char c = 'A'; c <= 'Z'; ++c) {
-                            lettersAtoZ.push_back(QLatin1Char(c));
-                        }
-                    }
-
-                    auto localeAwareLessThan = [this](QChar c1, QChar c2) -> bool {
-                        return m_collator.compare(c1, c2) < 0;
-                    };
+    const KFileItem item = itemData->item;
+    const QString name = item.text();
+    QVariant newGroupValue;
+    // Use the first character of the name as group indication
+    QChar newFirstChar = name.at(0).toUpper();
+    if (newFirstChar == QLatin1Char('~') && name.length() > 1) {
+        newFirstChar = name.at(1).toUpper();
+    }
 
-                    std::vector<QChar>::iterator it = std::lower_bound(lettersAtoZ.begin(), lettersAtoZ.end(), newFirstChar, localeAwareLessThan);
-                    if (it != lettersAtoZ.end()) {
-                        if (localeAwareLessThan(newFirstChar, *it)) {
-                            // newFirstChar belongs to the group preceding *it.
-                            // Example: for an umlaut 'A' in the German locale, *it would be 'B' now.
-                            --it;
-                        }
-                        newGroupValue = *it;
-                    }
+    if (newFirstChar.isLetter()) {
+        if (m_collator.compare(newFirstChar, QChar(QLatin1Char('A'))) >= 0 && m_collator.compare(newFirstChar, QChar(QLatin1Char('Z'))) <= 0) {
+            // WARNING! Symbols based on latin 'Z' like 'Z' with acute are treated wrong as non Latin and put in a new group.
 
-                } else {
-                    // Symbols from non Latin-based scripts
-                    newGroupValue = newFirstChar;
+            // Try to find a matching group in the range 'A' to 'Z'.
+            static std::vector<QChar> lettersAtoZ;
+            lettersAtoZ.reserve('Z' - 'A' + 1);
+            if (lettersAtoZ.empty()) {
+                for (char c = 'A'; c <= 'Z'; ++c) {
+                    lettersAtoZ.push_back(QLatin1Char(c));
                 }
-            } else if (newFirstChar >= QLatin1Char('0') && newFirstChar <= QLatin1Char('9')) {
-                // Apply group '0 - 9' for any name that starts with a digit
-                newGroupValue = i18nc("@title:group Groups that start with a digit", "0 - 9");
-            } else {
-                newGroupValue = i18nc("@title:group", "Others");
             }
 
-            if (newGroupValue != groupValue) {
-                groupValue = newGroupValue;
-                groups.append(QPair<int, QVariant>(i, newGroupValue));
+            auto localeAwareLessThan = [this](QChar c1, QChar c2) -> bool {
+                return m_collator.compare(c1, c2) < 0;
+            };
+
+            std::vector<QChar>::iterator it = std::lower_bound(lettersAtoZ.begin(), lettersAtoZ.end(), newFirstChar, localeAwareLessThan);
+            if (it != lettersAtoZ.end()) {
+                if (localeAwareLessThan(newFirstChar, *it)) {
+                    // newFirstChar belongs to the group preceding *it.
+                    // Example: for an umlaut 'A' in the German locale, *it would be 'B' now.
+                    --it;
+                }
+                newGroupValue = *it;
             }
 
-            firstChar = newFirstChar;
+        } else {
+            // Symbols from non Latin-based scripts
+            newGroupValue = newFirstChar;
+        }
+    } else if (newFirstChar >= QLatin1Char('0') && newFirstChar <= QLatin1Char('9')) {
+        // Apply group '0 - 9' for any name that starts with a digit
+        if (asString) {
+            newGroupValue = i18nc("@title:group Groups that start with a digit", "0 - 9");
+        } else {
+            newGroupValue = QChar('0');
+        }
+    } else {
+        if (asString) {
+            newGroupValue = i18nc("@title:group", "Others");
+        } else {
+            newGroupValue = QChar('.');
         }
     }
-    return groups;
+    return newGroupValue;
 }
 
-QList<QPair<int, QVariant> > KFileItemModel::sizeRoleGroups() const
+QVariant KFileItemModel::getSizeRoleGroup(const ItemData *itemData, bool asString) const
 {
-    Q_ASSERT(!m_itemData.isEmpty());
-
-    const int maxIndex = count() - 1;
-    QList<QPair<int, QVariant> > groups;
-
-    QString groupValue;
-    for (int i = 0; i <= maxIndex; ++i) {
-        if (isChildItem(i)) {
-            continue;
-        }
+    const KFileItem item = itemData->item;
 
-        const KFileItem& item = m_itemData.at(i)->item;
-        KIO::filesize_t fileSize = !item.isNull() ? item.size() : ~0U;
-        QString newGroupValue;
-        if (!item.isNull() && item.isDir()) {
-            if (DetailsModeSettings::directorySizeCount() || m_sortDirsFirst) {
-                newGroupValue = i18nc("@title:group Size", "Folders");
-            } else {
-                fileSize = m_itemData.at(i)->values.value("size").toULongLong();
-            }
-        }
-
-        if (newGroupValue.isEmpty()) {
-            if (fileSize < 5 * 1024 * 1024) { // < 5 MB
-                newGroupValue = i18nc("@title:group Size", "Small");
-            } else if (fileSize < 10 * 1024 * 1024) { // < 10 MB
-                newGroupValue = i18nc("@title:group Size", "Medium");
-            } else {
-                newGroupValue = i18nc("@title:group Size", "Big");
-            }
+    KIO::filesize_t fileSize = !item.isNull() ? item.size() : ~0U;
+    int newGroupValue = -1; // None
+    if (!item.isNull() && item.isDir()) {
+        if (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount || m_sortDirsFirst) {
+            newGroupValue = 0; // Folders
+        } else {
+            fileSize = itemData->values.value("size").toULongLong();
         }
+    }
 
-        if (newGroupValue != groupValue) {
-            groupValue = newGroupValue;
-            groups.append(QPair<int, QVariant>(i, newGroupValue));
+    if (newGroupValue < 0) {
+        if (fileSize < 5 * 1024 * 1024) { // < 5 MB
+            newGroupValue = 1; // Small
+        } else if (fileSize < 10 * 1024 * 1024) { // < 10 MB
+            newGroupValue = 2; // Medium
+        } else {
+            newGroupValue = 3; // Big
         }
     }
 
-    return groups;
+    if (asString) {
+        char const *groupNames[] = {"Folders", "Small", "Medium", "Big"};
+        return i18nc("@title:group Size", groupNames[newGroupValue]);
+    } else {
+        return newGroupValue;
+    }
 }
 
-QList<QPair<int, QVariant> > KFileItemModel::timeRoleGroups(const std::function<QDateTime(const ItemData *)> &fileTimeCb) const
+QVariant KFileItemModel::getTimeRoleGroup(const std::function<QDateTime(const ItemData *)> &fileTimeCb, const ItemData *itemData, bool asString) const
 {
-    Q_ASSERT(!m_itemData.isEmpty());
-
-    const int maxIndex = count() - 1;
-    QList<QPair<int, QVariant> > groups;
-
     const QDate currentDate = QDate::currentDate();
-
-    QDate previousFileDate;
-    QString groupValue;
-    for (int i = 0; i <= maxIndex; ++i) {
-        if (isChildItem(i)) {
-            continue;
-        }
-
-        const QDateTime fileTime = fileTimeCb(m_itemData.at(i));
-        const QDate fileDate = fileTime.date();
-        if (fileDate == previousFileDate) {
-            // The current item is in the same group as the previous item
-            continue;
+    const QDateTime fileTime = fileTimeCb(itemData);
+    const QDate fileDate = fileTime.date();
+    const int daysDistance = fileDate.daysTo(currentDate);
+
+    int intGroupValue;
+    QString strGroupValue;
+
+    if (!asString) {
+        // Simplified grouping algorithm, preserving dates
+        // but not taking "pretty printing" into account
+        if (currentDate.year() == fileDate.year() && currentDate.month() == fileDate.month()) {
+            if (daysDistance < 7) {
+                intGroupValue = daysDistance; // Today, Yesterday and week days
+            } else if (daysDistance < 14) {
+                intGroupValue = 10; // One Week Ago
+            } else if (daysDistance < 21) {
+                intGroupValue = 20; // Two Weeks Ago
+            } else if (daysDistance < 28) {
+                intGroupValue = 30; // Three Weeks Ago
+            } else {
+                intGroupValue = 40; // Earlier This Month
+            }
+        } else {
+            const QDate lastMonthDate = currentDate.addMonths(-1);
+            if (lastMonthDate.year() == fileDate.year() && lastMonthDate.month() == fileDate.month()) {
+                if (daysDistance < 7) {
+                    intGroupValue = daysDistance; // Today, Yesterday and week days (Month, Year)
+                } else if (daysDistance < 14) {
+                    intGroupValue = 9; // One Week Ago (Month, Year)
+                } else if (daysDistance < 21) {
+                    intGroupValue = 19; // Two Weeks Ago (Month, Year)
+                } else if (daysDistance < 28) {
+                    intGroupValue = 29; // Three Weeks Ago (Month, Year)
+                } else {
+                    intGroupValue = 39; // Earlier on Month, Year
+                }
+            } else {
+                // The trick will fail for dates past April, 178956967 or before 1 AD.
+                intGroupValue = 2147483647 - (fileDate.year() * 12 + fileDate.month() - 1); // Month, Year; newer < older
+            }
         }
-        previousFileDate = fileDate;
-
-        const int daysDistance = fileDate.daysTo(currentDate);
-
-        QString newGroupValue;
-        if (currentDate.year() == fileDate.year() &&
-            currentDate.month() == fileDate.month()) {
-
+        return QVariant(intGroupValue);
+    } else {
+        if (currentDate.year() == fileDate.year() && currentDate.month() == fileDate.month()) {
             switch (daysDistance / 7) {
             case 0:
                 switch (daysDistance) {
-                case 0:  newGroupValue = i18nc("@title:group Date", "Today"); break;
-                case 1:  newGroupValue = i18nc("@title:group Date", "Yesterday"); break;
+                case 0:
+                    strGroupValue = i18nc("@title:group Date", "Today");
+                    break;
+                case 1:
+                    strGroupValue = i18nc("@title:group Date", "Yesterday");
+                    break;
                 default:
-                    newGroupValue = fileTime.toString(
-                        i18nc("@title:group Date: The week day name: dddd", "dddd"));
-                    newGroupValue = i18nc("Can be used to script translation of \"dddd\""
-                        "with context @title:group Date", "%1", newGroupValue);
+                    strGroupValue = fileTime.toString(i18nc("@title:group Date: The week day name: dddd", "dddd"));
+                    strGroupValue = i18nc(
+                        "Can be used to script translation of \"dddd\""
+                        "with context @title:group Date",
+                        "%1",
+                        strGroupValue);
                 }
                 break;
             case 1:
-                newGroupValue = i18nc("@title:group Date", "One Week Ago");
+                strGroupValue = i18nc("@title:group Date", "One Week Ago");
                 break;
             case 2:
-                newGroupValue = i18nc("@title:group Date", "Two Weeks Ago");
+                strGroupValue = i18nc("@title:group Date", "Two Weeks Ago");
                 break;
             case 3:
-                newGroupValue = i18nc("@title:group Date", "Three Weeks Ago");
+                strGroupValue = i18nc("@title:group Date", "Three Weeks Ago");
                 break;
             case 4:
             case 5:
-                newGroupValue = i18nc("@title:group Date", "Earlier this Month");
+                strGroupValue = i18nc("@title:group Date", "Earlier this Month");
                 break;
             default:
                 Q_ASSERT(false);
             }
         } else {
             const QDate lastMonthDate = currentDate.addMonths(-1);
-            if  (lastMonthDate.year() == fileDate.year() &&
-                 lastMonthDate.month() == fileDate.month()) {
-
+            if (lastMonthDate.year() == fileDate.year() && lastMonthDate.month() == fileDate.month()) {
                 if (daysDistance == 1) {
-                    const KLocalizedString format = ki18nc("@title:group Date: "
-                                                    "MMMM is full month name in current locale, and yyyy is "
-                                                    "full year number. You must keep the ' don't use any fancy \" or Â« or similar. The ' is not shown to the user, it's there to mark a part of the text that should not be formatted as a date", "'Yesterday' (MMMM, yyyy)");
+                    const KLocalizedString format = ki18nc(
+                        "@title:group Date: "
+                        "MMMM is full month name in current locale, and yyyy is "
+                        "full year number. You must keep the ' don't use any fancy \" or Â« or similar. The ' is not shown to the user, it's there to mark a "
+                        "part of the text that should not be formatted as a date",
+                        "'Yesterday' (MMMM, yyyy)");
                     const QString translatedFormat = format.toString();
                     if (translatedFormat.count(QLatin1Char('\'')) == 2) {
-                        newGroupValue = fileTime.toString(translatedFormat);
-                        newGroupValue = i18nc("Can be used to script translation of "
+                        strGroupValue = fileTime.toString(translatedFormat);
+                        strGroupValue = i18nc(
+                            "Can be used to script translation of "
                             "\"'Yesterday' (MMMM, yyyy)\" with context @title:group Date",
-                            "%1", newGroupValue);
+                            "%1",
+                            strGroupValue);
                     } else {
-                        qCWarning(DolphinDebug).nospace() << "A wrong translation was found: " << translatedFormat << ". Please file a bug report at bugs.kde.org";
-                        const QString untranslatedFormat = format.toString({ QLatin1String("en_US") });
-                        newGroupValue = fileTime.toString(untranslatedFormat);
+                        qCWarning(DolphinDebug).nospace()
+                            << "A wrong translation was found: " << translatedFormat << ". Please file a bug report at bugs.kde.org";
+                        const QString untranslatedFormat = format.toString({QLatin1String("en_US")});
+                        strGroupValue = fileTime.toString(untranslatedFormat);
                     }
                 } else if (daysDistance <= 7) {
-                    newGroupValue = fileTime.toString(i18nc("@title:group Date: "
-                        "The week day name: dddd, MMMM is full month name "
-                        "in current locale, and yyyy is full year number.",
-                        "dddd (MMMM, yyyy)"));
-                    newGroupValue = i18nc("Can be used to script translation of "
+                    strGroupValue =
+                        fileTime.toString(i18nc("@title:group Date: "
+                                                "The week day name: dddd, MMMM is full month name "
+                                                "in current locale, and yyyy is full year number.",
+                                                "dddd (MMMM, yyyy)"));
+                    strGroupValue = i18nc(
+                        "Can be used to script translation of "
                         "\"dddd (MMMM, yyyy)\" with context @title:group Date",
-                        "%1", newGroupValue);
+                        "%1",
+                        strGroupValue);
                 } else if (daysDistance <= 7 * 2) {
-                    const KLocalizedString format = ki18nc("@title:group Date: "
-                                                           "MMMM is full month name in current locale, and yyyy is "
-                                                           "full year number. You must keep the ' don't use any fancy \" or Â« or similar. The ' is not shown to the user, it's there to mark a part of the text that should not be formatted as a date", "'One Week Ago' (MMMM, yyyy)");
+                    const KLocalizedString format = ki18nc(
+                        "@title:group Date: "
+                        "MMMM is full month name in current locale, and yyyy is "
+                        "full year number. You must keep the ' don't use any fancy \" or Â« or similar. The ' is not shown to the user, it's there to mark a "
+                        "part of the text that should not be formatted as a date",
+                        "'One Week Ago' (MMMM, yyyy)");
                     const QString translatedFormat = format.toString();
                     if (translatedFormat.count(QLatin1Char('\'')) == 2) {
-                        newGroupValue = fileTime.toString(translatedFormat);
-                        newGroupValue = i18nc("Can be used to script translation of "
+                        strGroupValue = fileTime.toString(translatedFormat);
+                        strGroupValue = i18nc(
+                            "Can be used to script translation of "
                             "\"'One Week Ago' (MMMM, yyyy)\" with context @title:group Date",
-                            "%1", newGroupValue);
+                            "%1",
+                            strGroupValue);
                     } else {
-                        qCWarning(DolphinDebug).nospace() << "A wrong translation was found: " << translatedFormat << ". Please file a bug report at bugs.kde.org";
-                        const QString untranslatedFormat = format.toString({ QLatin1String("en_US") });
-                        newGroupValue = fileTime.toString(untranslatedFormat);
+                        qCWarning(DolphinDebug).nospace()
+                            << "A wrong translation was found: " << translatedFormat << ". Please file a bug report at bugs.kde.org";
+                        const QString untranslatedFormat = format.toString({QLatin1String("en_US")});
+                        strGroupValue = fileTime.toString(untranslatedFormat);
                     }
                 } else if (daysDistance <= 7 * 3) {
-                    const KLocalizedString format = ki18nc("@title:group Date: "
-                                                           "MMMM is full month name in current locale, and yyyy is "
-                                                           "full year number. You must keep the ' don't use any fancy \" or Â« or similar. The ' is not shown to the user, it's there to mark a part of the text that should not be formatted as a date", "'Two Weeks Ago' (MMMM, yyyy)");
+                    const KLocalizedString format = ki18nc(
+                        "@title:group Date: "
+                        "MMMM is full month name in current locale, and yyyy is "
+                        "full year number. You must keep the ' don't use any fancy \" or Â« or similar. The ' is not shown to the user, it's there to mark a "
+                        "part of the text that should not be formatted as a date",
+                        "'Two Weeks Ago' (MMMM, yyyy)");
                     const QString translatedFormat = format.toString();
                     if (translatedFormat.count(QLatin1Char('\'')) == 2) {
-                        newGroupValue = fileTime.toString(translatedFormat);
-                        newGroupValue = i18nc("Can be used to script translation of "
+                        strGroupValue = fileTime.toString(translatedFormat);
+                        strGroupValue = i18nc(
+                            "Can be used to script translation of "
                             "\"'Two Weeks Ago' (MMMM, yyyy)\" with context @title:group Date",
-                            "%1", newGroupValue);
+                            "%1",
+                            strGroupValue);
                     } else {
-                        qCWarning(DolphinDebug).nospace() << "A wrong translation was found: " << translatedFormat << ". Please file a bug report at bugs.kde.org";
-                        const QString untranslatedFormat = format.toString({ QLatin1String("en_US") });
-                        newGroupValue = fileTime.toString(untranslatedFormat);
+                        qCWarning(DolphinDebug).nospace()
+                            << "A wrong translation was found: " << translatedFormat << ". Please file a bug report at bugs.kde.org";
+                        const QString untranslatedFormat = format.toString({QLatin1String("en_US")});
+                        strGroupValue = fileTime.toString(untranslatedFormat);
                     }
                 } else if (daysDistance <= 7 * 4) {
-                    const KLocalizedString format = ki18nc("@title:group Date: "
-                                                           "MMMM is full month name in current locale, and yyyy is "
-                                                           "full year number. You must keep the ' don't use any fancy \" or Â« or similar. The ' is not shown to the user, it's there to mark a part of the text that should not be formatted as a date", "'Three Weeks Ago' (MMMM, yyyy)");
+                    const KLocalizedString format = ki18nc(
+                        "@title:group Date: "
+                        "MMMM is full month name in current locale, and yyyy is "
+                        "full year number. You must keep the ' don't use any fancy \" or Â« or similar. The ' is not shown to the user, it's there to mark a "
+                        "part of the text that should not be formatted as a date",
+                        "'Three Weeks Ago' (MMMM, yyyy)");
                     const QString translatedFormat = format.toString();
                     if (translatedFormat.count(QLatin1Char('\'')) == 2) {
-                        newGroupValue = fileTime.toString(translatedFormat);
-                        newGroupValue = i18nc("Can be used to script translation of "
+                        strGroupValue = fileTime.toString(translatedFormat);
+                        strGroupValue = i18nc(
+                            "Can be used to script translation of "
                             "\"'Three Weeks Ago' (MMMM, yyyy)\" with context @title:group Date",
-                            "%1", newGroupValue);
+                            "%1",
+                            strGroupValue);
                     } else {
-                        qCWarning(DolphinDebug).nospace() << "A wrong translation was found: " << translatedFormat << ". Please file a bug report at bugs.kde.org";
-                        const QString untranslatedFormat = format.toString({ QLatin1String("en_US") });
-                        newGroupValue = fileTime.toString(untranslatedFormat);
+                        qCWarning(DolphinDebug).nospace()
+                            << "A wrong translation was found: " << translatedFormat << ". Please file a bug report at bugs.kde.org";
+                        const QString untranslatedFormat = format.toString({QLatin1String("en_US")});
+                        strGroupValue = fileTime.toString(untranslatedFormat);
                     }
                 } else {
-                    const KLocalizedString format = ki18nc("@title:group Date: "
-                                                           "MMMM is full month name in current locale, and yyyy is "
-                                                           "full year number. You must keep the ' don't use any fancy \" or Â« or similar. The ' is not shown to the user, it's there to mark a part of the text that should not be formatted as a date", "'Earlier on' MMMM, yyyy");
+                    const KLocalizedString format = ki18nc(
+                        "@title:group Date: "
+                        "MMMM is full month name in current locale, and yyyy is "
+                        "full year number. You must keep the ' don't use any fancy \" or Â« or similar. The ' is not shown to the user, it's there to mark a "
+                        "part of the text that should not be formatted as a date",
+                        "'Earlier on' MMMM, yyyy");
                     const QString translatedFormat = format.toString();
                     if (translatedFormat.count(QLatin1Char('\'')) == 2) {
-                        newGroupValue = fileTime.toString(translatedFormat);
-                        newGroupValue = i18nc("Can be used to script translation of "
+                        strGroupValue = fileTime.toString(translatedFormat);
+                        strGroupValue = i18nc(
+                            "Can be used to script translation of "
                             "\"'Earlier on' MMMM, yyyy\" with context @title:group Date",
-                            "%1", newGroupValue);
+                            "%1",
+                            strGroupValue);
                     } else {
-                        qCWarning(DolphinDebug).nospace() << "A wrong translation was found: " << translatedFormat << ". Please file a bug report at bugs.kde.org";
-                        const QString untranslatedFormat = format.toString({ QLatin1String("en_US") });
-                        newGroupValue = fileTime.toString(untranslatedFormat);
+                        qCWarning(DolphinDebug).nospace()
+                            << "A wrong translation was found: " << translatedFormat << ". Please file a bug report at bugs.kde.org";
+                        const QString untranslatedFormat = format.toString({QLatin1String("en_US")});
+                        strGroupValue = fileTime.toString(untranslatedFormat);
                     }
                 }
             } else {
-                newGroupValue = fileTime.toString(i18nc("@title:group "
-                    "The month and year: MMMM is full month name in current locale, "
-                    "and yyyy is full year number", "MMMM, yyyy"));
-                newGroupValue = i18nc("Can be used to script translation of "
+                strGroupValue =
+                    fileTime.toString(i18nc("@title:group "
+                                            "The month and year: MMMM is full month name in current locale, "
+                                            "and yyyy is full year number",
+                                            "MMMM, yyyy"));
+                strGroupValue = i18nc(
+                    "Can be used to script translation of "
                     "\"MMMM, yyyy\" with context @title:group Date",
-                    "%1", newGroupValue);
+                    "%1",
+                    strGroupValue);
             }
         }
+        return QVariant(strGroupValue);
+    }
+}
+
+QString KFileItemModel::getGenericStringRoleGroup(const QByteArray &role, const ItemData *itemData) const
+{
+    return itemData->values.value(role).toString();
+}
+
+QList<QPair<int, QVariant>> KFileItemModel::nameRoleGroups() const
+{
+    Q_ASSERT(!m_itemData.isEmpty());
+
+    const int maxIndex = count() - 1;
+    QList<QPair<int, QVariant>> groups;
+
+    QString groupValue;
+    QChar firstChar;
+    for (int i = 0; i <= maxIndex; ++i) {
+        if (isChildItem(i)) {
+            continue;
+        }
+
+        QString newGroupValue = getNameRoleGroup(m_itemData.at(i)).toString();
+
+        if (newGroupValue != groupValue) {
+            groupValue = newGroupValue;
+            groups.append(QPair<int, QVariant>(i, newGroupValue));
+        }
+
+        // firstChar = newFirstChar;
+    }
+    return groups;
+}
+
+QList<QPair<int, QVariant>> KFileItemModel::sizeRoleGroups() const
+{
+    Q_ASSERT(!m_itemData.isEmpty());
+
+    const int maxIndex = count() - 1;
+    QList<QPair<int, QVariant>> groups;
+
+    QString groupValue;
+    for (int i = 0; i <= maxIndex; ++i) {
+        if (isChildItem(i)) {
+            continue;
+        }
+
+        QString newGroupValue = getSizeRoleGroup(m_itemData.at(i)).toString();
 
         if (newGroupValue != groupValue) {
             groupValue = newGroupValue;
@@ -2452,12 +2805,47 @@ QList<QPair<int, QVariant> > KFileItemModel::timeRoleGroups(const std::function<
     return groups;
 }
 
-QList<QPair<int, QVariant> > KFileItemModel::permissionRoleGroups() const
+QList<QPair<int, QVariant>> KFileItemModel::timeRoleGroups(const std::function<QDateTime(const ItemData *)> &fileTimeCb) const
 {
     Q_ASSERT(!m_itemData.isEmpty());
 
     const int maxIndex = count() - 1;
-    QList<QPair<int, QVariant> > groups;
+    QList<QPair<int, QVariant>> groups;
+
+    const QDate currentDate = QDate::currentDate();
+
+    QDate previousFileDate;
+    QString groupValue;
+    for (int i = 0; i <= maxIndex; ++i) {
+        if (isChildItem(i)) {
+            continue;
+        }
+
+        const QDateTime fileTime = fileTimeCb(m_itemData.at(i));
+        const QDate fileDate = fileTime.date();
+        if (fileDate == previousFileDate) {
+            // The current item is in the same group as the previous item
+            continue;
+        }
+        previousFileDate = fileDate;
+
+        QString newGroupValue = getTimeRoleGroup(fileTimeCb, m_itemData.at(i)).toString();
+
+        if (newGroupValue != groupValue) {
+            groupValue = newGroupValue;
+            groups.append(QPair<int, QVariant>(i, newGroupValue));
+        }
+    }
+
+    return groups;
+}
+
+QList<QPair<int, QVariant>> KFileItemModel::permissionRoleGroups() const
+{
+    Q_ASSERT(!m_itemData.isEmpty());
+
+    const int maxIndex = count() - 1;
+    QList<QPair<int, QVariant>> groups;
 
     QString permissionsString;
     QString groupValue;
@@ -2466,7 +2854,7 @@ QList<QPair<int, QVariant> > KFileItemModel::permissionRoleGroups() const
             continue;
         }
 
-        const ItemDataitemData = m_itemData.at(i);
+        const ItemData *itemData = m_itemData.at(i);
         const QString newPermissionsString = itemData->values.value("permissions").toString();
         if (newPermissionsString == permissionsString) {
             continue;
@@ -2486,7 +2874,7 @@ QList<QPair<int, QVariant> > KFileItemModel::permissionRoleGroups() const
         if (info.permission(QFile::ExeUser)) {
             user += i18nc("@item:intext Access permission, concatenated", "Execute, ");
         }
-        user = user.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : user.mid(0, user.count() - 2);
+        user = user.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : user.mid(0, user.length() - 2);
 
         // Set group string
         QString group;
@@ -2499,7 +2887,7 @@ QList<QPair<int, QVariant> > KFileItemModel::permissionRoleGroups() const
         if (info.permission(QFile::ExeGroup)) {
             group += i18nc("@item:intext Access permission, concatenated", "Execute, ");
         }
-        group = group.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : group.mid(0, group.count() - 2);
+        group = group.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : group.mid(0, group.length() - 2);
 
         // Set others string
         QString others;
@@ -2512,7 +2900,7 @@ QList<QPair<int, QVariant> > KFileItemModel::permissionRoleGroups() const
         if (info.permission(QFile::ExeOther)) {
             others += i18nc("@item:intext Access permission, concatenated", "Execute, ");
         }
-        others = others.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : others.mid(0, others.count() - 2);
+        others = others.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : others.mid(0, others.length() - 2);
 
         const QString newGroupValue = i18nc("@title:group Files and folders by permissions", "User: %1 | Group: %2 | Others: %3", user, group, others);
         if (newGroupValue != groupValue) {
@@ -2524,18 +2912,19 @@ QList<QPair<int, QVariant> > KFileItemModel::permissionRoleGroups() const
     return groups;
 }
 
-QList<QPair<int, QVariant> > KFileItemModel::ratingRoleGroups() const
+QList<QPair<int, QVariant>> KFileItemModel::ratingRoleGroups() const
 {
     Q_ASSERT(!m_itemData.isEmpty());
 
     const int maxIndex = count() - 1;
-    QList<QPair<int, QVariant> > groups;
+    QList<QPair<int, QVariant>> groups;
 
     int groupValue = -1;
     for (int i = 0; i <= maxIndex; ++i) {
         if (isChildItem(i)) {
             continue;
         }
+
         const int newGroupValue = m_itemData.at(i)->values.value("rating", 0).toInt();
         if (newGroupValue != groupValue) {
             groupValue = newGroupValue;
@@ -2546,12 +2935,12 @@ QList<QPair<int, QVariant> > KFileItemModel::ratingRoleGroups() const
     return groups;
 }
 
-QList<QPair<int, QVariant> > KFileItemModel::genericStringRoleGroups(const QByteArray& role) const
+QList<QPair<int, QVariant>> KFileItemModel::genericStringRoleGroups(const QByteArray &role) const
 {
     Q_ASSERT(!m_itemData.isEmpty());
 
     const int maxIndex = count() - 1;
-    QList<QPair<int, QVariant> > groups;
+    QList<QPair<int, QVariant>> groups;
 
     bool isFirstGroupValue = true;
     QString groupValue;
@@ -2559,7 +2948,8 @@ QList<QPair<int, QVariant> > KFileItemModel::genericStringRoleGroups(const QByte
         if (isChildItem(i)) {
             continue;
         }
-        const QString newGroupValue = m_itemData.at(i)->values.value(role).toString();
+
+        const QString newGroupValue = getGenericStringRoleGroup(role, m_itemData.at(i));
         if (newGroupValue != groupValue || isFirstGroupValue) {
             groupValue = newGroupValue;
             groups.append(QPair<int, QVariant>(i, newGroupValue));
@@ -2598,55 +2988,61 @@ void KFileItemModel::emitSortProgress(int resolvedCount)
     }
 }
 
-const KFileItemModel::RoleInfoMap* KFileItemModel::rolesInfoMap(int& count)
+const KFileItemModel::RoleInfoMap *KFileItemModel::rolesInfoMap(int &count)
 {
     static const RoleInfoMap rolesInfoMap[] = {
-    //  |         role           |        roleType        |                role translation         |         group translation                     | requires Baloo | requires indexer
-        { nullptr,               NoRole,                  KLazyLocalizedString(),                    KLazyLocalizedString(),                            false,           false },
-        { "text",                NameRole,                kli18nc("@label", "Name"),                 KLazyLocalizedString(),                            false,           false },
-        { "size",                SizeRole,                kli18nc("@label", "Size"),                 KLazyLocalizedString(),                            false,           false },
-        { "modificationtime",    ModificationTimeRole,    kli18nc("@label", "Modified"),             KLazyLocalizedString(),                            false,           false },
-        { "creationtime",        CreationTimeRole,        kli18nc("@label", "Created"),              KLazyLocalizedString(),                            false,           false },
-        { "accesstime",          AccessTimeRole,          kli18nc("@label", "Accessed"),             KLazyLocalizedString(),                            false,           false },
-        { "type",                TypeRole,                kli18nc("@label", "Type"),                 KLazyLocalizedString(),                            false,           false },
-        { "rating",              RatingRole,              kli18nc("@label", "Rating"),               KLazyLocalizedString(),                            true,            false },
-        { "tags",                TagsRole,                kli18nc("@label", "Tags"),                 KLazyLocalizedString(),                            true,            false },
-        { "comment",             CommentRole,             kli18nc("@label", "Comment"),              KLazyLocalizedString(),                            true,            false },
-        { "title",               TitleRole,               kli18nc("@label", "Title"),                kli18nc("@label", "Document"),                     true,            true  },
-        { "wordCount",           WordCountRole,           kli18nc("@label", "Word Count"),           kli18nc("@label", "Document"),                     true,            true  },
-        { "lineCount",           LineCountRole,           kli18nc("@label", "Line Count"),           kli18nc("@label", "Document"),                     true,            true  },
-        { "imageDateTime",       ImageDateTimeRole,       kli18nc("@label", "Date Photographed"),    kli18nc("@label", "Image"),                        true,            true  },
-        { "dimensions",          DimensionsRole,          kli18nc("@label width x height", "Dimensions"), kli18nc("@label", "Image"),                   true,            true  },
-        { "width",               WidthRole,               kli18nc("@label", "Width"),                kli18nc("@label", "Image"),                        true,            true  },
-        { "height",              HeightRole,              kli18nc("@label", "Height"),               kli18nc("@label", "Image"),                        true,            true  },
-        { "orientation",         OrientationRole,         kli18nc("@label", "Orientation"),          kli18nc("@label", "Image"),                        true,            true  },
-        { "artist",              ArtistRole,              kli18nc("@label", "Artist"),               kli18nc("@label", "Audio"),                        true,            true  },
-        { "genre",               GenreRole,               kli18nc("@label", "Genre"),                kli18nc("@label", "Audio"),                        true,            true  },
-        { "album",               AlbumRole,               kli18nc("@label", "Album"),                kli18nc("@label", "Audio"),                        true,            true  },
-        { "duration",            DurationRole,            kli18nc("@label", "Duration"),             kli18nc("@label", "Audio"),                        true,            true  },
-        { "bitrate",             BitrateRole,             kli18nc("@label", "Bitrate"),              kli18nc("@label", "Audio"),                        true,            true  },
-        { "track",               TrackRole,               kli18nc("@label", "Track"),                kli18nc("@label", "Audio"),                        true,            true  },
-        { "releaseYear",         ReleaseYearRole,         kli18nc("@label", "Release Year"),         kli18nc("@label", "Audio"),                        true,            true  },
-        { "aspectRatio",         AspectRatioRole,         kli18nc("@label", "Aspect Ratio"),         kli18nc("@label", "Video"),                        true,            true  },
-        { "frameRate",           FrameRateRole,           kli18nc("@label", "Frame Rate"),           kli18nc("@label", "Video"),                        true,            true  },
-        { "path",                PathRole,                kli18nc("@label", "Path"),                 kli18nc("@label", "Other"),                        false,           false },
-        { "deletiontime",        DeletionTimeRole,        kli18nc("@label", "Deletion Time"),        kli18nc("@label", "Other"),                        false,           false },
-        { "destination",         DestinationRole,         kli18nc("@label", "Link Destination"),     kli18nc("@label", "Other"),                        false,           false },
-        { "originUrl",           OriginUrlRole,           kli18nc("@label", "Downloaded From"),      kli18nc("@label", "Other"),                        true,            false },
-        { "permissions",         PermissionsRole,         kli18nc("@label", "Permissions"),          kli18nc("@label", "Other"),                        false,           false },
-        { "owner",               OwnerRole,               kli18nc("@label", "Owner"),                kli18nc("@label", "Other"),                        false,           false },
-        { "group",               GroupRole,               kli18nc("@label", "User Group"),           kli18nc("@label", "Other"),                        false,           false },
+        // clang-format off
+    //  |         role           |        roleType        |                role translation          |         group translation                                        | requires Baloo | requires indexer
+        { nullptr,               NoRole,                  kli18nc("@label", "None"),                 KLazyLocalizedString(),        KLazyLocalizedString(),                    false,           false },
+        { "text",                NameRole,                kli18nc("@label", "Name"),                 KLazyLocalizedString(),        KLazyLocalizedString(),                    false,           false },
+        { "size",                SizeRole,                kli18nc("@label", "Size"),                 KLazyLocalizedString(),        KLazyLocalizedString(),                    false,           false },
+        { "modificationtime",    ModificationTimeRole,    kli18nc("@label", "Modified"),             KLazyLocalizedString(),        kli18nc("@tooltip", "The date format can be selected in settings."),                    false,           false },
+        { "creationtime",        CreationTimeRole,        kli18nc("@label", "Created"),              KLazyLocalizedString(),        kli18nc("@tooltip", "The date format can be selected in settings."),                    false,           false },
+        { "accesstime",          AccessTimeRole,          kli18nc("@label", "Accessed"),             KLazyLocalizedString(),        kli18nc("@tooltip", "The date format can be selected in settings."),                    false,           false },
+        { "type",                TypeRole,                kli18nc("@label", "Type"),                 KLazyLocalizedString(),        KLazyLocalizedString(),                    false,           false },
+        { "rating",              RatingRole,              kli18nc("@label", "Rating"),               KLazyLocalizedString(),        KLazyLocalizedString(),                    true,            false },
+        { "tags",                TagsRole,                kli18nc("@label", "Tags"),                 KLazyLocalizedString(),        KLazyLocalizedString(),                    true,            false },
+        { "comment",             CommentRole,             kli18nc("@label", "Comment"),              KLazyLocalizedString(),        KLazyLocalizedString(),                    true,            false },
+        { "title",               TitleRole,               kli18nc("@label", "Title"),                kli18nc("@label", "Document"), KLazyLocalizedString(),                    true,            true  },
+        { "author",              AuthorRole,              kli18nc("@label", "Author"),               kli18nc("@label", "Document"), KLazyLocalizedString(),                    true,            true  },
+        { "publisher",           PublisherRole,           kli18nc("@label", "Publisher"),            kli18nc("@label", "Document"), KLazyLocalizedString(),                    true,            true  },
+        { "pageCount",           PageCountRole,           kli18nc("@label", "Page Count"),           kli18nc("@label", "Document"), KLazyLocalizedString(),                    true,            true  },
+        { "wordCount",           WordCountRole,           kli18nc("@label", "Word Count"),           kli18nc("@label", "Document"), KLazyLocalizedString(),                    true,            true  },
+        { "lineCount",           LineCountRole,           kli18nc("@label", "Line Count"),           kli18nc("@label", "Document"), KLazyLocalizedString(),                    true,            true  },
+        { "imageDateTime",       ImageDateTimeRole,       kli18nc("@label", "Date Photographed"),    kli18nc("@label", "Image"),    KLazyLocalizedString(),                    true,            true  },
+        { "dimensions",          DimensionsRole,          kli18nc("@label width x height", "Dimensions"), kli18nc("@label", "Image"), KLazyLocalizedString(),                  true,            true  },
+        { "width",               WidthRole,               kli18nc("@label", "Width"),                kli18nc("@label", "Image"),    KLazyLocalizedString(),                    true,            true  },
+        { "height",              HeightRole,              kli18nc("@label", "Height"),               kli18nc("@label", "Image"),    KLazyLocalizedString(),                    true,            true  },
+        { "orientation",         OrientationRole,         kli18nc("@label", "Orientation"),          kli18nc("@label", "Image"),    KLazyLocalizedString(),                    true,            true  },
+        { "artist",              ArtistRole,              kli18nc("@label", "Artist"),               kli18nc("@label", "Audio"),    KLazyLocalizedString(),                    true,            true  },
+        { "genre",               GenreRole,               kli18nc("@label", "Genre"),                kli18nc("@label", "Audio"),    KLazyLocalizedString(),                    true,            true  },
+        { "album",               AlbumRole,               kli18nc("@label", "Album"),                kli18nc("@label", "Audio"),    KLazyLocalizedString(),                    true,            true  },
+        { "duration",            DurationRole,            kli18nc("@label", "Duration"),             kli18nc("@label", "Audio"),    KLazyLocalizedString(),                    true,            true  },
+        { "bitrate",             BitrateRole,             kli18nc("@label", "Bitrate"),              kli18nc("@label", "Audio"),    KLazyLocalizedString(),                    true,            true  },
+        { "track",               TrackRole,               kli18nc("@label", "Track"),                kli18nc("@label", "Audio"),    KLazyLocalizedString(),                    true,            true  },
+        { "releaseYear",         ReleaseYearRole,         kli18nc("@label", "Release Year"),         kli18nc("@label", "Audio"),    KLazyLocalizedString(),                    true,            true  },
+        { "aspectRatio",         AspectRatioRole,         kli18nc("@label", "Aspect Ratio"),         kli18nc("@label", "Video"),    KLazyLocalizedString(),                    true,            true  },
+        { "frameRate",           FrameRateRole,           kli18nc("@label", "Frame Rate"),           kli18nc("@label", "Video"),    KLazyLocalizedString(),                    true,            true  },
+        { "path",                PathRole,                kli18nc("@label", "Path"),                 kli18nc("@label", "Other"),    KLazyLocalizedString(),                    false,           false },
+        { "extension",           ExtensionRole,           kli18nc("@label", "File Extension"),       kli18nc("@label", "Other"),    KLazyLocalizedString(),                    false,           false },
+        { "deletiontime",        DeletionTimeRole,        kli18nc("@label", "Deletion Time"),        kli18nc("@label", "Other"),    KLazyLocalizedString(),                    false,           false },
+        { "destination",         DestinationRole,         kli18nc("@label", "Link Destination"),     kli18nc("@label", "Other"),    KLazyLocalizedString(),                    false,           false },
+        { "originUrl",           OriginUrlRole,           kli18nc("@label", "Downloaded From"),      kli18nc("@label", "Other"),    KLazyLocalizedString(),                    true,            false },
+        { "permissions",         PermissionsRole,         kli18nc("@label", "Permissions"),          kli18nc("@label", "Other"),    kli18nc("@tooltip", "The permission format can be changed in settings. Options are Symbolic, Numeric (Octal) or Combined formats"),        false,           false },
+        { "owner",               OwnerRole,               kli18nc("@label", "Owner"),                kli18nc("@label", "Other"),    KLazyLocalizedString(),                    false,           false },
+        { "group",               GroupRole,               kli18nc("@label", "User Group"),           kli18nc("@label", "Other"),    KLazyLocalizedString(),                    false,           false },
     };
+    // clang-format on
 
     count = sizeof(rolesInfoMap) / sizeof(RoleInfoMap);
     return rolesInfoMap;
 }
 
-void KFileItemModel::determineMimeTypes(const KFileItemListitems, int timeout)
+void KFileItemModel::determineMimeTypes(const KFileItemList &items, int timeout)
 {
     QElapsedTimer timer;
     timer.start();
-    for (const KFileItemitem : items) {
+    for (const KFileItem &item : items) {
         // Only determine mime types for files here. For directories,
         // KFileItem::determineMimeType() reads the .directory file inside to
         // load the icon, but this is not necessary at all if we just need the
@@ -2664,7 +3060,7 @@ void KFileItemModel::determineMimeTypes(const KFileItemList& items, int timeout)
     }
 }
 
-QByteArray KFileItemModel::sharedValue(const QByteArrayvalue)
+QByteArray KFileItemModel::sharedValue(const QByteArray &value)
 {
     static QSet<QByteArray> pool;
     const QSet<QByteArray>::const_iterator it = pool.constFind(value);
@@ -2701,14 +3097,13 @@ bool KFileItemModel::isConsistent() const
 
         // Check if the items are sorted correctly.
         if (i > 0 && !lessThan(m_itemData.at(i - 1), m_itemData.at(i), m_collator)) {
-            qCWarning(DolphinDebug) << "The order of items" << i - 1 << "and" << i << "is wrong:"
-                << fileItem(i - 1) << fileItem(i);
+            qCWarning(DolphinDebug) << "The order of items" << i - 1 << "and" << i << "is wrong:" << fileItem(i - 1) << fileItem(i);
             return false;
         }
 
         // Check if all parent-child relationships are consistent.
-        const ItemDatadata = m_itemData.at(i);
-        const ItemDataparent = data->parent;
+        const ItemData *data = m_itemData.at(i);
+        const ItemData *parent = data->parent;
         if (parent) {
             if (expandedParentsCount(data) != expandedParentsCount(parent) + 1) {
                 qCWarning(DolphinDebug) << "expandedParentsCount is inconsistent for parent" << parent->item << "and child" << data->item;
@@ -2717,7 +3112,8 @@ bool KFileItemModel::isConsistent() const
 
             const int parentIndex = index(parent->item);
             if (parentIndex >= i) {
-                qCWarning(DolphinDebug) << "Index" << parentIndex << "of parent" << parent->item << "is not smaller than index" << i << "of child" << data->item;
+                qCWarning(DolphinDebug) << "Index" << parentIndex << "of parent" << parent->item << "is not smaller than index" << i << "of child"
+                                        << data->item;
                 return false;
             }
         }
@@ -2737,3 +3133,5 @@ void KFileItemModel::slotListerError(KIO::Job *job)
         Q_EMIT errorMessage(!errorString.isEmpty() ? errorString : i18nc("@info:status", "Unknown error."));
     }
 }
+
+#include "moc_kfileitemmodel.cpp"