]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/kitemviews/kfileitemmodel.cpp
Configurable Show hidden files and folders last toggle
[dolphin.git] / src / kitemviews / kfileitemmodel.cpp
index ac3c33e22e44af1a0680c1684c50dfd0139c084b..ef80b4edbd7a265eb2b73eae158aa18c1261000a 100644 (file)
@@ -1,23 +1,10 @@
-/*****************************************************************************
- *   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 "private/kfileitemmodeldirlister.h"
 #include "private/kfileitemmodelsortalgorithm.h"
 
+#include <kio_version.h>
 #include <KLocalizedString>
 #include <KUrlMimeData>
 
 #include <QElapsedTimer>
 #include <QMimeData>
+#include <QMimeDatabase>
 #include <QTimer>
 #include <QWidget>
-#include <QMutex>
+#include <QRecursiveMutex>
+#include <QIcon>
 
-Q_GLOBAL_STATIC_WITH_ARGS(QMutex, s_collatorMutex, (QMutex::Recursive))
+Q_GLOBAL_STATIC(QRecursiveMutex, s_collatorMutex)
 
 // #define KFILEITEMMODEL_DEBUG
 
@@ -44,6 +34,7 @@ KFileItemModel::KFileItemModel(QObject* parent) :
     KItemModelBase("text", parent),
     m_dirLister(nullptr),
     m_sortDirsFirst(true),
+    m_sortHiddenLast(true),
     m_sortRole(NameRole),
     m_sortingProgressPercent(-1),
     m_roles(),
@@ -73,7 +64,6 @@ KFileItemModel::KFileItemModel(QObject* parent) :
 
     connect(m_dirLister, &KFileItemModelDirLister::started, this, &KFileItemModel::directoryLoadingStarted);
     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);
@@ -84,6 +74,12 @@ KFileItemModel::KFileItemModel(QObject* parent) :
     connect(m_dirLister, QOverload<const QUrl&, const QUrl&>::of(&KCoreDirLister::redirection), this, &KFileItemModel::directoryRedirection);
     connect(m_dirLister, &KFileItemModelDirLister::urlIsFileError, this, &KFileItemModel::urlIsFileError);
 
+#if KIO_VERSION < QT_VERSION_CHECK(5, 79, 0)
+    connect(m_dirLister, QOverload<const QUrl&>::of(&KCoreDirLister::completed), this, &KFileItemModel::slotCompleted);
+#else
+    connect(m_dirLister, &KCoreDirLister::listingDirCompleted, this, &KFileItemModel::slotCompleted);
+#endif
+
     // Apply default roles that should be determined
     resetRoles();
     m_requestRole[NameRole] = true;
@@ -217,6 +213,19 @@ bool KFileItemModel::sortDirectoriesFirst() const
     return m_sortDirsFirst;
 }
 
+void KFileItemModel::setSortHiddenLast(bool hiddenLast)
+{
+    if (hiddenLast != m_sortHiddenLast) {
+        m_sortHiddenLast = hiddenLast;
+        resortAllItems();
+    }
+}
+
+bool KFileItemModel::sortHiddenLast() const
+{
+    return m_sortHiddenLast;
+}
+
 void KFileItemModel::setShowHiddenFiles(bool show)
 {
     m_dirLister->setShowingDotFiles(show);
@@ -247,7 +256,7 @@ 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;
     const ItemData* lastAddedItem = nullptr;
@@ -433,7 +442,8 @@ 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;
 
@@ -499,7 +509,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
@@ -537,7 +547,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 {
@@ -639,7 +649,7 @@ void KFileItemModel::expandParentDirectories(const QUrl &url)
     // 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(), QString::SkipEmptyParts);
+    const QStringList subDirs = url.path().mid(pos).split(QDir::separator(), Qt::SkipEmptyParts);
     for (int i = 0; i < subDirs.count() - 1; ++i) {
         QString path = urlToExpand.path();
         if (!path.endsWith(QLatin1Char('/'))) {
@@ -741,7 +751,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));
         }
@@ -861,7 +871,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());
     }
 
@@ -904,13 +914,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();
         }
     }
 
@@ -929,7 +939,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) {
+        // Iterate over a const copy because items are deleted and inserted within the loop
+        const auto urlsToExpand = m_urlsToExpand;
+        for(const QUrl &url : urlsToExpand) {
             const int indexForUrl = index(url);
             if (indexForUrl >= 0) {
                 m_urlsToExpand.remove(url);
@@ -946,7 +958,7 @@ void KFileItemModel::slotCompleted()
         m_urlsToExpand.clear();
     }
 
-    emit directoryLoadingCompleted();
+    Q_EMIT directoryLoadingCompleted();
 }
 
 void KFileItemModel::slotCanceled()
@@ -954,7 +966,7 @@ void KFileItemModel::slotCanceled()
     m_maximumUpdateIntervalTimer->stop();
     dispatchPendingItemsToInsert();
 
-    emit directoryLoadingCanceled();
+    Q_EMIT directoryLoadingCanceled();
 }
 
 void KFileItemModel::slotItemsAdded(const QUrl &directoryUrl, const KFileItemList& items)
@@ -995,7 +1007,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);
@@ -1003,7 +1015,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 {
@@ -1017,6 +1029,8 @@ void KFileItemModel::slotItemsAdded(const QUrl &directoryUrl, const KFileItemLis
         // emitted during the maximum update interval.
         m_maximumUpdateIntervalTimer->start();
     }
+
+    Q_EMIT fileItemsChanged({KFileItem(directoryUrl)});
 }
 
 void KFileItemModel::slotItemsDeleted(const KFileItemList& items)
@@ -1025,8 +1039,9 @@ void KFileItemModel::slotItemsDeleted(const KFileItemList& items)
 
     QVector<int> indexesToRemove;
     indexesToRemove.reserve(items.count());
+    KFileItemList dirsChanged;
 
-    foreach (const KFileItem& item, items) {
+    for (const KFileItem& item : items) {
         const int indexForItem = index(item);
         if (indexForItem >= 0) {
             indexesToRemove.append(indexForItem);
@@ -1038,6 +1053,11 @@ void KFileItemModel::slotItemsDeleted(const KFileItemList& items)
                 m_filteredItems.erase(it);
             }
         }
+
+        QUrl parentUrl = item.url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
+        if (dirsChanged.findByUrl(parentUrl).isNull()) {
+            dirsChanged << KFileItem(parentUrl);
+        }
     }
 
     std::sort(indexesToRemove.begin(), indexesToRemove.end());
@@ -1048,7 +1068,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);
@@ -1065,6 +1085,8 @@ void KFileItemModel::slotItemsDeleted(const KFileItemList& items)
     const KItemRangeList itemRanges = KItemRangeList::fromSortedContainer(indexesToRemove);
     removeFilteredChildren(itemRanges);
     removeItems(itemRanges, DeleteItemData);
+
+    Q_EMIT fileItemsChanged(dirsChanged);
 }
 
 void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >& items)
@@ -1079,6 +1101,7 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
     indexes.reserve(items.count());
 
     QSet<QByteArray> changedRoles;
+    KFileItemList changedFiles;
 
     QListIterator<QPair<KFileItem, KFileItem> > it(items);
     while (it.hasNext()) {
@@ -1104,6 +1127,7 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
 
             m_items.remove(oldItem.url());
             m_items.insert(newItem.url(), indexForItem);
+            changedFiles.append(newItem);
             indexes.append(indexForItem);
         } else {
             // Check if 'oldItem' is one of the filtered items.
@@ -1132,6 +1156,8 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
     std::sort(indexes.begin(), indexes.end());
     const KItemRangeList itemRangeList = KItemRangeList::fromSortedContainer(indexes);
     emitItemsChangedAndTriggerResorting(itemRangeList, changedRoles);
+
+    Q_EMIT fileItemsChanged(changedFiles);
 }
 
 void KFileItemModel::slotClear()
@@ -1155,7 +1181,7 @@ 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();
@@ -1271,7 +1297,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();
@@ -1288,7 +1314,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) {
@@ -1324,7 +1350,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
@@ -1342,7 +1368,7 @@ QList<KFileItemModel::ItemData*> KFileItemModel::createItemDataList(const QUrl&
     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;
@@ -1363,7 +1389,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.
-        foreach (ItemData* itemData, itemDataList) {
+        for (ItemData* itemData : qAsConst(itemDataList)) {
             if (itemData->values.isEmpty()) {
                 itemData->values = retrieveData(itemData->item, itemData->parent);
             }
@@ -1372,7 +1398,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()) {
@@ -1440,12 +1466,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;
@@ -1667,7 +1693,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());
@@ -1715,7 +1747,18 @@ bool KFileItemModel::lessThan(const ItemData* a, const ItemData* b, const QColla
         }
     }
 
-    if (m_sortDirsFirst || m_sortRole == SizeRole) {
+    // 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) {
@@ -1764,44 +1807,53 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const
         break;
 
     case SizeRole: {
-        if (itemA.isDir()) {
-            // See "if (m_sortFoldersFirst || m_sortRole == SizeRole)" in KFileItemModel::lessThan():
-            Q_ASSERT(itemB.isDir());
-
-            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()) {
-                result = -1;
+        if (DetailsModeSettings::directorySizeCount() && itemA.isDir()) {
+            // folders first then
+            // items A and B are folders thanks to lessThan checks
+            auto valueA = a->values.value("count");
+            auto valueB = b->values.value("count");
+            if (valueA.isNull()) {
+                if (valueB.isNull()) {
+                    result = 0;
+                    break;
+                } else {
+                    result = -1;
+                    break;
+                }
             } else if (valueB.isNull()) {
                 result = +1;
+                break;
             } else {
-                if (valueA < valueB) {
-                    return -1;
+                if (valueA.toLongLong() < valueB.toLongLong()) {
+                    result = -1;
+                    break;
+                } else if (valueA.toLongLong() > valueB.toLongLong()) {
+                    result = +1;
+                    break;
                 } else {
-                    return +1;
+                    result = 0;
+                    break;
                 }
             }
+        }
+        KIO::filesize_t sizeA = 0;
+        if (itemA.isDir()) {
+            sizeA = a->values.value("size").toULongLong();
         } else {
-            // See "if (m_sortFoldersFirst || m_sortRole == SizeRole)" in KFileItemModel::lessThan():
-            Q_ASSERT(!itemB.isDir());
-            const KIO::filesize_t sizeA = itemA.size();
-            const KIO::filesize_t sizeB = itemB.size();
-            if (sizeA > sizeB) {
-                result = +1;
-            } else if (sizeA < sizeB) {
-                result = -1;
-            } else {
-                result = 0;
-            }
+            sizeA = itemA.size();
+        }
+        KIO::filesize_t sizeB = 0;
+        if (itemB.isDir()) {
+            sizeB = b->values.value("size").toULongLong();
+        } else {
+            sizeB = itemB.size();
+        }
+        if (sizeA > sizeB) {
+            result = +1;
+        } else if (sizeA < sizeB) {
+            result = -1;
+        } else {
+            result = 0;
         }
         break;
     }
@@ -1998,16 +2050,24 @@ QList<QPair<int, QVariant> > KFileItemModel::sizeRoleGroups() const
         }
 
         const KFileItem& item = m_itemData.at(i)->item;
-        const KIO::filesize_t fileSize = !item.isNull() ? item.size() : ~0U;
+        KIO::filesize_t fileSize = !item.isNull() ? item.size() : ~0U;
         QString newGroupValue;
         if (!item.isNull() && item.isDir()) {
-            newGroupValue = i18nc("@title:group Size", "Folders");
-        } else if (fileSize < 5 * 1024 * 1024) {
-            newGroupValue = i18nc("@title:group Size", "Small");
-        } else if (fileSize < 10 * 1024 * 1024) {
-            newGroupValue = i18nc("@title:group Size", "Medium");
-        } else {
-            newGroupValue = i18nc("@title:group Size", "Big");
+            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");
+            }
         }
 
         if (newGroupValue != groupValue) {
@@ -2085,7 +2145,7 @@ QList<QPair<int, QVariant> > KFileItemModel::timeRoleGroups(const std::function<
                 if (daysDistance == 1) {
                     const KLocalizedString format = ki18nc("@title:group Date: "
                                                     "MMMM is full month name in current locale, and yyyy is "
-                                                    "full year number", "'Yesterday' (MMMM, yyyy)");
+                                                    "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);
@@ -2100,7 +2160,7 @@ QList<QPair<int, QVariant> > KFileItemModel::timeRoleGroups(const std::function<
                 } 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",
+                        "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",
@@ -2108,7 +2168,7 @@ QList<QPair<int, QVariant> > KFileItemModel::timeRoleGroups(const std::function<
                 } 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", "'One Week Ago' (MMMM, yyyy)");
+                                                           "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);
@@ -2123,7 +2183,7 @@ QList<QPair<int, QVariant> > KFileItemModel::timeRoleGroups(const std::function<
                 } 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", "'Two Weeks Ago' (MMMM, yyyy)");
+                                                           "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);
@@ -2138,7 +2198,7 @@ QList<QPair<int, QVariant> > KFileItemModel::timeRoleGroups(const std::function<
                 } 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", "'Three Weeks Ago' (MMMM, yyyy)");
+                                                           "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);
@@ -2153,7 +2213,7 @@ QList<QPair<int, QVariant> > KFileItemModel::timeRoleGroups(const std::function<
                 } else {
                     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");
+                                                           "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);
@@ -2319,14 +2379,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);
         }
     }
 }
@@ -2378,7 +2438,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