X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/272aa65bb55dd31f58e8a8e24ea7bc8bd2712daa..c1200b3ddb9bc9a2ded9d4718e88f0284fa665d2:/src/kitemviews/kfileitemmodel.cpp diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index ef80b4edb..d12355100 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -11,10 +11,10 @@ #include "dolphin_generalsettings.h" #include "dolphin_detailsmodesettings.h" #include "dolphindebug.h" -#include "private/kfileitemmodeldirlister.h" #include "private/kfileitemmodelsortalgorithm.h" -#include +#include +#include #include #include @@ -34,7 +34,7 @@ KFileItemModel::KFileItemModel(QObject* parent) : KItemModelBase("text", parent), m_dirLister(nullptr), m_sortDirsFirst(true), - m_sortHiddenLast(true), + m_sortHiddenLast(false), m_sortRole(NameRole), m_sortingProgressPercent(-1), m_roles(), @@ -54,7 +54,8 @@ KFileItemModel::KFileItemModel(QObject* parent) : loadSortingSettings(); - m_dirLister = new KFileItemModelDirLister(this); + m_dirLister = new KDirLister(this); + m_dirLister->setAutoErrorHandlingEnabled(false); m_dirLister->setDelayedMimeTypes(true); const QWidget* parentWidget = qobject_cast(parent); @@ -62,23 +63,17 @@ KFileItemModel::KFileItemModel(QObject* parent) : m_dirLister->setMainWindow(parentWidget->window()); } - connect(m_dirLister, &KFileItemModelDirLister::started, this, &KFileItemModel::directoryLoadingStarted); + connect(m_dirLister, &KCoreDirLister::started, this, &KFileItemModel::directoryLoadingStarted); connect(m_dirLister, QOverload<>::of(&KCoreDirLister::canceled), this, &KFileItemModel::slotCanceled); - connect(m_dirLister, &KFileItemModelDirLister::itemsAdded, this, &KFileItemModel::slotItemsAdded); - connect(m_dirLister, &KFileItemModelDirLister::itemsDeleted, this, &KFileItemModel::slotItemsDeleted); - connect(m_dirLister, &KFileItemModelDirLister::refreshItems, this, &KFileItemModel::slotRefreshItems); + connect(m_dirLister, &KCoreDirLister::itemsAdded, this, &KFileItemModel::slotItemsAdded); + connect(m_dirLister, &KCoreDirLister::itemsDeleted, this, &KFileItemModel::slotItemsDeleted); + connect(m_dirLister, &KCoreDirLister::refreshItems, this, &KFileItemModel::slotRefreshItems); connect(m_dirLister, QOverload<>::of(&KCoreDirLister::clear), this, &KFileItemModel::slotClear); - connect(m_dirLister, &KFileItemModelDirLister::infoMessage, this, &KFileItemModel::infoMessage); - connect(m_dirLister, &KFileItemModelDirLister::errorMessage, this, &KFileItemModel::errorMessage); - connect(m_dirLister, &KFileItemModelDirLister::percent, this, &KFileItemModel::directoryLoadingProgress); + connect(m_dirLister, &KCoreDirLister::infoMessage, this, &KFileItemModel::infoMessage); + connect(m_dirLister, &KCoreDirLister::jobError, this, &KFileItemModel::slotListerError); + connect(m_dirLister, &KCoreDirLister::percent, this, &KFileItemModel::directoryLoadingProgress); connect(m_dirLister, QOverload::of(&KCoreDirLister::redirection), this, &KFileItemModel::directoryRedirection); - connect(m_dirLister, &KFileItemModelDirLister::urlIsFileError, this, &KFileItemModel::urlIsFileError); - -#if KIO_VERSION < QT_VERSION_CHECK(5, 79, 0) - connect(m_dirLister, QOverload::of(&KCoreDirLister::completed), this, &KFileItemModel::slotCompleted); -#else connect(m_dirLister, &KCoreDirLister::listingDirCompleted, this, &KFileItemModel::slotCompleted); -#endif // Apply default roles that should be determined resetRoles(); @@ -700,45 +695,87 @@ QStringList KFileItemModel::mimeTypeFilters() const return m_filter.mimeTypes(); } - void KFileItemModel::applyFilters() { - // Check which shown items from m_itemData must get - // hidden and hence moved to m_filteredItems. - QVector newFilteredIndexes; + // ===STEP 1=== + // Check which previously shown items from m_itemData must now get + // hidden and hence moved from m_itemData into m_filteredItems. - const int itemCount = m_itemData.count(); - for (int index = 0; index < itemCount; ++index) { - ItemData* itemData = m_itemData.at(index); - - // Only filter non-expanded items as child items may never - // exist without a parent item - if (!itemData->values.value("isExpanded").toBool()) { - const KFileItem item = itemData->item; - if (!m_filter.matches(item)) { - newFilteredIndexes.append(index); - m_filteredItems.insert(item, itemData); - } + QList newFilteredIndexes; // This structure is good for prepending. We will want an ascending sorted Container at the end, this will do fine. + + // This pointer will refer to the next confirmed shown item from the point of + // view of the current "itemData" in the upcoming "for" loop. + ItemData *itemShownBelow = nullptr; + + // We will iterate backwards because it's convenient to know beforehand if the item just below is its child or not. + for (int index = m_itemData.count() - 1; index >= 0; --index) { + ItemData *itemData = m_itemData.at(index); + + if (m_filter.matches(itemData->item) + || (itemShownBelow && itemShownBelow->parent == itemData && itemData->values.value("isExpanded").toBool())) { + // We could've entered here for two reasons: + // 1. This item passes the filter itself + // 2. This is an expanded folder that doesn't pass the filter but sees a filter-passing child just below + + // So this item must remain shown. + // Lets register this item as the next shown item from the point of view of the next iteration of this for loop + itemShownBelow = itemData; + } else { + // We hide this item for now, however, for expanded folders this is not final: + // if after the next "for" loop we discover that its children must now be shown with the newly applied fliter, we shall re-insert it + newFilteredIndexes.prepend(index); + m_filteredItems.insert(itemData->item, itemData); + // indexShownBelow doesn't get updated since this item will be hidden } } - const KItemRangeList removedRanges = KItemRangeList::fromSortedContainer(newFilteredIndexes); - removeItems(removedRanges, KeepItemData); + // This will remove the newly filtered items from m_itemData + removeItems(KItemRangeList::fromSortedContainer(newFilteredIndexes), KeepItemData); + // ===STEP 2=== // Check which hidden items from m_filteredItems should - // get visible again and hence removed from m_filteredItems. - QList newVisibleItems; + // become visible again and hence moved from m_filteredItems back into m_itemData. - QHash::iterator it = m_filteredItems.begin(); + QList newVisibleItems; + + QHash ancestorsOfNewVisibleItems; // We will make sure these also become visible in step 3. + + QHash::iterator it = m_filteredItems.begin(); while (it != m_filteredItems.end()) { if (m_filter.matches(it.key())) { newVisibleItems.append(it.value()); + + // If this is a child of an expanded folder, we must make sure that its whole parental chain will also be shown. + // We will go up through its parental chain until we either: + // 1 - reach the "root item" of the current view, i.e the currently opened folder on Dolphin. Their children have their ItemData::parent set to nullptr. + // or + // 2 - we reach an unfiltered parent or a previously discovered ancestor. + for (ItemData *parent = it.value()->parent; parent && !ancestorsOfNewVisibleItems.contains(parent->item) && m_filteredItems.contains(parent->item); + parent = parent->parent) { + // We wish we could remove this parent from m_filteredItems right now, but we are iterating over it + // and it would mess up the iteration. We will mark it to be removed in step 3. + ancestorsOfNewVisibleItems.insert(parent->item, parent); + } + it = m_filteredItems.erase(it); } else { + // Item remains filtered for now + // However, for expanded folders this is not final, we may discover later that it has unfiltered descendants. ++it; } } + // ===STEP 3=== + // Handles the ancestorsOfNewVisibleItems. + // Now that we are done iterating through m_filteredItems we can safely move the ancestorsOfNewVisibleItems from m_filteredItems to newVisibleItems. + for (it = ancestorsOfNewVisibleItems.begin(); it != ancestorsOfNewVisibleItems.end(); it++) { + if (m_filteredItems.remove(it.key())) { + // m_filteredItems still contained this ancestor until now so we can be sure that we aren't adding a duplicate ancestor to newVisibleItems. + newVisibleItems.append(it.value()); + } + } + + // This will insert the newly discovered unfiltered items into m_itemData insertItems(newVisibleItems); } @@ -1103,32 +1140,46 @@ void KFileItemModel::slotRefreshItems(const QList >& QSet changedRoles; KFileItemList changedFiles; + // Contains the indexes of the currently visible items + // that should get hidden and hence moved to m_filteredItems. + QVector newFilteredIndexes; + + // Contains currently hidden items that should + // get visible and hence removed from m_filteredItems + QList newVisibleItems; + QListIterator > it(items); while (it.hasNext()) { const QPair& itemPair = it.next(); const KFileItem& oldItem = itemPair.first; const KFileItem& newItem = itemPair.second; const int indexForItem = index(oldItem); + const bool newItemMatchesFilter = m_filter.matches(newItem); if (indexForItem >= 0) { m_itemData[indexForItem]->item = newItem; // Keep old values as long as possible if they could not retrieved synchronously yet. // The update of the values will be done asynchronously by KFileItemModelRolesUpdater. - QHashIterator it(retrieveData(newItem, m_itemData.at(indexForItem)->parent)); - QHash& values = m_itemData[indexForItem]->values; + ItemData * const itemData = m_itemData.at(indexForItem); + QHashIterator it(retrieveData(newItem, itemData->parent)); while (it.hasNext()) { it.next(); const QByteArray& role = it.key(); - if (values.value(role) != it.value()) { - values.insert(role, it.value()); + if (itemData->values.value(role) != it.value()) { + itemData->values.insert(role, it.value()); changedRoles.insert(role); } } m_items.remove(oldItem.url()); - m_items.insert(newItem.url(), indexForItem); - changedFiles.append(newItem); - indexes.append(indexForItem); + if (newItemMatchesFilter) { + m_items.insert(newItem.url(), indexForItem); + changedFiles.append(newItem); + indexes.append(indexForItem); + } else { + newFilteredIndexes.append(indexForItem); + m_filteredItems.insert(newItem, itemData); + } } else { // Check if 'oldItem' is one of the filtered items. QHash::iterator it = m_filteredItems.find(oldItem); @@ -1141,11 +1192,22 @@ void KFileItemModel::slotRefreshItems(const QList >& itemData->values.clear(); m_filteredItems.erase(it); - m_filteredItems.insert(newItem, itemData); + if (newItemMatchesFilter) { + newVisibleItems.append(itemData); + } else { + m_filteredItems.insert(newItem, itemData); + } } } } + // Hide items, previously visible that should get hidden + const KItemRangeList removedRanges = KItemRangeList::fromSortedContainer(newFilteredIndexes); + removeItems(removedRanges, KeepItemData); + + // Show previously hidden items that should get visible + insertItems(newVisibleItems); + // If the changed items have been created recently, they might not be in m_items yet. // In that case, the list 'indexes' might be empty. if (indexes.isEmpty()) { @@ -1796,6 +1858,11 @@ void KFileItemModel::sort(const QList::iterator &begi int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const QCollator& collator) const { + // This function must never return 0, because that would break stable + // sorting, which leads to all kinds of bugs. + // See: https://bugs.kde.org/show_bug.cgi?id=433247 + // If two items have equal sort values, let the fallbacks at the bottom of + // the function handle it. const KFileItem& itemA = a->item; const KFileItem& itemB = b->item; @@ -1813,29 +1880,21 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const auto valueA = a->values.value("count"); auto valueB = b->values.value("count"); if (valueA.isNull()) { - if (valueB.isNull()) { - result = 0; - break; - } else { - result = -1; - break; + if (!valueB.isNull()) { + return -1; } } else if (valueB.isNull()) { - result = +1; - break; + return +1; } else { if (valueA.toLongLong() < valueB.toLongLong()) { - result = -1; - break; + return -1; } else if (valueA.toLongLong() > valueB.toLongLong()) { - result = +1; - break; - } else { - result = 0; - break; + return +1; } } + break; } + KIO::filesize_t sizeA = 0; if (itemA.isDir()) { sizeA = a->values.value("size").toULongLong(); @@ -1848,12 +1907,10 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const } else { sizeB = itemB.size(); } - if (sizeA > sizeB) { - result = +1; - } else if (sizeA < sizeB) { - result = -1; - } else { - result = 0; + if (sizeA < sizeB) { + return -1; + } else if (sizeA > sizeB) { + return +1; } break; } @@ -1862,9 +1919,9 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const const long long dateTimeA = itemA.entry().numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1); const long long dateTimeB = itemB.entry().numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1); if (dateTimeA < dateTimeB) { - result = -1; + return -1; } else if (dateTimeA > dateTimeB) { - result = +1; + return +1; } break; } @@ -1873,9 +1930,9 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const const long long dateTimeA = itemA.entry().numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1); const long long dateTimeB = itemB.entry().numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1); if (dateTimeA < dateTimeB) { - result = -1; + return -1; } else if (dateTimeA > dateTimeB) { - result = +1; + return +1; } break; } @@ -1884,9 +1941,9 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const const QDateTime dateTimeA = a->values.value("deletiontime").toDateTime(); const QDateTime dateTimeB = b->values.value("deletiontime").toDateTime(); if (dateTimeA < dateTimeB) { - result = -1; + return -1; } else if (dateTimeA > dateTimeB) { - result = +1; + return +1; } break; } @@ -1907,9 +1964,9 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const const QString roleValueA = a->values.value(role).toString(); const QString roleValueB = b->values.value(role).toString(); if (!roleValueA.isEmpty() && roleValueB.isEmpty()) { - result = -1; + return -1; } else if (roleValueA.isEmpty() && !roleValueB.isEmpty()) { - result = +1; + return +1; } else if (isRoleValueNatural(m_sortRole)) { result = stringCompare(roleValueA, roleValueB, collator); } else { @@ -2517,3 +2574,15 @@ bool KFileItemModel::isConsistent() const return true; } + +void KFileItemModel::slotListerError(KIO::Job *job) +{ + if (job->error() == 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.")); + } +}