From: Aurélien Gâteau Date: Thu, 21 Mar 2013 13:12:28 +0000 (+0100) Subject: Merge remote-tracking branch 'origin/KDE/4.10' X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/commitdiff_plain/6dfcd7afd9336ee73d78f703e208088aa3f5ca60?hp=-c Merge remote-tracking branch 'origin/KDE/4.10' @emmanuel: this merge includes your fix from https://git.reviewboard.kde.org/r/109488/ . I assume this is not a problem. CCMAIL: emmanuelpescosta099@gmail.com --- 6dfcd7afd9336ee73d78f703e208088aa3f5ca60 diff --combined src/kitemviews/kfileitemmodel.cpp index 5d1a1968a,26521a6b6..7b039ec4a --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@@ -1,23 -1,21 +1,23 @@@ -/*************************************************************************** - * Copyright (C) 2011 by Peter Penz * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * - ***************************************************************************/ +/***************************************************************************** + * Copyright (C) 2011 by Peter Penz * + * Copyright (C) 2013 by Frank Reininghaus * + * Copyright (C) 2013 by Emmanuel Pescosta * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + *****************************************************************************/ #include "kfileitemmodel.h" @@@ -55,10 -53,12 +55,10 @@@ KFileItemModel::KFileItemModel(QObject m_resortAllItemsTimer(0), m_pendingItemsToInsert(), m_groups(), - m_expandedParentsCountRoot(UninitializedExpandedParentsCountRoot), m_expandedDirs(), m_urlsToExpand() { m_dirLister = new KFileItemModelDirLister(this); - m_dirLister->setAutoUpdate(true); m_dirLister->setDelayedMimeTypes(true); const QWidget* parentWidget = qobject_cast(parent); @@@ -69,7 -69,7 +69,7 @@@ connect(m_dirLister, SIGNAL(started(KUrl)), this, SIGNAL(directoryLoadingStarted())); connect(m_dirLister, SIGNAL(canceled()), this, SLOT(slotCanceled())); connect(m_dirLister, SIGNAL(completed(KUrl)), this, SLOT(slotCompleted())); - connect(m_dirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList))); + connect(m_dirLister, SIGNAL(itemsAdded(KUrl,KFileItemList)), this, SLOT(slotItemsAdded(KUrl,KFileItemList))); connect(m_dirLister, SIGNAL(itemsDeleted(KFileItemList)), this, SLOT(slotItemsDeleted(KFileItemList))); connect(m_dirLister, SIGNAL(refreshItems(QList >)), this, SLOT(slotRefreshItems(QList >))); connect(m_dirLister, SIGNAL(clear()), this, SLOT(slotClear())); @@@ -110,7 -110,7 +110,7 @@@ KFileItemModel::~KFileItemModel() { qDeleteAll(m_itemData); - m_itemData.clear(); + qDeleteAll(m_filteredItems.values()); } void KFileItemModel::loadDirectory(const KUrl& url) @@@ -120,6 -120,11 +120,11 @@@ void KFileItemModel::refreshDirectory(const KUrl& url) { + // Refresh all expanded directories first (Bug 295300) + foreach (const KUrl& expandedUrl, m_expandedDirs) { + m_dirLister->openUrl(expandedUrl, KDirLister::Reload); + } + m_dirLister->openUrl(url, KDirLister::Reload); } @@@ -397,7 -402,7 +402,7 @@@ void KFileItemModel::setRoles(const QSe // Update m_data with the changed requested roles const int maxIndex = count() - 1; for (int i = 0; i <= maxIndex; ++i) { - m_itemData[i]->values = retrieveData(m_itemData.at(i)->item); + m_itemData[i]->values = retrieveData(m_itemData.at(i)->item, m_itemData.at(i)->parent); } kWarning() << "TODO: Emitting itemsChanged() with no information what has changed!"; @@@ -422,8 -427,7 +427,8 @@@ bool KFileItemModel::setExpanded(int in return false; } - const KUrl url = m_itemData.at(index)->item.url(); + const KFileItem item = m_itemData.at(index)->item; + const KUrl url = item.url(); if (expanded) { m_expandedDirs.insert(url); m_dirLister->openUrl(url, KDirLister::Keep); @@@ -431,11 -435,38 +436,11 @@@ m_expandedDirs.remove(url); m_dirLister->stop(url); + removeFilteredChildren(KFileItemList() << item); - KFileItemList itemsToRemove; - const int expandedParentsCount = data(index)["expandedParentsCount"].toInt(); - ++index; - while (index < count() && data(index)["expandedParentsCount"].toInt() > expandedParentsCount) { - itemsToRemove.append(m_itemData.at(index)->item); - ++index; - } - - QSet urlsToRemove; - urlsToRemove.reserve(itemsToRemove.count() + 1); - urlsToRemove.insert(url); - foreach (const KFileItem& item, itemsToRemove) { - KUrl url = item.url(); - url.adjustPath(KUrl::RemoveTrailingSlash); - urlsToRemove.insert(url); - } - - QSet::iterator it = m_filteredItems.begin(); - while (it != m_filteredItems.end()) { - const KUrl url = it->url(); - KUrl parentUrl = url.upUrl(); - parentUrl.adjustPath(KUrl::RemoveTrailingSlash); - - if (urlsToRemove.contains(parentUrl)) { - it = m_filteredItems.erase(it); - } else { - ++it; - } - } - - removeItems(itemsToRemove); + const KFileItemList itemsToRemove = childItems(item); + removeFilteredChildren(itemsToRemove); + removeItems(itemsToRemove, DeleteItemData); } return true; @@@ -545,57 -576,31 +550,57 @@@ void KFileItemModel::applyFilters( // Only filter non-expanded items as child items may never // exist without a parent item if (!itemData->values.value("isExpanded").toBool()) { - if (!m_filter.matches(itemData->item)) { - newFilteredItems.append(itemData->item); - m_filteredItems.insert(itemData->item); + const KFileItem item = itemData->item; + if (!m_filter.matches(item)) { + newFilteredItems.append(item); + m_filteredItems.insert(item, itemData); } } } - removeItems(newFilteredItems); + removeItems(newFilteredItems, KeepItemData); // Check which hidden items from m_filteredItems should // get visible again and hence removed from m_filteredItems. - KFileItemList newVisibleItems; + QList newVisibleItems; - QMutableSetIterator it(m_filteredItems); - while (it.hasNext()) { - const KFileItem item = it.next(); - if (m_filter.matches(item)) { - newVisibleItems.append(item); - it.remove(); + QHash::iterator it = m_filteredItems.begin(); + while (it != m_filteredItems.end()) { + if (m_filter.matches(it.key())) { + newVisibleItems.append(it.value()); + it = m_filteredItems.erase(it); + } else { + ++it; } } insertItems(newVisibleItems); } +void KFileItemModel::removeFilteredChildren(const KFileItemList& parentsList) +{ + if (m_filteredItems.isEmpty()) { + 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(); + + QHash::iterator it = m_filteredItems.begin(); + while (it != m_filteredItems.end()) { + const ItemData* parent = it.value()->parent; + + if (parent && parents.contains(parent->item)) { + delete it.value(); + it = m_filteredItems.erase(it); + } else { + ++it; + } + } +} + QList KFileItemModel::rolesInformation() { static QList rolesInfo; @@@ -681,7 -686,7 +686,7 @@@ void KFileItemModel::resortAllItems( m_items.clear(); // Resort the items - KFileItemModelSortAlgorithm::sort(this, m_itemData.begin(), m_itemData.end()); + sort(m_itemData.begin(), m_itemData.end()); for (int i = 0; i < itemCount; ++i) { m_items.insert(m_itemData.at(i)->item.url(), i); } @@@ -743,14 -748,11 +748,14 @@@ void KFileItemModel::slotCanceled( emit directoryLoadingCanceled(); } -void KFileItemModel::slotNewItems(const KFileItemList& items) +void KFileItemModel::slotItemsAdded(const KUrl& directoryUrl, const KFileItemList& items) { Q_ASSERT(!items.isEmpty()); - if (m_requestRole[ExpandedParentsCountRole] && m_expandedParentsCountRoot >= 0) { + KUrl parentUrl = directoryUrl; + parentUrl.adjustPath(KUrl::RemoveTrailingSlash); + + if (m_requestRole[ExpandedParentsCountRole]) { // To be able to compare whether the new items may be inserted as children // of a parent item the pending items must be added to the model first. dispatchPendingItemsToInsert(); @@@ -771,6 -773,8 +776,6 @@@ // KDirLister keeps the children of items that got expanded once even if // they got collapsed again with KFileItemModel::setExpanded(false). So it must be // checked whether the parent for new items is still expanded. - KUrl parentUrl = item.url().upUrl(); - parentUrl.adjustPath(KUrl::RemoveTrailingSlash); const int parentIndex = m_items.value(parentUrl, -1); if (parentIndex >= 0 && !m_itemData[parentIndex]->values.value("isExpanded").toBool()) { // The parent is not expanded. @@@ -778,21 -782,22 +783,21 @@@ } } + QList itemDataList = createItemDataList(parentUrl, items); + if (!m_filter.hasSetFilters()) { - m_pendingItemsToInsert.append(items); + m_pendingItemsToInsert.append(itemDataList); } else { // The name or type filter is active. Hide filtered items // before inserting them into the model and remember // the filtered items in m_filteredItems. - KFileItemList filteredItems; - foreach (const KFileItem& item, items) { - if (m_filter.matches(item)) { - filteredItems.append(item); + foreach (ItemData* itemData, itemDataList) { + if (m_filter.matches(itemData->item)) { + m_pendingItemsToInsert.append(itemData); } else { - m_filteredItems.insert(item); + m_filteredItems.insert(itemData->item, itemData); } } - - m_pendingItemsToInsert.append(filteredItems); } if (useMaximumUpdateInterval() && !m_maximumUpdateIntervalTimer->isActive()) { @@@ -807,7 -812,7 +812,7 @@@ void KFileItemModel::slotItemsDeleted(c dispatchPendingItemsToInsert(); KFileItemList itemsToRemove = items; - if (m_requestRole[ExpandedParentsCountRole] && m_expandedParentsCountRoot >= 0) { + 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)); @@@ -816,19 -821,38 +821,19 @@@ if (!m_filteredItems.isEmpty()) { foreach (const KFileItem& item, itemsToRemove) { - m_filteredItems.remove(item); - } - - if (m_requestRole[ExpandedParentsCountRole] && m_expandedParentsCountRoot >= 0) { - // Remove all filtered children of deleted items. First, we put the - // deleted URLs into a set to provide fast lookup while iterating - // over m_filteredItems and prevent quadratic complexity if there - // are N removed items and N filtered items. - QSet urlsToRemove; - urlsToRemove.reserve(itemsToRemove.count()); - foreach (const KFileItem& item, itemsToRemove) { - KUrl url = item.url(); - url.adjustPath(KUrl::RemoveTrailingSlash); - urlsToRemove.insert(url); + QHash::iterator it = m_filteredItems.find(item); + if (it != m_filteredItems.end()) { + delete it.value(); + m_filteredItems.erase(it); } + } - QSet::iterator it = m_filteredItems.begin(); - while (it != m_filteredItems.end()) { - const KUrl url = it->url(); - KUrl parentUrl = url.upUrl(); - parentUrl.adjustPath(KUrl::RemoveTrailingSlash); - - if (urlsToRemove.contains(parentUrl)) { - it = m_filteredItems.erase(it); - } else { - ++it; - } - } + if (m_requestRole[ExpandedParentsCountRole]) { + removeFilteredChildren(itemsToRemove); } } - removeItems(itemsToRemove); + removeItems(itemsToRemove, DeleteItemData); } void KFileItemModel::slotRefreshItems(const QList >& items) @@@ -855,7 -879,7 +860,7 @@@ // 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)); + QHashIterator it(retrieveData(newItem, m_itemData.at(index)->parent)); while (it.hasNext()) { it.next(); m_itemData[index]->values.insert(it.key(), it.value()); @@@ -910,7 -934,6 +915,7 @@@ void KFileItemModel::slotClear( kDebug() << "Clearing all items"; #endif + qDeleteAll(m_filteredItems.values()); m_filteredItems.clear(); m_groups.clear(); @@@ -918,6 -941,8 +923,6 @@@ m_resortAllItemsTimer->stop(); m_pendingItemsToInsert.clear(); - m_expandedParentsCountRoot = UninitializedExpandedParentsCountRoot; - const int removedCount = m_itemData.count(); if (removedCount > 0) { qDeleteAll(m_itemData); @@@ -948,12 -973,19 +953,12 @@@ void KFileItemModel::dispatchPendingIte } } -void KFileItemModel::insertItems(const KFileItemList& items) +void KFileItemModel::insertItems(QList& items) { if (items.isEmpty()) { return; } - if (m_sortRole == 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). - determineMimeTypes(items, 200); - } - #ifdef KFILEITEMMODEL_DEBUG QElapsedTimer timer; timer.start(); @@@ -963,7 -995,8 +968,7 @@@ m_groups.clear(); - QList sortedItems = createItemDataList(items); - KFileItemModelSortAlgorithm::sort(this, sortedItems.begin(), sortedItems.end()); + sort(items.begin(), items.end()); #ifdef KFILEITEMMODEL_DEBUG kDebug() << "[TIME] Sorting:" << timer.elapsed(); @@@ -975,12 -1008,12 +980,12 @@@ int insertedAtIndex = -1; // Index for the current item-range int insertedCount = 0; // Count for the current item-range int previouslyInsertedCount = 0; // Sum of previously inserted items for all ranges - while (sourceIndex < sortedItems.count()) { + while (sourceIndex < items.count()) { // Find target index from m_items to insert the current item // in a sorted order const int previousTargetIndex = targetIndex; while (targetIndex < m_itemData.count()) { - if (!lessThan(m_itemData.at(targetIndex), sortedItems.at(sourceIndex))) { + if (!lessThan(m_itemData.at(targetIndex), items.at(sourceIndex))) { break; } ++targetIndex; @@@ -994,9 -1027,9 +999,9 @@@ } // Insert item at the position targetIndex by transferring - // the ownership of the item-data from sortedItems to m_itemData. + // the ownership of the item-data from 'items' to m_itemData. // m_items will be inserted after the loop (see comment below) - m_itemData.insert(targetIndex, sortedItems.at(sourceIndex)); + m_itemData.insert(targetIndex, items.at(sourceIndex)); ++insertedCount; if (insertedAtIndex < 0) { @@@ -1010,7 -1043,6 +1015,7 @@@ // The indexes of all m_items must be adjusted, not only the index // of the new items const int itemDataCount = m_itemData.count(); + m_items.reserve(itemDataCount); for (int i = 0; i < itemDataCount; ++i) { m_items.insert(m_itemData.at(i)->item.url(), i); } @@@ -1023,124 -1055,115 +1028,124 @@@ #endif } -void KFileItemModel::removeItems(const KFileItemList& items) +static KItemRangeList sortedIndexesToKItemRangeList(const QList& sortedNumbers) { - if (items.isEmpty()) { - return; + if (sortedNumbers.empty()) { + return KItemRangeList(); + } + + KItemRangeList result; + + QList::const_iterator it = sortedNumbers.begin(); + int index = *it; + int count = 1; + + ++it; + + QList::const_iterator end = sortedNumbers.end(); + while (it != end) { + if (*it == index + count) { + ++count; + } else { + result << KItemRange(index, count); + index = *it; + count = 1; + } + ++it; } + result << KItemRange(index, count); + return result; +} + +void KFileItemModel::removeItems(const KFileItemList& items, RemoveItemsBehavior behavior) +{ #ifdef KFILEITEMMODEL_DEBUG kDebug() << "Removing " << items.count() << "items"; #endif m_groups.clear(); - QList sortedItems; - sortedItems.reserve(items.count()); - foreach (const KFileItem& item, items) { - const int index = m_items.value(item.url(), -1); - if (index >= 0) { - sortedItems.append(m_itemData.at(index)); - } - } - KFileItemModelSortAlgorithm::sort(this, sortedItems.begin(), sortedItems.end()); - + // 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); - // Calculate the item ranges that will get deleted - KItemRangeList itemRanges; - int removedAtIndex = -1; - int removedCount = 0; - int targetIndex = 0; - foreach (const ItemData* itemData, sortedItems) { - const KFileItem& itemToRemove = itemData->item; + // Prevent repeated expensive rehashing by using QHash::erase(), + // rather than QHash::remove(). + QHash::iterator it = m_items.find(url); + m_items.erase(it); - const int previousTargetIndex = targetIndex; - while (targetIndex < m_itemData.count()) { - if (m_itemData.at(targetIndex)->item.url() == itemToRemove.url()) { - break; + if (behavior == DeleteItemData) { + delete m_itemData.at(index); } - ++targetIndex; - } - if (targetIndex >= m_itemData.count()) { - kWarning() << "Item that should be deleted has not been found!"; - return; - } - if (targetIndex - previousTargetIndex > 0 && removedAtIndex >= 0) { - itemRanges << KItemRange(removedAtIndex, removedCount); - removedAtIndex = targetIndex; - removedCount = 0; + m_itemData[index] = 0; } + } - indexesToRemove.append(targetIndex); - if (removedAtIndex < 0) { - removedAtIndex = targetIndex; - } - ++removedCount; - ++targetIndex; + if (indexesToRemove.isEmpty()) { + return; } - // Delete the items - for (int i = indexesToRemove.count() - 1; i >= 0; --i) { - const int indexToRemove = indexesToRemove.at(i); - ItemData* data = m_itemData.at(indexToRemove); + std::sort(indexesToRemove.begin(), indexesToRemove.end()); - m_items.remove(data->item.url()); + // Step 2: Remove the ItemData pointers from the list m_itemData. + const KItemRangeList itemRanges = sortedIndexesToKItemRangeList(indexesToRemove); + int target = itemRanges.at(0).index; + int source = itemRanges.at(0).index + itemRanges.at(0).count; + int nextRange = 1; - delete data; - m_itemData.removeAt(indexToRemove); - } + const int oldItemDataCount = m_itemData.count(); + while (source < oldItemDataCount) { + m_itemData[target] = m_itemData[source]; + ++target; + ++source; - // The indexes of all m_items must be adjusted, not only the index - // of the removed items - const int itemDataCount = m_itemData.count(); - for (int i = 0; i < itemDataCount; ++i) { - m_items.insert(m_itemData.at(i)->item.url(), i); + if (nextRange < itemRanges.count() && source == itemRanges.at(nextRange).index) { + // Skip the items in the next removed range. + source += itemRanges.at(nextRange).count; + ++nextRange; + } } - if (count() <= 0) { - m_expandedParentsCountRoot = UninitializedExpandedParentsCountRoot; + m_itemData.erase(m_itemData.end() - indexesToRemove.count(), m_itemData.end()); + + // Step 3: Adjust indexes in the hash m_items. Note that all indexes + // might have been changed by the removal of the items. + const int newItemDataCount = m_itemData.count(); + for (int i = 0; i < newItemDataCount; ++i) { + m_items.insert(m_itemData.at(i)->item.url(), i); } - itemRanges << KItemRange(removedAtIndex, removedCount); emit itemsRemoved(itemRanges); } -QList KFileItemModel::createItemDataList(const KFileItemList& items) const +QList KFileItemModel::createItemDataList(const KUrl& parentUrl, const KFileItemList& items) const { + if (m_sortRole == 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). + determineMimeTypes(items, 200); + } + + const int parentIndex = m_items.value(parentUrl, -1); + ItemData* parentItem = parentIndex < 0 ? 0 : m_itemData.at(parentIndex); + QList itemDataList; itemDataList.reserve(items.count()); foreach (const KFileItem& item, items) { ItemData* itemData = new ItemData(); itemData->item = item; - itemData->values = retrieveData(item); - itemData->parent = 0; - - const bool determineParent = m_requestRole[ExpandedParentsCountRole] - && itemData->values["expandedParentsCount"].toInt() > 0; - if (determineParent) { - KUrl parentUrl = item.url().upUrl(); - parentUrl.adjustPath(KUrl::RemoveTrailingSlash); - const int parentIndex = m_items.value(parentUrl, -1); - if (parentIndex >= 0) { - itemData->parent = m_itemData.at(parentIndex); - } else { - kWarning() << "Parent item not found for" << item.url(); - } - } - + itemData->values = retrieveData(item, parentItem); + itemData->parent = parentItem; itemDataList.append(itemData); } @@@ -1161,8 -1184,9 +1166,8 @@@ void KFileItemModel::removeExpandedItem // The m_expandedParentsCountRoot may not get reset before all items with // a bigger count have been removed. - removeItems(expandedItems); + removeItems(expandedItems, DeleteItemData); - m_expandedParentsCountRoot = UninitializedExpandedParentsCountRoot; m_expandedDirs.clear(); } @@@ -1225,7 -1249,7 +1230,7 @@@ QByteArray KFileItemModel::roleForType( return roles.value(roleType); } -QHash KFileItemModel::retrieveData(const KFileItem& item) const +QHash KFileItemModel::retrieveData(const KFileItem& item, const ItemData* parent) const { // It is important to insert only roles that are fast to retrieve. E.g. // KFileItem::iconName() can be very expensive if the MIME-type is unknown @@@ -1315,12 -1339,28 +1320,12 @@@ } if (m_requestRole[ExpandedParentsCountRole]) { - if (m_expandedParentsCountRoot == UninitializedExpandedParentsCountRoot) { - const KUrl rootUrl = m_dirLister->url(); - const QString protocol = rootUrl.protocol(); - const bool forceExpandedParentsCountRoot = (protocol == QLatin1String("trash") || - protocol == QLatin1String("nepomuk") || - protocol == QLatin1String("remote") || - protocol.contains(QLatin1String("search"))); - if (forceExpandedParentsCountRoot) { - m_expandedParentsCountRoot = ForceExpandedParentsCountRoot; - } else { - const QString rootDir = rootUrl.path(KUrl::AddTrailingSlash); - m_expandedParentsCountRoot = rootDir.count('/'); - } + int level = 0; + if (parent) { + level = parent->values["expandedParentsCount"].toInt() + 1; } - if (m_expandedParentsCountRoot == ForceExpandedParentsCountRoot) { - data.insert("expandedParentsCount", -1); - } else { - const QString dir = item.url().directory(KUrl::AppendTrailingSlash); - const int level = dir.count('/') - m_expandedParentsCountRoot; - data.insert("expandedParentsCount", level); - } + data.insert("expandedParentsCount", level); } if (item.isMimeTypeKnown()) { @@@ -1338,34 -1378,11 +1343,34 @@@ bool KFileItemModel::lessThan(const Ite { int result = 0; - if (m_expandedParentsCountRoot >= 0) { - result = expandedParentsCountCompare(a, b); - if (result != 0) { - // The items have parents with different expansion levels - return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0; + if (a->parent != b->parent) { + const int expansionLevelA = a->values.value("expandedParentsCount").toInt(); + const int expansionLevelB = b->values.value("expandedParentsCount").toInt(); + + // If b has a higher expansion level than a, check if a is a parent + // of b, and make sure that both expansion levels are equal otherwise. + for (int i = expansionLevelB; i > expansionLevelA; --i) { + if (b->parent == a) { + return true; + } + b = b->parent; + } + + // If a has a higher expansion level than a, check if b is a parent + // of a, and make sure that both expansion levels are equal otherwise. + for (int i = expansionLevelA; i > expansionLevelB; --i) { + if (a->parent == b) { + return false; + } + a = a->parent; + } + + Q_ASSERT(a->values.value("expandedParentsCount").toInt() == b->values.value("expandedParentsCount").toInt()); + + // Compare the last parents of a and b which are different. + while (a->parent != b->parent) { + a = a->parent; + b = b->parent; } } @@@ -1384,44 -1401,6 +1389,44 @@@ return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0; } +/** + * Helper class for KFileItemModel::sort(). + */ +class KFileItemModelLessThan +{ +public: + KFileItemModelLessThan(const KFileItemModel* model) : + m_model(model) + { + } + + bool operator()(const KFileItemModel::ItemData* a, const KFileItemModel::ItemData* b) const + { + return m_model->lessThan(a, b); + } + +private: + const KFileItemModel* m_model; +}; + +void KFileItemModel::sort(QList::iterator begin, + QList::iterator end) const +{ + KFileItemModelLessThan lessThan(this); + + if (m_sortRole == NameRole) { + // Sorting by name can be expensive, in particular if natural sorting is + // enabled. Use all CPU cores to speed up the sorting process. + static const int numberOfThreads = QThread::idealThreadCount(); + parallelMergeSort(begin, end, lessThan, numberOfThreads); + } else { + // Sorting by other roles is quite fast. Use only one thread to prevent + // problems caused by non-reentrant comparison functions, see + // https://bugs.kde.org/show_bug.cgi?id=312679 + mergeSort(begin, end, lessThan); + } +} + int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b) const { const KFileItem& itemA = a->item; @@@ -1546,6 -1525,88 +1551,6 @@@ int KFileItemModel::stringCompare(cons : QString::compare(a, b, Qt::CaseSensitive); } -int KFileItemModel::expandedParentsCountCompare(const ItemData* a, const ItemData* b) const -{ - const KUrl urlA = a->item.url(); - const KUrl urlB = b->item.url(); - if (urlA.directory() == urlB.directory()) { - // Both items have the same directory as parent - return 0; - } - - // Check whether one item is the parent of the other item - if (urlA.isParentOf(urlB)) { - return (sortOrder() == Qt::AscendingOrder) ? -1 : +1; - } else if (urlB.isParentOf(urlA)) { - return (sortOrder() == Qt::AscendingOrder) ? +1 : -1; - } - - // Determine the maximum common path of both items and - // remember the index in 'index' - const QString pathA = urlA.path(); - const QString pathB = urlB.path(); - - const int maxIndex = qMin(pathA.length(), pathB.length()) - 1; - int index = 0; - while (index <= maxIndex && pathA.at(index) == pathB.at(index)) { - ++index; - } - if (index > maxIndex) { - index = maxIndex; - } - while (index > 0 && (pathA.at(index) != QLatin1Char('/') || pathB.at(index) != QLatin1Char('/'))) { - --index; - } - - // Determine the first sub-path after the common path and - // check whether it represents a directory or already a file - bool isDirA = true; - const QString subPathA = subPath(a->item, pathA, index, &isDirA); - bool isDirB = true; - const QString subPathB = subPath(b->item, pathB, index, &isDirB); - - if (m_sortDirsFirst || m_sortRole == SizeRole) { - if (isDirA && !isDirB) { - return (sortOrder() == Qt::AscendingOrder) ? -1 : +1; - } else if (!isDirA && isDirB) { - return (sortOrder() == Qt::AscendingOrder) ? +1 : -1; - } - } - - // Compare the items of the parents that represent the first - // different path after the common path. - const QString parentPathA = pathA.left(index) + subPathA; - const QString parentPathB = pathB.left(index) + subPathB; - - const ItemData* parentA = a; - while (parentA && parentA->item.url().path() != parentPathA) { - parentA = parentA->parent; - } - - const ItemData* parentB = b; - while (parentB && parentB->item.url().path() != parentPathB) { - parentB = parentB->parent; - } - - if (parentA && parentB) { - return sortRoleCompare(parentA, parentB); - } - - kWarning() << "Child items without parent detected:" << a->item.url() << b->item.url(); - return QString::compare(urlA.url(), urlB.url(), Qt::CaseSensitive); -} - -QString KFileItemModel::subPath(const KFileItem& item, - const QString& itemPath, - int start, - bool* isDir) const -{ - Q_ASSERT(isDir); - const int pathIndex = itemPath.indexOf('/', start + 1); - *isDir = (pathIndex > 0) || item.isDir(); - return itemPath.mid(start, pathIndex - start); -} - bool KFileItemModel::useMaximumUpdateInterval() const { return !m_dirLister->url().isLocalFile(); @@@ -1658,6 -1719,12 +1663,6 @@@ QList > KFileItemM const QDate currentDate = KDateTime::currentLocalDateTime().date(); - int yearForCurrentWeek = 0; - int currentWeek = currentDate.weekNumber(&yearForCurrentWeek); - if (yearForCurrentWeek == currentDate.year() + 1) { - currentWeek = 53; - } - QDate previousModifiedDate; QString groupValue; for (int i = 0; i <= maxIndex; ++i) { @@@ -1675,9 -1742,20 +1680,9 @@@ const int daysDistance = modifiedDate.daysTo(currentDate); - int yearForModifiedWeek = 0; - int modifiedWeek = modifiedDate.weekNumber(&yearForModifiedWeek); - if (yearForModifiedWeek == modifiedDate.year() + 1) { - modifiedWeek = 53; - } - QString newGroupValue; if (currentDate.year() == modifiedDate.year() && currentDate.month() == modifiedDate.month()) { - if (modifiedWeek > currentWeek) { - // Usecase: modified date = 2010-01-01, current date = 2010-01-22 - // modified week = 53, current week = 3 - modifiedWeek = 0; - } - switch (currentWeek - modifiedWeek) { + switch (daysDistance / 7) { case 0: switch (daysDistance) { case 0: newGroupValue = i18nc("@title:group Date", "Today"); break; @@@ -1686,7 -1764,7 +1691,7 @@@ } break; case 1: - newGroupValue = i18nc("@title:group Date", "Last Week"); + newGroupValue = i18nc("@title:group Date", "One Week Ago"); break; case 2: newGroupValue = i18nc("@title:group Date", "Two Weeks Ago"); @@@ -1709,7 -1787,7 +1714,7 @@@ } else if (daysDistance <= 7) { newGroupValue = modifiedTime.toString(i18nc("@title:group The week day name: %A, %B is full month name in current locale, and %Y is full year number", "%A (%B, %Y)")); } else if (daysDistance <= 7 * 2) { - newGroupValue = modifiedTime.toString(i18nc("@title:group Date: %B is full month name in current locale, and %Y is full year number", "Last Week (%B, %Y)")); + newGroupValue = modifiedTime.toString(i18nc("@title:group Date: %B is full month name in current locale, and %Y is full year number", "One Week Ago (%B, %Y)")); } else if (daysDistance <= 7 * 3) { newGroupValue = modifiedTime.toString(i18nc("@title:group Date: %B is full month name in current locale, and %Y is full year number", "Two Weeks Ago (%B, %Y)")); } else if (daysDistance <= 7 * 4) { @@@ -1930,7 -2008,7 +1935,7 @@@ void KFileItemModel::determineMimeTypes { QElapsedTimer timer; timer.start(); - foreach (KFileItem item, items) { // krazy:exclude=foreach + foreach (const KFileItem& item, items) { // krazy:exclude=foreach item.determineMimeType(); if (timer.elapsed() > timeout) { // Don't block the user interface, let the remaining items @@@ -1940,51 -2018,4 +1945,51 @@@ } } +bool KFileItemModel::isConsistent() const +{ + if (m_items.count() != m_itemData.count()) { + return false; + } + + for (int i = 0; i < count(); ++i) { + // Check if m_items and m_itemData are consistent. + const KFileItem item = fileItem(i); + if (item.isNull()) { + qWarning() << "Item" << i << "is null"; + return false; + } + + const int itemIndex = index(item); + if (itemIndex != i) { + qWarning() << "Item" << i << "has a wrong index:" << itemIndex; + return false; + } + + // Check if the items are sorted correctly. + if (i > 0 && !lessThan(m_itemData.at(i - 1), m_itemData.at(i))) { + qWarning() << "The order of items" << i - 1 << "and" << i << "is wrong:" + << fileItem(i - 1) << fileItem(i); + return false; + } + + // Check if all parent-child relationships are consistent. + const ItemData* data = m_itemData.at(i); + const ItemData* parent = data->parent; + if (parent) { + if (data->values.value("expandedParentsCount").toInt() != parent->values.value("expandedParentsCount").toInt() + 1) { + qWarning() << "expandedParentsCount is inconsistent for parent" << parent->item << "and child" << data->item; + return false; + } + + const int parentIndex = index(parent->item); + if (parentIndex >= i) { + qWarning() << "Index" << parentIndex << "of parent" << parent->item << "is not smaller than index" << i << "of child" << data->item; + return false; + } + } + } + + return true; +} + #include "kfileitemmodel.moc"