]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/kitemviews/kfileitemmodel.cpp
Merge remote-tracking branch 'origin/master' into frameworks
[dolphin.git] / src / kitemviews / kfileitemmodel.cpp
index 1b4911deceb6c1614d52592d4e39e11b333d3668..51bf546f9a2c133b16d262fe10f70e4794bbf3d2 100644 (file)
 
 #include "kfileitemmodel.h"
 
-#include <KDirModel>
 #include <KGlobalSettings>
 #include <KLocale>
 #include <KStringHandler>
 #include <KDebug>
+#include <kstringhandler_deprecated.h> //TODO: port to QCollator
 
 #include "private/kfileitemmodelsortalgorithm.h"
 #include "private/kfileitemmodeldirlister.h"
@@ -69,18 +69,17 @@ KFileItemModel::KFileItemModel(QObject* parent) :
         m_dirLister->setMainWindow(parentWidget->window());
     }
 
-    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(itemsAdded(KUrl,KFileItemList)), this, SLOT(slotItemsAdded(KUrl,KFileItemList)));
-    connect(m_dirLister, SIGNAL(itemsDeleted(KFileItemList)), this, SLOT(slotItemsDeleted(KFileItemList)));
-    connect(m_dirLister, SIGNAL(refreshItems(QList<QPair<KFileItem,KFileItem> >)), this, SLOT(slotRefreshItems(QList<QPair<KFileItem,KFileItem> >)));
-    connect(m_dirLister, SIGNAL(clear()), this, SLOT(slotClear()));
-    connect(m_dirLister, SIGNAL(clear(KUrl)), this, SLOT(slotClear(KUrl)));
-    connect(m_dirLister, SIGNAL(infoMessage(QString)), this, SIGNAL(infoMessage(QString)));
-    connect(m_dirLister, SIGNAL(errorMessage(QString)), this, SIGNAL(errorMessage(QString)));
-    connect(m_dirLister, SIGNAL(redirection(KUrl,KUrl)), this, SIGNAL(directoryRedirection(KUrl,KUrl)));
-    connect(m_dirLister, SIGNAL(urlIsFileError(KUrl)), this, SIGNAL(urlIsFileError(KUrl)));
+    connect(m_dirLister, &KFileItemModelDirLister::started, this, &KFileItemModel::directoryLoadingStarted);
+    connect(m_dirLister, static_cast<void(KFileItemModelDirLister::*)()>(&KFileItemModelDirLister::canceled), this, &KFileItemModel::slotCanceled);
+    connect(m_dirLister, static_cast<void(KFileItemModelDirLister::*)(const QUrl&)>(&KFileItemModelDirLister::completed), this, &KFileItemModel::slotCompleted);
+    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, static_cast<void(KFileItemModelDirLister::*)()>(&KFileItemModelDirLister::clear), this, &KFileItemModel::slotClear);
+    connect(m_dirLister, &KFileItemModelDirLister::infoMessage, this, &KFileItemModel::infoMessage);
+    connect(m_dirLister, &KFileItemModelDirLister::errorMessage, this, &KFileItemModel::errorMessage);
+    connect(m_dirLister, static_cast<void(KFileItemModelDirLister::*)(const QUrl&, const QUrl&)>(&KFileItemModelDirLister::redirection), this, &KFileItemModel::directoryRedirection);
+    connect(m_dirLister, &KFileItemModelDirLister::urlIsFileError, this, &KFileItemModel::urlIsFileError);
 
     // Apply default roles that should be determined
     resetRoles();
@@ -96,7 +95,7 @@ KFileItemModel::KFileItemModel(QObject* parent) :
     m_maximumUpdateIntervalTimer = new QTimer(this);
     m_maximumUpdateIntervalTimer->setInterval(2000);
     m_maximumUpdateIntervalTimer->setSingleShot(true);
-    connect(m_maximumUpdateIntervalTimer, SIGNAL(timeout()), this, SLOT(dispatchPendingItemsToInsert()));
+    connect(m_maximumUpdateIntervalTimer, &QTimer::timeout, this, &KFileItemModel::dispatchPendingItemsToInsert);
 
     // When changing the value of an item which represents the sort-role a resorting must be
     // triggered. Especially in combination with KFileItemModelRolesUpdater this might be done
@@ -105,9 +104,10 @@ KFileItemModel::KFileItemModel(QObject* parent) :
     m_resortAllItemsTimer = new QTimer(this);
     m_resortAllItemsTimer->setInterval(500);
     m_resortAllItemsTimer->setSingleShot(true);
-    connect(m_resortAllItemsTimer, SIGNAL(timeout()), this, SLOT(resortAllItems()));
+    connect(m_resortAllItemsTimer, &QTimer::timeout, this, &KFileItemModel::resortAllItems);
 
-    connect(KGlobalSettings::self(), SIGNAL(naturalSortingChanged()), this, SLOT(slotNaturalSortingChanged()));
+    connect(KGlobalSettings::self(), &KGlobalSettings::naturalSortingChanged,
+            this, &KFileItemModel::slotNaturalSortingChanged);
 }
 
 KFileItemModel::~KFileItemModel()
@@ -152,7 +152,12 @@ int KFileItemModel::count() const
 QHash<QByteArray, QVariant> 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<QByteArray, QVariant>();
 }
@@ -163,7 +168,7 @@ bool KFileItemModel::setData(int index, const QHash<QByteArray, QVariant>& value
         return false;
     }
 
-    QHash<QByteArray, QVariant> currentValues = m_itemData.at(index)->values;
+    QHash<QByteArray, QVariant> currentValues = data(index);
 
     // Determine which roles have been changed
     QSet<QByteArray> changedRoles;
@@ -190,11 +195,7 @@ bool KFileItemModel::setData(int index, const QHash<QByteArray, QVariant>& value
         m_itemData[index]->item.setUrl(url);
     }
 
-    emit itemsChanged(KItemRangeList() << KItemRange(index, 1), changedRoles);
-
-    if (changedRoles.contains(sortRole())) {
-        m_resortAllItemsTimer->start();
-    }
+    emitItemsChangedAndTriggerResorting(KItemRangeList() << KItemRange(index, 1), changedRoles);
 
     return true;
 }
@@ -217,7 +218,7 @@ void KFileItemModel::setShowHiddenFiles(bool show)
     m_dirLister->setShowingDotFiles(show);
     m_dirLister->emitChanges();
     if (show) {
-        slotCompleted();
+        dispatchPendingItemsToInsert();
     }
 }
 
@@ -236,7 +237,7 @@ bool KFileItemModel::showDirectoriesOnly() const
     return m_dirLister->dirOnlyMode();
 }
 
-QMimeData* KFileItemModel::createMimeData(const QSet<int>& indexes) const
+QMimeData* KFileItemModel::createMimeData(const KItemSet& indexes) const
 {
     QMimeData* data = new QMimeData();
 
@@ -246,11 +247,23 @@ QMimeData* KFileItemModel::createMimeData(const QSet<int>& indexes) const
     KUrl::List urls;
     KUrl::List mostLocalUrls;
     bool canUseMostLocalUrls = true;
+    const ItemData* lastAddedItem = 0;
 
-    QSetIterator<int> it(indexes);
-    while (it.hasNext()) {
-        const int index = it.next();
-        const KFileItem item = fileItem(index);
+    foreach (int index, indexes) {
+        const ItemData* itemData = m_itemData.at(index);
+        const ItemData* parent = itemData->parent;
+
+        while (parent && parent != lastAddedItem) {
+            parent = parent->parent;
+        }
+
+        if (parent && parent == lastAddedItem) {
+            // A parent of 'itemData' has been added already.
+            continue;
+        }
+
+        lastAddedItem = itemData;
+        const KFileItem& item = itemData->item;
         if (!item.isNull()) {
             urls << item.targetUrl();
 
@@ -263,9 +276,7 @@ QMimeData* KFileItemModel::createMimeData(const QSet<int>& indexes) const
     }
 
     const bool different = canUseMostLocalUrls && mostLocalUrls != urls;
-    urls = KDirModel::simplifiedUrlList(urls); // TODO: Check if we still need KDirModel for this in KDE 5.0
     if (different) {
-        mostLocalUrls = KDirModel::simplifiedUrlList(mostLocalUrls);
         urls.populateMimeData(mostLocalUrls, data);
     } else {
         urls.populateMimeData(data);
@@ -278,12 +289,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;
         }
     }
@@ -345,27 +356,50 @@ KFileItem KFileItemModel::fileItem(int index) const
 
 KFileItem KFileItemModel::fileItem(const KUrl& url) const
 {
-    const int index = m_items.value(url, -1);
-    if (index >= 0) {
-        return m_itemData.at(index)->item;
+    const int indexForUrl = index(url);
+    if (indexForUrl >= 0) {
+        return m_itemData.at(indexForUrl)->item;
     }
     return KFileItem();
 }
 
 int KFileItemModel::index(const KFileItem& item) const
 {
-    if (item.isNull()) {
-        return -1;
-    }
-
-    return m_items.value(item.url(), -1);
+    return index(KUrl(item.url()));
 }
 
 int KFileItemModel::index(const KUrl& url) const
 {
     KUrl urlToFind = url;
     urlToFind.adjustPath(KUrl::RemoveTrailingSlash);
-    return m_items.value(urlToFind, -1);
+
+    const int itemCount = m_itemData.count();
+    int itemsInHash = m_items.count();
+
+    int index = m_items.value(urlToFind, -1);
+    while (index < 0 && itemsInHash < itemCount) {
+        // Not all URLs are stored yet in m_items. We grow m_items until either
+        // urlToFind is found, or all URLs have been stored in m_items.
+        // Note that we do not add the URLs to m_items one by one, but in
+        // larger blocks. After each block, we check if urlToFind is in
+        // m_items. We could in principle compare urlToFind with each URL while
+        // we are going through m_itemData, but comparing two QUrls will,
+        // unlike calling qHash for the URLs, trigger a parsing of the URLs
+        // which costs both CPU cycles and memory.
+        const int blockSize = 1000;
+        const int currentBlockEnd = qMin(itemsInHash + blockSize, itemCount);
+        for (int i = itemsInHash; i < currentBlockEnd; ++i) {
+            const KUrl nextUrl = m_itemData.at(i)->item.url();
+            m_items.insert(nextUrl, i);
+        }
+
+        itemsInHash = currentBlockEnd;
+        index = m_items.value(urlToFind, -1);
+    }
+
+    Q_ASSERT(index >= 0 || m_items.count() == m_itemData.count());
+
+    return index;
 }
 
 KFileItem KFileItemModel::rootItem() const
@@ -414,6 +448,15 @@ void KFileItemModel::setRoles(const QSet<QByteArray>& roles)
         kWarning() << "TODO: Emitting itemsChanged() with no information what has changed!";
         emit itemsChanged(KItemRangeList() << KItemRange(0, count()), QSet<QByteArray>());
     }
+
+    // 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<KFileItem, ItemData*>::iterator filteredIt = m_filteredItems.begin();
+    const QHash<KFileItem, ItemData*>::iterator filteredEnd = m_filteredItems.end();
+    while (filteredIt != filteredEnd) {
+        (*filteredIt)->values.clear();
+        ++filteredIt;
+    }
 }
 
 QSet<QByteArray> KFileItemModel::roles() const
@@ -439,15 +482,51 @@ 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<KUrl::List>();
+        foreach (const KUrl& url, previouslyExpandedChildren) {
+            m_urlsToExpand.insert(url);
+        }
     } else {
+        // Note that there might be (indirect) children of the folder which is to be collapsed in
+        // m_pendingItemsToInsert. To prevent that they will be inserted into the model later,
+        // possibly without a parent, which might result in a crash, we insert all pending items
+        // right now. All new items which would be without a parent will then be removed.
+        dispatchPendingItemsToInsert();
+
+        // Check if the index of the collapsed folder has changed. If that is the case, then items
+        // were inserted before the collapsed folder, and its index needs to be updated.
+        if (m_itemData.at(index)->item != item) {
+            index = this->index(item);
+        }
+
         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();
+                const KUrl 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
+                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;
@@ -464,7 +543,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;
 }
@@ -472,10 +553,7 @@ bool KFileItemModel::isExpandable(int index) const
 int KFileItemModel::expandedParentsCount(int index) const
 {
     if (index >= 0 && index < count()) {
-        const int parentsCount = m_itemData.at(index)->values.value("expandedParentsCount").toInt();
-        if (parentsCount > 0) {
-            return parentsCount;
-        }
+        return expandedParentsCount(m_itemData.at(index));
     }
     return 0;
 }
@@ -551,21 +629,25 @@ void KFileItemModel::applyFilters()
 {
     // Check which shown items from m_itemData must get
     // hidden and hence moved to m_filteredItems.
-    KFileItemList newFilteredItems;
+    QVector<int> 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.
@@ -584,22 +666,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<KFileItem> parents = parentsList.toSet();
+    QSet<ItemData*> parents;
+    foreach (const KItemRange& range, itemRanges) {
+        for (int index = range.index; index < range.index + range.count; ++index) {
+            parents.insert(m_itemData.at(index));
+        }
+    }
 
     QHash<KFileItem, ItemData*>::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 {
@@ -627,7 +711,7 @@ QList<KFileItemModel::RoleInfo> KFileItemModel::rolesInformation()
                     // menus tries to put the actions into sub menus otherwise.
                     info.group = QString();
                 }
-                info.requiresNepomuk = map[i].requiresNepomuk;
+                info.requiresBaloo = map[i].requiresBaloo;
                 info.requiresIndexer = map[i].requiresIndexer;
                 rolesInfo.append(info);
             }
@@ -689,8 +773,8 @@ void KFileItemModel::resortAllItems()
         oldUrls.append(itemData->item.url());
     }
 
-    m_groups.clear();
     m_items.clear();
+    m_items.reserve(itemCount);
 
     // Resort the items
     sort(m_itemData.begin(), m_itemData.end());
@@ -698,20 +782,45 @@ void KFileItemModel::resortAllItems()
         m_items.insert(m_itemData.at(i)->item.url(), i);
     }
 
-    // Determine the indexes that have been moved
-    QList<int> movedToIndexes;
-    movedToIndexes.reserve(itemCount);
-    for (int i = 0; i < itemCount; i++) {
-        const int newIndex = m_items.value(oldUrls.at(i));
-        movedToIndexes.append(newIndex);
+    // Determine the first index that has been moved.
+    int firstMovedIndex = 0;
+    while (firstMovedIndex < itemCount
+           && firstMovedIndex == m_items.value(oldUrls.at(firstMovedIndex))) {
+        ++firstMovedIndex;
     }
 
-    // Don't check whether items have really been moved and always emit a
-    // itemsMoved() signal after resorting: In case of grouped items
-    // the groups might change even if the items themselves don't change their
-    // position. Let the receiver of the signal decide whether a check for moved
-    // items makes sense.
-    emit itemsMoved(KItemRange(0, itemCount), movedToIndexes);
+    const bool itemsHaveMoved = firstMovedIndex < itemCount;
+    if (itemsHaveMoved) {
+        m_groups.clear();
+
+        int lastMovedIndex = itemCount - 1;
+        while (lastMovedIndex > firstMovedIndex
+               && lastMovedIndex == m_items.value(oldUrls.at(lastMovedIndex))) {
+            --lastMovedIndex;
+        }
+
+        Q_ASSERT(firstMovedIndex <= lastMovedIndex);
+
+        // Create a list movedToIndexes, which has the property that
+        // movedToIndexes[i] is the new index of the item with the old index
+        // firstMovedIndex + i.
+        const int movedItemsCount = lastMovedIndex - firstMovedIndex + 1;
+        QList<int> movedToIndexes;
+        movedToIndexes.reserve(movedItemsCount);
+        for (int i = firstMovedIndex; i <= lastMovedIndex; ++i) {
+            const int newIndex = m_items.value(oldUrls.at(i));
+            movedToIndexes.append(newIndex);
+        }
+
+        emit itemsMoved(KItemRange(firstMovedIndex, movedItemsCount), movedToIndexes);
+    } else if (groupedSorting()) {
+        // The groups might have changed even if the order of the items has not.
+        const QList<QPair<int, QVariant> > oldGroups = m_groups;
+        m_groups.clear();
+        if (groups() != oldGroups) {
+            emit groupsChanged();
+        }
+    }
 
 #ifdef KFILEITEMMODEL_DEBUG
     kDebug() << "[TIME] Resorting of" << itemCount << "items:" << timer.elapsed();
@@ -728,10 +837,10 @@ void KFileItemModel::slotCompleted()
         // Therefore, some URLs in m_restoredExpandedUrls might not be visible yet
         // -> we expand the first visible URL we find in m_restoredExpandedUrls.
         foreach (const KUrl& url, m_urlsToExpand) {
-            const int index = m_items.value(url, -1);
-            if (index >= 0) {
+            const int indexForUrl = index(url);
+            if (indexForUrl >= 0) {
                 m_urlsToExpand.remove(url);
-                if (setExpanded(index, true)) {
+                if (setExpanded(indexForUrl, true)) {
                     // The dir lister has been triggered. This slot will be called
                     // again after the directory has been expanded.
                     return;
@@ -768,15 +877,12 @@ void KFileItemModel::slotItemsAdded(const KUrl& directoryUrl, const KFileItemLis
     }
 
     if (m_requestRole[ExpandedParentsCountRole]) {
-        KFileItem item = items.first();
-
         // If the expanding of items is enabled, the call
         // dirLister->openUrl(url, KDirLister::Keep) in KFileItemModel::setExpanded()
         // might result in emitting the same items twice due to the Keep-parameter.
         // This case happens if an item gets expanded, collapsed and expanded again
         // before the items could be loaded for the first expansion.
-        const int index = m_items.value(item.url(), -1);
-        if (index >= 0) {
+        if (index(KUrl(items.first().url())) >= 0) {
             // The items are already part of the model.
             return;
         }
@@ -790,7 +896,7 @@ void KFileItemModel::slotItemsAdded(const KUrl& directoryUrl, const KFileItemLis
         // 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.
-        const int parentIndex = m_items.value(parentUrl, -1);
+        const int parentIndex = index(parentUrl);
         if (parentIndex >= 0 && !m_itemData[parentIndex]->values.value("isExpanded").toBool()) {
             // The parent is not expanded.
             return;
@@ -825,29 +931,48 @@ 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<int> indexesToRemove;
+    indexesToRemove.reserve(items.count());
 
-    if (!m_filteredItems.isEmpty()) {
-        foreach (const KFileItem& item, itemsToRemove) {
+    foreach (const KFileItem& item, items) {
+        const int indexForItem = index(item);
+        if (indexForItem >= 0) {
+            indexesToRemove.append(indexForItem);
+        } else {
+            // Probably the item has been filtered.
             QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.find(item);
             if (it != m_filteredItems.end()) {
                 delete it.value();
                 m_filteredItems.erase(it);
             }
         }
+    }
+
+    std::sort(indexesToRemove.begin(), indexesToRemove.end());
 
-        if (m_requestRole[ExpandedParentsCountRole]) {
-            removeFilteredChildren(itemsToRemove);
+    if (m_requestRole[ExpandedParentsCountRole] && !m_expandedDirs.isEmpty()) {
+        // Assure that removing a parent item also results in removing all children
+        QVector<int> indexesToRemoveWithChildren;
+        indexesToRemoveWithChildren.reserve(m_itemData.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<QPair<KFileItem, KFileItem> >& items)
@@ -868,14 +993,14 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
         const QPair<KFileItem, KFileItem>& itemPair = it.next();
         const KFileItem& oldItem = itemPair.first;
         const KFileItem& newItem = itemPair.second;
-        const int index = m_items.value(oldItem.url(), -1);
-        if (index >= 0) {
-            m_itemData[index]->item = newItem;
+        const int indexForItem = index(oldItem);
+        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<QByteArray, QVariant> it(retrieveData(newItem, m_itemData.at(index)->parent));
-            QHash<QByteArray, QVariant>& values = m_itemData[index]->values;
+            QHashIterator<QByteArray, QVariant> it(retrieveData(newItem, m_itemData.at(indexForItem)->parent));
+            QHash<QByteArray, QVariant>& values = m_itemData[indexForItem]->values;
             while (it.hasNext()) {
                 it.next();
                 const QByteArray& role = it.key();
@@ -886,8 +1011,22 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
             }
 
             m_items.remove(oldItem.url());
-            m_items.insert(newItem.url(), index);
-            indexes.append(index);
+            m_items.insert(newItem.url(), indexForItem);
+            indexes.append(indexForItem);
+        } else {
+            // Check if 'oldItem' is one of the filtered items.
+            QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.find(oldItem);
+            if (it != m_filteredItems.end()) {
+                ItemData* itemData = it.value();
+                itemData->item = newItem;
+
+                // The data stored in 'values' might have changed. Therefore, we clear
+                // 'values' and re-populate it the next time it is requested via data(int).
+                itemData->values.clear();
+
+                m_filteredItems.erase(it);
+                m_filteredItems.insert(newItem, itemData);
+            }
         }
     }
 
@@ -899,35 +1038,8 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
 
     // Extract the item-ranges out of the changed indexes
     qSort(indexes);
-
-    KItemRangeList itemRangeList;
-    int previousIndex = indexes.at(0);
-    int rangeIndex = previousIndex;
-    int rangeCount = 1;
-
-    const int maxIndex = indexes.count() - 1;
-    for (int i = 1; i <= maxIndex; ++i) {
-        const int currentIndex = indexes.at(i);
-        if (currentIndex == previousIndex + 1) {
-            ++rangeCount;
-        } else {
-            itemRangeList.append(KItemRange(rangeIndex, rangeCount));
-
-            rangeIndex = currentIndex;
-            rangeCount = 1;
-        }
-        previousIndex = currentIndex;
-    }
-
-    if (rangeCount > 0) {
-        itemRangeList.append(KItemRange(rangeIndex, rangeCount));
-    }
-
-    emit itemsChanged(itemRangeList, changedRoles);
-
-    if (changedRoles.contains(sortRole())) {
-        resortAllItems();
-    }
+    const KItemRangeList itemRangeList = KItemRangeList::fromSortedContainer(indexes);
+    emitItemsChangedAndTriggerResorting(itemRangeList, changedRoles);
 }
 
 void KFileItemModel::slotClear()
@@ -957,11 +1069,6 @@ void KFileItemModel::slotClear()
     m_expandedDirs.clear();
 }
 
-void KFileItemModel::slotClear(const KUrl& url)
-{
-    Q_UNUSED(url);
-}
-
 void KFileItemModel::slotNaturalSortingChanged()
 {
     m_naturalSorting = KGlobalSettings::naturalSorting();
@@ -990,6 +1097,15 @@ void KFileItemModel::insertItems(QList<ItemData*>& 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());
 
@@ -1013,7 +1129,7 @@ void KFileItemModel::insertItems(QList<ItemData*>& newItems)
             m_itemData.append(0);
         }
 
-        // We build the new list m_items in reverse order to minimize
+        // We build the new list m_itemData in reverse order to minimize
         // the number of moves and guarantee O(N) complexity.
         int targetIndex = totalItemCount - 1;
         int sourceIndexExistingItems = existingItemCount - 1;
@@ -1051,11 +1167,9 @@ void KFileItemModel::insertItems(QList<ItemData*>& newItems)
         std::reverse(itemRanges.begin(), itemRanges.end());
     }
 
-    // The indexes starting from the first inserted item must be adjusted.
-    m_items.reserve(totalItemCount);
-    for (int i = itemRanges.front().index; i < totalItemCount; ++i) {
-        m_items.insert(m_itemData.at(i)->item.url(), i);
-    }
+    // The indexes in m_items are not correct anymore. Therefore, we clear m_items.
+    // It will be re-populated with the updated indices if index(const KUrl&) is called.
+    m_items.clear();
 
     emit itemsInserted(itemRanges);
 
@@ -1064,59 +1178,20 @@ void KFileItemModel::insertItems(QList<ItemData*>& newItems)
 #endif
 }
 
-static KItemRangeList sortedIndexesToKItemRangeList(const QList<int>& sortedNumbers)
+void KFileItemModel::removeItems(const KItemRangeList& itemRanges, RemoveItemsBehavior behavior)
 {
-    if (sortedNumbers.empty()) {
-        return KItemRangeList();
-    }
-
-    KItemRangeList result;
-
-    QList<int>::const_iterator it = sortedNumbers.begin();
-    int index = *it;
-    int count = 1;
-
-    ++it;
-
-    QList<int>::const_iterator end = sortedNumbers.end();
-    while (it != end) {
-        if (*it == index + count) {
-            ++count;
-        } else {
-            result << KItemRange(index, count);
-            index = *it;
-            count = 1;
-        }
-        ++it;
+    if (itemRanges.isEmpty()) {
+        return;
     }
 
-    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();
 
-    // Step 1: Determine the indexes of the removed items, remove them from
-    //         the hash m_items, and free the ItemData.
-    QList<int> 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);
-
-            // Prevent repeated expensive rehashing by using QHash::erase(),
-            // rather than QHash::remove().
-            QHash<KUrl, int>::iterator it = m_items.find(url);
-            m_items.erase(it);
+    // Step 1: Remove the items from m_itemData, 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) {
             if (behavior == DeleteItemData) {
                 delete m_itemData.at(index);
             }
@@ -1125,14 +1200,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 = sortedIndexesToKItemRangeList(indexesToRemove);
     int target = itemRanges.at(0).index;
     int source = itemRanges.at(0).index + itemRanges.at(0).count;
     int nextRange = 1;
@@ -1150,14 +1218,11 @@ 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.
-    const int newItemDataCount = m_itemData.count();
-    for (int i = itemRanges.front().index; i < newItemDataCount; ++i) {
-        m_items.insert(m_itemData.at(i)->item.url(), i);
-    }
+    // The indexes in m_items are not correct anymore. Therefore, we clear m_items.
+    // It will be re-populated with the updated indices if index(const KUrl&) is called.
+    m_items.clear();
 
     emit itemsRemoved(itemRanges);
 }
@@ -1171,7 +1236,7 @@ QList<KFileItemModel::ItemData*> KFileItemModel::createItemDataList(const KUrl&
         determineMimeTypes(items, 200);
     }
 
-    const int parentIndex = m_items.value(parentUrl, -1);
+    const int parentIndex = index(parentUrl);
     ItemData* parentItem = parentIndex < 0 ? 0 : m_itemData.at(parentIndex);
 
     QList<ItemData*> itemDataList;
@@ -1180,7 +1245,6 @@ QList<KFileItemModel::ItemData*> 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);
     }
@@ -1188,23 +1252,141 @@ QList<KFileItemModel::ItemData*> KFileItemModel::createItemDataList(const KUrl&
     return itemDataList;
 }
 
+void KFileItemModel::prepareItemsForSorting(QList<ItemData*>& 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"
+    // if the corresponding item is expanded, and it is not a top-level item.
+    const ItemData* parent = data->parent;
+    if (parent) {
+        if (parent->parent) {
+            Q_ASSERT(parent->values.contains("expandedParentsCount"));
+            return parent->values.value("expandedParentsCount").toInt() + 1;
+        } else {
+            return 1;
+        }
+    } else {
+        return 0;
+    }
+}
+
 void KFileItemModel::removeExpandedItems()
 {
-    KFileItemList expandedItems;
+    QVector<int> indexesToRemove;
 
     const int maxIndex = m_itemData.count() - 1;
     for (int i = 0; i <= maxIndex; ++i) {
         const ItemData* itemData = m_itemData.at(i);
-        if (itemData->values.value("expandedParentsCount").toInt() > 0) {
-            expandedItems.append(itemData->item);
+        if (itemData->parent) {
+            indexesToRemove.append(i);
         }
     }
 
-    // The m_expandedParentsCountRoot may not get reset before all items with
-    // a bigger count have been removed.
-    removeItems(expandedItems, DeleteItemData);
-
+    removeItems(KItemRangeList::fromSortedContainer(indexesToRemove), DeleteItemData);
     m_expandedDirs.clear();
+
+    // Also remove all filtered items which have a parent.
+    QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.begin();
+    const QHash<KFileItem, ItemData*>::iterator end = m_filteredItems.end();
+
+    while (it != end) {
+        if (it.value()->parent) {
+            delete it.value();
+            it = m_filteredItems.erase(it);
+        } else {
+            ++it;
+        }
+    }
+}
+
+void KFileItemModel::emitItemsChangedAndTriggerResorting(const KItemRangeList& itemRanges, const QSet<QByteArray>& changedRoles)
+{
+    emit itemsChanged(itemRanges, changedRoles);
+
+    // 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))) {
+        foreach (const KItemRange& range, itemRanges) {
+            bool needsResorting = false;
+
+            const int first = range.index;
+            const int last = range.index + range.count - 1;
+
+            // Resorting the model is necessary if
+            // (a)  The first item in the range is "lessThan" its predecessor,
+            // (b)  the successor of the last item is "lessThan" the last item, or
+            // (c)  the internal order of the items in the range is incorrect.
+            if (first > 0
+                && lessThan(m_itemData.at(first), m_itemData.at(first - 1))) {
+                needsResorting = true;
+            } else if (last < count() - 1
+                && lessThan(m_itemData.at(last + 1), m_itemData.at(last))) {
+                needsResorting = true;
+            } else {
+                for (int index = first; index < last; ++index) {
+                    if (lessThan(m_itemData.at(index + 1), m_itemData.at(index))) {
+                        needsResorting = true;
+                        break;
+                    }
+                }
+            }
+
+            if (needsResorting) {
+                m_resortAllItemsTimer->start();
+                return;
+            }
+        }
+    }
+
+    if (groupedSorting() && changedRoles.contains(sortRole())) {
+        // The position is still correct, but the groups might have changed
+        // if the changed item is either the first or the last item in a
+        // group.
+        // In principle, we could try to find out if the item really is the
+        // first or last one in its group and then update the groups
+        // (possibly with a delayed timer to make sure that we don't
+        // re-calculate the groups very often if items are updated one by
+        // one), but starting m_resortAllItemsTimer is easier.
+        m_resortAllItemsTimer->start();
+    }
 }
 
 void KFileItemModel::resetRoles()
@@ -1275,33 +1457,28 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item,
     data.insert(sharedValue("url"), item.url());
 
     const bool isDir = item.isDir();
-    if (m_requestRole[IsDirRole]) {
-        data.insert(sharedValue("isDir"), isDir);
+    if (m_requestRole[IsDirRole] && isDir) {
+        data.insert(sharedValue("isDir"), true);
     }
 
-    if (m_requestRole[IsLinkRole]) {
-        const bool isLink = item.isLink();
-        data.insert(sharedValue("isLink"), isLink);
+    if (m_requestRole[IsLinkRole] && item.isLink()) {
+        data.insert(sharedValue("isLink"), true);
     }
 
     if (m_requestRole[NameRole]) {
         data.insert(sharedValue("text"), item.text());
     }
 
-    if (m_requestRole[SizeRole]) {
-        if (isDir) {
-            data.insert(sharedValue("size"), QVariant());
-        } else {
-            data.insert(sharedValue("size"), item.size());
-        }
+    if (m_requestRole[SizeRole] && !isDir) {
+        data.insert(sharedValue("size"), item.size());
     }
 
     if (m_requestRole[DateRole]) {
         // Don't use KFileItem::timeString() as this is too expensive when
         // having several thousands of items. Instead the formatting of the
         // date-time will be done on-demand by the view when the date will be shown.
-        const KDateTime dateTime = item.time(KFileItem::ModificationTime);
-        data.insert(sharedValue("date"), dateTime.dateTime());
+        const QDateTime dateTime = item.time(KFileItem::ModificationTime);
+        data.insert(sharedValue("date"), dateTime);
     }
 
     if (m_requestRole[PermissionsRole]) {
@@ -1326,7 +1503,7 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item,
 
     if (m_requestRole[PathRole]) {
         QString path;
-        if (item.url().protocol() == QLatin1String("trash")) {
+        if (item.url().scheme() == QLatin1String("trash")) {
             path = item.entry().stringValue(KIO::UDSEntry::UDS_EXTRA);
         } else {
             // For performance reasons cache the home-path in a static QString
@@ -1347,17 +1524,15 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item,
         data.insert(sharedValue("path"), path);
     }
 
-    if (m_requestRole[IsExpandableRole]) {
-        data.insert(sharedValue("isExpandable"), item.isDir());
+    if (m_requestRole[IsExpandableRole] && isDir) {
+        data.insert(sharedValue("isExpandable"), true);
     }
 
     if (m_requestRole[ExpandedParentsCountRole]) {
-        int level = 0;
         if (parent) {
-            level = parent->values["expandedParentsCount"].toInt() + 1;
+            const int level = expandedParentsCount(parent) + 1;
+            data.insert(sharedValue("expandedParentsCount"), level);
         }
-
-        data.insert(sharedValue("expandedParentsCount"), level);
     }
 
     if (item.isMimeTypeKnown()) {
@@ -1366,6 +1541,9 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item,
         if (m_requestRole[TypeRole]) {
             data.insert(sharedValue("type"), item.mimeComment());
         }
+    } else if (m_requestRole[TypeRole] && isDir) {
+        static const QString folderMimeType = item.mimeComment();
+        data.insert(sharedValue("type"), folderMimeType);
     }
 
     return data;
@@ -1376,8 +1554,8 @@ bool KFileItemModel::lessThan(const ItemData* a, const ItemData* b) const
     int result = 0;
 
     if (a->parent != b->parent) {
-        const int expansionLevelA = a->values.value("expandedParentsCount").toInt();
-        const int expansionLevelB = b->values.value("expandedParentsCount").toInt();
+        const int expansionLevelA = expandedParentsCount(a);
+        const int expansionLevelB = expandedParentsCount(b);
 
         // 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.
@@ -1397,7 +1575,7 @@ bool KFileItemModel::lessThan(const ItemData* a, const ItemData* b) const
             a = a->parent;
         }
 
-        Q_ASSERT(a->values.value("expandedParentsCount").toInt() == b->values.value("expandedParentsCount").toInt());
+        Q_ASSERT(expandedParentsCount(a) == expandedParentsCount(b));
 
         // Compare the last parents of a and b which are different.
         while (a->parent != b->parent) {
@@ -1504,8 +1682,8 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b) const
     }
 
     case DateRole: {
-        const KDateTime dateTimeA = itemA.time(KFileItem::ModificationTime);
-        const KDateTime dateTimeB = itemB.time(KFileItem::ModificationTime);
+        const QDateTime dateTimeA = itemA.time(KFileItem::ModificationTime);
+        const QDateTime dateTimeB = itemB.time(KFileItem::ModificationTime);
         if (dateTimeA < dateTimeB) {
             result = -1;
         } else if (dateTimeA > dateTimeB) {
@@ -1607,7 +1785,7 @@ QList<QPair<int, QVariant> > 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();
@@ -1697,7 +1875,7 @@ QList<QPair<int, QVariant> > KFileItemModel::dateRoleGroups() const
     const int maxIndex = count() - 1;
     QList<QPair<int, QVariant> > groups;
 
-    const QDate currentDate = KDateTime::currentLocalDateTime().date();
+    const QDate currentDate = QDate::currentDate();
 
     QDate previousModifiedDate;
     QString groupValue;
@@ -1706,7 +1884,7 @@ QList<QPair<int, QVariant> > KFileItemModel::dateRoleGroups() const
             continue;
         }
 
-        const KDateTime modifiedTime = m_itemData.at(i)->item.time(KFileItem::ModificationTime);
+        const QDateTime modifiedTime = m_itemData.at(i)->item.time(KFileItem::ModificationTime);
         const QDate modifiedDate = modifiedTime.date();
         if (modifiedDate == previousModifiedDate) {
             // The current item is in the same group as the previous item
@@ -1793,7 +1971,7 @@ QList<QPair<int, QVariant> > KFileItemModel::permissionRoleGroups() const
         }
         permissionsString = newPermissionsString;
 
-        const QFileInfo info(itemData->item.url().pathOrUrl());
+        const QFileInfo info(itemData->item.url().toLocalFile());
 
         // Set user string
         QString user;
@@ -1890,23 +2068,6 @@ QList<QPair<int, QVariant> > 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 = m_itemData.at(index)->values.value("expandedParentsCount").toInt();
-        ++index;
-        while (index < m_itemData.count() && m_itemData.at(index)->values.value("expandedParentsCount").toInt() > 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.
@@ -1938,7 +2099,7 @@ void KFileItemModel::emitSortProgress(int resolvedCount)
 const KFileItemModel::RoleInfoMap* KFileItemModel::rolesInfoMap(int& count)
 {
     static const RoleInfoMap rolesInfoMap[] = {
-    //  | role         | roleType       | role translation                                | group translation           | requires Nepomuk | requires indexer
+    //  | role         | roleType       | role translation                                | group translation           | requires Baloo   | requires indexer
         { 0,             NoRole,          0, 0,                                             0, 0,                                     false, false },
         { "text",        NameRole,        I18N_NOOP2_NOSTRIP("@label", "Name"),             0, 0,                                     false, false },
         { "size",        SizeRole,        I18N_NOOP2_NOSTRIP("@label", "Size"),             0, 0,                                     false, false },
@@ -1972,7 +2133,15 @@ void KFileItemModel::determineMimeTypes(const KFileItemList& items, int timeout)
     QElapsedTimer timer;
     timer.start();
     foreach (const KFileItem& item, items) { // krazy:exclude=foreach
-        item.determineMimeType();
+        // Only determine mime types for files here. For directories,
+        // KFileItem::determineMimeType() reads the .directory file inside to
+        // load the icon, but this is not necessary at all if we just need the
+        // type. Some special code for setting the correct mime type for
+        // directories is in retrieveData().
+        if (!item.isDir()) {
+            item.determineMimeType();
+        }
+
         if (timer.elapsed() > timeout) {
             // Don't block the user interface, let the remaining items
             // be resolved asynchronously.
@@ -1996,7 +2165,9 @@ QByteArray KFileItemModel::sharedValue(const QByteArray& value)
 
 bool KFileItemModel::isConsistent() const
 {
-    if (m_items.count() != m_itemData.count()) {
+    // m_items may contain less items than m_itemData because m_items
+    // is populated lazily, see KFileItemModel::index(const KUrl& url).
+    if (m_items.count() > m_itemData.count()) {
         return false;
     }
 
@@ -2025,7 +2196,7 @@ bool KFileItemModel::isConsistent() const
         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) {
+            if (expandedParentsCount(data) != expandedParentsCount(parent) + 1) {
                 qWarning() << "expandedParentsCount is inconsistent for parent" << parent->item << "and child" << data->item;
                 return false;
             }