]> cloud.milkyroute.net Git - dolphin.git/commitdiff
Reverted resortAllItems() in favor of a group comparator for lessThan. Minor bug...
authorZakhar Afonin <aza22u419@student.bmstu.ru>
Thu, 13 Jun 2024 06:33:52 +0000 (09:33 +0300)
committerZakhar Afonin <aza22u419@student.bmstu.ru>
Thu, 13 Jun 2024 06:33:52 +0000 (09:33 +0300)
src/dolphinpart.rc
src/dolphinui.rc
src/kitemviews/kfileitemmodel.cpp
src/kitemviews/kfileitemmodel.h
src/views/dolphinviewactionhandler.cpp

index d13f4aaed7a1b69b0bfbb5c048f74e3518f01f61..13f0f2172bfb3e1d3ac9bbae42fc4db3c92fa55f 100644 (file)
@@ -23,6 +23,7 @@
   </Menu>
   <Menu name="view"><text>&amp;View</text>
    <Action name="sort" />
+   <Action name="group" />
    <Action name="additional_info" />
    <Action name="show_preview" />
    <Action name="show_in_groups" />
index 2f7ea857d1c5f44ea617ae56aac8e369b19fb3f0..d884fe300476daae672672d836714d38b6bca21c 100644 (file)
@@ -43,6 +43,7 @@
             <Action name="view_zoom_out"/>
             <Separator/>
             <Action name="sort" />
+            <Action name="group" />
             <Action name="view_mode" />
             <Action name="additional_info" />
             <Action name="show_preview" />
index ad0fb31a7f703dfae86ddf0b369d2e15f2b2ea79..34a113eded441ec22753792b6471f9287f667833 100644 (file)
@@ -1049,44 +1049,8 @@ void KFileItemModel::resortAllItems()
     m_items.clear();
     m_items.reserve(itemCount);
 
-    const QList<QPair<int, QVariant>> oldGroups = m_groups;
-
-    if (groupedSorting() && m_groupRole) {
-        // Hacky way to implement grouped sorting without rewriting more code.
-        // 1. Sort all items by grouping criteria. "Folders first" priority to be ignored.
-        // 2. Generate groups, which will stay usable after in-group sorting.
-        // 3. Perform sorts by the original sorting criteria inside groups.
-
-        RoleType originalSortRole = m_sortRole;
-        Qt::SortOrder originalSortOrder = sortOrder();
-        bool originalSortDirsFirst = m_sortDirsFirst;
-        m_sortRole = m_groupRole;
-        setSortOrder(groupOrder(), false);
-        m_sortDirsFirst = false;
-
-        sort(m_itemData.begin(), m_itemData.end());
-        m_groups.clear();
-        groups();
-
-        m_sortRole = originalSortRole;
-        setSortOrder(originalSortOrder, false);
-        m_sortDirsFirst = originalSortDirsFirst;
-
-        int lastIndex = 0, newIndex = 0;
-        for (int i = 0; i < m_groups.count() - 1; ++i) {
-            qCritical() << m_groups[i];
-            fflush(stderr);
-            newIndex = m_groups[i + 1].first;
-            sort(m_itemData.begin() + lastIndex, m_itemData.begin() + newIndex);
-            lastIndex = newIndex;
-        }
-    } else {
-        if (!m_groups.isEmpty()) {
-            m_groups.clear();
-        }
-        sort(m_itemData.begin(), m_itemData.end());
-    }
-
+    // Resort the items
+    sort(m_itemData.begin(), m_itemData.end());
     for (int i = 0; i < itemCount; ++i) {
         m_items.insert(m_itemData.at(i)->item.url(), i);
     }
@@ -1121,12 +1085,15 @@ void KFileItemModel::resortAllItems()
 
         Q_EMIT itemsMoved(KItemRange(firstMovedIndex, movedItemsCount), movedToIndexes);
     } else if (groupedSorting()) {
-        if (m_groups != oldGroups) {
+        // 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) {
             Q_EMIT groupsChanged();
         }
     }
 
-#ifdef KFILEITEMMODEL_DEBUGf
+#ifdef KFILEITEMMODEL_DEBUG
     qCDebug(DolphinDebug) << "[TIME] Resorting of" << itemCount << "items:" << timer.elapsed();
 #endif
 }
@@ -1641,7 +1608,7 @@ void KFileItemModel::insertItems(QList<ItemData *> &newItems)
     }
 
     // N
-    //resortAllItems();
+    // resortAllItems();
     // The indexes in m_items are not correct anymore. Therefore, we clear m_items.
     // It will be re-populated with the updated indices if index(const QUrl&) is called.
     m_items.clear();
@@ -2102,31 +2069,34 @@ bool KFileItemModel::lessThan(const ItemData *a, const ItemData *b, const QColla
         }
     }
 
-    // Show hidden files and folders last
-    if (m_sortHiddenLast) {
-        const bool isHiddenA = a->item.isHidden();
-        const bool isHiddenB = b->item.isHidden();
-        if (isHiddenA && !isHiddenB) {
-            return false;
-        } else if (!isHiddenA && isHiddenB) {
-            return true;
+    result = groupRoleCompare(a, b, collator);
+    if (result == 0) {
+        // Show hidden files and folders last
+        if (m_sortHiddenLast) {
+            const bool isHiddenA = a->item.isHidden();
+            const bool isHiddenB = b->item.isHidden();
+            if (isHiddenA && !isHiddenB) {
+                return false;
+            } else if (!isHiddenA && isHiddenB) {
+                return true;
+            }
         }
-    }
-
-    if (m_sortDirsFirst
-        || (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount && m_sortRole == SizeRole)) {
-        const bool isDirA = a->item.isDir();
-        const bool isDirB = b->item.isDir();
-        if (isDirA && !isDirB) {
-            return true;
-        } else if (!isDirA && isDirB) {
-            return false;
+        if (m_sortDirsFirst
+            || (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount && m_sortRole == SizeRole)) {
+            const bool isDirA = a->item.isDir();
+            const bool isDirB = b->item.isDir();
+            if (isDirA && !isDirB) {
+                return true;
+            } else if (!isDirA && isDirB) {
+                return false;
+            }
         }
+        result = sortRoleCompare(a, b, collator);
+        result = (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0;
+    } else {
+        result = (groupOrder() == Qt::AscendingOrder) ? result < 0 : result > 0;
     }
-
-    result = sortRoleCompare(a, b, collator);
-
-    return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0;
+    return result;
 }
 
 void KFileItemModel::sort(const QList<KFileItemModel::ItemData *>::iterator &begin, const QList<KFileItemModel::ItemData *>::iterator &end) const
@@ -2317,6 +2287,141 @@ int KFileItemModel::sortRoleCompare(const ItemData *a, const ItemData *b, const
     return QString::compare(itemA.url().url(), itemB.url().url(), Qt::CaseSensitive);
 }
 
+int KFileItemModel::groupRoleCompare(const ItemData *a, const ItemData *b, const QCollator &collator) const
+{
+    // Unlike sortRoleCompare, this function can and often will return 0.
+    const KFileItem &itemA = a->item;
+    const KFileItem &itemB = b->item;
+
+    int result = 0;
+
+    switch (m_groupRole) {
+    case NoRole:
+        break;
+    case NameRole: {
+        QChar groupA = getNameRoleGroup(a, false).toChar();
+        QChar groupB = getNameRoleGroup(b, false).toChar();
+        if (groupA < groupB) {
+            result = -1;
+        } else if (groupA > groupB) {
+            result = 1;
+        }
+        break;
+    }
+    case SizeRole: {
+        int groupA = getSizeRoleGroup(a, false).toInt();
+        int groupB = getSizeRoleGroup(b, false).toInt();
+        if (groupA < groupB) {
+            result = -1;
+        } else if (groupA > groupB) {
+            result = 1;
+        }
+        break;
+    }
+    case ModificationTimeRole: {
+        int groupA = getTimeRoleGroup(
+                         [](const ItemData *item) {
+                             return item->item.time(KFileItem::ModificationTime);
+                         },
+                         a,
+                         false)
+                         .toInt();
+        int groupB = getTimeRoleGroup(
+                         [](const ItemData *item) {
+                             return item->item.time(KFileItem::ModificationTime);
+                         },
+                         b,
+                         false)
+                         .toInt();
+        if (groupA < groupB) {
+            result = -1;
+        } else if (groupA > groupB) {
+            result = 1;
+        }
+        break;
+    }
+    case CreationTimeRole: {
+        int groupA = getTimeRoleGroup(
+                         [](const ItemData *item) {
+                             return item->item.time(KFileItem::CreationTime);
+                         },
+                         a,
+                         false)
+                         .toInt();
+        int groupB = getTimeRoleGroup(
+                         [](const ItemData *item) {
+                             return item->item.time(KFileItem::CreationTime);
+                         },
+                         b,
+                         false)
+                         .toInt();
+        if (groupA < groupB) {
+            result = -1;
+        } else if (groupA > groupB) {
+            result = 1;
+        }
+        break;
+    }
+    case AccessTimeRole: {
+        int groupA = getTimeRoleGroup(
+                         [](const ItemData *item) {
+                             return item->item.time(KFileItem::AccessTime);
+                         },
+                         a,
+                         false)
+                         .toInt();
+        int groupB = getTimeRoleGroup(
+                         [](const ItemData *item) {
+                             return item->item.time(KFileItem::AccessTime);
+                         },
+                         b,
+                         false)
+                         .toInt();
+        if (groupA < groupB) {
+            result = -1;
+        } else if (groupA > groupB) {
+            result = 1;
+        }
+        break;
+    }
+    case DeletionTimeRole: {
+        int groupA = getTimeRoleGroup(
+                         [](const ItemData *item) {
+                             return item->values.value("deletiontime").toDateTime();
+                         },
+                         a,
+                         false)
+                         .toInt();
+        int groupB = getTimeRoleGroup(
+                         [](const ItemData *item) {
+                             return item->values.value("deletiontime").toDateTime();
+                         },
+                         b,
+                         false)
+                         .toInt();
+        if (groupA < groupB) {
+            result = -1;
+        } else if (groupA > groupB) {
+            result = 1;
+        }
+        break;
+    }
+    // case PermissionsRole:
+    //  case RatingRole:
+    default: {
+        QString groupA = getGenericStringRoleGroup(groupRole(), a);
+        QString groupB = getGenericStringRoleGroup(groupRole(), b);
+        if (groupA < groupB) {
+            result = -1;
+        } else if (groupA > groupB) {
+            result = 1;
+        }
+        break;
+    }
+    }
+    return result;
+}
+
 int KFileItemModel::stringCompare(const QString &a, const QString &b, const QCollator &collator) const
 {
     QMutexLocker collatorLock(s_collatorMutex());
@@ -2336,180 +2441,174 @@ int KFileItemModel::stringCompare(const QString &a, const QString &b, const QCol
     return QString::compare(a, b, Qt::CaseSensitive);
 }
 
-QList<QPair<int, QVariant>> KFileItemModel::nameRoleGroups() const
+QVariant KFileItemModel::getNameRoleGroup(const ItemData *itemData, bool asString) const
 {
-    Q_ASSERT(!m_itemData.isEmpty());
-
-    const int maxIndex = count() - 1;
-    QList<QPair<int, QVariant>> groups;
-
-    QString groupValue;
-    QChar firstChar;
-    for (int i = 0; i <= maxIndex; ++i) {
-        if (isChildItem(i)) {
-            continue;
-        }
-
-        const QString name = m_itemData.at(i)->item.text();
-
-        // Use the first character of the name as group indication
-        QChar newFirstChar = name.at(0).toUpper();
-        if (newFirstChar == QLatin1Char('~') && name.length() > 1) {
-            newFirstChar = name.at(1).toUpper();
-        }
-
-        if (firstChar != newFirstChar) {
-            QString newGroupValue;
-            if (newFirstChar.isLetter()) {
-                if (m_collator.compare(newFirstChar, QChar(QLatin1Char('A'))) >= 0 && m_collator.compare(newFirstChar, QChar(QLatin1Char('Z'))) <= 0) {
-                    // WARNING! Symbols based on latin 'Z' like 'Z' with acute are treated wrong as non Latin and put in a new group.
-
-                    // Try to find a matching group in the range 'A' to 'Z'.
-                    static std::vector<QChar> lettersAtoZ;
-                    lettersAtoZ.reserve('Z' - 'A' + 1);
-                    if (lettersAtoZ.empty()) {
-                        for (char c = 'A'; c <= 'Z'; ++c) {
-                            lettersAtoZ.push_back(QLatin1Char(c));
-                        }
-                    }
+    const KFileItem item = itemData->item;
+    const QString name = item.text();
+    QVariant newGroupValue;
+    // Use the first character of the name as group indication
+    QChar newFirstChar = name.at(0).toUpper();
+    if (newFirstChar == QLatin1Char('~') && name.length() > 1) {
+        newFirstChar = name.at(1).toUpper();
+    }
 
-                    auto localeAwareLessThan = [this](QChar c1, QChar c2) -> bool {
-                        return m_collator.compare(c1, c2) < 0;
-                    };
+    if (newFirstChar.isLetter()) {
+        if (m_collator.compare(newFirstChar, QChar(QLatin1Char('A'))) >= 0 && m_collator.compare(newFirstChar, QChar(QLatin1Char('Z'))) <= 0) {
+            // WARNING! Symbols based on latin 'Z' like 'Z' with acute are treated wrong as non Latin and put in a new group.
 
-                    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;
-                    }
-
-                } else {
-                    // Symbols from non Latin-based scripts
-                    newGroupValue = newFirstChar;
+            // Try to find a matching group in the range 'A' to 'Z'.
+            static std::vector<QChar> lettersAtoZ;
+            lettersAtoZ.reserve('Z' - 'A' + 1);
+            if (lettersAtoZ.empty()) {
+                for (char c = 'A'; c <= 'Z'; ++c) {
+                    lettersAtoZ.push_back(QLatin1Char(c));
                 }
-            } else if (newFirstChar >= QLatin1Char('0') && newFirstChar <= QLatin1Char('9')) {
-                // Apply group '0 - 9' for any name that starts with a digit
-                newGroupValue = i18nc("@title:group Groups that start with a digit", "0 - 9");
-            } else {
-                newGroupValue = i18nc("@title:group", "Others");
             }
 
-            if (newGroupValue != groupValue) {
-                groupValue = newGroupValue;
-                groups.append(QPair<int, QVariant>(i, newGroupValue));
+            auto localeAwareLessThan = [this](QChar c1, QChar c2) -> bool {
+                return m_collator.compare(c1, c2) < 0;
+            };
+
+            std::vector<QChar>::iterator it = std::lower_bound(lettersAtoZ.begin(), lettersAtoZ.end(), newFirstChar, localeAwareLessThan);
+            if (it != lettersAtoZ.end()) {
+                if (localeAwareLessThan(newFirstChar, *it)) {
+                    // newFirstChar belongs to the group preceding *it.
+                    // Example: for an umlaut 'A' in the German locale, *it would be 'B' now.
+                    --it;
+                }
+                newGroupValue = *it;
             }
 
-            firstChar = newFirstChar;
+        } else {
+            // Symbols from non Latin-based scripts
+            newGroupValue = newFirstChar;
+        }
+    } else if (newFirstChar >= QLatin1Char('0') && newFirstChar <= QLatin1Char('9')) {
+        // Apply group '0 - 9' for any name that starts with a digit
+        if (asString) {
+            newGroupValue = i18nc("@title:group Groups that start with a digit", "0 - 9");
+        } else {
+            newGroupValue = QChar('0');
+        }
+    } else {
+        if (asString) {
+            newGroupValue = i18nc("@title:group", "Others");
+        } else {
+            newGroupValue = QChar('.');
         }
     }
-    return groups;
+    return newGroupValue;
 }
 
-QList<QPair<int, QVariant>> KFileItemModel::sizeRoleGroups() const
+QVariant KFileItemModel::getSizeRoleGroup(const ItemData *itemData, bool asString) const
 {
-    Q_ASSERT(!m_itemData.isEmpty());
-
-    const int maxIndex = count() - 1;
-    QList<QPair<int, QVariant>> groups;
+    const KFileItem item = itemData->item;
 
-    QString groupValue;
-    for (int i = 0; i <= maxIndex; ++i) {
-        if (isChildItem(i)) {
-            continue;
-        }
-
-        const KFileItem &item = m_itemData.at(i)->item;
-        KIO::filesize_t fileSize = !item.isNull() ? item.size() : ~0U;
-        QString newGroupValue;
-        if (!item.isNull() && item.isDir()) {
-            if (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount || m_sortDirsFirst) {
-                newGroupValue = i18nc("@title:group Size", "Folders");
-            } else {
-                fileSize = m_itemData.at(i)->values.value("size").toULongLong();
-            }
-        }
-
-        if (newGroupValue.isEmpty()) {
-            if (fileSize < 5 * 1024 * 1024) { // < 5 MB
-                newGroupValue = i18nc("@title:group Size", "Small");
-            } else if (fileSize < 10 * 1024 * 1024) { // < 10 MB
-                newGroupValue = i18nc("@title:group Size", "Medium");
-            } else {
-                newGroupValue = i18nc("@title:group Size", "Big");
-            }
+    KIO::filesize_t fileSize = !item.isNull() ? item.size() : ~0U;
+    int newGroupValue = -1; // None
+    if (!item.isNull() && item.isDir()) {
+        if (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount || m_sortDirsFirst) {
+            newGroupValue = 0; // Folders
+        } else {
+            fileSize = itemData->values.value("size").toULongLong();
         }
+    }
 
-        if (newGroupValue != groupValue) {
-            groupValue = newGroupValue;
-            groups.append(QPair<int, QVariant>(i, newGroupValue));
+    if (newGroupValue < 0) {
+        if (fileSize < 5 * 1024 * 1024) { // < 5 MB
+            newGroupValue = 1; // Small
+        } else if (fileSize < 10 * 1024 * 1024) { // < 10 MB
+            newGroupValue = 2; // Medium
+        } else {
+            newGroupValue = 3; // Big
         }
     }
 
-    return groups;
+    if (asString) {
+        char const *groupNames[] = {"Folders", "Small", "Medium", "Big"};
+        return i18nc("@title:group Size", groupNames[newGroupValue]);
+    } else {
+        return newGroupValue;
+    }
 }
 
-QList<QPair<int, QVariant>> KFileItemModel::timeRoleGroups(const std::function<QDateTime(const ItemData *)> &fileTimeCb) const
+QVariant KFileItemModel::getTimeRoleGroup(const std::function<QDateTime(const ItemData *)> &fileTimeCb, const ItemData *itemData, bool asString) const
 {
-    Q_ASSERT(!m_itemData.isEmpty());
-
-    const int maxIndex = count() - 1;
-    QList<QPair<int, QVariant>> groups;
-
     const QDate currentDate = QDate::currentDate();
+    const QDateTime fileTime = fileTimeCb(itemData);
+    const QDate fileDate = fileTime.date();
+    const int daysDistance = fileDate.daysTo(currentDate);
 
-    QDate previousFileDate;
-    QString groupValue;
-    for (int i = 0; i <= maxIndex; ++i) {
-        if (isChildItem(i)) {
-            continue;
-        }
+    int intGroupValue;
+    QString strGroupValue;
 
-        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;
+    if (!asString) {
+        // Simplified grouping algorithm, preserving dates
+        // but not taking "pretty printing" into account
+        if (currentDate.year() == fileDate.year() && currentDate.month() == fileDate.month()) {
+            if (daysDistance < 7) {
+                intGroupValue = daysDistance; // Today, Yesterday and week days
+            } else if (daysDistance < 14) {
+                intGroupValue = 10; // One Week Ago
+            } else if (daysDistance < 21) {
+                intGroupValue = 20; // Two Weeks Ago
+            } else if (daysDistance < 28) {
+                intGroupValue = 30; // Three Weeks Ago
+            } else {
+                intGroupValue = 40; // Earlier This Month
+            }
+        } else {
+            const QDate lastMonthDate = currentDate.addMonths(-1);
+            if (lastMonthDate.year() == fileDate.year() && lastMonthDate.month() == fileDate.month()) {
+                if (daysDistance < 7) {
+                    intGroupValue = daysDistance; // Today, Yesterday and week days (Month, Year)
+                } else if (daysDistance < 14) {
+                    intGroupValue = 9; // One Week Ago (Month, Year)
+                } else if (daysDistance < 21) {
+                    intGroupValue = 19; // Two Weeks Ago (Month, Year)
+                } else if (daysDistance < 28) {
+                    intGroupValue = 29; // Three Weeks Ago (Month, Year)
+                } else {
+                    intGroupValue = 39; // Earlier on Month, Year
+                }
+            } else {
+                // The trick will fail for dates past April, 178956967 or before 1 AD.
+                intGroupValue = 2147483647 - (fileDate.year() * 12 + fileDate.month() - 1); // Month, Year; newer < older
+            }
         }
-        previousFileDate = fileDate;
-
-        const int daysDistance = fileDate.daysTo(currentDate);
-
-        QString newGroupValue;
+        return QVariant(intGroupValue);
+    } else {
         if (currentDate.year() == fileDate.year() && currentDate.month() == fileDate.month()) {
             switch (daysDistance / 7) {
             case 0:
                 switch (daysDistance) {
                 case 0:
-                    newGroupValue = i18nc("@title:group Date", "Today");
+                    strGroupValue = i18nc("@title:group Date", "Today");
                     break;
                 case 1:
-                    newGroupValue = i18nc("@title:group Date", "Yesterday");
+                    strGroupValue = i18nc("@title:group Date", "Yesterday");
                     break;
                 default:
-                    newGroupValue = fileTime.toString(i18nc("@title:group Date: The week day name: dddd", "dddd"));
-                    newGroupValue = i18nc(
+                    strGroupValue = fileTime.toString(i18nc("@title:group Date: The week day name: dddd", "dddd"));
+                    strGroupValue = i18nc(
                         "Can be used to script translation of \"dddd\""
                         "with context @title:group Date",
                         "%1",
-                        newGroupValue);
+                        strGroupValue);
                 }
                 break;
             case 1:
-                newGroupValue = i18nc("@title:group Date", "One Week Ago");
+                strGroupValue = i18nc("@title:group Date", "One Week Ago");
                 break;
             case 2:
-                newGroupValue = i18nc("@title:group Date", "Two Weeks Ago");
+                strGroupValue = i18nc("@title:group Date", "Two Weeks Ago");
                 break;
             case 3:
-                newGroupValue = i18nc("@title:group Date", "Three Weeks Ago");
+                strGroupValue = i18nc("@title:group Date", "Three Weeks Ago");
                 break;
             case 4:
             case 5:
-                newGroupValue = i18nc("@title:group Date", "Earlier this Month");
+                strGroupValue = i18nc("@title:group Date", "Earlier this Month");
                 break;
             default:
                 Q_ASSERT(false);
@@ -2526,29 +2625,29 @@ QList<QPair<int, QVariant>> KFileItemModel::timeRoleGroups(const std::function<Q
                         "'Yesterday' (MMMM, yyyy)");
                     const QString translatedFormat = format.toString();
                     if (translatedFormat.count(QLatin1Char('\'')) == 2) {
-                        newGroupValue = fileTime.toString(translatedFormat);
-                        newGroupValue = i18nc(
+                        strGroupValue = fileTime.toString(translatedFormat);
+                        strGroupValue = i18nc(
                             "Can be used to script translation of "
                             "\"'Yesterday' (MMMM, yyyy)\" with context @title:group Date",
                             "%1",
-                            newGroupValue);
+                            strGroupValue);
                     } else {
                         qCWarning(DolphinDebug).nospace()
                             << "A wrong translation was found: " << translatedFormat << ". Please file a bug report at bugs.kde.org";
                         const QString untranslatedFormat = format.toString({QLatin1String("en_US")});
-                        newGroupValue = fileTime.toString(untranslatedFormat);
+                        strGroupValue = fileTime.toString(untranslatedFormat);
                     }
                 } else if (daysDistance <= 7) {
-                    newGroupValue =
+                    strGroupValue =
                         fileTime.toString(i18nc("@title:group Date: "
                                                 "The week day name: dddd, MMMM is full month name "
                                                 "in current locale, and yyyy is full year number.",
                                                 "dddd (MMMM, yyyy)"));
-                    newGroupValue = i18nc(
+                    strGroupValue = i18nc(
                         "Can be used to script translation of "
                         "\"dddd (MMMM, yyyy)\" with context @title:group Date",
                         "%1",
-                        newGroupValue);
+                        strGroupValue);
                 } else if (daysDistance <= 7 * 2) {
                     const KLocalizedString format = ki18nc(
                         "@title:group Date: "
@@ -2558,17 +2657,17 @@ QList<QPair<int, QVariant>> KFileItemModel::timeRoleGroups(const std::function<Q
                         "'One Week Ago' (MMMM, yyyy)");
                     const QString translatedFormat = format.toString();
                     if (translatedFormat.count(QLatin1Char('\'')) == 2) {
-                        newGroupValue = fileTime.toString(translatedFormat);
-                        newGroupValue = i18nc(
+                        strGroupValue = fileTime.toString(translatedFormat);
+                        strGroupValue = i18nc(
                             "Can be used to script translation of "
                             "\"'One Week Ago' (MMMM, yyyy)\" with context @title:group Date",
                             "%1",
-                            newGroupValue);
+                            strGroupValue);
                     } else {
                         qCWarning(DolphinDebug).nospace()
                             << "A wrong translation was found: " << translatedFormat << ". Please file a bug report at bugs.kde.org";
                         const QString untranslatedFormat = format.toString({QLatin1String("en_US")});
-                        newGroupValue = fileTime.toString(untranslatedFormat);
+                        strGroupValue = fileTime.toString(untranslatedFormat);
                     }
                 } else if (daysDistance <= 7 * 3) {
                     const KLocalizedString format = ki18nc(
@@ -2579,17 +2678,17 @@ QList<QPair<int, QVariant>> KFileItemModel::timeRoleGroups(const std::function<Q
                         "'Two Weeks Ago' (MMMM, yyyy)");
                     const QString translatedFormat = format.toString();
                     if (translatedFormat.count(QLatin1Char('\'')) == 2) {
-                        newGroupValue = fileTime.toString(translatedFormat);
-                        newGroupValue = i18nc(
+                        strGroupValue = fileTime.toString(translatedFormat);
+                        strGroupValue = i18nc(
                             "Can be used to script translation of "
                             "\"'Two Weeks Ago' (MMMM, yyyy)\" with context @title:group Date",
                             "%1",
-                            newGroupValue);
+                            strGroupValue);
                     } else {
                         qCWarning(DolphinDebug).nospace()
                             << "A wrong translation was found: " << translatedFormat << ". Please file a bug report at bugs.kde.org";
                         const QString untranslatedFormat = format.toString({QLatin1String("en_US")});
-                        newGroupValue = fileTime.toString(untranslatedFormat);
+                        strGroupValue = fileTime.toString(untranslatedFormat);
                     }
                 } else if (daysDistance <= 7 * 4) {
                     const KLocalizedString format = ki18nc(
@@ -2600,17 +2699,17 @@ QList<QPair<int, QVariant>> KFileItemModel::timeRoleGroups(const std::function<Q
                         "'Three Weeks Ago' (MMMM, yyyy)");
                     const QString translatedFormat = format.toString();
                     if (translatedFormat.count(QLatin1Char('\'')) == 2) {
-                        newGroupValue = fileTime.toString(translatedFormat);
-                        newGroupValue = i18nc(
+                        strGroupValue = fileTime.toString(translatedFormat);
+                        strGroupValue = i18nc(
                             "Can be used to script translation of "
                             "\"'Three Weeks Ago' (MMMM, yyyy)\" with context @title:group Date",
                             "%1",
-                            newGroupValue);
+                            strGroupValue);
                     } else {
                         qCWarning(DolphinDebug).nospace()
                             << "A wrong translation was found: " << translatedFormat << ". Please file a bug report at bugs.kde.org";
                         const QString untranslatedFormat = format.toString({QLatin1String("en_US")});
-                        newGroupValue = fileTime.toString(untranslatedFormat);
+                        strGroupValue = fileTime.toString(untranslatedFormat);
                     }
                 } else {
                     const KLocalizedString format = ki18nc(
@@ -2621,32 +2720,116 @@ QList<QPair<int, QVariant>> KFileItemModel::timeRoleGroups(const std::function<Q
                         "'Earlier on' MMMM, yyyy");
                     const QString translatedFormat = format.toString();
                     if (translatedFormat.count(QLatin1Char('\'')) == 2) {
-                        newGroupValue = fileTime.toString(translatedFormat);
-                        newGroupValue = i18nc(
+                        strGroupValue = fileTime.toString(translatedFormat);
+                        strGroupValue = i18nc(
                             "Can be used to script translation of "
                             "\"'Earlier on' MMMM, yyyy\" with context @title:group Date",
                             "%1",
-                            newGroupValue);
+                            strGroupValue);
                     } else {
                         qCWarning(DolphinDebug).nospace()
                             << "A wrong translation was found: " << translatedFormat << ". Please file a bug report at bugs.kde.org";
                         const QString untranslatedFormat = format.toString({QLatin1String("en_US")});
-                        newGroupValue = fileTime.toString(untranslatedFormat);
+                        strGroupValue = fileTime.toString(untranslatedFormat);
                     }
                 }
             } else {
-                newGroupValue =
+                strGroupValue =
                     fileTime.toString(i18nc("@title:group "
                                             "The month and year: MMMM is full month name in current locale, "
                                             "and yyyy is full year number",
                                             "MMMM, yyyy"));
-                newGroupValue = i18nc(
+                strGroupValue = i18nc(
                     "Can be used to script translation of "
                     "\"MMMM, yyyy\" with context @title:group Date",
                     "%1",
-                    newGroupValue);
+                    strGroupValue);
             }
         }
+        return QVariant(strGroupValue);
+    }
+}
+
+QString KFileItemModel::getGenericStringRoleGroup(const QByteArray &role, const ItemData *itemData) const
+{
+    return itemData->values.value(role).toString();
+}
+
+QList<QPair<int, QVariant>> KFileItemModel::nameRoleGroups() const
+{
+    Q_ASSERT(!m_itemData.isEmpty());
+
+    const int maxIndex = count() - 1;
+    QList<QPair<int, QVariant>> groups;
+
+    QString groupValue;
+    QChar firstChar;
+    for (int i = 0; i <= maxIndex; ++i) {
+        if (isChildItem(i)) {
+            continue;
+        }
+
+        QString newGroupValue = getNameRoleGroup(m_itemData.at(i)).toString();
+
+        if (newGroupValue != groupValue) {
+            groupValue = newGroupValue;
+            groups.append(QPair<int, QVariant>(i, newGroupValue));
+        }
+
+        // firstChar = newFirstChar;
+    }
+    return groups;
+}
+
+QList<QPair<int, QVariant>> KFileItemModel::sizeRoleGroups() const
+{
+    Q_ASSERT(!m_itemData.isEmpty());
+
+    const int maxIndex = count() - 1;
+    QList<QPair<int, QVariant>> groups;
+
+    QString groupValue;
+    for (int i = 0; i <= maxIndex; ++i) {
+        if (isChildItem(i)) {
+            continue;
+        }
+
+        QString newGroupValue = getSizeRoleGroup(m_itemData.at(i)).toString();
+
+        if (newGroupValue != groupValue) {
+            groupValue = newGroupValue;
+            groups.append(QPair<int, QVariant>(i, newGroupValue));
+        }
+    }
+
+    return groups;
+}
+
+QList<QPair<int, QVariant>> KFileItemModel::timeRoleGroups(const std::function<QDateTime(const ItemData *)> &fileTimeCb) const
+{
+    Q_ASSERT(!m_itemData.isEmpty());
+
+    const int maxIndex = count() - 1;
+    QList<QPair<int, QVariant>> groups;
+
+    const QDate currentDate = QDate::currentDate();
+
+    QDate previousFileDate;
+    QString groupValue;
+    for (int i = 0; i <= maxIndex; ++i) {
+        if (isChildItem(i)) {
+            continue;
+        }
+
+        const QDateTime fileTime = fileTimeCb(m_itemData.at(i));
+        const QDate fileDate = fileTime.date();
+        if (fileDate == previousFileDate) {
+            // The current item is in the same group as the previous item
+            continue;
+        }
+        previousFileDate = fileDate;
+
+        QString newGroupValue = getTimeRoleGroup(fileTimeCb, m_itemData.at(i)).toString();
 
         if (newGroupValue != groupValue) {
             groupValue = newGroupValue;
@@ -2741,6 +2924,7 @@ QList<QPair<int, QVariant>> KFileItemModel::ratingRoleGroups() const
         if (isChildItem(i)) {
             continue;
         }
+
         const int newGroupValue = m_itemData.at(i)->values.value("rating", 0).toInt();
         if (newGroupValue != groupValue) {
             groupValue = newGroupValue;
@@ -2764,7 +2948,8 @@ QList<QPair<int, QVariant>> KFileItemModel::genericStringRoleGroups(const QByteA
         if (isChildItem(i)) {
             continue;
         }
-        const QString newGroupValue = m_itemData.at(i)->values.value(role).toString();
+
+        const QString newGroupValue = getGenericStringRoleGroup(role, m_itemData.at(i));
         if (newGroupValue != groupValue || isFirstGroupValue) {
             groupValue = newGroupValue;
             groups.append(QPair<int, QVariant>(i, newGroupValue));
index 6cbfab6037aa25d2e25efa05423e1f155da6de14..725ba71bdbe503593db89d9f724ba8a2cbcafa98 100644 (file)
@@ -454,8 +454,22 @@ private:
      */
     int sortRoleCompare(const ItemData *a, const ItemData *b, const QCollator &collator) const;
 
+    /**
+     * Helper method for lessThan() and expandedParentsCountCompare(): Compares
+     * the passed item-data using m_groupRole as criteria. Both items must
+     * have the same parent item, otherwise the comparison will be wrong.
+     */
+    int groupRoleCompare(const ItemData *a, const ItemData *b, const QCollator &collator) const;
+
     int stringCompare(const QString &a, const QString &b, const QCollator &collator) const;
 
+    QVariant getNameRoleGroup(const ItemData *itemData, bool asString = true) const;
+    QVariant getSizeRoleGroup(const ItemData *itemData, bool asString = true) const;
+    QVariant getTimeRoleGroup(const std::function<QDateTime(const ItemData *)> &fileTimeCb, const ItemData *itemData, bool asString = true) const;
+    QVariant getPermissionRoleGroup(const ItemData *itemData, bool asString = true) const;
+    QVariant getRatingRoleGroup(const ItemData *itemData, bool asString = true) const;
+    QString getGenericStringRoleGroup(const QByteArray &role, const ItemData *itemData) const;
+
     QList<QPair<int, QVariant>> nameRoleGroups() const;
     QList<QPair<int, QVariant>> sizeRoleGroups() const;
     QList<QPair<int, QVariant>> timeRoleGroups(const std::function<QDateTime(const ItemData *)> &fileTimeCb) const;
index 7a3c758a57890e8f6a433f38134829163f1ffc07..7454ed0b947f2979483d052da5a7a4a122c1ae08 100644 (file)
@@ -278,17 +278,17 @@ void DolphinViewActionHandler::createActions(SelectionMode::ActionTextHelper *ac
 
     sortByActionMenu->addSeparator();
 
-    QActionGroup *group = new QActionGroup(sortByActionMenu);
-    group->setExclusive(true);
+    QActionGroup *groupForSort = new QActionGroup(sortByActionMenu);
+    groupForSort->setExclusive(true);
 
     KToggleAction *sortAscendingAction = m_actionCollection->add<KToggleAction>(QStringLiteral("sort_ascending"));
-    sortAscendingAction->setActionGroup(group);
+    sortAscendingAction->setActionGroup(groupForSort);
     connect(sortAscendingAction, &QAction::triggered, this, [this] {
         m_currentView->setSortOrder(Qt::AscendingOrder);
     });
 
     KToggleAction *sortDescendingAction = m_actionCollection->add<KToggleAction>(QStringLiteral("sort_descending"));
-    sortDescendingAction->setActionGroup(group);
+    sortDescendingAction->setActionGroup(groupForSort);
     connect(sortDescendingAction, &QAction::triggered, this, [this] {
         m_currentView->setSortOrder(Qt::DescendingOrder);
     });
@@ -314,14 +314,17 @@ void DolphinViewActionHandler::createActions(SelectionMode::ActionTextHelper *ac
 
     groupByActionMenu->addSeparator();
 
+    QActionGroup *groupForGroup = new QActionGroup(groupByActionMenu);
+    groupForGroup->setExclusive(true);
+
     KToggleAction *groupAscendingAction = m_actionCollection->add<KToggleAction>(QStringLiteral("group_ascending"));
-    groupAscendingAction->setActionGroup(group);
+    groupAscendingAction->setActionGroup(groupForGroup);
     connect(groupAscendingAction, &QAction::triggered, this, [this] {
         m_currentView->setGroupOrder(Qt::AscendingOrder);
     });
 
     KToggleAction *groupDescendingAction = m_actionCollection->add<KToggleAction>(QStringLiteral("group_descending"));
-    groupDescendingAction->setActionGroup(group);
+    groupDescendingAction->setActionGroup(groupForGroup);
     connect(groupDescendingAction, &QAction::triggered, this, [this] {
         m_currentView->setGroupOrder(Qt::DescendingOrder);
     });
@@ -605,9 +608,9 @@ void DolphinViewActionHandler::slotGroupOrderChanged(Qt::SortOrder order)
 {
     QAction *descending = m_actionCollection->action(QStringLiteral("group_descending"));
     QAction *ascending = m_actionCollection->action(QStringLiteral("group_ascending"));
-    const bool sortDescending = (order == Qt::DescendingOrder);
-    descending->setChecked(sortDescending);
-    ascending->setChecked(!sortDescending);
+    const bool groupDescending = (order == Qt::DescendingOrder);
+    descending->setChecked(groupDescending);
+    ascending->setChecked(!groupDescending);
 }
 
 void DolphinViewActionHandler::slotSortFoldersFirstChanged(bool foldersFirst)
@@ -836,7 +839,7 @@ void DolphinViewActionHandler::slotGroupTriggered(QAction *action)
     // actions and the sub-menu-actions. If an action gets checked, it must
     // be assured that all other actions get unchecked, except the ascending/
     // descending actions
-    for (QAction *groupAction : std::as_const(m_sortByActions)) {
+    for (QAction *groupAction : std::as_const(m_groupByActions)) {
         KActionMenu *actionMenu = qobject_cast<KActionMenu *>(groupAction);
         if (actionMenu) {
             const auto actions = actionMenu->menu()->actions();