X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/187933a7a6acb7f50ab397f5170308b8aab76bbc..b4e80645e8e39ef7fcc1545136bad06ab3dd5f3e:/src/kitemviews/kfileitemmodel.cpp diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index afe8f71c4..603c16e0d 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -20,6 +20,9 @@ #include #include +#ifndef QT_NO_ACCESSIBILITY +#include +#endif #include #include #include @@ -39,6 +42,7 @@ KFileItemModel::KFileItemModel(QObject *parent) , m_sortDirsFirst(true) , m_sortHiddenLast(false) , m_sortRole(NameRole) + , m_groupRole(NoRole) , m_sortingProgressPercent(-1) , m_roles() , m_itemData() @@ -202,13 +206,20 @@ bool KFileItemModel::setData(int index, const QHash &value return false; } - m_itemData[index]->values = currentValues; if (changedRoles.contains("text")) { QUrl url = m_itemData[index]->item.url(); + m_items.remove(url); url = url.adjusted(QUrl::RemoveFilename); url.setPath(url.path() + currentValues["text"].toString()); m_itemData[index]->item.setUrl(url); + m_items.insert(url, index); + + if (!changedRoles.contains("url")) { + changedRoles.insert("url"); + currentValues["url"] = url; + } } + m_itemData[index]->values = currentValues; emitItemsChangedAndTriggerResorting(KItemRangeList() << KItemRange(index, 1), changedRoles); @@ -325,16 +336,32 @@ QMimeData *KFileItemModel::createMimeData(const KItemSet &indexes) const return data; } +namespace +{ +QString removeMarks(const QString &original) +{ + const auto normalized = original.normalized(QString::NormalizationForm_D); + QString res; + for (auto ch : normalized) { + if (!ch.isMark()) { + res.append(ch); + } + } + return res; +} +} + int KFileItemModel::indexForKeyboardSearch(const QString &text, int startFromIndex) const { + const auto noMarkText = removeMarks(text); startFromIndex = qMax(0, startFromIndex); for (int i = startFromIndex; i < count(); ++i) { - if (fileItem(i).text().startsWith(text, Qt::CaseInsensitive)) { + if (removeMarks(fileItem(i).text()).startsWith(noMarkText, Qt::CaseInsensitive)) { return i; } } for (int i = 0; i < startFromIndex; ++i) { - if (fileItem(i).text().startsWith(text, Qt::CaseInsensitive)) { + if (removeMarks(fileItem(i).text()).startsWith(noMarkText, Qt::CaseInsensitive)) { return i; } } @@ -946,6 +973,15 @@ QList KFileItemModel::rolesInformation() return rolesInfo; } +QList KFileItemModel::extraGroupingInformation() +{ + static QList rolesInfo{ + {QByteArray("none"), kli18nc("@label", "No grouping").toString(), nullptr, nullptr, false, false}, + {QByteArray("followSort"), kli18nc("@label", "Follow sorting").toString(), nullptr, nullptr, false, false} + }; + return rolesInfo; +} + void KFileItemModel::onGroupedSortingChanged(bool current) { Q_UNUSED(current) @@ -975,14 +1011,11 @@ void KFileItemModel::onSortRoleChanged(const QByteArray ¤t, const QByteArr } } -void KFileItemModel::onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous, bool resortItems) +void KFileItemModel::onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) { Q_UNUSED(current) Q_UNUSED(previous) - - if (resortItems) { - resortAllItems(); - } + resortAllItems(); } void KFileItemModel::onGroupRoleChanged(const QByteArray ¤t, const QByteArray &previous, bool resortItems) @@ -1008,14 +1041,11 @@ void KFileItemModel::onGroupRoleChanged(const QByteArray ¤t, const QByteAr } } -void KFileItemModel::onGroupOrderChanged(Qt::SortOrder current, Qt::SortOrder previous, bool resortItems) +void KFileItemModel::onGroupOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) { Q_UNUSED(current) Q_UNUSED(previous) - - if (resortItems) { - resortAllItems(); - } + resortAllItems(); } void KFileItemModel::loadSortingSettings() @@ -1040,6 +1070,7 @@ void KFileItemModel::loadSortingSettings() // Workaround for bug https://bugreports.qt.io/browse/QTBUG-69361 // Force the clean state of QCollator in single thread to avoid thread safety problems in sort m_collator.compare(QString(), QString()); + ContentDisplaySettings::self(); } void KFileItemModel::resortAllItems() @@ -1105,7 +1136,8 @@ void KFileItemModel::resortAllItems() } Q_EMIT itemsMoved(KItemRange(firstMovedIndex, movedItemsCount), movedToIndexes); - } else if (groupedSorting()) { + } + if (groupedSorting()) { // The groups might have changed even if the order of the items has not. const QList> oldGroups = m_groups; m_groups.clear(); @@ -1690,7 +1722,7 @@ void KFileItemModel::removeItems(const KItemRangeList &itemRanges, RemoveItemsBe QList KFileItemModel::createItemDataList(const QUrl &parentUrl, const KFileItemList &items) const { - if (m_sortRole == TypeRole) { + if (m_sortRole == TypeRole || m_groupRole == TypeRole) { // Try to resolve the MIME-types synchronously to prevent a reordering of // the items when sorting by type (per default MIME-types are resolved // asynchronously by KFileItemModelRolesUpdater). @@ -1714,9 +1746,9 @@ QList KFileItemModel::createItemDataList(const QUrl return itemDataList; } -void KFileItemModel::prepareItemsForSorting(QList &itemDataList) +void KFileItemModel::prepareItemsWithRole(QList &itemDataList, RoleType roleType) { - switch (m_sortRole) { + switch (roleType) { case ExtensionRole: case PermissionsRole: case OwnerRole: @@ -1755,6 +1787,12 @@ void KFileItemModel::prepareItemsForSorting(QList &itemDataList) } } +void KFileItemModel::prepareItemsForSorting(QList &itemDataList) +{ + prepareItemsWithRole(itemDataList, m_sortRole); + prepareItemsWithRole(itemDataList, m_groupRole); +} + int KFileItemModel::expandedParentsCount(const ItemData *data) { // The hash 'values' is only guaranteed to contain the key "expandedParentsCount" @@ -2100,8 +2138,7 @@ bool KFileItemModel::lessThan(const ItemData *a, const ItemData *b, const QColla return true; } } - if (m_sortDirsFirst - || (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount && m_sortRole == SizeRole)) { + if (m_sortDirsFirst || (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount && m_sortRole == SizeRole)) { const bool isDirA = a->item.isDir(); const bool isDirB = b->item.isDir(); if (isDirA && !isDirB) { @@ -2311,104 +2348,99 @@ int KFileItemModel::groupRoleCompare(const ItemData *a, const ItemData *b, const // Unlike sortRoleCompare, this function can and often will return 0. int result = 0; - int groupA, groupB; + ItemGroupInfo groupA, groupB; switch (m_groupRole) { case NoRole: // Non-trivial grouping behavior might be handled there in the future. return 0; case NameRole: - groupA = nameRoleGroup(a, false).comparable; - groupB = nameRoleGroup(b, false).comparable; + groupA = nameRoleGroup(a, false); + groupB = nameRoleGroup(b, false); break; case SizeRole: - groupA = sizeRoleGroup(a, false).comparable; - groupB = sizeRoleGroup(b, false).comparable; + groupA = sizeRoleGroup(a, false); + groupB = sizeRoleGroup(b, false); break; case ModificationTimeRole: groupA = timeRoleGroup( - [](const ItemData *item) { - return item->item.time(KFileItem::ModificationTime); - }, - a, - false) - .comparable; + [](const ItemData *item) { + return item->item.time(KFileItem::ModificationTime); + }, + a, + false); groupB = timeRoleGroup( - [](const ItemData *item) { - return item->item.time(KFileItem::ModificationTime); - }, - b, - false) - .comparable; + [](const ItemData *item) { + return item->item.time(KFileItem::ModificationTime); + }, + b, + false); break; case CreationTimeRole: groupA = timeRoleGroup( - [](const ItemData *item) { - return item->item.time(KFileItem::CreationTime); - }, - a, - false) - .comparable; + [](const ItemData *item) { + return item->item.time(KFileItem::CreationTime); + }, + a, + false); groupB = timeRoleGroup( - [](const ItemData *item) { - return item->item.time(KFileItem::CreationTime); - }, - b, - false) - .comparable; + [](const ItemData *item) { + return item->item.time(KFileItem::CreationTime); + }, + b, + false); break; case AccessTimeRole: groupA = timeRoleGroup( - [](const ItemData *item) { - return item->item.time(KFileItem::AccessTime); - }, - a, - false) - .comparable; + [](const ItemData *item) { + return item->item.time(KFileItem::AccessTime); + }, + a, + false); groupB = timeRoleGroup( - [](const ItemData *item) { - return item->item.time(KFileItem::AccessTime); - }, - b, - false) - .comparable; + [](const ItemData *item) { + return item->item.time(KFileItem::AccessTime); + }, + b, + false); break; case DeletionTimeRole: groupA = timeRoleGroup( - [](const ItemData *item) { - return item->values.value("deletiontime").toDateTime(); - }, - a, - false) - .comparable; + [](const ItemData *item) { + return item->values.value("deletiontime").toDateTime(); + }, + a, + false); groupB = timeRoleGroup( - [](const ItemData *item) { - return item->values.value("deletiontime").toDateTime(); - }, - b, - false) - .comparable; + [](const ItemData *item) { + return item->values.value("deletiontime").toDateTime(); + }, + b, + false); break; case PermissionsRole: - groupA = permissionRoleGroup(a, false).comparable; - groupB = permissionRoleGroup(b, false).comparable; + groupA = permissionRoleGroup(a, false); + groupB = permissionRoleGroup(b, false); break; case RatingRole: - groupA = ratingRoleGroup(a, false).comparable; - groupB = ratingRoleGroup(b, false).comparable; + groupA = ratingRoleGroup(a, false); + groupB = ratingRoleGroup(b, false); + break; + case TypeRole: + groupA = typeRoleGroup(a); + groupB = typeRoleGroup(b); break; default: { - QString strGroupA = genericStringRoleGroup(groupRole(), a); - QString strGroupB = genericStringRoleGroup(groupRole(), b); - result = stringCompare(strGroupA, strGroupB, collator); + groupA = genericStringRoleGroup(groupRole(), a); + groupB = genericStringRoleGroup(groupRole(), b); break; } } - if (result == 0) { - if (groupA < groupB) { - result = -1; - } else if (groupA > groupB) { - result = 1; - } + if (groupA.comparable < groupB.comparable) { + result = -1; + } else if (groupA.comparable > groupB.comparable) { + result = 1; + } else { + result = stringCompare(groupA.text, groupB.text, collator); } return result; } @@ -2418,7 +2450,24 @@ int KFileItemModel::stringCompare(const QString &a, const QString &b, const QCol QMutexLocker collatorLock(s_collatorMutex()); if (m_naturalSorting) { - return collator.compare(a, b); + // Split extension, taking into account it can be empty + constexpr QString::SectionFlags flags = QString::SectionSkipEmpty | QString::SectionIncludeLeadingSep; + + // Sort by baseName first + const QString aBaseName = a.section('.', 0, 0, flags); + const QString bBaseName = b.section('.', 0, 0, flags); + + const int res = collator.compare(aBaseName, bBaseName); + if (res != 0 || (aBaseName.length() == a.length() && bBaseName.length() == b.length())) { + return res; + } + + // sliced() has undefined behavior when pos < 0 or pos > size(). + Q_ASSERT(aBaseName.length() <= a.length() && aBaseName.length() >= 0); + Q_ASSERT(bBaseName.length() <= b.length() && bBaseName.length() >= 0); + + // baseNames were equal, sort by extension + return collator.compare(a.sliced(aBaseName.length()), b.sliced(bBaseName.length())); } const int result = QString::compare(a, b, collator.caseSensitivity()); @@ -2434,6 +2483,7 @@ int KFileItemModel::stringCompare(const QString &a, const QString &b, const QCol KFileItemModel::ItemGroupInfo KFileItemModel::nameRoleGroup(const ItemData *itemData, bool withString) const { + static bool oldWithString; static ItemGroupInfo oldGroupInfo; static QChar oldFirstChar; ItemGroupInfo groupInfo; @@ -2445,8 +2495,8 @@ KFileItemModel::ItemGroupInfo KFileItemModel::nameRoleGroup(const ItemData *item // Use the first character of the name as group indication firstChar = name.at(0).toUpper(); - - if (firstChar == oldFirstChar) { + + if (firstChar == oldFirstChar && withString == oldWithString) { return oldGroupInfo; } if (firstChar == QLatin1Char('~') && name.length() > 1) { @@ -2501,6 +2551,7 @@ KFileItemModel::ItemGroupInfo KFileItemModel::nameRoleGroup(const ItemData *item } groupInfo.comparable = (int)'.'; } + oldWithString = withString; oldFirstChar = firstChar; oldGroupInfo = groupInfo; return groupInfo; @@ -2508,8 +2559,6 @@ KFileItemModel::ItemGroupInfo KFileItemModel::nameRoleGroup(const ItemData *item KFileItemModel::ItemGroupInfo KFileItemModel::sizeRoleGroup(const ItemData *itemData, bool withString) const { - static ItemGroupInfo oldGroupInfo; - static KIO::filesize_t oldFileSize; ItemGroupInfo groupInfo; KIO::filesize_t fileSize; @@ -2518,15 +2567,12 @@ KFileItemModel::ItemGroupInfo KFileItemModel::sizeRoleGroup(const ItemData *item groupInfo.comparable = -1; // None if (!item.isNull() && item.isDir()) { - if (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount || m_sortDirsFirst) { + if (ContentDisplaySettings::directorySizeMode() != ContentDisplaySettings::EnumDirectorySizeMode::ContentSize) { groupInfo.comparable = 0; // Folders } else { fileSize = itemData->values.value("size").toULongLong(); } } - if (fileSize == oldFileSize) { - return oldGroupInfo; - } if (groupInfo.comparable < 0) { if (fileSize < 5 * 1024 * 1024) { // < 5 MB groupInfo.comparable = 1; // Small @@ -2541,23 +2587,40 @@ KFileItemModel::ItemGroupInfo KFileItemModel::sizeRoleGroup(const ItemData *item char const *groupNames[] = {"Folders", "Small", "Medium", "Big"}; groupInfo.text = i18nc("@title:group Size", groupNames[groupInfo.comparable]); } - oldFileSize = fileSize; - oldGroupInfo = groupInfo; return groupInfo; } KFileItemModel::ItemGroupInfo KFileItemModel::timeRoleGroup(const std::function &fileTimeCb, const ItemData *itemData, bool withString) const { + static bool oldWithString; static ItemGroupInfo oldGroupInfo; static QDate oldFileDate; ItemGroupInfo groupInfo; 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; + } + + const QLocale locale; + 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; + + const int daysDistance = fileDate.daysTo(currentDate); + + if (fileDate == oldFileDate && withString == oldWithString) { + return oldGroupInfo; + } // Simplified grouping algorithm, preserving dates // but not taking "pretty printing" into account if (currentDate.year() == fileDate.year() && currentDate.month() == fileDate.month()) { @@ -2603,8 +2666,8 @@ KFileItemModel::timeRoleGroup(const std::function & groupInfo.text = i18nc("@title:group Date", "Yesterday"); break; default: - groupInfo.text = fileTime.toString(i18nc("@title:group Date: The week day name: dddd", "dddd")); - groupInfo.text = i18nc( + newGroupValue = locale.toString(fileTime, i18nc("@title:group Date: The week day name: dddd", "dddd")); + newGroupValue = i18nc( "Can be used to script translation of \"dddd\"" "with context @title:group Date", "%1", @@ -2638,9 +2701,9 @@ KFileItemModel::timeRoleGroup(const std::function & "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) { - groupInfo.text = fileTime.toString(translatedFormat); - groupInfo.text = i18nc( + if (const int count = translatedFormat.count(QLatin1Char('\'')); count >= 2 && count % 2 == 0) { + newGroupValue = locale.toString(fileTime, translatedFormat); + newGroupValue = i18nc( "Can be used to script translation of " "\"'Yesterday' (MMMM, yyyy)\" with context @title:group Date", "%1", @@ -2649,15 +2712,15 @@ KFileItemModel::timeRoleGroup(const std::function & 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")}); - groupInfo.text = fileTime.toString(untranslatedFormat); + newGroupValue = locale.toString(fileTime, untranslatedFormat); } - } else if (daysDistance < 7) { - groupInfo.text = - 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)")); - groupInfo.text = i18nc( + } else if (daysDistance <= 7) { + newGroupValue = locale.toString(fileTime, + 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( "Can be used to script translation of " "\"dddd (MMMM, yyyy)\" with context @title:group Date", "%1", @@ -2670,9 +2733,9 @@ KFileItemModel::timeRoleGroup(const std::function & "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) { - groupInfo.text = fileTime.toString(translatedFormat); - groupInfo.text = i18nc( + if (const int count = translatedFormat.count(QLatin1Char('\'')); count >= 2 && count % 2 == 0) { + newGroupValue = locale.toString(fileTime, translatedFormat); + newGroupValue = i18nc( "Can be used to script translation of " "\"'One Week Ago' (MMMM, yyyy)\" with context @title:group Date", "%1", @@ -2681,7 +2744,7 @@ KFileItemModel::timeRoleGroup(const std::function & 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")}); - groupInfo.text = fileTime.toString(untranslatedFormat); + newGroupValue = locale.toString(fileTime, untranslatedFormat); } } else if (daysDistance < 7 * 3) { const KLocalizedString format = ki18nc( @@ -2691,9 +2754,9 @@ KFileItemModel::timeRoleGroup(const std::function & "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) { - groupInfo.text = fileTime.toString(translatedFormat); - groupInfo.text = i18nc( + if (const int count = translatedFormat.count(QLatin1Char('\'')); count >= 2 && count % 2 == 0) { + newGroupValue = locale.toString(fileTime, translatedFormat); + newGroupValue = i18nc( "Can be used to script translation of " "\"'Two Weeks Ago' (MMMM, yyyy)\" with context @title:group Date", "%1", @@ -2702,7 +2765,7 @@ KFileItemModel::timeRoleGroup(const std::function & 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")}); - groupInfo.text = fileTime.toString(untranslatedFormat); + newGroupValue = locale.toString(fileTime, untranslatedFormat); } } else if (daysDistance < 7 * 4) { const KLocalizedString format = ki18nc( @@ -2712,9 +2775,9 @@ KFileItemModel::timeRoleGroup(const std::function & "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) { - groupInfo.text = fileTime.toString(translatedFormat); - groupInfo.text = i18nc( + if (const int count = translatedFormat.count(QLatin1Char('\'')); count >= 2 && count % 2 == 0) { + newGroupValue = locale.toString(fileTime, translatedFormat); + newGroupValue = i18nc( "Can be used to script translation of " "\"'Three Weeks Ago' (MMMM, yyyy)\" with context @title:group Date", "%1", @@ -2723,7 +2786,7 @@ KFileItemModel::timeRoleGroup(const std::function & 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")}); - groupInfo.text = fileTime.toString(untranslatedFormat); + newGroupValue = locale.toString(fileTime, untranslatedFormat); } } else { const KLocalizedString format = ki18nc( @@ -2733,9 +2796,9 @@ KFileItemModel::timeRoleGroup(const std::function & "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) { - groupInfo.text = fileTime.toString(translatedFormat); - groupInfo.text = i18nc( + if (const int count = translatedFormat.count(QLatin1Char('\'')); count >= 2 && count % 2 == 0) { + newGroupValue = locale.toString(fileTime, translatedFormat); + newGroupValue = i18nc( "Can be used to script translation of " "\"'Earlier on' MMMM, yyyy\" with context @title:group Date", "%1", @@ -2744,16 +2807,16 @@ KFileItemModel::timeRoleGroup(const std::function & 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")}); - groupInfo.text = fileTime.toString(untranslatedFormat); + newGroupValue = locale.toString(fileTime, untranslatedFormat); } } } else { - groupInfo.text = - 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")); - groupInfo.text = i18nc( + newGroupValue = locale.toString(fileTime, + 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( "Can be used to script translation of " "\"MMMM, yyyy\" with context @title:group Date", "%1", @@ -2761,6 +2824,7 @@ KFileItemModel::timeRoleGroup(const std::function & } } } + oldWithString = withString; oldFileDate = fileDate; oldGroupInfo = groupInfo; return groupInfo; @@ -2768,13 +2832,14 @@ KFileItemModel::timeRoleGroup(const std::function & KFileItemModel::ItemGroupInfo KFileItemModel::permissionRoleGroup(const ItemData *itemData, bool withString) const { + static bool oldWithString; static ItemGroupInfo oldGroupInfo; static QFileDevice::Permissions oldPermissions; ItemGroupInfo groupInfo; const QFileInfo info(itemData->item.url().toLocalFile()); const QFileDevice::Permissions permissions = info.permissions(); - if (permissions == oldPermissions) { + if (permissions == oldPermissions && withString == oldWithString) { return oldGroupInfo; } groupInfo.comparable = (int)permissions; @@ -2820,6 +2885,7 @@ KFileItemModel::ItemGroupInfo KFileItemModel::permissionRoleGroup(const ItemData others = others.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : others.mid(0, others.length() - 2); groupInfo.text = i18nc("@title:group Files and folders by permissions", "User: %1 | Group: %2 | Others: %3", user, group, others); } + oldWithString = withString; oldPermissions = permissions; oldGroupInfo = groupInfo; return groupInfo; @@ -2832,14 +2898,14 @@ KFileItemModel::ItemGroupInfo KFileItemModel::ratingRoleGroup(const ItemData *it if (withString) { // Dolphin does not currently use string representation of star rating // as stars are rendered as graphics in group headers. - groupInfo.text = i18nc("@item:intext Rated N (stars)", "Rated ") + QString::number(groupInfo.comparable); + groupInfo.text = i18nc("@item:intext Rated N (stars)", "Rated %i", QString::number(groupInfo.comparable)); } return groupInfo; } -QString KFileItemModel::genericStringRoleGroup(const QByteArray &role, const ItemData *itemData) const +KFileItemModel::ItemGroupInfo KFileItemModel::genericStringRoleGroup(const QByteArray &role, const ItemData *itemData) const { - return itemData->values.value(role).toString(); + return {0, itemData->values.value(role).toString()}; } QList> KFileItemModel::nameRoleGroups() const @@ -2888,6 +2954,20 @@ QList> KFileItemModel::sizeRoleGroups() const return groups; } +KFileItemModel::ItemGroupInfo KFileItemModel::typeRoleGroup(const ItemData *itemData) const +{ + int priority = 0; + if (itemData->item.isDir() && m_sortDirsFirst) { + // Ensure folders stay first regardless of grouping order + if (groupOrder() == Qt::AscendingOrder) { + priority = -1; + } else { + priority = 1; + } + } + return {priority, itemData->values.value("type").toString()}; +} + QList> KFileItemModel::timeRoleGroups(const std::function &fileTimeCb) const { Q_ASSERT(!m_itemData.isEmpty()); @@ -2966,17 +3046,17 @@ QList> KFileItemModel::genericStringRoleGroups(const QByteA const int maxIndex = count() - 1; QList> groups; - QString groupText; + ItemGroupInfo groupInfo; for (int i = 0; i <= maxIndex; ++i) { if (isChildItem(i)) { continue; } - QString newGroupText = genericStringRoleGroup(role, m_itemData.at(i)); + ItemGroupInfo newGroupInfo = genericStringRoleGroup(role, m_itemData.at(i)); - if (newGroupText != groupText) { - groupText = newGroupText; - groups.append(QPair(i, newGroupText)); + if (newGroupInfo != groupInfo) { + groupInfo = newGroupInfo; + groups.append(QPair(i, newGroupInfo.text)); } } return groups; @@ -3045,6 +3125,7 @@ const KFileItemModel::RoleInfoMap *KFileItemModel::rolesInfoMap(int &count) { "releaseYear", ReleaseYearRole, kli18nc("@label", "Release Year"), kli18nc("@label", "Audio"), KLazyLocalizedString(), true, true }, { "aspectRatio", AspectRatioRole, kli18nc("@label", "Aspect Ratio"), kli18nc("@label", "Video"), KLazyLocalizedString(), true, true }, { "frameRate", FrameRateRole, kli18nc("@label", "Frame Rate"), kli18nc("@label", "Video"), KLazyLocalizedString(), true, true }, + { "duration", DurationRole, kli18nc("@label", "Duration"), kli18nc("@label", "Video"), KLazyLocalizedString(), true, true }, { "path", PathRole, kli18nc("@label", "Path"), kli18nc("@label", "Other"), KLazyLocalizedString(), false, false }, { "extension", ExtensionRole, kli18nc("@label", "File Extension"), kli18nc("@label", "Other"), KLazyLocalizedString(), false, false }, { "deletiontime", DeletionTimeRole, kli18nc("@label", "Deletion Time"), kli18nc("@label", "Other"), KLazyLocalizedString(), false, false }, @@ -3146,13 +3227,14 @@ bool KFileItemModel::isConsistent() const void KFileItemModel::slotListerError(KIO::Job *job) { - if (job->error() == KIO::ERR_IS_FILE) { + const int jobError = job->error(); + if (jobError == KIO::ERR_IS_FILE) { if (auto *listJob = qobject_cast(job)) { Q_EMIT urlIsFileError(listJob->url()); } } else { const QString errorString = job->errorString(); - Q_EMIT errorMessage(!errorString.isEmpty() ? errorString : i18nc("@info:status", "Unknown error.")); + Q_EMIT errorMessage(!errorString.isEmpty() ? errorString : i18nc("@info:status", "Unknown error."), jobError); } }