X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/ed2d352c42a6d517d4f29b3582c0e00aa34fe647..e57f6215659ee36877c7c36c9e3fcba0ba5d03a0:/src/kitemviews/kfileitemmodel.cpp diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index bbe725dfd..4386bca16 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -12,13 +12,17 @@ #include "dolphin_generalsettings.h" #include "dolphindebug.h" #include "private/kfileitemmodelsortalgorithm.h" +#include "views/draganddrophelper.h" #include #include +#include #include #include -#include +#ifndef QT_NO_ACCESSIBILITY +#include +#endif #include #include #include @@ -26,7 +30,6 @@ #include #include #include -#include #include Q_GLOBAL_STATIC(QRecursiveMutex, s_collatorMutex) @@ -100,13 +103,14 @@ KFileItemModel::KFileItemModel(QObject *parent) // for a lot of items within a quite small timeslot. To prevent expensive resortings the // resorting is postponed until the timer has been exceeded. m_resortAllItemsTimer = new QTimer(this); - m_resortAllItemsTimer->setInterval(500); + m_resortAllItemsTimer->setInterval(100); // 100 is a middle ground between sorting too frequently which makes the view unreadable + // and sorting too infrequently which leads to users seeing an outdated sort order. m_resortAllItemsTimer->setSingleShot(true); connect(m_resortAllItemsTimer, &QTimer::timeout, this, &KFileItemModel::resortAllItems); connect(GeneralSettings::self(), &GeneralSettings::sortingChoiceChanged, this, &KFileItemModel::slotSortingChoiceChanged); - setShowTrashMime(m_dirLister->showHiddenFiles()); + setShowTrashMime(m_dirLister->showHiddenFiles() || !GeneralSettings::hideXTrashFile()); } KFileItemModel::~KFileItemModel() @@ -201,13 +205,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); @@ -240,35 +251,31 @@ bool KFileItemModel::sortHiddenLast() const return m_sortHiddenLast; } -void KFileItemModel::setShowTrashMime(bool show) +void KFileItemModel::setShowTrashMime(bool showTrashMime) { const auto trashMime = QStringLiteral("application/x-trash"); QStringList excludeFilter = m_filter.excludeMimeTypes(); - bool wasShown = !excludeFilter.contains(trashMime); - if (show) { - if (!wasShown) { - excludeFilter.removeAll(trashMime); - } - } else { - if (wasShown) { - excludeFilter.append(trashMime); - } + if (showTrashMime) { + excludeFilter.removeAll(trashMime); + } else if (!excludeFilter.contains(trashMime)) { + excludeFilter.append(trashMime); } - if (wasShown != show) { - setExcludeMimeTypeFilter(excludeFilter); + setExcludeMimeTypeFilter(excludeFilter); +} + +void KFileItemModel::scheduleResortAllItems() +{ + if (!m_resortAllItemsTimer->isActive()) { + m_resortAllItemsTimer->start(); } } void KFileItemModel::setShowHiddenFiles(bool show) { -#if KIO_VERSION < QT_VERSION_CHECK(5, 100, 0) - m_dirLister->setShowingDotFiles(show); -#else m_dirLister->setShowHiddenFiles(show); -#endif - setShowTrashMime(show); + setShowTrashMime(show || !GeneralSettings::hideXTrashFile()); m_dirLister->emitChanges(); if (show) { dispatchPendingItemsToInsert(); @@ -277,11 +284,7 @@ void KFileItemModel::setShowHiddenFiles(bool show) bool KFileItemModel::showHiddenFiles() const { -#if KIO_VERSION < QT_VERSION_CHECK(5, 100, 0) - return m_dirLister->showingDotFiles(); -#else return m_dirLister->showHiddenFiles(); -#endif } void KFileItemModel::setShowDirectoriesOnly(bool enabled) @@ -332,16 +335,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; } } @@ -356,7 +375,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 @@ -624,9 +654,7 @@ bool KFileItemModel::setExpanded(int index, bool expanded) m_expandedDirs.remove(targetUrl); m_dirLister->stop(url); -#if KIO_VERSION >= QT_VERSION_CHECK(5, 92, 0) m_dirLister->forgetDirs(url); -#endif const int parentLevel = expandedParentsCount(index); const int itemCount = m_itemData.count(); @@ -642,9 +670,7 @@ bool KFileItemModel::setExpanded(int index, bool expanded) const QUrl url = itemData->item.url(); m_expandedDirs.remove(targetUrl); m_dirLister->stop(url); // TODO: try to unit-test this, see https://bugs.kde.org/show_bug.cgi?id=332102#c11 -#if KIO_VERSION >= QT_VERSION_CHECK(5, 92, 0) m_dirLister->forgetDirs(url); -#endif expandedChildren.append(targetUrl); } ++childIndex; @@ -994,7 +1020,7 @@ void KFileItemModel::resortAllItems() // been moved because of the resorting. QList oldUrls; oldUrls.reserve(itemCount); - for (const ItemData *itemData : qAsConst(m_itemData)) { + for (const ItemData *itemData : std::as_const(m_itemData)) { oldUrls.append(itemData->item.url()); } @@ -1239,7 +1265,7 @@ void KFileItemModel::slotItemsDeleted(const KFileItemList &items) indexesToRemoveWithChildren.reserve(m_itemData.count()); const int itemCount = m_itemData.count(); - for (int index : qAsConst(indexesToRemove)) { + for (int index : std::as_const(indexesToRemove)) { indexesToRemoveWithChildren.append(index); const int parentLevel = expandedParentsCount(index); @@ -1657,7 +1683,7 @@ void KFileItemModel::prepareItemsForSorting(QList &itemDataList) case DeletionTimeRole: // These roles can be determined with retrieveData, and they have to be stored // in the QHash "values" for the sorting. - for (ItemData *itemData : qAsConst(itemDataList)) { + for (ItemData *itemData : std::as_const(itemDataList)) { if (itemData->values.isEmpty()) { itemData->values = retrieveData(itemData->item, itemData->parent); } @@ -1666,7 +1692,7 @@ void KFileItemModel::prepareItemsForSorting(QList &itemDataList) case TypeRole: // At least store the data including the file type for items with known MIME type. - for (ItemData *itemData : qAsConst(itemDataList)) { + for (ItemData *itemData : std::as_const(itemDataList)) { if (itemData->values.isEmpty()) { const KFileItem item = itemData->item; if (item.isDir() || item.isMimeTypeKnown()) { @@ -1738,7 +1764,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; @@ -1763,7 +1790,7 @@ void KFileItemModel::emitItemsChangedAndTriggerResorting(const KItemRangeList &i } if (needsResorting) { - m_resortAllItemsTimer->start(); + scheduleResortAllItems(); return; } } @@ -2029,7 +2056,8 @@ bool KFileItemModel::lessThan(const ItemData *a, const ItemData *b, const QColla } } - if (m_sortDirsFirst || (ContentDisplaySettings::directorySizeCount() && 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) { @@ -2081,7 +2109,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"); @@ -2237,7 +2265,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()); @@ -2341,7 +2386,7 @@ QList> KFileItemModel::sizeRoleGroups() const KIO::filesize_t fileSize = !item.isNull() ? item.size() : ~0U; QString newGroupValue; if (!item.isNull() && item.isDir()) { - if (ContentDisplaySettings::directorySizeCount() || m_sortDirsFirst) { + if (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount || m_sortDirsFirst) { newGroupValue = i18nc("@title:group Size", "Folders"); } else { fileSize = m_itemData.at(i)->values.value("size").toULongLong(); @@ -2383,6 +2428,7 @@ QList> KFileItemModel::timeRoleGroups(const std::function> KFileItemModel::timeRoleGroups(const std::function> KFileItemModel::timeRoleGroups(const std::function= 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", @@ -2451,14 +2497,14 @@ QList> KFileItemModel::timeRoleGroups(const std::function> KFileItemModel::timeRoleGroups(const std::function= 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", @@ -2483,7 +2529,7 @@ QList> KFileItemModel::timeRoleGroups(const std::function> KFileItemModel::timeRoleGroups(const std::function= 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", @@ -2504,7 +2550,7 @@ QList> KFileItemModel::timeRoleGroups(const std::function> KFileItemModel::timeRoleGroups(const std::function= 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", @@ -2525,7 +2571,7 @@ QList> KFileItemModel::timeRoleGroups(const std::function> KFileItemModel::timeRoleGroups(const std::function= 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", @@ -2546,15 +2592,15 @@ QList> KFileItemModel::timeRoleGroups(const std::function> KFileItemModel::permissionRoleGroups() const if (info.permission(QFile::ExeUser)) { user += i18nc("@item:intext Access permission, concatenated", "Execute, "); } - user = user.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : user.mid(0, user.count() - 2); + user = user.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : user.mid(0, user.length() - 2); // Set group string QString group; @@ -2619,7 +2665,7 @@ QList> KFileItemModel::permissionRoleGroups() const if (info.permission(QFile::ExeGroup)) { group += i18nc("@item:intext Access permission, concatenated", "Execute, "); } - group = group.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : group.mid(0, group.count() - 2); + group = group.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : group.mid(0, group.length() - 2); // Set others string QString others; @@ -2632,7 +2678,7 @@ QList> KFileItemModel::permissionRoleGroups() const if (info.permission(QFile::ExeOther)) { others += i18nc("@item:intext Access permission, concatenated", "Execute, "); } - others = others.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : others.mid(0, others.count() - 2); + others = others.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : others.mid(0, others.length() - 2); const QString newGroupValue = i18nc("@title:group Files and folders by permissions", "User: %1 | Group: %2 | Others: %3", user, group, others); if (newGroupValue != groupValue) { @@ -2753,6 +2799,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 }, @@ -2854,13 +2901,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); } }