]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/kitemviews/kfileitemmodel.cpp
Revert "Revert "Disable Hidden Files Last sort by default""
[dolphin.git] / src / kitemviews / kfileitemmodel.cpp
index b000d649c13cd675bd3bb2e3a6f8293d4ab2a5cc..bf2597903ddef9263b3e05f068d1ad34d11ef138 100644 (file)
 #include "dolphin_generalsettings.h"
 #include "dolphin_detailsmodesettings.h"
 #include "dolphindebug.h"
-#include "private/kfileitemmodeldirlister.h"
 #include "private/kfileitemmodelsortalgorithm.h"
 
-#include <kio_version.h>
+#include <KDirLister>
+#include <KIO/Job>
 #include <KLocalizedString>
 #include <KUrlMimeData>
 
@@ -34,6 +34,7 @@ KFileItemModel::KFileItemModel(QObject* parent) :
     KItemModelBase("text", parent),
     m_dirLister(nullptr),
     m_sortDirsFirst(true),
+    m_sortHiddenLast(false),
     m_sortRole(NameRole),
     m_sortingProgressPercent(-1),
     m_roles(),
@@ -53,7 +54,8 @@ KFileItemModel::KFileItemModel(QObject* parent) :
 
     loadSortingSettings();
 
-    m_dirLister = new KFileItemModelDirLister(this);
+    m_dirLister = new KDirLister(this);
+    m_dirLister->setAutoErrorHandlingEnabled(false);
     m_dirLister->setDelayedMimeTypes(true);
 
     const QWidget* parentWidget = qobject_cast<QWidget*>(parent);
@@ -61,23 +63,17 @@ KFileItemModel::KFileItemModel(QObject* parent) :
         m_dirLister->setMainWindow(parentWidget->window());
     }
 
-    connect(m_dirLister, &KFileItemModelDirLister::started, this, &KFileItemModel::directoryLoadingStarted);
+    connect(m_dirLister, &KCoreDirLister::started, this, &KFileItemModel::directoryLoadingStarted);
     connect(m_dirLister, QOverload<>::of(&KCoreDirLister::canceled), this, &KFileItemModel::slotCanceled);
-    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, &KCoreDirLister::itemsAdded, this, &KFileItemModel::slotItemsAdded);
+    connect(m_dirLister, &KCoreDirLister::itemsDeleted, this, &KFileItemModel::slotItemsDeleted);
+    connect(m_dirLister, &KCoreDirLister::refreshItems, this, &KFileItemModel::slotRefreshItems);
     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, &KFileItemModelDirLister::percent, this, &KFileItemModel::directoryLoadingProgress);
+    connect(m_dirLister, &KCoreDirLister::infoMessage, this, &KFileItemModel::infoMessage);
+    connect(m_dirLister, &KCoreDirLister::jobError, this, &KFileItemModel::slotListerError);
+    connect(m_dirLister, &KCoreDirLister::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);
-
-#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();
@@ -212,6 +208,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);
@@ -1015,6 +1024,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)
@@ -1023,6 +1034,7 @@ void KFileItemModel::slotItemsDeleted(const KFileItemList& items)
 
     QVector<int> indexesToRemove;
     indexesToRemove.reserve(items.count());
+    KFileItemList dirsChanged;
 
     for (const KFileItem& item : items) {
         const int indexForItem = index(item);
@@ -1036,6 +1048,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());
@@ -1063,6 +1080,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)
@@ -1077,6 +1096,15 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
     indexes.reserve(items.count());
 
     QSet<QByteArray> changedRoles;
+    KFileItemList changedFiles;
+
+    // Contains the indexes of the currently visible items
+    // that should get hidden and hence moved to m_filteredItems.
+    QVector<int> newFilteredIndexes;
+
+    // Contains currently hidden items that should
+    // get visible and hence removed from m_filteredItems
+    QList<ItemData*> newVisibleItems;
 
     QListIterator<QPair<KFileItem, KFileItem> > it(items);
     while (it.hasNext()) {
@@ -1084,25 +1112,32 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
         const KFileItem& oldItem = itemPair.first;
         const KFileItem& newItem = itemPair.second;
         const int indexForItem = index(oldItem);
+        const bool newItemMatchesFilter = m_filter.matches(newItem);
         if (indexForItem >= 0) {
             m_itemData[indexForItem]->item = newItem;
 
             // Keep old values as long as possible if they could not retrieved synchronously yet.
             // The update of the values will be done asynchronously by KFileItemModelRolesUpdater.
-            QHashIterator<QByteArray, QVariant> it(retrieveData(newItem, m_itemData.at(indexForItem)->parent));
-            QHash<QByteArray, QVariant>& values = m_itemData[indexForItem]->values;
+            ItemData * const itemData = m_itemData.at(indexForItem);
+            QHashIterator<QByteArray, QVariant> it(retrieveData(newItem, itemData->parent));
             while (it.hasNext()) {
                 it.next();
                 const QByteArray& role = it.key();
-                if (values.value(role) != it.value()) {
-                    values.insert(role, it.value());
+                if (itemData->values.value(role) != it.value()) {
+                    itemData->values.insert(role, it.value());
                     changedRoles.insert(role);
                 }
             }
 
             m_items.remove(oldItem.url());
-            m_items.insert(newItem.url(), indexForItem);
-            indexes.append(indexForItem);
+            if (newItemMatchesFilter) {
+                m_items.insert(newItem.url(), indexForItem);
+                changedFiles.append(newItem);
+                indexes.append(indexForItem);
+            } else {
+                newFilteredIndexes.append(indexForItem);
+                m_filteredItems.insert(newItem, itemData);
+            }
         } else {
             // Check if 'oldItem' is one of the filtered items.
             QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.find(oldItem);
@@ -1115,11 +1150,22 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
                 itemData->values.clear();
 
                 m_filteredItems.erase(it);
-                m_filteredItems.insert(newItem, itemData);
+                if (newItemMatchesFilter) {
+                    newVisibleItems.append(itemData);
+                } else {
+                    m_filteredItems.insert(newItem, itemData);
+                }
             }
         }
     }
 
+    // Hide items, previously visible that should get hidden
+    const KItemRangeList removedRanges = KItemRangeList::fromSortedContainer(newFilteredIndexes);
+    removeItems(removedRanges, KeepItemData);
+
+    // Show previously hidden items that should get visible
+    insertItems(newVisibleItems);
+
     // If the changed items have been created recently, they might not be in m_items yet.
     // In that case, the list 'indexes' might be empty.
     if (indexes.isEmpty()) {
@@ -1130,6 +1176,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()
@@ -1719,7 +1767,18 @@ bool KFileItemModel::lessThan(const ItemData* a, const ItemData* b, const QColla
         }
     }
 
-    if (m_sortDirsFirst) {
+    // 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) {
@@ -1757,6 +1816,11 @@ void KFileItemModel::sort(const QList<KFileItemModel::ItemData*>::iterator &begi
 
 int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const QCollator& collator) const
 {
+    // This function must never return 0, because that would break stable
+    // sorting, which leads to all kinds of bugs.
+    // See: https://bugs.kde.org/show_bug.cgi?id=433247
+    // If two items have equal sort values, let the fallbacks at the bottom of
+    // the function handle it.
     const KFileItem& itemA = a->item;
     const KFileItem& itemB = b->item;
 
@@ -1768,32 +1832,27 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const
         break;
 
     case SizeRole: {
-        if (DetailsModeSettings::directorySizeCount() && (itemA.isDir() || itemB.isDir())) {
+        if (DetailsModeSettings::directorySizeCount() && itemA.isDir()) {
             // folders first then
-            if (itemA.isDir() && itemB.isDir()) {
-                auto valueA = a->values.value("count");
-                auto valueB = b->values.value("count");
-                if (valueA.isNull()) {
-                    if (valueB.isNull()) {
-                        return 0;
-                    } else {
-                        return -1;
-                    }
-                } else if (valueB.isNull()) {
-                    return +1;
-                } else {
-                    if (valueA.toLongLong() < valueB.toLongLong()) {
-                        return -1;
-                    } else {
-                        return +1;
-                    }
+            // 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()) {
+                    return -1;
                 }
-            } else if (itemA.isDir()) {
-                return 1;
+            } else if (valueB.isNull()) {
+                return +1;
             } else {
-                return -1;
+                if (valueA.toLongLong() < valueB.toLongLong()) {
+                    return -1;
+                } else if (valueA.toLongLong() > valueB.toLongLong()) {
+                    return +1;
+                }
             }
+            break;
         }
+
         KIO::filesize_t sizeA = 0;
         if (itemA.isDir()) {
             sizeA = a->values.value("size").toULongLong();
@@ -1806,12 +1865,10 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const
         } else {
             sizeB = itemB.size();
         }
-        if (sizeA > sizeB) {
-            result = +1;
-        } else if (sizeA < sizeB) {
-            result = -1;
-        } else {
-            result = 0;
+        if (sizeA < sizeB) {
+            return -1;
+        } else if (sizeA > sizeB) {
+            return +1;
         }
         break;
     }
@@ -1820,9 +1877,9 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const
         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;
+            return -1;
         } else if (dateTimeA > dateTimeB) {
-            result = +1;
+            return +1;
         }
         break;
     }
@@ -1831,9 +1888,9 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const
         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;
+            return -1;
         } else if (dateTimeA > dateTimeB) {
-            result = +1;
+            return +1;
         }
         break;
     }
@@ -1842,9 +1899,9 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const
         const QDateTime dateTimeA = a->values.value("deletiontime").toDateTime();
         const QDateTime dateTimeB = b->values.value("deletiontime").toDateTime();
         if (dateTimeA < dateTimeB) {
-            result = -1;
+            return -1;
         } else if (dateTimeA > dateTimeB) {
-            result = +1;
+            return +1;
         }
         break;
     }
@@ -1865,9 +1922,9 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const
         const QString roleValueA = a->values.value(role).toString();
         const QString roleValueB = b->values.value(role).toString();
         if (!roleValueA.isEmpty() && roleValueB.isEmpty()) {
-            result = -1;
+            return -1;
         } else if (roleValueA.isEmpty() && !roleValueB.isEmpty()) {
-            result = +1;
+            return +1;
         } else if (isRoleValueNatural(m_sortRole)) {
             result = stringCompare(roleValueA, roleValueB, collator);
         } else {
@@ -2008,16 +2065,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) {
@@ -2095,7 +2160,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);
@@ -2110,7 +2175,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",
@@ -2118,7 +2183,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);
@@ -2133,7 +2198,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);
@@ -2148,7 +2213,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);
@@ -2163,7 +2228,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);
@@ -2467,3 +2532,15 @@ bool KFileItemModel::isConsistent() const
 
     return true;
 }
+
+void KFileItemModel::slotListerError(KIO::Job *job)
+{
+    if (job->error() == KIO::ERR_IS_FILE) {
+        if (auto *listJob = qobject_cast<KIO::ListJob *>(job)) {
+            Q_EMIT urlIsFileError(listJob->url());
+        }
+    } else {
+        const QString errorString = job->errorString();
+        Q_EMIT errorMessage(!errorString.isEmpty() ? errorString : i18nc("@info:status", "Unknown error."));
+    }
+}