]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/kitemviews/kfileitemmodel.cpp
"Grouped sorting" defaults to true. Grouping is still disabled by default because...
[dolphin.git] / src / kitemviews / kfileitemmodel.cpp
index afe8f71c4e47f00d44e5172ca769adc115bb8042..5b7b781a8fbdc6d8631b63771b9724f92db7e014 100644 (file)
@@ -39,6 +39,7 @@ KFileItemModel::KFileItemModel(QObject *parent)
     , m_sortDirsFirst(true)
     , m_sortHiddenLast(false)
     , m_sortRole(NameRole)
+    , m_groupRole(NoRole)
     , m_sortingProgressPercent(-1)
     , m_roles()
     , m_itemData()
@@ -946,6 +947,15 @@ QList<KFileItemModel::RoleInfo> KFileItemModel::rolesInformation()
     return rolesInfo;
 }
 
+QList<KFileItemModel::RoleInfo> KFileItemModel::extraGroupingInformation()
+{
+    static QList<RoleInfo> rolesInfo{
+        {QByteArray("none"),         kli18nc("@label", "No grouping").toString(),       nullptr,     nullptr,     false,      false},
+        {QByteArray("followSort"),   kli18nc("@label", "Follow sorting").toString(),    nullptr,    nullptr,     false,      false}
+    };
+    return rolesInfo;
+}
+
 void KFileItemModel::onGroupedSortingChanged(bool current)
 {
     Q_UNUSED(current)
@@ -975,14 +985,11 @@ void KFileItemModel::onSortRoleChanged(const QByteArray &current, const QByteArr
     }
 }
 
-void KFileItemModel::onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous, bool resortItems)
+void KFileItemModel::onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous)
 {
     Q_UNUSED(current)
     Q_UNUSED(previous)
-
-    if (resortItems) {
-        resortAllItems();
-    }
+    resortAllItems();
 }
 
 void KFileItemModel::onGroupRoleChanged(const QByteArray &current, const QByteArray &previous, bool resortItems)
@@ -1008,14 +1015,11 @@ void KFileItemModel::onGroupRoleChanged(const QByteArray &current, const QByteAr
     }
 }
 
-void KFileItemModel::onGroupOrderChanged(Qt::SortOrder current, Qt::SortOrder previous, bool resortItems)
+void KFileItemModel::onGroupOrderChanged(Qt::SortOrder current, Qt::SortOrder previous)
 {
     Q_UNUSED(current)
     Q_UNUSED(previous)
-
-    if (resortItems) {
-        resortAllItems();
-    }
+    resortAllItems();
 }
 
 void KFileItemModel::loadSortingSettings()
@@ -1040,6 +1044,7 @@ void KFileItemModel::loadSortingSettings()
     // 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());
+    ContentDisplaySettings::self();
 }
 
 void KFileItemModel::resortAllItems()
@@ -1105,7 +1110,8 @@ void KFileItemModel::resortAllItems()
         }
 
         Q_EMIT itemsMoved(KItemRange(firstMovedIndex, movedItemsCount), movedToIndexes);
-    } else if (groupedSorting()) {
+    }
+    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();
@@ -1690,7 +1696,7 @@ void KFileItemModel::removeItems(const KItemRangeList &itemRanges, RemoveItemsBe
 
 QList<KFileItemModel::ItemData *> KFileItemModel::createItemDataList(const QUrl &parentUrl, const KFileItemList &items) const
 {
-    if (m_sortRole == TypeRole) {
+    if (m_sortRole == TypeRole || m_groupRole == TypeRole) {
         // Try to resolve the MIME-types synchronously to prevent a reordering of
         // the items when sorting by type (per default MIME-types are resolved
         // asynchronously by KFileItemModelRolesUpdater).
@@ -1714,9 +1720,9 @@ QList<KFileItemModel::ItemData *> KFileItemModel::createItemDataList(const QUrl
     return itemDataList;
 }
 
-void KFileItemModel::prepareItemsForSorting(QList<ItemData *> &itemDataList)
+void KFileItemModel::prepareItemsWithRole(QList<ItemData *> &itemDataList, RoleType roleType)
 {
-    switch (m_sortRole) {
+    switch (roleType) {
     case ExtensionRole:
     case PermissionsRole:
     case OwnerRole:
@@ -1755,6 +1761,12 @@ void KFileItemModel::prepareItemsForSorting(QList<ItemData *> &itemDataList)
     }
 }
 
+void KFileItemModel::prepareItemsForSorting(QList<ItemData *> &itemDataList)
+{
+    prepareItemsWithRole(itemDataList, m_sortRole);
+    prepareItemsWithRole(itemDataList, m_groupRole);
+}
+
 int KFileItemModel::expandedParentsCount(const ItemData *data)
 {
     // The hash 'values' is only guaranteed to contain the key "expandedParentsCount"
@@ -2100,8 +2112,7 @@ bool KFileItemModel::lessThan(const ItemData *a, const ItemData *b, const QColla
                 return true;
             }
         }
-        if (m_sortDirsFirst
-            || (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount && m_sortRole == SizeRole)) {
+        if (m_sortDirsFirst || (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount && m_sortRole == SizeRole)) {
             const bool isDirA = a->item.isDir();
             const bool isDirB = b->item.isDir();
             if (isDirA && !isDirB) {
@@ -2311,104 +2322,99 @@ int KFileItemModel::groupRoleCompare(const ItemData *a, const ItemData *b, const
     // Unlike sortRoleCompare, this function can and often will return 0.
     int result = 0;
 
-    int groupA, groupB;
+    ItemGroupInfo groupA, groupB;
     switch (m_groupRole) {
     case NoRole:
         // Non-trivial grouping behavior might be handled there in the future.
         return 0;
     case NameRole:
-        groupA = nameRoleGroup(a, false).comparable;
-        groupB = nameRoleGroup(b, false).comparable;
+        groupA = nameRoleGroup(a, false);
+        groupB = nameRoleGroup(b, false);
         break;
     case SizeRole:
-        groupA = sizeRoleGroup(a, false).comparable;
-        groupB = sizeRoleGroup(b, false).comparable;
+        groupA = sizeRoleGroup(a, false);
+        groupB = sizeRoleGroup(b, false);
         break;
     case ModificationTimeRole:
         groupA = timeRoleGroup(
-                     [](const ItemData *item) {
-                         return item->item.time(KFileItem::ModificationTime);
-                     },
-                     a,
-                     false)
-                     .comparable;
+            [](const ItemData *item) {
+                return item->item.time(KFileItem::ModificationTime);
+            },
+            a,
+            false);
         groupB = timeRoleGroup(
-                     [](const ItemData *item) {
-                         return item->item.time(KFileItem::ModificationTime);
-                     },
-                     b,
-                     false)
-                     .comparable;
+            [](const ItemData *item) {
+                return item->item.time(KFileItem::ModificationTime);
+            },
+            b,
+            false);
         break;
     case CreationTimeRole:
         groupA = timeRoleGroup(
-                     [](const ItemData *item) {
-                         return item->item.time(KFileItem::CreationTime);
-                     },
-                     a,
-                     false)
-                     .comparable;
+            [](const ItemData *item) {
+                return item->item.time(KFileItem::CreationTime);
+            },
+            a,
+            false);
         groupB = timeRoleGroup(
-                     [](const ItemData *item) {
-                         return item->item.time(KFileItem::CreationTime);
-                     },
-                     b,
-                     false)
-                     .comparable;
+            [](const ItemData *item) {
+                return item->item.time(KFileItem::CreationTime);
+            },
+            b,
+            false);
         break;
     case AccessTimeRole:
         groupA = timeRoleGroup(
-                     [](const ItemData *item) {
-                         return item->item.time(KFileItem::AccessTime);
-                     },
-                     a,
-                     false)
-                     .comparable;
+            [](const ItemData *item) {
+                return item->item.time(KFileItem::AccessTime);
+            },
+            a,
+            false);
         groupB = timeRoleGroup(
-                     [](const ItemData *item) {
-                         return item->item.time(KFileItem::AccessTime);
-                     },
-                     b,
-                     false)
-                     .comparable;
+            [](const ItemData *item) {
+                return item->item.time(KFileItem::AccessTime);
+            },
+            b,
+            false);
         break;
     case DeletionTimeRole:
         groupA = timeRoleGroup(
-                     [](const ItemData *item) {
-                         return item->values.value("deletiontime").toDateTime();
-                     },
-                     a,
-                     false)
-                     .comparable;
+            [](const ItemData *item) {
+                return item->values.value("deletiontime").toDateTime();
+            },
+            a,
+            false);
         groupB = timeRoleGroup(
-                     [](const ItemData *item) {
-                         return item->values.value("deletiontime").toDateTime();
-                     },
-                     b,
-                     false)
-                     .comparable;
+            [](const ItemData *item) {
+                return item->values.value("deletiontime").toDateTime();
+            },
+            b,
+            false);
         break;
     case PermissionsRole:
-        groupA = permissionRoleGroup(a, false).comparable;
-        groupB = permissionRoleGroup(b, false).comparable;
+        groupA = permissionRoleGroup(a, false);
+        groupB = permissionRoleGroup(b, false);
         break;
     case RatingRole:
-        groupA = ratingRoleGroup(a, false).comparable;
-        groupB = ratingRoleGroup(b, false).comparable;
+        groupA = ratingRoleGroup(a, false);
+        groupB = ratingRoleGroup(b, false);
+        break;
+    case TypeRole:
+        groupA = typeRoleGroup(a);
+        groupB = typeRoleGroup(b);
         break;
     default: {
-        QString strGroupA = genericStringRoleGroup(groupRole(), a);
-        QString strGroupB = genericStringRoleGroup(groupRole(), b);
-        result = stringCompare(strGroupA, strGroupB, collator);
+        groupA = genericStringRoleGroup(groupRole(), a);
+        groupB = genericStringRoleGroup(groupRole(), b);
         break;
     }
     }
-    if (result == 0) {
-        if (groupA < groupB) {
-            result = -1;
-        } else if (groupA > groupB) {
-            result = 1;
-        }
+    if (groupA.comparable < groupB.comparable) {
+        result = -1;
+    } else if (groupA.comparable > groupB.comparable) {
+        result = 1;
+    } else {
+        result = stringCompare(groupA.text, groupB.text, collator);
     }
     return result;
 }
@@ -2434,6 +2440,7 @@ int KFileItemModel::stringCompare(const QString &a, const QString &b, const QCol
 
 KFileItemModel::ItemGroupInfo KFileItemModel::nameRoleGroup(const ItemData *itemData, bool withString) const
 {
+    static bool oldWithString;
     static ItemGroupInfo oldGroupInfo;
     static QChar oldFirstChar;
     ItemGroupInfo groupInfo;
@@ -2445,8 +2452,8 @@ KFileItemModel::ItemGroupInfo KFileItemModel::nameRoleGroup(const ItemData *item
 
     // Use the first character of the name as group indication
     firstChar = name.at(0).toUpper();
-
-    if (firstChar == oldFirstChar) {
+    
+    if (firstChar == oldFirstChar && withString == oldWithString) {
         return oldGroupInfo;
     }
     if (firstChar == QLatin1Char('~') && name.length() > 1) {
@@ -2501,6 +2508,7 @@ KFileItemModel::ItemGroupInfo KFileItemModel::nameRoleGroup(const ItemData *item
         }
         groupInfo.comparable = (int)'.';
     }
+    oldWithString = withString;
     oldFirstChar = firstChar;
     oldGroupInfo = groupInfo;
     return groupInfo;
@@ -2508,8 +2516,6 @@ KFileItemModel::ItemGroupInfo KFileItemModel::nameRoleGroup(const ItemData *item
 
 KFileItemModel::ItemGroupInfo KFileItemModel::sizeRoleGroup(const ItemData *itemData, bool withString) const
 {
-    static ItemGroupInfo oldGroupInfo;
-    static KIO::filesize_t oldFileSize;
     ItemGroupInfo groupInfo;
     KIO::filesize_t fileSize;
 
@@ -2518,15 +2524,12 @@ KFileItemModel::ItemGroupInfo KFileItemModel::sizeRoleGroup(const ItemData *item
 
     groupInfo.comparable = -1; // None
     if (!item.isNull() && item.isDir()) {
-        if (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount || m_sortDirsFirst) {
+        if (ContentDisplaySettings::directorySizeMode() != ContentDisplaySettings::EnumDirectorySizeMode::ContentSize) {
             groupInfo.comparable = 0; // Folders
         } else {
             fileSize = itemData->values.value("size").toULongLong();
         }
     }
-    if (fileSize == oldFileSize) {
-        return oldGroupInfo;
-    }
     if (groupInfo.comparable < 0) {
         if (fileSize < 5 * 1024 * 1024) { // < 5 MB
             groupInfo.comparable = 1; // Small
@@ -2541,14 +2544,13 @@ KFileItemModel::ItemGroupInfo KFileItemModel::sizeRoleGroup(const ItemData *item
         char const *groupNames[] = {"Folders", "Small", "Medium", "Big"};
         groupInfo.text = i18nc("@title:group Size", groupNames[groupInfo.comparable]);
     }
-    oldFileSize = fileSize;
-    oldGroupInfo = groupInfo;
     return groupInfo;
 }
 
 KFileItemModel::ItemGroupInfo
 KFileItemModel::timeRoleGroup(const std::function<QDateTime(const ItemData *)> &fileTimeCb, const ItemData *itemData, bool withString) const
 {
+    static bool oldWithString;
     static ItemGroupInfo oldGroupInfo;
     static QDate oldFileDate;
     ItemGroupInfo groupInfo;
@@ -2558,6 +2560,9 @@ KFileItemModel::timeRoleGroup(const std::function<QDateTime(const ItemData *)> &
     const QDate fileDate = fileTime.date();
     const int daysDistance = fileDate.daysTo(currentDate);
 
+    if (fileDate == oldFileDate && withString == oldWithString) {
+        return oldGroupInfo;
+    }
     // Simplified grouping algorithm, preserving dates
     // but not taking "pretty printing" into account
     if (currentDate.year() == fileDate.year() && currentDate.month() == fileDate.month()) {
@@ -2761,6 +2766,7 @@ KFileItemModel::timeRoleGroup(const std::function<QDateTime(const ItemData *)> &
             }
         }
     }
+    oldWithString = withString;
     oldFileDate = fileDate;
     oldGroupInfo = groupInfo;
     return groupInfo;
@@ -2768,13 +2774,14 @@ KFileItemModel::timeRoleGroup(const std::function<QDateTime(const ItemData *)> &
 
 KFileItemModel::ItemGroupInfo KFileItemModel::permissionRoleGroup(const ItemData *itemData, bool withString) const
 {
+    static bool oldWithString;
     static ItemGroupInfo oldGroupInfo;
     static QFileDevice::Permissions oldPermissions;
     ItemGroupInfo groupInfo;
 
     const QFileInfo info(itemData->item.url().toLocalFile());
     const QFileDevice::Permissions permissions = info.permissions();
-    if (permissions == oldPermissions) {
+    if (permissions == oldPermissions && withString == oldWithString) {
         return oldGroupInfo;
     }
     groupInfo.comparable = (int)permissions;
@@ -2820,6 +2827,7 @@ KFileItemModel::ItemGroupInfo KFileItemModel::permissionRoleGroup(const ItemData
         others = others.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : others.mid(0, others.length() - 2);
         groupInfo.text = i18nc("@title:group Files and folders by permissions", "User: %1 | Group: %2 | Others: %3", user, group, others);
     }
+    oldWithString = withString;
     oldPermissions = permissions;
     oldGroupInfo = groupInfo;
     return groupInfo;
@@ -2832,14 +2840,14 @@ KFileItemModel::ItemGroupInfo KFileItemModel::ratingRoleGroup(const ItemData *it
     if (withString) {
         // Dolphin does not currently use string representation of star rating
         // as stars are rendered as graphics in group headers.
-        groupInfo.text = i18nc("@item:intext Rated N (stars)", "Rated ") + QString::number(groupInfo.comparable);
+        groupInfo.text = i18nc("@item:intext Rated N (stars)", "Rated %i", QString::number(groupInfo.comparable));
     }
     return groupInfo;
 }
 
-QString KFileItemModel::genericStringRoleGroup(const QByteArray &role, const ItemData *itemData) const
+KFileItemModel::ItemGroupInfo KFileItemModel::genericStringRoleGroup(const QByteArray &role, const ItemData *itemData) const
 {
-    return itemData->values.value(role).toString();
+    return {0, itemData->values.value(role).toString()};
 }
 
 QList<QPair<int, QVariant>> KFileItemModel::nameRoleGroups() const
@@ -2888,6 +2896,20 @@ QList<QPair<int, QVariant>> KFileItemModel::sizeRoleGroups() const
     return groups;
 }
 
+KFileItemModel::ItemGroupInfo KFileItemModel::typeRoleGroup(const ItemData *itemData) const
+{
+    int priority = 0;
+    if (itemData->item.isDir() && m_sortDirsFirst) {
+        // Ensure folders stay first regardless of grouping order
+        if (groupOrder() == Qt::AscendingOrder) {
+            priority = -1;
+        } else {
+            priority = 1;
+        }
+    }
+    return {priority, itemData->values.value("type").toString()};
+}
+
 QList<QPair<int, QVariant>> KFileItemModel::timeRoleGroups(const std::function<QDateTime(const ItemData *)> &fileTimeCb) const
 {
     Q_ASSERT(!m_itemData.isEmpty());
@@ -2966,17 +2988,17 @@ QList<QPair<int, QVariant>> KFileItemModel::genericStringRoleGroups(const QByteA
     const int maxIndex = count() - 1;
     QList<QPair<int, QVariant>> groups;
 
-    QString groupText;
+    ItemGroupInfo groupInfo;
     for (int i = 0; i <= maxIndex; ++i) {
         if (isChildItem(i)) {
             continue;
         }
 
-        QString newGroupText = genericStringRoleGroup(role, m_itemData.at(i));
+        ItemGroupInfo newGroupInfo = genericStringRoleGroup(role, m_itemData.at(i));
 
-        if (newGroupText != groupText) {
-            groupText = newGroupText;
-            groups.append(QPair<int, QVariant>(i, newGroupText));
+        if (newGroupInfo != groupInfo) {
+            groupInfo = newGroupInfo;
+            groups.append(QPair<int, QVariant>(i, newGroupInfo.text));
         }
     }
     return groups;