X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/65eefdce67e8c7bea6156e7f52f293f2cc09a140..b5eea6e5c5ee83a94ef9f8910c839b16fce168d6:/src/kitemviews/kfileitemmodel.cpp diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index 81c3be640..34a113ede 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -12,6 +12,7 @@ #include "dolphin_generalsettings.h" #include "dolphindebug.h" #include "private/kfileitemmodelsortalgorithm.h" +#include "views/draganddrophelper.h" #include #include @@ -33,7 +34,7 @@ Q_GLOBAL_STATIC(QRecursiveMutex, s_collatorMutex) // #define KFILEITEMMODEL_DEBUG KFileItemModel::KFileItemModel(QObject *parent) - : KItemModelBase("text", parent) + : KItemModelBase("text", "text", parent) , m_dirLister(nullptr) , m_sortDirsFirst(true) , m_sortHiddenLast(false) @@ -348,7 +349,18 @@ bool KFileItemModel::supportsDropping(int index) const } else { item = fileItem(index); } - return !item.isNull() && ((item.isDir() && item.isWritable()) || item.isDesktopFile()); + return !item.isNull() && DragAndDropHelper::supportsDropping(item); +} + +bool KFileItemModel::canEnterOnHover(int index) const +{ + KFileItem item; + if (index == -1) { + item = rootItem(); + } else { + item = fileItem(index); + } + return !item.isNull() && (item.isDir() || item.isDesktopFile()); } QString KFileItemModel::roleDescription(const QByteArray &role) const @@ -375,7 +387,10 @@ QList> KFileItemModel::groups() const QElapsedTimer timer; timer.start(); #endif - switch (typeForRole(sortRole())) { + switch (typeForRole(groupRole())) { + case NoRole: + m_groups.clear(); + break; case NameRole: m_groups = nameRoleGroups(); break; @@ -409,7 +424,7 @@ QList> KFileItemModel::groups() const m_groups = ratingRoleGroups(); break; default: - m_groups = genericStringRoleGroups(sortRole()); + m_groups = genericStringRoleGroups(groupRole()); break; } @@ -874,6 +889,39 @@ void KFileItemModel::removeFilteredChildren(const KItemRangeList &itemRanges) } } +KFileItemModel::RoleInfo KFileItemModel::roleInformation(const QByteArray &role) +{ + static QHash information; + if (information.isEmpty()) { + int count = 0; + const RoleInfoMap *map = rolesInfoMap(count); + for (int i = 0; i < count; ++i) { + RoleInfo info; + info.role = map[i].role; + info.translation = map[i].roleTranslation.toString(); + if (!map[i].groupTranslation.isEmpty()) { + info.group = map[i].groupTranslation.toString(); + } else { + // For top level roles, groupTranslation is 0. We must make sure that + // info.group is an empty string then because the code that generates + // menus tries to put the actions into sub menus otherwise. + info.group = QString(); + } + info.requiresBaloo = map[i].requiresBaloo; + info.requiresIndexer = map[i].requiresIndexer; + if (!map[i].tooltipTranslation.isEmpty()) { + info.tooltip = map[i].tooltipTranslation.toString(); + } else { + info.tooltip = QString(); + } + + information.insert(map[i].role, info); + } + } + + return information.value(role); +} + QList KFileItemModel::rolesInformation() { static QList rolesInfo; @@ -882,24 +930,7 @@ QList KFileItemModel::rolesInformation() const RoleInfoMap *map = rolesInfoMap(count); for (int i = 0; i < count; ++i) { if (map[i].roleType != NoRole) { - RoleInfo info; - info.role = map[i].role; - info.translation = map[i].roleTranslation.toString(); - if (!map[i].groupTranslation.isEmpty()) { - info.group = map[i].groupTranslation.toString(); - } else { - // For top level roles, groupTranslation is 0. We must make sure that - // info.group is an empty string then because the code that generates - // menus tries to put the actions into sub menus otherwise. - info.group = QString(); - } - info.requiresBaloo = map[i].requiresBaloo; - info.requiresIndexer = map[i].requiresIndexer; - if (!map[i].tooltipTranslation.isEmpty()) { - info.tooltip = map[i].tooltipTranslation.toString(); - } else { - info.tooltip = QString(); - } + RoleInfo info = roleInformation(map[i].role); rolesInfo.append(info); } } @@ -930,11 +961,40 @@ void KFileItemModel::onSortRoleChanged(const QByteArray ¤t, const QByteArr } } -void KFileItemModel::onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) +void KFileItemModel::onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous, bool resortItems) { Q_UNUSED(current) Q_UNUSED(previous) - resortAllItems(); + + if (resortItems) { + resortAllItems(); + } +} + +void KFileItemModel::onGroupRoleChanged(const QByteArray ¤t, const QByteArray &previous, bool resortItems) +{ + Q_UNUSED(previous) + m_groupRole = typeForRole(current); + + if (!m_requestRole[m_sortRole]) { + QSet newRoles = m_roles; + newRoles << current; + setRoles(newRoles); + } + + if (resortItems) { + resortAllItems(); + } +} + +void KFileItemModel::onGroupOrderChanged(Qt::SortOrder current, Qt::SortOrder previous, bool resortItems) +{ + Q_UNUSED(current) + Q_UNUSED(previous) + + if (resortItems) { + resortAllItems(); + } } void KFileItemModel::loadSortingSettings() @@ -1547,6 +1607,8 @@ void KFileItemModel::insertItems(QList &newItems) std::reverse(itemRanges.begin(), itemRanges.end()); } + // N + // 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(); @@ -1726,7 +1788,8 @@ void KFileItemModel::emitItemsChangedAndTriggerResorting(const KItemRangeList &i // Trigger a resorting if necessary. Note that this can happen even if the sort // role has not changed at all because the file name can be used as a fallback. - if (changedRoles.contains(sortRole()) || changedRoles.contains(roleForType(NameRole))) { + if (changedRoles.contains(sortRole()) || changedRoles.contains(roleForType(NameRole)) + || (changedRoles.contains("count") && sortRole() == "size")) { // "count" is used in the "size" sort role, so this might require a resorting. for (const KItemRange &range : itemRanges) { bool needsResorting = false; @@ -1751,7 +1814,7 @@ void KFileItemModel::emitItemsChangedAndTriggerResorting(const KItemRangeList &i } if (needsResorting) { - m_resortAllItemsTimer->start(); + scheduleResortAllItems(); return; } } @@ -2006,30 +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::directorySizeCount() && 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 @@ -2069,7 +2136,7 @@ int KFileItemModel::sortRoleCompare(const ItemData *a, const ItemData *b, const break; case SizeRole: { - if (ContentDisplaySettings::directorySizeCount() && itemA.isDir()) { + if (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount && itemA.isDir()) { // folders first then // items A and B are folders thanks to lessThan checks auto valueA = a->values.value("count"); @@ -2220,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()); @@ -2239,180 +2441,174 @@ int KFileItemModel::stringCompare(const QString &a, const QString &b, const QCol return QString::compare(a, b, Qt::CaseSensitive); } -QList> KFileItemModel::nameRoleGroups() const +QVariant KFileItemModel::getNameRoleGroup(const ItemData *itemData, bool asString) const { - Q_ASSERT(!m_itemData.isEmpty()); - - const int maxIndex = count() - 1; - QList> 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 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; - }; + 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(); + } - 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; - } + 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. - } else { - // Symbols from non Latin-based scripts - newGroupValue = newFirstChar; + // 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)); } - } 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)); + 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; } - 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> KFileItemModel::sizeRoleGroups() const +QVariant KFileItemModel::getSizeRoleGroup(const ItemData *itemData, bool asString) const { - Q_ASSERT(!m_itemData.isEmpty()); - - const int maxIndex = count() - 1; - QList> groups; - - 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::directorySizeCount() || m_sortDirsFirst) { - newGroupValue = i18nc("@title:group Size", "Folders"); - } else { - fileSize = m_itemData.at(i)->values.value("size").toULongLong(); - } - } + const KFileItem item = itemData->item; - 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(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> KFileItemModel::timeRoleGroups(const std::function &fileTimeCb) const +QVariant KFileItemModel::getTimeRoleGroup(const std::function &fileTimeCb, const ItemData *itemData, bool asString) const { - Q_ASSERT(!m_itemData.isEmpty()); - - const int maxIndex = count() - 1; - QList> 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); @@ -2429,29 +2625,29 @@ QList> KFileItemModel::timeRoleGroups(const std::function> KFileItemModel::timeRoleGroups(const std::function> KFileItemModel::timeRoleGroups(const std::function> KFileItemModel::timeRoleGroups(const std::function> KFileItemModel::timeRoleGroups(const std::functionvalues.value(role).toString(); +} + +QList> KFileItemModel::nameRoleGroups() const +{ + Q_ASSERT(!m_itemData.isEmpty()); + + const int maxIndex = count() - 1; + QList> 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(i, newGroupValue)); + } + + // firstChar = newFirstChar; + } + return groups; +} + +QList> KFileItemModel::sizeRoleGroups() const +{ + Q_ASSERT(!m_itemData.isEmpty()); + + const int maxIndex = count() - 1; + QList> 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(i, newGroupValue)); + } + } + + return groups; +} + +QList> KFileItemModel::timeRoleGroups(const std::function &fileTimeCb) const +{ + Q_ASSERT(!m_itemData.isEmpty()); + + const int maxIndex = count() - 1; + QList> 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; @@ -2644,6 +2924,7 @@ QList> KFileItemModel::ratingRoleGroups() const if (isChildItem(i)) { continue; } + const int newGroupValue = m_itemData.at(i)->values.value("rating", 0).toInt(); if (newGroupValue != groupValue) { groupValue = newGroupValue; @@ -2667,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)); @@ -2711,7 +2993,7 @@ const KFileItemModel::RoleInfoMap *KFileItemModel::rolesInfoMap(int &count) static const RoleInfoMap rolesInfoMap[] = { // clang-format off // | role | roleType | role translation | group translation | requires Baloo | requires indexer - { nullptr, NoRole, KLazyLocalizedString(), KLazyLocalizedString(), KLazyLocalizedString(), false, false }, + { nullptr, NoRole, kli18nc("@label", "None"), KLazyLocalizedString(), KLazyLocalizedString(), false, false }, { "text", NameRole, kli18nc("@label", "Name"), KLazyLocalizedString(), KLazyLocalizedString(), false, false }, { "size", SizeRole, kli18nc("@label", "Size"), KLazyLocalizedString(), KLazyLocalizedString(), false, false }, { "modificationtime", ModificationTimeRole, kli18nc("@label", "Modified"), KLazyLocalizedString(), kli18nc("@tooltip", "The date format can be selected in settings."), false, false },