X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/926782d3be2502c6d3e87ef992aa371c081d72b4..8210d5e472a2bff9f1a1f16b0740df25822b5362:/src/kitemviews/kfileitemmodel.cpp diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index bd905bf07..739384bf9 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -152,7 +152,12 @@ int KFileItemModel::count() const QHash KFileItemModel::data(int index) const { if (index >= 0 && index < count()) { - return m_itemData.at(index)->values; + ItemData* data = m_itemData.at(index); + if (data->values.isEmpty()) { + data->values = retrieveData(data->item, data->parent); + } + + return data->values; } return QHash(); } @@ -163,7 +168,7 @@ bool KFileItemModel::setData(int index, const QHash& value return false; } - QHash currentValues = m_itemData.at(index)->values; + QHash currentValues = data(index); // Determine which roles have been changed QSet changedRoles; @@ -232,7 +237,7 @@ bool KFileItemModel::showDirectoriesOnly() const return m_dirLister->dirOnlyMode(); } -QMimeData* KFileItemModel::createMimeData(const QSet& indexes) const +QMimeData* KFileItemModel::createMimeData(const KItemSet& indexes) const { QMimeData* data = new QMimeData(); @@ -243,9 +248,7 @@ QMimeData* KFileItemModel::createMimeData(const QSet& indexes) const KUrl::List mostLocalUrls; bool canUseMostLocalUrls = true; - QSetIterator it(indexes); - while (it.hasNext()) { - const int index = it.next(); + foreach (int index, indexes) { const KFileItem item = fileItem(index); if (!item.isNull()) { urls << item.targetUrl(); @@ -274,12 +277,12 @@ int KFileItemModel::indexForKeyboardSearch(const QString& text, int startFromInd { startFromIndex = qMax(0, startFromIndex); for (int i = startFromIndex; i < count(); ++i) { - if (data(i)["text"].toString().startsWith(text, Qt::CaseInsensitive)) { + if (fileItem(i).text().startsWith(text, Qt::CaseInsensitive)) { return i; } } for (int i = 0; i < startFromIndex; ++i) { - if (data(i)["text"].toString().startsWith(text, Qt::CaseInsensitive)) { + if (fileItem(i).text().startsWith(text, Qt::CaseInsensitive)) { return i; } } @@ -410,6 +413,15 @@ void KFileItemModel::setRoles(const QSet& roles) kWarning() << "TODO: Emitting itemsChanged() with no information what has changed!"; emit itemsChanged(KItemRangeList() << KItemRange(0, count()), QSet()); } + + // Clear the 'values' of all filtered items. They will be re-populated with the + // correct roles the next time 'values' will be accessed via data(int). + QHash::iterator filteredIt = m_filteredItems.begin(); + const QHash::iterator filteredEnd = m_filteredItems.end(); + while (filteredIt != filteredEnd) { + (*filteredIt)->values.clear(); + ++filteredIt; + } } QSet KFileItemModel::roles() const @@ -435,15 +447,37 @@ bool KFileItemModel::setExpanded(int index, bool expanded) if (expanded) { m_expandedDirs.insert(targetUrl, url); m_dirLister->openUrl(url, KDirLister::Keep); + + const KUrl::List previouslyExpandedChildren = m_itemData.at(index)->values.value("previouslyExpandedChildren").value(); + foreach (const KUrl& url, previouslyExpandedChildren) { + m_urlsToExpand.insert(url); + } } else { m_expandedDirs.remove(targetUrl); m_dirLister->stop(url); - removeFilteredChildren(KFileItemList() << item); + const int parentLevel = expandedParentsCount(index); + const int itemCount = m_itemData.count(); + const int firstChildIndex = index + 1; + + KUrl::List expandedChildren; + + int childIndex = firstChildIndex; + while (childIndex < itemCount && expandedParentsCount(childIndex) > parentLevel) { + ItemData* itemData = m_itemData.at(childIndex); + if (itemData->values.value("isExpanded").toBool()) { + const KUrl targetUrl = itemData->item.targetUrl(); + m_expandedDirs.remove(targetUrl); + expandedChildren.append(targetUrl); + } + ++childIndex; + } + const int childrenCount = childIndex - firstChildIndex; + + removeFilteredChildren(KItemRangeList() << KItemRange(index, 1 + childrenCount)); + removeItems(KItemRangeList() << KItemRange(firstChildIndex, childrenCount), DeleteItemData); - const KFileItemList itemsToRemove = childItems(item); - removeFilteredChildren(itemsToRemove); - removeItems(itemsToRemove, DeleteItemData); + m_itemData.at(index)->values.insert("previouslyExpandedChildren", expandedChildren); } return true; @@ -460,7 +494,9 @@ bool KFileItemModel::isExpanded(int index) const bool KFileItemModel::isExpandable(int index) const { if (index >= 0 && index < count()) { - return m_itemData.at(index)->values.value("isExpandable").toBool(); + // Call data (instead of accessing m_itemData directly) + // to ensure that the value is initialized. + return data(index).value("isExpandable").toBool(); } return false; } @@ -544,21 +580,25 @@ void KFileItemModel::applyFilters() { // Check which shown items from m_itemData must get // hidden and hence moved to m_filteredItems. - KFileItemList newFilteredItems; + QVector newFilteredIndexes; + + const int itemCount = m_itemData.count(); + for (int index = 0; index < itemCount; ++index) { + ItemData* itemData = m_itemData.at(index); - foreach (ItemData* itemData, m_itemData) { // 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)) { - newFilteredItems.append(item); + newFilteredIndexes.append(index); m_filteredItems.insert(item, itemData); } } } - removeItems(newFilteredItems, KeepItemData); + const KItemRangeList removedRanges = KItemRangeList::fromSortedContainer(newFilteredIndexes); + removeItems(removedRanges, KeepItemData); // Check which hidden items from m_filteredItems should // get visible again and hence removed from m_filteredItems. @@ -577,22 +617,24 @@ void KFileItemModel::applyFilters() insertItems(newVisibleItems); } -void KFileItemModel::removeFilteredChildren(const KFileItemList& parentsList) +void KFileItemModel::removeFilteredChildren(const KItemRangeList& itemRanges) { - if (m_filteredItems.isEmpty()) { + if (m_filteredItems.isEmpty() || !m_requestRole[ExpandedParentsCountRole]) { + // There are either no filtered items, or it is not possible to expand + // folders -> there cannot be any filtered children. return; } - // First, we put the parent items into a set to provide fast lookup - // while iterating over m_filteredItems and prevent quadratic - // complexity if there are N parents and N filtered items. - const QSet parents = parentsList.toSet(); + QSet parents; + foreach (const KItemRange& range, itemRanges) { + for (int index = range.index; index < range.index + range.count; ++index) { + parents.insert(m_itemData.at(index)); + } + } QHash::iterator it = m_filteredItems.begin(); while (it != m_filteredItems.end()) { - const ItemData* parent = it.value()->parent; - - if (parent && parents.contains(parent->item)) { + if (parents.contains(it.value()->parent)) { delete it.value(); it = m_filteredItems.erase(it); } else { @@ -842,29 +884,49 @@ void KFileItemModel::slotItemsDeleted(const KFileItemList& items) { dispatchPendingItemsToInsert(); - KFileItemList itemsToRemove = items; - if (m_requestRole[ExpandedParentsCountRole]) { - // Assure that removing a parent item also results in removing all children - foreach (const KFileItem& item, items) { - itemsToRemove.append(childItems(item)); - } - } + QVector indexesToRemove; + indexesToRemove.reserve(items.count()); - if (!m_filteredItems.isEmpty()) { - foreach (const KFileItem& item, itemsToRemove) { + foreach (const KFileItem& item, items) { + const KUrl url = item.url(); + const int index = m_items.value(url, -1); + if (index >= 0) { + indexesToRemove.append(index); + } else { + // Probably the item has been filtered. QHash::iterator it = m_filteredItems.find(item); if (it != m_filteredItems.end()) { delete it.value(); m_filteredItems.erase(it); } } + } - if (m_requestRole[ExpandedParentsCountRole]) { - removeFilteredChildren(itemsToRemove); + std::sort(indexesToRemove.begin(), indexesToRemove.end()); + + if (m_requestRole[ExpandedParentsCountRole] && !m_expandedDirs.isEmpty()) { + // Assure that removing a parent item also results in removing all children + QVector indexesToRemoveWithChildren; + indexesToRemoveWithChildren.reserve(m_items.count()); + + const int itemCount = m_itemData.count(); + foreach (int index, indexesToRemove) { + indexesToRemoveWithChildren.append(index); + + const int parentLevel = expandedParentsCount(index); + int childIndex = index + 1; + while (childIndex < itemCount && expandedParentsCount(childIndex) > parentLevel) { + indexesToRemoveWithChildren.append(childIndex); + ++childIndex; + } } + + indexesToRemove = indexesToRemoveWithChildren; } - removeItems(itemsToRemove, DeleteItemData); + const KItemRangeList itemRanges = KItemRangeList::fromSortedContainer(indexesToRemove); + removeFilteredChildren(itemRanges); + removeItems(itemRanges, DeleteItemData); } void KFileItemModel::slotRefreshItems(const QList >& items) @@ -980,6 +1042,15 @@ void KFileItemModel::insertItems(QList& newItems) #endif m_groups.clear(); + prepareItemsForSorting(newItems); + + if (m_sortRole == NameRole && m_naturalSorting) { + // Natural sorting of items can be very slow. However, it becomes much + // faster if the input sequence is already mostly sorted. Therefore, we + // first sort 'newItems' according to the QStrings returned by + // KFileItem::text() using QString::operator<(), which is quite fast. + parallelMergeSort(newItems.begin(), newItems.end(), nameLessThan, QThread::idealThreadCount()); + } sort(newItems.begin(), newItems.end()); @@ -1054,23 +1125,21 @@ void KFileItemModel::insertItems(QList& newItems) #endif } -void KFileItemModel::removeItems(const KFileItemList& items, RemoveItemsBehavior behavior) +void KFileItemModel::removeItems(const KItemRangeList& itemRanges, RemoveItemsBehavior behavior) { -#ifdef KFILEITEMMODEL_DEBUG - kDebug() << "Removing " << items.count() << "items"; -#endif + if (itemRanges.isEmpty()) { + return; + } m_groups.clear(); - // Step 1: Determine the indexes of the removed items, remove them from - // the hash m_items, and free the ItemData. - QList indexesToRemove; - indexesToRemove.reserve(items.count()); - foreach (const KFileItem& item, items) { - const KUrl url = item.url(); - const int index = m_items.value(url, -1); - if (index >= 0) { - indexesToRemove.append(index); + // Step 1: Remove the items from the hash m_items, and free the ItemData. + int removedItemsCount = 0; + foreach (const KItemRange& range, itemRanges) { + removedItemsCount += range.count; + + for (int index = range.index; index < range.index + range.count; ++index) { + const KUrl url = m_itemData.at(index)->item.url(); // Prevent repeated expensive rehashing by using QHash::erase(), // rather than QHash::remove(). @@ -1085,14 +1154,7 @@ void KFileItemModel::removeItems(const KFileItemList& items, RemoveItemsBehavior } } - if (indexesToRemove.isEmpty()) { - return; - } - - std::sort(indexesToRemove.begin(), indexesToRemove.end()); - // Step 2: Remove the ItemData pointers from the list m_itemData. - const KItemRangeList itemRanges = KItemRangeList::fromSortedContainer(indexesToRemove); int target = itemRanges.at(0).index; int source = itemRanges.at(0).index + itemRanges.at(0).count; int nextRange = 1; @@ -1110,7 +1172,7 @@ void KFileItemModel::removeItems(const KFileItemList& items, RemoveItemsBehavior } } - m_itemData.erase(m_itemData.end() - indexesToRemove.count(), m_itemData.end()); + m_itemData.erase(m_itemData.end() - removedItemsCount, m_itemData.end()); // Step 3: Adjust indexes in the hash m_items, starting from the // index of the first removed item. @@ -1140,7 +1202,6 @@ QList KFileItemModel::createItemDataList(const KUrl& foreach (const KFileItem& item, items) { ItemData* itemData = new ItemData(); itemData->item = item; - itemData->values = retrieveData(item, parentItem); itemData->parent = parentItem; itemDataList.append(itemData); } @@ -1148,6 +1209,45 @@ QList KFileItemModel::createItemDataList(const KUrl& return itemDataList; } +void KFileItemModel::prepareItemsForSorting(QList& itemDataList) +{ + switch (m_sortRole) { + case PermissionsRole: + case OwnerRole: + case GroupRole: + case DestinationRole: + case PathRole: + // These roles can be determined with retrieveData, and they have to be stored + // in the QHash "values" for the sorting. + foreach (ItemData* itemData, itemDataList) { + if (itemData->values.isEmpty()) { + itemData->values = retrieveData(itemData->item, itemData->parent); + } + } + break; + + case TypeRole: + // At least store the data including the file type for items with known MIME type. + foreach (ItemData* itemData, itemDataList) { + if (itemData->values.isEmpty()) { + const KFileItem item = itemData->item; + if (item.isDir() || item.isMimeTypeKnown()) { + itemData->values = retrieveData(itemData->item, itemData->parent); + } + } + } + break; + + default: + // The other roles are either resolved by KFileItemModelRolesUpdater + // (this includes the SizeRole for directories), or they do not need + // to be stored in the QHash "values" for sorting because the data can + // be retrieved directly from the KFileItem (NameRole, SizeRole for files, + // DateRole). + break; + } +} + int KFileItemModel::expandedParentsCount(const ItemData* data) { // The hash 'values' is only guaranteed to contain the key "expandedParentsCount" @@ -1167,17 +1267,17 @@ int KFileItemModel::expandedParentsCount(const ItemData* data) void KFileItemModel::removeExpandedItems() { - KFileItemList expandedItems; + QVector indexesToRemove; const int maxIndex = m_itemData.count() - 1; for (int i = 0; i <= maxIndex; ++i) { const ItemData* itemData = m_itemData.at(i); if (itemData->parent) { - expandedItems.append(itemData->item); + indexesToRemove.append(i); } } - removeItems(expandedItems, DeleteItemData); + removeItems(KItemRangeList::fromSortedContainer(indexesToRemove), DeleteItemData); m_expandedDirs.clear(); // Also remove all filtered items which have a parent. @@ -1642,7 +1742,7 @@ QList > KFileItemModel::nameRoleGroups() const continue; } - const QString name = m_itemData.at(i)->values.value("text").toString(); + 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(); @@ -1925,23 +2025,6 @@ QList > KFileItemModel::genericStringRoleGroups(const QByte return groups; } -KFileItemList KFileItemModel::childItems(const KFileItem& item) const -{ - KFileItemList items; - - int index = m_items.value(item.url(), -1); - if (index >= 0) { - const int parentLevel = expandedParentsCount(index); - ++index; - while (index < m_itemData.count() && expandedParentsCount(index) > parentLevel) { - items.append(m_itemData.at(index)->item); - ++index; - } - } - - return items; -} - void KFileItemModel::emitSortProgress(int resolvedCount) { // Be tolerant against a resolvedCount with a wrong range.