diff --git a/src/dolphinpart.rc b/src/dolphinpart.rc index d13f4aaed7..13f0f2172b 100644 --- a/src/dolphinpart.rc +++ b/src/dolphinpart.rc @@ -23,6 +23,7 @@ &View + diff --git a/src/dolphinui.rc b/src/dolphinui.rc index 2f7ea857d1..d884fe3004 100644 --- a/src/dolphinui.rc +++ b/src/dolphinui.rc @@ -43,6 +43,7 @@ + diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index ad0fb31a7f..34a113eded 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -1049,44 +1049,8 @@ void KFileItemModel::resortAllItems() m_items.clear(); m_items.reserve(itemCount); - const QList> 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> 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 &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::iterator &begin, const QList::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,6 +2441,320 @@ int KFileItemModel::stringCompare(const QString &a, const QString &b, const QCol return QString::compare(a, b, Qt::CaseSensitive); } +QVariant KFileItemModel::getNameRoleGroup(const ItemData *itemData, bool asString) const +{ + 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(); + } + + 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 lettersAtoZ; + lettersAtoZ.reserve('Z' - 'A' + 1); + if (lettersAtoZ.empty()) { + for (char c = 'A'; c <= 'Z'; ++c) { + lettersAtoZ.push_back(QLatin1Char(c)); + } + } + + auto localeAwareLessThan = [this](QChar c1, QChar c2) -> bool { + return m_collator.compare(c1, c2) < 0; + }; + + std::vector::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; + } + } 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 newGroupValue; +} + +QVariant KFileItemModel::getSizeRoleGroup(const ItemData *itemData, bool asString) const +{ + const KFileItem item = itemData->item; + + 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 < 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 + } + } + + if (asString) { + char const *groupNames[] = {"Folders", "Small", "Medium", "Big"}; + return i18nc("@title:group Size", groupNames[newGroupValue]); + } else { + return newGroupValue; + } +} + +QVariant KFileItemModel::getTimeRoleGroup(const std::function &fileTimeCb, const ItemData *itemData, bool asString) const +{ + const QDate currentDate = QDate::currentDate(); + const QDateTime fileTime = fileTimeCb(itemData); + const QDate fileDate = fileTime.date(); + const int daysDistance = fileDate.daysTo(currentDate); + + int intGroupValue; + QString strGroupValue; + + 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 + } + } + return QVariant(intGroupValue); + } else { + if (currentDate.year() == fileDate.year() && currentDate.month() == fileDate.month()) { + switch (daysDistance / 7) { + case 0: + switch (daysDistance) { + case 0: + strGroupValue = i18nc("@title:group Date", "Today"); + break; + case 1: + strGroupValue = i18nc("@title:group Date", "Yesterday"); + break; + default: + 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", + strGroupValue); + } + break; + case 1: + strGroupValue = i18nc("@title:group Date", "One Week Ago"); + break; + case 2: + strGroupValue = i18nc("@title:group Date", "Two Weeks Ago"); + break; + case 3: + strGroupValue = i18nc("@title:group Date", "Three Weeks Ago"); + break; + case 4: + case 5: + strGroupValue = i18nc("@title:group Date", "Earlier this Month"); + break; + default: + Q_ASSERT(false); + } + } else { + const QDate lastMonthDate = currentDate.addMonths(-1); + if (lastMonthDate.year() == fileDate.year() && lastMonthDate.month() == fileDate.month()) { + if (daysDistance == 1) { + const KLocalizedString format = ki18nc( + "@title:group Date: " + "MMMM is full month name in current locale, and yyyy is " + "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) { + strGroupValue = fileTime.toString(translatedFormat); + strGroupValue = i18nc( + "Can be used to script translation of " + "\"'Yesterday' (MMMM, yyyy)\" with context @title:group Date", + "%1", + 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")}); + strGroupValue = fileTime.toString(untranslatedFormat); + } + } else if (daysDistance <= 7) { + 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)")); + strGroupValue = i18nc( + "Can be used to script translation of " + "\"dddd (MMMM, yyyy)\" with context @title:group Date", + "%1", + strGroupValue); + } 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. 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) { + strGroupValue = fileTime.toString(translatedFormat); + strGroupValue = i18nc( + "Can be used to script translation of " + "\"'One Week Ago' (MMMM, yyyy)\" with context @title:group Date", + "%1", + 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")}); + strGroupValue = fileTime.toString(untranslatedFormat); + } + } 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. 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) { + strGroupValue = fileTime.toString(translatedFormat); + strGroupValue = i18nc( + "Can be used to script translation of " + "\"'Two Weeks Ago' (MMMM, yyyy)\" with context @title:group Date", + "%1", + 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")}); + strGroupValue = fileTime.toString(untranslatedFormat); + } + } 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. 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) { + strGroupValue = fileTime.toString(translatedFormat); + strGroupValue = i18nc( + "Can be used to script translation of " + "\"'Three Weeks Ago' (MMMM, yyyy)\" with context @title:group Date", + "%1", + 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")}); + strGroupValue = fileTime.toString(untranslatedFormat); + } + } else { + const KLocalizedString format = ki18nc( + "@title:group Date: " + "MMMM is full month name in current locale, and yyyy is " + "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) { + strGroupValue = fileTime.toString(translatedFormat); + strGroupValue = i18nc( + "Can be used to script translation of " + "\"'Earlier on' MMMM, yyyy\" with context @title:group Date", + "%1", + 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")}); + strGroupValue = fileTime.toString(untranslatedFormat); + } + } + } else { + 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")); + strGroupValue = i18nc( + "Can be used to script translation of " + "\"MMMM, yyyy\" with context @title:group Date", + "%1", + strGroupValue); + } + } + return QVariant(strGroupValue); + } +} + +QString KFileItemModel::getGenericStringRoleGroup(const QByteArray &role, const ItemData *itemData) const +{ + return itemData->values.value(role).toString(); +} + QList> KFileItemModel::nameRoleGroups() const { Q_ASSERT(!m_itemData.isEmpty()); @@ -2350,61 +2769,14 @@ QList> KFileItemModel::nameRoleGroups() const continue; } - const QString name = m_itemData.at(i)->item.text(); + QString newGroupValue = getNameRoleGroup(m_itemData.at(i)).toString(); - // 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 (newGroupValue != groupValue) { + groupValue = newGroupValue; + groups.append(QPair(i, newGroupValue)); } - 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 lettersAtoZ; - lettersAtoZ.reserve('Z' - 'A' + 1); - if (lettersAtoZ.empty()) { - for (char c = 'A'; c <= 'Z'; ++c) { - lettersAtoZ.push_back(QLatin1Char(c)); - } - } - - auto localeAwareLessThan = [this](QChar c1, QChar c2) -> bool { - return m_collator.compare(c1, c2) < 0; - }; - - std::vector::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; - } - } 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(i, newGroupValue)); - } - - firstChar = newFirstChar; - } + // firstChar = newFirstChar; } return groups; } @@ -2422,26 +2794,7 @@ QList> KFileItemModel::sizeRoleGroups() const 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"); - } - } + QString newGroupValue = getSizeRoleGroup(m_itemData.at(i)).toString(); if (newGroupValue != groupValue) { groupValue = newGroupValue; @@ -2476,177 +2829,7 @@ QList> KFileItemModel::timeRoleGroups(const std::function> 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> 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(i, newGroupValue)); diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h index 6cbfab6037..725ba71bdb 100644 --- a/src/kitemviews/kfileitemmodel.h +++ b/src/kitemviews/kfileitemmodel.h @@ -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 &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> nameRoleGroups() const; QList> sizeRoleGroups() const; QList> timeRoleGroups(const std::function &fileTimeCb) const; diff --git a/src/views/dolphinviewactionhandler.cpp b/src/views/dolphinviewactionhandler.cpp index 7a3c758a57..7454ed0b94 100644 --- a/src/views/dolphinviewactionhandler.cpp +++ b/src/views/dolphinviewactionhandler.cpp @@ -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(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(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(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(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(groupAction); if (actionMenu) { const auto actions = actionMenu->menu()->actions();