]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/kitemviews/kfileitemmodel.cpp
make sure we use valid icons
[dolphin.git] / src / kitemviews / kfileitemmodel.cpp
index eac3ddf8b5c39d4769ab3dac500f2d00a05ca33f..c06202fd82fb84cc8aa2ca2d568901ca2d63972e 100644 (file)
@@ -1,47 +1,37 @@
-/*****************************************************************************
- *   Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com>               *
- *   Copyright (C) 2013 by Frank Reininghaus <frank78ac@googlemail.com>      *
- *   Copyright (C) 2013 by Emmanuel Pescosta <emmanuelpescosta099@gmail.com> *
- *                                                                           *
- *   This program is free software; you can redistribute it and/or modify    *
- *   it under the terms of the GNU General Public License as published by    *
- *   the Free Software Foundation; either version 2 of the License, or       *
- *   (at your option) any later version.                                     *
- *                                                                           *
- *   This program is distributed in the hope that it will be useful,         *
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of          *
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
- *   GNU General Public License for more details.                            *
- *                                                                           *
- *   You should have received a copy of the GNU General Public License       *
- *   along with this program; if not, write to the                           *
- *   Free Software Foundation, Inc.,                                         *
- *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA              *
- *****************************************************************************/
+/*
+ * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com>
+ * SPDX-FileCopyrightText: 2013 Frank Reininghaus <frank78ac@googlemail.com>
+ * SPDX-FileCopyrightText: 2013 Emmanuel Pescosta <emmanuelpescosta099@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
 
 #include "kfileitemmodel.h"
 
 #include "dolphin_generalsettings.h"
-
-#include <KLocalizedString>
+#include "dolphin_detailsmodesettings.h"
 #include "dolphindebug.h"
-
-#include "private/kfileitemmodelsortalgorithm.h"
 #include "private/kfileitemmodeldirlister.h"
+#include "private/kfileitemmodelsortalgorithm.h"
 
+#include <KLocalizedString>
+#include <KUrlMimeData>
+
+#include <QElapsedTimer>
 #include <QMimeData>
+#include <QMimeDatabase>
 #include <QTimer>
 #include <QWidget>
+#include <QMutex>
+#include <QIcon>
 
-#include <algorithm>
-#include <vector>
+Q_GLOBAL_STATIC_WITH_ARGS(QMutex, s_collatorMutex, (QMutex::Recursive))
 
 // #define KFILEITEMMODEL_DEBUG
 
 KFileItemModel::KFileItemModel(QObject* parent) :
     KItemModelBase("text", parent),
-    m_dirLister(0),
-    m_naturalSorting(GeneralSettings::naturalSorting()),
+    m_dirLister(nullptr),
     m_sortDirsFirst(true),
     m_sortRole(NameRole),
     m_sortingProgressPercent(-1),
@@ -51,16 +41,17 @@ KFileItemModel::KFileItemModel(QObject* parent) :
     m_filter(),
     m_filteredItems(),
     m_requestRole(),
-    m_maximumUpdateIntervalTimer(0),
-    m_resortAllItemsTimer(0),
+    m_maximumUpdateIntervalTimer(nullptr),
+    m_resortAllItemsTimer(nullptr),
     m_pendingItemsToInsert(),
     m_groups(),
     m_expandedDirs(),
     m_urlsToExpand()
 {
-    m_collator.setCaseSensitivity(Qt::CaseInsensitive);
     m_collator.setNumericMode(true);
 
+    loadSortingSettings();
+
     m_dirLister = new KFileItemModelDirLister(this);
     m_dirLister->setDelayedMimeTypes(true);
 
@@ -70,15 +61,16 @@ KFileItemModel::KFileItemModel(QObject* parent) :
     }
 
     connect(m_dirLister, &KFileItemModelDirLister::started, this, &KFileItemModel::directoryLoadingStarted);
-    connect(m_dirLister, static_cast<void(KFileItemModelDirLister::*)()>(&KFileItemModelDirLister::canceled), this, &KFileItemModel::slotCanceled);
-    connect(m_dirLister, static_cast<void(KFileItemModelDirLister::*)(const QUrl&)>(&KFileItemModelDirLister::completed), this, &KFileItemModel::slotCompleted);
+    connect(m_dirLister, QOverload<>::of(&KCoreDirLister::canceled), this, &KFileItemModel::slotCanceled);
+    connect(m_dirLister, QOverload<const QUrl&>::of(&KCoreDirLister::completed), this, &KFileItemModel::slotCompleted);
     connect(m_dirLister, &KFileItemModelDirLister::itemsAdded, this, &KFileItemModel::slotItemsAdded);
     connect(m_dirLister, &KFileItemModelDirLister::itemsDeleted, this, &KFileItemModel::slotItemsDeleted);
     connect(m_dirLister, &KFileItemModelDirLister::refreshItems, this, &KFileItemModel::slotRefreshItems);
-    connect(m_dirLister, static_cast<void(KFileItemModelDirLister::*)()>(&KFileItemModelDirLister::clear), this, &KFileItemModel::slotClear);
+    connect(m_dirLister, QOverload<>::of(&KCoreDirLister::clear), this, &KFileItemModel::slotClear);
     connect(m_dirLister, &KFileItemModelDirLister::infoMessage, this, &KFileItemModel::infoMessage);
     connect(m_dirLister, &KFileItemModelDirLister::errorMessage, this, &KFileItemModel::errorMessage);
-    connect(m_dirLister, static_cast<void(KFileItemModelDirLister::*)(const QUrl&, const QUrl&)>(&KFileItemModelDirLister::redirection), this, &KFileItemModel::directoryRedirection);
+    connect(m_dirLister, &KFileItemModelDirLister::percent, this, &KFileItemModel::directoryLoadingProgress);
+    connect(m_dirLister, QOverload<const QUrl&, const QUrl&>::of(&KCoreDirLister::redirection), this, &KFileItemModel::directoryRedirection);
     connect(m_dirLister, &KFileItemModelDirLister::urlIsFileError, this, &KFileItemModel::urlIsFileError);
 
     // Apply default roles that should be determined
@@ -89,6 +81,7 @@ KFileItemModel::KFileItemModel(QObject* parent) :
     m_roles.insert("text");
     m_roles.insert("isDir");
     m_roles.insert("isLink");
+    m_roles.insert("isHidden");
 
     // For slow KIO-slaves like used for searching it makes sense to show results periodically even
     // before the completed() or canceled() signal has been emitted.
@@ -106,14 +99,13 @@ KFileItemModel::KFileItemModel(QObject* parent) :
     m_resortAllItemsTimer->setSingleShot(true);
     connect(m_resortAllItemsTimer, &QTimer::timeout, this, &KFileItemModel::resortAllItems);
 
-    connect(GeneralSettings::self(), &GeneralSettings::naturalSortingChanged,
-            this, &KFileItemModel::slotNaturalSortingChanged);
+    connect(GeneralSettings::self(), &GeneralSettings::sortingChoiceChanged, this, &KFileItemModel::slotSortingChoiceChanged);
 }
 
 KFileItemModel::~KFileItemModel()
 {
     qDeleteAll(m_itemData);
-    qDeleteAll(m_filteredItems.values());
+    qDeleteAll(m_filteredItems);
     qDeleteAll(m_pendingItemsToInsert);
 }
 
@@ -244,13 +236,12 @@ QMimeData* KFileItemModel::createMimeData(const KItemSet& indexes) const
 
     // The following code has been taken from KDirModel::mimeData()
     // (kdelibs/kio/kio/kdirmodel.cpp)
-    // Copyright (C) 2006 David Faure <faure@kde.org>
+    // SPDX-FileCopyrightText: 2006 David Faure <faure@kde.org>
     QList<QUrl> urls;
     QList<QUrl> mostLocalUrls;
-    bool canUseMostLocalUrls = true;
-    const ItemData* lastAddedItem = 0;
+    const ItemData* lastAddedItem = nullptr;
 
-    foreach (int index, indexes) {
+    for (int index : indexes) {
         const ItemData* itemData = m_itemData.at(index);
         const ItemData* parent = itemData->parent;
 
@@ -266,23 +257,14 @@ QMimeData* KFileItemModel::createMimeData(const KItemSet& indexes) const
         lastAddedItem = itemData;
         const KFileItem& item = itemData->item;
         if (!item.isNull()) {
-            urls << item.targetUrl();
+            urls << item.url();
 
             bool isLocal;
-            mostLocalUrls << item.mostLocalUrl(isLocal);
-            if (!isLocal) {
-                canUseMostLocalUrls = false;
-            }
+            mostLocalUrls << item.mostLocalUrl(&isLocal);
         }
     }
 
-    const bool different = canUseMostLocalUrls && mostLocalUrls != urls;
-    if (different) {
-        data->setUrls(mostLocalUrls);
-    } else {
-        data->setUrls(urls);
-    }
-
+    KUrlMimeData::setUrls(urls, mostLocalUrls, data);
     return data;
 }
 
@@ -315,6 +297,9 @@ QString KFileItemModel::roleDescription(const QByteArray& role) const
         int count = 0;
         const RoleInfoMap* map = rolesInfoMap(count);
         for (int i = 0; i < count; ++i) {
+            if (!map[i].roleTranslation) {
+                continue;
+            }
             description.insert(map[i].role, i18nc(map[i].roleTranslationContext, map[i].roleTranslation));
         }
     }
@@ -332,7 +317,26 @@ QList<QPair<int, QVariant> > KFileItemModel::groups() const
         switch (typeForRole(sortRole())) {
         case NameRole:        m_groups = nameRoleGroups(); break;
         case SizeRole:        m_groups = sizeRoleGroups(); break;
-        case DateRole:        m_groups = dateRoleGroups(); break;
+        case ModificationTimeRole:
+            m_groups = timeRoleGroups([](const ItemData *item) {
+                return item->item.time(KFileItem::ModificationTime);
+            });
+            break;
+        case CreationTimeRole:
+            m_groups = timeRoleGroups([](const ItemData *item) {
+                return item->item.time(KFileItem::CreationTime);
+            });
+            break;
+        case AccessTimeRole:
+            m_groups = timeRoleGroups([](const ItemData *item) {
+                return item->item.time(KFileItem::AccessTime);
+            });
+            break;
+        case DeletionTimeRole:
+            m_groups = timeRoleGroups([](const ItemData *item) {
+                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;
@@ -418,15 +422,19 @@ int KFileItemModel::index(const QUrl& url) const
                 indexesForUrl.insert(m_itemData.at(i)->item.url(), i);
             }
 
-            foreach (const QUrl& url, indexesForUrl.uniqueKeys()) {
+            const auto uniqueKeys = indexesForUrl.uniqueKeys();
+            for (const QUrl& url : uniqueKeys) {
                 if (indexesForUrl.count(url) > 1) {
                     qCWarning(DolphinDebug) << "Multiple items found with the URL" << url;
-                    foreach (int index, indexesForUrl.values(url)) {
-                        const ItemData* data = m_itemData.at(index);
-                        qCWarning(DolphinDebug) << "index" << index << ":" << data->item;
+
+                    auto it = indexesForUrl.find(url);
+                    while (it != indexesForUrl.end() && it.key() == url) {
+                        const ItemData* data = m_itemData.at(it.value());
+                        qCWarning(DolphinDebug) << "index" << it.value() << ":" << data->item;
                         if (data->parent) {
                             qCWarning(DolphinDebug) << "parent" << data->parent->item;
                         }
+                        ++it;
                     }
                 }
             }
@@ -481,7 +489,7 @@ void KFileItemModel::setRoles(const QSet<QByteArray>& roles)
             m_itemData[i]->values = retrieveData(m_itemData.at(i)->item, m_itemData.at(i)->parent);
         }
 
-        emit itemsChanged(KItemRangeList() << KItemRange(0, count()), changedRoles);
+        Q_EMIT itemsChanged(KItemRangeList() << KItemRange(0, count()), changedRoles);
     }
 
     // Clear the 'values' of all filtered items. They will be re-populated with the
@@ -519,7 +527,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>();
-        foreach (const QVariant& var, previouslyExpandedChildren) {
+        for (const QVariant& var : previouslyExpandedChildren) {
             m_urlsToExpand.insert(var.toUrl());
         }
     } else {
@@ -595,7 +603,12 @@ int KFileItemModel::expandedParentsCount(int index) const
 
 QSet<QUrl> KFileItemModel::expandedDirectories() const
 {
-    return m_expandedDirs.values().toSet();
+    QSet<QUrl> result;
+    const auto dirs = m_expandedDirs;
+    for (const auto &dir : dirs) {
+        result.insert(dir);
+    }
+    return result;
 }
 
 void KFileItemModel::restoreExpandedDirectories(const QSet<QUrl> &urls)
@@ -605,16 +618,24 @@ void KFileItemModel::restoreExpandedDirectories(const QSet<QUrl> &urls)
 
 void KFileItemModel::expandParentDirectories(const QUrl &url)
 {
-    const int pos = m_dirLister->url().path().length();
 
     // 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
     // expanded.
     QUrl urlToExpand = m_dirLister->url();
-    const QStringList subDirs = url.path().mid(pos).split(QDir::separator());
+    const int pos = urlToExpand.path().length();
+
+    // first subdir can be empty, if m_dirLister->url().path() does not end with '/'
+    // this happens if baseUrl is not root but a home directory, see FoldersPanel,
+    // so using QString::SkipEmptyParts
+    const QStringList subDirs = url.path().mid(pos).split(QDir::separator(), Qt::SkipEmptyParts);
     for (int i = 0; i < subDirs.count() - 1; ++i) {
-        urlToExpand.setPath(urlToExpand.path() + '/' + subDirs.at(i));
+        QString path = urlToExpand.path();
+        if (!path.endsWith(QLatin1Char('/'))) {
+            path.append(QLatin1Char('/'));
+        }
+        urlToExpand.setPath(path + subDirs.at(i));
         m_urlsToExpand.insert(urlToExpand);
     }
 
@@ -710,7 +731,7 @@ void KFileItemModel::removeFilteredChildren(const KItemRangeList& itemRanges)
     }
 
     QSet<ItemData*> parents;
-    foreach (const KItemRange& range, itemRanges) {
+    for (const KItemRange& range : itemRanges) {
         for (int index = range.index; index < range.index + range.count; ++index) {
             parents.insert(m_itemData.at(index));
         }
@@ -758,13 +779,13 @@ QList<KFileItemModel::RoleInfo> KFileItemModel::rolesInformation()
 
 void KFileItemModel::onGroupedSortingChanged(bool current)
 {
-    Q_UNUSED(current);
+    Q_UNUSED(current)
     m_groups.clear();
 }
 
-void KFileItemModel::onSortRoleChanged(const QByteArray& current, const QByteArray& previous)
+void KFileItemModel::onSortRoleChanged(const QByteArray& current, const QByteArray& previous, bool resortItems)
 {
-    Q_UNUSED(previous);
+    Q_UNUSED(previous)
     m_sortRole = typeForRole(current);
 
     if (!m_requestRole[m_sortRole]) {
@@ -773,16 +794,42 @@ void KFileItemModel::onSortRoleChanged(const QByteArray& current, const QByteArr
         setRoles(newRoles);
     }
 
-    resortAllItems();
+    if (resortItems) {
+        resortAllItems();
+    }
 }
 
 void KFileItemModel::onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous)
 {
-    Q_UNUSED(current);
-    Q_UNUSED(previous);
+    Q_UNUSED(current)
+    Q_UNUSED(previous)
     resortAllItems();
 }
 
+void KFileItemModel::loadSortingSettings()
+{
+    using Choice = GeneralSettings::EnumSortingChoice;
+    switch (GeneralSettings::sortingChoice()) {
+    case Choice::NaturalSorting:
+        m_naturalSorting = true;
+        m_collator.setCaseSensitivity(Qt::CaseInsensitive);
+        break;
+    case Choice::CaseSensitiveSorting:
+        m_naturalSorting = false;
+        m_collator.setCaseSensitivity(Qt::CaseSensitive);
+        break;
+    case Choice::CaseInsensitiveSorting:
+        m_naturalSorting = false;
+        m_collator.setCaseSensitivity(Qt::CaseInsensitive);
+        break;
+    default:
+        Q_UNREACHABLE();
+    }
+    // Workaround for bug https://bugreports.qt.io/browse/QTBUG-69361
+    // Force the clean state of QCollator in single thread to avoid thread safety problems in sort
+    m_collator.compare(QString(), QString());
+}
+
 void KFileItemModel::resortAllItems()
 {
     m_resortAllItemsTimer->stop();
@@ -804,7 +851,7 @@ void KFileItemModel::resortAllItems()
     // been moved because of the resorting.
     QList<QUrl> oldUrls;
     oldUrls.reserve(itemCount);
-    foreach (const ItemData* itemData, m_itemData) {
+    for (const ItemData* itemData : qAsConst(m_itemData)) {
         oldUrls.append(itemData->item.url());
     }
 
@@ -847,13 +894,13 @@ void KFileItemModel::resortAllItems()
             movedToIndexes.append(newIndex);
         }
 
-        emit itemsMoved(KItemRange(firstMovedIndex, movedItemsCount), movedToIndexes);
+        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;
         m_groups.clear();
         if (groups() != oldGroups) {
-            emit groupsChanged();
+            Q_EMIT groupsChanged();
         }
     }
 
@@ -864,6 +911,7 @@ void KFileItemModel::resortAllItems()
 
 void KFileItemModel::slotCompleted()
 {
+    m_maximumUpdateIntervalTimer->stop();
     dispatchPendingItemsToInsert();
 
     if (!m_urlsToExpand.isEmpty()) {
@@ -871,7 +919,9 @@ void KFileItemModel::slotCompleted()
         // Note that the parent folder must be expanded before any of its subfolders become visible.
         // Therefore, some URLs in m_restoredExpandedUrls might not be visible yet
         // -> we expand the first visible URL we find in m_restoredExpandedUrls.
-        foreach (const QUrl& url, m_urlsToExpand) {
+        QMutableSetIterator<QUrl> it(m_urlsToExpand);
+        while (it.hasNext()) {
+            const QUrl url = it.next();
             const int indexForUrl = index(url);
             if (indexForUrl >= 0) {
                 m_urlsToExpand.remove(url);
@@ -888,7 +938,7 @@ void KFileItemModel::slotCompleted()
         m_urlsToExpand.clear();
     }
 
-    emit directoryLoadingCompleted();
+    Q_EMIT directoryLoadingCompleted();
 }
 
 void KFileItemModel::slotCanceled()
@@ -896,7 +946,7 @@ void KFileItemModel::slotCanceled()
     m_maximumUpdateIntervalTimer->stop();
     dispatchPendingItemsToInsert();
 
-    emit directoryLoadingCanceled();
+    Q_EMIT directoryLoadingCanceled();
 }
 
 void KFileItemModel::slotItemsAdded(const QUrl &directoryUrl, const KFileItemList& items)
@@ -937,7 +987,7 @@ void KFileItemModel::slotItemsAdded(const QUrl &directoryUrl, const KFileItemLis
         }
     }
 
-    QList<ItemData*> itemDataList = createItemDataList(parentUrl, items);
+    const QList<ItemData*> itemDataList = createItemDataList(parentUrl, items);
 
     if (!m_filter.hasSetFilters()) {
         m_pendingItemsToInsert.append(itemDataList);
@@ -945,7 +995,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.
-        foreach (ItemData* itemData, itemDataList) {
+        for (ItemData* itemData : itemDataList) {
             if (m_filter.matches(itemData->item)) {
                 m_pendingItemsToInsert.append(itemData);
             } else {
@@ -954,7 +1004,7 @@ void KFileItemModel::slotItemsAdded(const QUrl &directoryUrl, const KFileItemLis
         }
     }
 
-    if (useMaximumUpdateInterval() && !m_maximumUpdateIntervalTimer->isActive()) {
+    if (!m_maximumUpdateIntervalTimer->isActive()) {
         // Assure that items get dispatched if no completed() or canceled() signal is
         // emitted during the maximum update interval.
         m_maximumUpdateIntervalTimer->start();
@@ -968,7 +1018,7 @@ void KFileItemModel::slotItemsDeleted(const KFileItemList& items)
     QVector<int> indexesToRemove;
     indexesToRemove.reserve(items.count());
 
-    foreach (const KFileItem& item, items) {
+    for (const KFileItem& item : items) {
         const int indexForItem = index(item);
         if (indexForItem >= 0) {
             indexesToRemove.append(indexForItem);
@@ -990,7 +1040,7 @@ void KFileItemModel::slotItemsDeleted(const KFileItemList& items)
         indexesToRemoveWithChildren.reserve(m_itemData.count());
 
         const int itemCount = m_itemData.count();
-        foreach (int index, indexesToRemove) {
+        for (int index : qAsConst(indexesToRemove)) {
             indexesToRemoveWithChildren.append(index);
 
             const int parentLevel = expandedParentsCount(index);
@@ -1071,7 +1121,7 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
     }
 
     // Extract the item-ranges out of the changed indexes
-    qSort(indexes);
+    std::sort(indexes.begin(), indexes.end());
     const KItemRangeList itemRangeList = KItemRangeList::fromSortedContainer(indexes);
     emitItemsChangedAndTriggerResorting(itemRangeList, changedRoles);
 }
@@ -1082,7 +1132,7 @@ void KFileItemModel::slotClear()
     qCDebug(DolphinDebug) << "Clearing all items";
 #endif
 
-    qDeleteAll(m_filteredItems.values());
+    qDeleteAll(m_filteredItems);
     m_filteredItems.clear();
     m_groups.clear();
 
@@ -1097,15 +1147,15 @@ void KFileItemModel::slotClear()
         qDeleteAll(m_itemData);
         m_itemData.clear();
         m_items.clear();
-        emit itemsRemoved(KItemRangeList() << KItemRange(0, removedCount));
+        Q_EMIT itemsRemoved(KItemRangeList() << KItemRange(0, removedCount));
     }
 
     m_expandedDirs.clear();
 }
 
-void KFileItemModel::slotNaturalSortingChanged()
+void KFileItemModel::slotSortingChoiceChanged()
 {
-    m_naturalSorting = GeneralSettings::naturalSorting();
+    loadSortingSettings();
     resortAllItems();
 }
 
@@ -1133,12 +1183,20 @@ void KFileItemModel::insertItems(QList<ItemData*>& newItems)
     m_groups.clear();
     prepareItemsForSorting(newItems);
 
-    if (m_sortRole == NameRole && m_naturalSorting) {
-        // Natural sorting of items can be very slow. However, it becomes much
-        // faster if the input sequence is already mostly sorted. Therefore, we
-        // first sort 'newItems' according to the QStrings returned by
-        // KFileItem::text() using QString::operator<(), which is quite fast.
-        parallelMergeSort(newItems.begin(), newItems.end(), nameLessThan, QThread::idealThreadCount());
+    // Natural sorting of items can be very slow. However, it becomes much faster
+    // if the input sequence is already mostly sorted. Therefore, we first sort
+    // 'newItems' according to the QStrings using QString::operator<(), which is quite fast.
+    if (m_naturalSorting) {
+        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)
+            {
+                const QByteArray role = roleForType(m_sortRole);
+                return a->values.value(role).toString() < b->values.value(role).toString();
+            };
+            parallelMergeSort(newItems.begin(), newItems.end(), lambdaLessThan, QThread::idealThreadCount());
+        }
     }
 
     sort(newItems.begin(), newItems.end());
@@ -1160,7 +1218,7 @@ void KFileItemModel::insertItems(QList<ItemData*>& newItems)
     } else {
         m_itemData.reserve(totalItemCount);
         for (int i = existingItemCount; i < totalItemCount; ++i) {
-            m_itemData.append(0);
+            m_itemData.append(nullptr);
         }
 
         // We build the new list m_itemData in reverse order to minimize
@@ -1205,7 +1263,7 @@ void KFileItemModel::insertItems(QList<ItemData*>& newItems)
     // It will be re-populated with the updated indices if index(const QUrl&) is called.
     m_items.clear();
 
-    emit itemsInserted(itemRanges);
+    Q_EMIT itemsInserted(itemRanges);
 
 #ifdef KFILEITEMMODEL_DEBUG
     qCDebug(DolphinDebug) << "[TIME] Inserting of" << newItems.count() << "items:" << timer.elapsed();
@@ -1222,7 +1280,7 @@ void KFileItemModel::removeItems(const KItemRangeList& itemRanges, RemoveItemsBe
 
     // Step 1: Remove the items from m_itemData, and free the ItemData.
     int removedItemsCount = 0;
-    foreach (const KItemRange& range, itemRanges) {
+    for (const KItemRange& range : itemRanges) {
         removedItemsCount += range.count;
 
         for (int index = range.index; index < range.index + range.count; ++index) {
@@ -1230,7 +1288,7 @@ void KFileItemModel::removeItems(const KItemRangeList& itemRanges, RemoveItemsBe
                 delete m_itemData.at(index);
             }
 
-            m_itemData[index] = 0;
+            m_itemData[index] = nullptr;
         }
     }
 
@@ -1258,7 +1316,7 @@ void KFileItemModel::removeItems(const KItemRangeList& itemRanges, RemoveItemsBe
     // It will be re-populated with the updated indices if index(const QUrl&) is called.
     m_items.clear();
 
-    emit itemsRemoved(itemRanges);
+    Q_EMIT itemsRemoved(itemRanges);
 }
 
 QList<KFileItemModel::ItemData*> KFileItemModel::createItemDataList(const QUrl& parentUrl, const KFileItemList& items) const
@@ -1271,12 +1329,12 @@ QList<KFileItemModel::ItemData*> KFileItemModel::createItemDataList(const QUrl&
     }
 
     const int parentIndex = index(parentUrl);
-    ItemData* parentItem = parentIndex < 0 ? 0 : m_itemData.at(parentIndex);
+    ItemData* parentItem = parentIndex < 0 ? nullptr : m_itemData.at(parentIndex);
 
     QList<ItemData*> itemDataList;
     itemDataList.reserve(items.count());
 
-    foreach (const KFileItem& item, items) {
+    for (const KFileItem& item : items) {
         ItemData* itemData = new ItemData();
         itemData->item = item;
         itemData->parent = parentItem;
@@ -1294,9 +1352,10 @@ void KFileItemModel::prepareItemsForSorting(QList<ItemData*>& itemDataList)
     case GroupRole:
     case DestinationRole:
     case PathRole:
+    case DeletionTimeRole:
         // These roles can be determined with retrieveData, and they have to be stored
         // in the QHash "values" for the sorting.
-        foreach (ItemData* itemData, itemDataList) {
+        for (ItemData* itemData : qAsConst(itemDataList)) {
             if (itemData->values.isEmpty()) {
                 itemData->values = retrieveData(itemData->item, itemData->parent);
             }
@@ -1305,7 +1364,7 @@ void KFileItemModel::prepareItemsForSorting(QList<ItemData*>& itemDataList)
 
     case TypeRole:
         // At least store the data including the file type for items with known MIME type.
-        foreach (ItemData* itemData, itemDataList) {
+        for (ItemData* itemData : qAsConst(itemDataList)) {
             if (itemData->values.isEmpty()) {
                 const KFileItem item = itemData->item;
                 if (item.isDir() || item.isMimeTypeKnown()) {
@@ -1373,12 +1432,12 @@ void KFileItemModel::removeExpandedItems()
 
 void KFileItemModel::emitItemsChangedAndTriggerResorting(const KItemRangeList& itemRanges, const QSet<QByteArray>& changedRoles)
 {
-    emit itemsChanged(itemRanges, 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))) {
-        foreach (const KItemRange& range, itemRanges) {
+        for (const KItemRange& range : itemRanges) {
             bool needsResorting = false;
 
             const int first = range.index;
@@ -1446,6 +1505,7 @@ KFileItemModel::RoleType KFileItemModel::typeForRole(const QByteArray& role) con
         // with KFileItemModel::roleForType() in case if a change is done).
         roles.insert("isDir", IsDirRole);
         roles.insert("isLink", IsLinkRole);
+        roles.insert("isHidden", IsHiddenRole);
         roles.insert("isExpanded", IsExpandedRole);
         roles.insert("isExpandable", IsExpandableRole);
         roles.insert("expandedParentsCount", ExpandedParentsCountRole);
@@ -1472,6 +1532,7 @@ QByteArray KFileItemModel::roleForType(RoleType roleType) const
         // with KFileItemModel::typeForRole() in case if a change is done).
         roles.insert(IsDirRole, "isDir");
         roles.insert(IsLinkRole, "isLink");
+        roles.insert(IsHiddenRole, "isHidden");
         roles.insert(IsExpandedRole, "isExpanded");
         roles.insert(IsExpandableRole, "isExpandable");
         roles.insert(ExpandedParentsCountRole, "expandedParentsCount");
@@ -1499,6 +1560,10 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item,
         data.insert(sharedValue("isLink"), true);
     }
 
+    if (m_requestRole[IsHiddenRole]) {
+        data.insert(sharedValue("isHidden"), item.isHidden());
+    }
+
     if (m_requestRole[NameRole]) {
         data.insert(sharedValue("text"), item.text());
     }
@@ -1507,12 +1572,28 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item,
         data.insert(sharedValue("size"), item.size());
     }
 
-    if (m_requestRole[DateRole]) {
-        // Don't use KFileItem::timeString() as this is too expensive when
-        // having several thousands of items. Instead the formatting of the
-        // date-time will be done on-demand by the view when the date will be shown.
-        const QDateTime dateTime = item.time(KFileItem::ModificationTime);
-        data.insert(sharedValue("date"), dateTime);
+    if (m_requestRole[ModificationTimeRole]) {
+        // Don't use KFileItem::timeString() or KFileItem::time() as this is too expensive when
+        // having several thousands of items. Instead read the raw number from UDSEntry directly
+        // and the formatting of the date-time will be done on-demand by the view when the date will be shown.
+        const long long dateTime = item.entry().numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1);
+        data.insert(sharedValue("modificationtime"), dateTime);
+    }
+
+    if (m_requestRole[CreationTimeRole]) {
+        // Don't use KFileItem::timeString() or KFileItem::time() as this is too expensive when
+        // having several thousands of items. Instead read the raw number from UDSEntry directly
+        // and the formatting of the date-time will be done on-demand by the view when the date will be shown.
+        const long long dateTime = item.entry().numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1);
+        data.insert(sharedValue("creationtime"), dateTime);
+    }
+
+    if (m_requestRole[AccessTimeRole]) {
+        // Don't use KFileItem::timeString() or KFileItem::time() as this is too expensive when
+        // having several thousands of items. Instead read the raw number from UDSEntry directly
+        // and the formatting of the date-time will be done on-demand by the view when the date will be shown.
+        const long long dateTime = item.entry().numberValue(KIO::UDSEntry::UDS_ACCESS_TIME, -1);
+        data.insert(sharedValue("accesstime"), dateTime);
     }
 
     if (m_requestRole[PermissionsRole]) {
@@ -1530,7 +1611,7 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item,
     if (m_requestRole[DestinationRole]) {
         QString destination = item.linkDest();
         if (destination.isEmpty()) {
-            destination = QLatin1String("-");
+            destination = QLatin1Char('-');
         }
         data.insert(sharedValue("destination"), destination);
     }
@@ -1558,6 +1639,14 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item,
         data.insert(sharedValue("path"), path);
     }
 
+    if (m_requestRole[DeletionTimeRole]) {
+        QDateTime deletionTime;
+        if (item.url().scheme() == QLatin1String("trash")) {
+            deletionTime = QDateTime::fromString(item.entry().stringValue(KIO::UDSEntry::UDS_EXTRA + 1), Qt::ISODate);
+        }
+        data.insert(sharedValue("deletiontime"), deletionTime);
+    }
+
     if (m_requestRole[IsExpandableRole] && isDir) {
         data.insert(sharedValue("isExpandable"), true);
     }
@@ -1570,7 +1659,13 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item,
     }
 
     if (item.isMimeTypeKnown()) {
-        data.insert(sharedValue("iconName"), item.iconName());
+        QString iconName = item.iconName();
+        if (!QIcon::hasThemeIcon(iconName)) {
+            QMimeType mimeType = QMimeDatabase().mimeTypeForName(item.mimetype());
+            iconName = mimeType.genericIconName();
+        }
+
+        data.insert(sharedValue("iconName"), iconName);
 
         if (m_requestRole[TypeRole]) {
             data.insert(sharedValue("type"), item.mimeComment());
@@ -1633,53 +1728,24 @@ bool KFileItemModel::lessThan(const ItemData* a, const ItemData* b, const QColla
     return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0;
 }
 
-/**
- * Helper class for KFileItemModel::sort().
- */
-class KFileItemModelLessThan
+void KFileItemModel::sort(const QList<KFileItemModel::ItemData*>::iterator &begin,
+                          const QList<KFileItemModel::ItemData*>::iterator &end) const
 {
-public:
-    KFileItemModelLessThan(const KFileItemModel* model, const QCollator& collator) :
-        m_model(model),
-        m_collator(collator)
+    auto lambdaLessThan = [&] (const KFileItemModel::ItemData* a, const KFileItemModel::ItemData* b)
     {
-    }
-
-    KFileItemModelLessThan(const KFileItemModelLessThan& other) :
-        m_model(other.m_model),
-        m_collator()
-    {
-        m_collator.setCaseSensitivity(other.m_collator.caseSensitivity());
-        m_collator.setIgnorePunctuation(other.m_collator.ignorePunctuation());
-        m_collator.setLocale(other.m_collator.locale());
-        m_collator.setNumericMode(other.m_collator.numericMode());
-    }
-
-    bool operator()(const KFileItemModel::ItemData* a, const KFileItemModel::ItemData* b) const
-    {
-        return m_model->lessThan(a, b, m_collator);
-    }
-
-private:
-    const KFileItemModel* m_model;
-    QCollator m_collator;
-};
-
-void KFileItemModel::sort(QList<KFileItemModel::ItemData*>::iterator begin,
-                          QList<KFileItemModel::ItemData*>::iterator end) const
-{
-    KFileItemModelLessThan lessThan(this, m_collator);
+        return lessThan(a, b, m_collator);
+    };
 
-    if (m_sortRole == NameRole) {
-        // Sorting by name can be expensive, in particular if natural sorting is
+    if (m_sortRole == NameRole || isRoleValueNatural(m_sortRole)) {
+        // Sorting by string can be expensive, in particular if natural sorting is
         // enabled. Use all CPU cores to speed up the sorting process.
         static const int numberOfThreads = QThread::idealThreadCount();
-        parallelMergeSort(begin, end, lessThan, numberOfThreads);
+        parallelMergeSort(begin, end, lambdaLessThan, numberOfThreads);
     } else {
         // Sorting by other roles is quite fast. Use only one thread to prevent
         // problems caused by non-reentrant comparison functions, see
         // https://bugs.kde.org/show_bug.cgi?id=312679
-        mergeSort(begin, end, lessThan);
+        mergeSort(begin, end, lambdaLessThan);
     }
 }
 
@@ -1700,8 +1766,15 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const
             // See "if (m_sortFoldersFirst || m_sortRole == SizeRole)" in KFileItemModel::lessThan():
             Q_ASSERT(itemB.isDir());
 
-            const QVariant valueA = a->values.value("size");
-            const QVariant valueB = b->values.value("size");
+            QVariant valueA, valueB;
+            if (DetailsModeSettings::directorySizeCount()) {
+                valueA = a->values.value("count");
+                valueB = b->values.value("count");
+            } else {
+                // use dir size then
+                valueA = a->values.value("size");
+                valueB = b->values.value("size");
+            }
             if (valueA.isNull() && valueB.isNull()) {
                 result = 0;
             } else if (valueA.isNull()) {
@@ -1709,7 +1782,11 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const
             } else if (valueB.isNull()) {
                 result = +1;
             } else {
-                result = valueA.toInt() - valueB.toInt();
+                if (valueA.toLongLong() < valueB.toLongLong()) {
+                    return -1;
+                } else {
+                    return +1;
+                }
             }
         } else {
             // See "if (m_sortFoldersFirst || m_sortRole == SizeRole)" in KFileItemModel::lessThan():
@@ -1727,9 +1804,9 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const
         break;
     }
 
-    case DateRole: {
-        const QDateTime dateTimeA = itemA.time(KFileItem::ModificationTime);
-        const QDateTime dateTimeB = itemB.time(KFileItem::ModificationTime);
+    case ModificationTimeRole: {
+        const long long dateTimeA = itemA.entry().numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1);
+        const long long dateTimeB = itemB.entry().numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1);
         if (dateTimeA < dateTimeB) {
             result = -1;
         } else if (dateTimeA > dateTimeB) {
@@ -1738,23 +1815,52 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const
         break;
     }
 
-    case RatingRole: {
-        result = a->values.value("rating").toInt() - b->values.value("rating").toInt();
+    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);
+        if (dateTimeA < dateTimeB) {
+            result = -1;
+        } else if (dateTimeA > dateTimeB) {
+            result = +1;
+        }
         break;
     }
 
-    case ImageSizeRole: {
-        // Alway use a natural comparing to interpret the numbers of a string like
-        // "1600 x 1200" for having a correct sorting.
-        result = collator.compare(a->values.value("imageSize").toString(),
-                                  b->values.value("imageSize").toString());
+    case DeletionTimeRole: {
+        const QDateTime dateTimeA = a->values.value("deletiontime").toDateTime();
+        const QDateTime dateTimeB = b->values.value("deletiontime").toDateTime();
+        if (dateTimeA < dateTimeB) {
+            result = -1;
+        } else if (dateTimeA > dateTimeB) {
+            result = +1;
+        }
+        break;
+    }
+
+    case RatingRole:
+    case WidthRole:
+    case HeightRole:
+    case WordCountRole:
+    case LineCountRole:
+    case TrackRole:
+    case ReleaseYearRole: {
+        result = a->values.value(roleForType(m_sortRole)).toInt() - b->values.value(roleForType(m_sortRole)).toInt();
         break;
     }
 
     default: {
         const QByteArray role = roleForType(m_sortRole);
-        result = QString::compare(a->values.value(role).toString(),
-                                  b->values.value(role).toString());
+        const QString roleValueA = a->values.value(role).toString();
+        const QString roleValueB = b->values.value(role).toString();
+        if (!roleValueA.isEmpty() && roleValueB.isEmpty()) {
+            result = -1;
+        } else if (roleValueA.isEmpty() && !roleValueB.isEmpty()) {
+            result = +1;
+        } else if (isRoleValueNatural(m_sortRole)) {
+            result = stringCompare(roleValueA, roleValueB, collator);
+        } else {
+            result = QString::compare(roleValueA, roleValueB);
+        }
         break;
     }
 
@@ -1785,6 +1891,8 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const
 
 int KFileItemModel::stringCompare(const QString& a, const QString& b, const QCollator& collator) const
 {
+    QMutexLocker collatorLock(s_collatorMutex());
+
     if (m_naturalSorting) {
         return collator.compare(a, b);
     }
@@ -1800,11 +1908,6 @@ int KFileItemModel::stringCompare(const QString& a, const QString& b, const QCol
     return QString::compare(a, b, Qt::CaseSensitive);
 }
 
-bool KFileItemModel::useMaximumUpdateInterval() const
-{
-    return !m_dirLister->url().isLocalFile();
-}
-
 QList<QPair<int, QVariant> > KFileItemModel::nameRoleGroups() const
 {
     Q_ASSERT(!m_itemData.isEmpty());
@@ -1830,27 +1933,35 @@ QList<QPair<int, QVariant> > KFileItemModel::nameRoleGroups() const
         if (firstChar != newFirstChar) {
             QString newGroupValue;
             if (newFirstChar.isLetter()) {
-                // Try to find a matching group in the range 'A' to 'Z'.
-                static std::vector<QChar> lettersAtoZ;
-                if (lettersAtoZ.empty()) {
-                    for (char c = 'A'; c <= 'Z'; ++c) {
-                        lettersAtoZ.push_back(QLatin1Char(c));
+
+                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](const QChar& c1, const QChar& c2) -> bool {
-                    return m_collator.compare(c1, c2) < 0;
-                };
+                    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) && it != lettersAtoZ.begin()) {
-                        // newFirstChar belongs to the group preceding *it.
-                        // Example: for an umlaut 'A' in the German locale, *it would be 'B' now.
-                        --it;
+                    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;
                     }
-                    newGroupValue = *it;
+
                 } else {
+                    // Symbols from non Latin-based scripts
                     newGroupValue = newFirstChar;
                 }
             } else if (newFirstChar >= QLatin1Char('0') && newFirstChar <= QLatin1Char('9')) {
@@ -1906,7 +2017,7 @@ QList<QPair<int, QVariant> > KFileItemModel::sizeRoleGroups() const
     return groups;
 }
 
-QList<QPair<int, QVariant> > KFileItemModel::dateRoleGroups() const
+QList<QPair<int, QVariant> > KFileItemModel::timeRoleGroups(const std::function<QDateTime(const ItemData *)> &fileTimeCb) const
 {
     Q_ASSERT(!m_itemData.isEmpty());
 
@@ -1915,31 +2026,37 @@ QList<QPair<int, QVariant> > KFileItemModel::dateRoleGroups() const
 
     const QDate currentDate = QDate::currentDate();
 
-    QDate previousModifiedDate;
+    QDate previousFileDate;
     QString groupValue;
     for (int i = 0; i <= maxIndex; ++i) {
         if (isChildItem(i)) {
             continue;
         }
 
-        const QDateTime modifiedTime = m_itemData.at(i)->item.time(KFileItem::ModificationTime);
-        const QDate modifiedDate = modifiedTime.date();
-        if (modifiedDate == previousModifiedDate) {
+        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;
         }
-        previousModifiedDate = modifiedDate;
+        previousFileDate = fileDate;
 
-        const int daysDistance = modifiedDate.daysTo(currentDate);
+        const int daysDistance = fileDate.daysTo(currentDate);
 
         QString newGroupValue;
-        if (currentDate.year() == modifiedDate.year() && currentDate.month() == modifiedDate.month()) {
+        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;
-                default: newGroupValue = modifiedTime.toString(i18nc("@title:group The week day name: dddd", "dddd"));
+                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);
                 }
                 break;
             case 1:
@@ -1960,22 +2077,100 @@ QList<QPair<int, QVariant> > KFileItemModel::dateRoleGroups() const
             }
         } else {
             const QDate lastMonthDate = currentDate.addMonths(-1);
-            if  (lastMonthDate.year() == modifiedDate.year() && lastMonthDate.month() == modifiedDate.month()) {
+            if  (lastMonthDate.year() == fileDate.year() &&
+                 lastMonthDate.month() == fileDate.month()) {
+
                 if (daysDistance == 1) {
-                    newGroupValue = modifiedTime.toString(i18nc("@title:group Date: MMMM is full month name in current locale, and yyyy is full year number", "'Yesterday' (MMMM, yyyy)"));
+                    const KLocalizedString format = ki18nc("@title:group Date: "
+                                                    "MMMM is full month name in current locale, and yyyy is "
+                                                    "full year number", "'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 "
+                            "\"'Yesterday' (MMMM, yyyy)\" with context @title:group Date",
+                            "%1", newGroupValue);
+                    } 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);
+                    }
                 } else if (daysDistance <= 7) {
-                    newGroupValue = modifiedTime.toString(i18nc("@title:group The week day name: dddd, MMMM is full month name in current locale, and yyyy is full year number", "dddd (MMMM, yyyy)"));
+                    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 "
+                        "\"dddd (MMMM, yyyy)\" with context @title:group Date",
+                        "%1", newGroupValue);
                 } else if (daysDistance <= 7 * 2) {
-                    newGroupValue = modifiedTime.toString(i18nc("@title:group Date: MMMM is full month name in current locale, and yyyy is full year number", "'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", "'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 "
+                            "\"'One Week Ago' (MMMM, yyyy)\" with context @title:group Date",
+                            "%1", newGroupValue);
+                    } 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);
+                    }
                 } else if (daysDistance <= 7 * 3) {
-                    newGroupValue = modifiedTime.toString(i18nc("@title:group Date: MMMM is full month name in current locale, and yyyy is full year number", "'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", "'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 "
+                            "\"'Two Weeks Ago' (MMMM, yyyy)\" with context @title:group Date",
+                            "%1", newGroupValue);
+                    } 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);
+                    }
                 } else if (daysDistance <= 7 * 4) {
-                    newGroupValue = modifiedTime.toString(i18nc("@title:group Date: MMMM is full month name in current locale, and yyyy is full year number", "'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", "'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 "
+                            "\"'Three Weeks Ago' (MMMM, yyyy)\" with context @title:group Date",
+                            "%1", newGroupValue);
+                    } 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);
+                    }
                 } else {
-                    newGroupValue = modifiedTime.toString(i18nc("@title:group Date: MMMM is full month name in current locale, and yyyy is full year number", "'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", "'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 "
+                            "\"'Earlier on' MMMM, yyyy\" with context @title:group Date",
+                            "%1", newGroupValue);
+                    } 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);
+                    }
                 }
             } else {
-                newGroupValue = modifiedTime.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 = 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 "
+                    "\"MMMM, yyyy\" with context @title:group Date",
+                    "%1", newGroupValue);
             }
         }
 
@@ -2122,14 +2317,14 @@ void KFileItemModel::emitSortProgress(int resolvedCount)
             resortAllItems();
         }
 
-        emit directorySortingProgress(100);
+        Q_EMIT directorySortingProgress(100);
     } else if (itemCount > 0) {
         resolvedCount = qBound(0, resolvedCount, itemCount);
 
         const int progress = resolvedCount * 100 / itemCount;
         if (m_sortingProgressPercent != progress) {
             m_sortingProgressPercent = progress;
-            emit directorySortingProgress(progress);
+            Q_EMIT directorySortingProgress(progress);
         }
     }
 }
@@ -2137,29 +2332,40 @@ void KFileItemModel::emitSortProgress(int resolvedCount)
 const KFileItemModel::RoleInfoMap* KFileItemModel::rolesInfoMap(int& count)
 {
     static const RoleInfoMap rolesInfoMap[] = {
-    //  | role         | roleType       | role translation                                | group translation           | requires Baloo   | requires indexer
-        { 0,             NoRole,          0, 0,                                             0, 0,                                     false, false },
-        { "text",        NameRole,        I18N_NOOP2_NOSTRIP("@label", "Name"),             0, 0,                                     false, false },
-        { "size",        SizeRole,        I18N_NOOP2_NOSTRIP("@label", "Size"),             0, 0,                                     false, false },
-        { "date",        DateRole,        I18N_NOOP2_NOSTRIP("@label", "Date"),             0, 0,                                     false, false },
-        { "type",        TypeRole,        I18N_NOOP2_NOSTRIP("@label", "Type"),             0, 0,                                     false, false },
-        { "rating",      RatingRole,      I18N_NOOP2_NOSTRIP("@label", "Rating"),           0, 0,                                     true,  false },
-        { "tags",        TagsRole,        I18N_NOOP2_NOSTRIP("@label", "Tags"),             0, 0,                                     true,  false },
-        { "comment",     CommentRole,     I18N_NOOP2_NOSTRIP("@label", "Comment"),          0, 0,                                     true,  false },
-        { "wordCount",   WordCountRole,   I18N_NOOP2_NOSTRIP("@label", "Word Count"),       I18N_NOOP2_NOSTRIP("@label", "Document"), true,  true  },
-        { "lineCount",   LineCountRole,   I18N_NOOP2_NOSTRIP("@label", "Line Count"),       I18N_NOOP2_NOSTRIP("@label", "Document"), true,  true  },
-        { "imageSize",   ImageSizeRole,   I18N_NOOP2_NOSTRIP("@label", "Image Size"),       I18N_NOOP2_NOSTRIP("@label", "Image"),    true,  true  },
-        { "orientation", OrientationRole, I18N_NOOP2_NOSTRIP("@label", "Orientation"),      I18N_NOOP2_NOSTRIP("@label", "Image"),    true,  true  },
-        { "artist",      ArtistRole,      I18N_NOOP2_NOSTRIP("@label", "Artist"),           I18N_NOOP2_NOSTRIP("@label", "Audio"),    true,  true  },
-        { "album",       AlbumRole,       I18N_NOOP2_NOSTRIP("@label", "Album"),            I18N_NOOP2_NOSTRIP("@label", "Audio"),    true,  true  },
-        { "duration",    DurationRole,    I18N_NOOP2_NOSTRIP("@label", "Duration"),         I18N_NOOP2_NOSTRIP("@label", "Audio"),    true,  true  },
-        { "track",       TrackRole,       I18N_NOOP2_NOSTRIP("@label", "Track"),            I18N_NOOP2_NOSTRIP("@label", "Audio"),    true,  true  },
-        { "path",        PathRole,        I18N_NOOP2_NOSTRIP("@label", "Path"),             I18N_NOOP2_NOSTRIP("@label", "Other"),    false, false },
-        { "destination", DestinationRole, I18N_NOOP2_NOSTRIP("@label", "Link Destination"), I18N_NOOP2_NOSTRIP("@label", "Other"),    false, false },
-        { "copiedFrom",  CopiedFromRole,  I18N_NOOP2_NOSTRIP("@label", "Copied From"),      I18N_NOOP2_NOSTRIP("@label", "Other"),    true,  false },
-        { "permissions", PermissionsRole, I18N_NOOP2_NOSTRIP("@label", "Permissions"),      I18N_NOOP2_NOSTRIP("@label", "Other"),    false, false },
-        { "owner",       OwnerRole,       I18N_NOOP2_NOSTRIP("@label", "Owner"),            I18N_NOOP2_NOSTRIP("@label", "Other"),    false, false },
-        { "group",       GroupRole,       I18N_NOOP2_NOSTRIP("@label", "User Group"),       I18N_NOOP2_NOSTRIP("@label", "Other"),    false, false },
+    //  |         role           |        roleType        |                role translation                     |            group translation               | requires Baloo | requires indexer
+        { nullptr,               NoRole,                  nullptr, nullptr,                                     nullptr, nullptr,                            false,           false },
+        { "text",                NameRole,                I18NC_NOOP("@label", "Name"),                 nullptr, nullptr,                            false,           false },
+        { "size",                SizeRole,                I18NC_NOOP("@label", "Size"),                 nullptr, nullptr,                            false,           false },
+        { "modificationtime",    ModificationTimeRole,    I18NC_NOOP("@label", "Modified"),             nullptr, nullptr,                            false,           false },
+        { "creationtime",        CreationTimeRole,        I18NC_NOOP("@label", "Created"),              nullptr, nullptr,                            false,           false },
+        { "accesstime",          AccessTimeRole,          I18NC_NOOP("@label", "Accessed"),             nullptr, nullptr,                            false,           false },
+        { "type",                TypeRole,                I18NC_NOOP("@label", "Type"),                 nullptr, nullptr,                            false,           false },
+        { "rating",              RatingRole,              I18NC_NOOP("@label", "Rating"),               nullptr, nullptr,                            true,            false },
+        { "tags",                TagsRole,                I18NC_NOOP("@label", "Tags"),                 nullptr, nullptr,                            true,            false },
+        { "comment",             CommentRole,             I18NC_NOOP("@label", "Comment"),              nullptr, nullptr,                            true,            false },
+        { "title",               TitleRole,               I18NC_NOOP("@label", "Title"),                I18NC_NOOP("@label", "Document"),    true,            true  },
+        { "wordCount",           WordCountRole,           I18NC_NOOP("@label", "Word Count"),           I18NC_NOOP("@label", "Document"),    true,            true  },
+        { "lineCount",           LineCountRole,           I18NC_NOOP("@label", "Line Count"),           I18NC_NOOP("@label", "Document"),    true,            true  },
+        { "imageDateTime",       ImageDateTimeRole,       I18NC_NOOP("@label", "Date Photographed"),    I18NC_NOOP("@label", "Image"),       true,            true  },
+        { "width",               WidthRole,               I18NC_NOOP("@label", "Width"),                I18NC_NOOP("@label", "Image"),       true,            true  },
+        { "height",              HeightRole,              I18NC_NOOP("@label", "Height"),               I18NC_NOOP("@label", "Image"),       true,            true  },
+        { "orientation",         OrientationRole,         I18NC_NOOP("@label", "Orientation"),          I18NC_NOOP("@label", "Image"),       true,            true  },
+        { "artist",              ArtistRole,              I18NC_NOOP("@label", "Artist"),               I18NC_NOOP("@label", "Audio"),       true,            true  },
+        { "genre",               GenreRole,               I18NC_NOOP("@label", "Genre"),                I18NC_NOOP("@label", "Audio"),       true,            true  },
+        { "album",               AlbumRole,               I18NC_NOOP("@label", "Album"),                I18NC_NOOP("@label", "Audio"),       true,            true  },
+        { "duration",            DurationRole,            I18NC_NOOP("@label", "Duration"),             I18NC_NOOP("@label", "Audio"),       true,            true  },
+        { "bitrate",             BitrateRole,             I18NC_NOOP("@label", "Bitrate"),              I18NC_NOOP("@label", "Audio"),       true,            true  },
+        { "track",               TrackRole,               I18NC_NOOP("@label", "Track"),                I18NC_NOOP("@label", "Audio"),       true,            true  },
+        { "releaseYear",         ReleaseYearRole,         I18NC_NOOP("@label", "Release Year"),         I18NC_NOOP("@label", "Audio"),       true,            true  },
+        { "aspectRatio",         AspectRatioRole,         I18NC_NOOP("@label", "Aspect Ratio"),         I18NC_NOOP("@label", "Video"),       true,            true  },
+        { "frameRate",           FrameRateRole,           I18NC_NOOP("@label", "Frame Rate"),           I18NC_NOOP("@label", "Video"),       true,            true  },
+        { "path",                PathRole,                I18NC_NOOP("@label", "Path"),                 I18NC_NOOP("@label", "Other"),       false,           false },
+        { "deletiontime",        DeletionTimeRole,        I18NC_NOOP("@label", "Deletion Time"),        I18NC_NOOP("@label", "Other"),       false,           false },
+        { "destination",         DestinationRole,         I18NC_NOOP("@label", "Link Destination"),     I18NC_NOOP("@label", "Other"),       false,           false },
+        { "originUrl",           OriginUrlRole,           I18NC_NOOP("@label", "Downloaded From"),      I18NC_NOOP("@label", "Other"),       true,            false },
+        { "permissions",         PermissionsRole,         I18NC_NOOP("@label", "Permissions"),          I18NC_NOOP("@label", "Other"),       false,           false },
+        { "owner",               OwnerRole,               I18NC_NOOP("@label", "Owner"),                I18NC_NOOP("@label", "Other"),       false,           false },
+        { "group",               GroupRole,               I18NC_NOOP("@label", "User Group"),           I18NC_NOOP("@label", "Other"),       false,           false },
     };
 
     count = sizeof(rolesInfoMap) / sizeof(RoleInfoMap);
@@ -2170,7 +2376,7 @@ void KFileItemModel::determineMimeTypes(const KFileItemList& items, int timeout)
 {
     QElapsedTimer timer;
     timer.start();
-    foreach (const KFileItem& item, items) { // krazy:exclude=foreach
+    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
@@ -2209,7 +2415,7 @@ bool KFileItemModel::isConsistent() const
         return false;
     }
 
-    for (int i = 0; i < count(); ++i) {
+    for (int i = 0, iMax = count(); i < iMax; ++i) {
         // Check if m_items and m_itemData are consistent.
         const KFileItem item = fileItem(i);
         if (item.isNull()) {
@@ -2249,4 +2455,3 @@ bool KFileItemModel::isConsistent() const
 
     return true;
 }
-