]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/kitemviews/kfileitemmodel.cpp
Merge remote-tracking branch 'origin/KDE/4.10'
[dolphin.git] / src / kitemviews / kfileitemmodel.cpp
index d4c08a52e07ea9f0dee6669245adb138b6cf67c6..d30d9e5be054605e2abef0c19aa0f795e4d4ec1d 100644 (file)
@@ -1,21 +1,23 @@
-/***************************************************************************
- *   Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com>             *
- *                                                                         *
- *   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 <peter.penz19@gmail.com>               *
+ *   Copyright (C) 2013 by Frank Reininghaus <frank78ac@googlemail.com>      *
+ *   Copyright (C) 2013 by Emmanuel Pescosta <emmanuelpescosta099@gmail.com> *
+ *                                                                           *
+ *   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"
 
@@ -33,6 +35,9 @@
 #include <QTimer>
 #include <QWidget>
 
+#include <algorithm>
+#include <vector>
+
 // #define KFILEITEMMODEL_DEBUG
 
 KFileItemModel::KFileItemModel(QObject* parent) :
@@ -53,12 +58,10 @@ KFileItemModel::KFileItemModel(QObject* parent) :
     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<QWidget*>(parent);
@@ -69,7 +72,7 @@ KFileItemModel::KFileItemModel(QObject* parent) :
     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<QPair<KFileItem,KFileItem> >)), this, SLOT(slotRefreshItems(QList<QPair<KFileItem,KFileItem> >)));
     connect(m_dirLister, SIGNAL(clear()), this, SLOT(slotClear()));
@@ -110,7 +113,7 @@ KFileItemModel::KFileItemModel(QObject* parent) :
 KFileItemModel::~KFileItemModel()
 {
     qDeleteAll(m_itemData);
-    m_itemData.clear();
+    qDeleteAll(m_filteredItems.values());
 }
 
 void KFileItemModel::loadDirectory(const KUrl& url)
@@ -120,6 +123,11 @@ void KFileItemModel::loadDirectory(const KUrl& url)
 
 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 +405,7 @@ void KFileItemModel::setRoles(const QSet<QByteArray>& roles)
         // 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,7 +430,8 @@ bool KFileItemModel::setExpanded(int index, bool expanded)
         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);
@@ -430,15 +439,11 @@ bool KFileItemModel::setExpanded(int index, bool expanded)
         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;
-        }
-        removeItems(itemsToRemove);
+        const KFileItemList itemsToRemove = childItems(item);
+        removeFilteredChildren(itemsToRemove);
+        removeItems(itemsToRemove, DeleteItemData);
     }
 
     return true;
@@ -548,31 +553,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<ItemData*> newVisibleItems;
 
-    QMutableSetIterator<KFileItem> it(m_filteredItems);
-    while (it.hasNext()) {
-        const KFileItem item = it.next();
-        if (m_filter.matches(item)) {
-            newVisibleItems.append(item);
-            it.remove();
+    QHash<KFileItem, ItemData*>::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<KFileItem> parents = parentsList.toSet();
+
+    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)) {
+            delete it.value();
+            it = m_filteredItems.erase(it);
+        } else {
+            ++it;
+        }
+    }
+}
+
 QList<KFileItemModel::RoleInfo> KFileItemModel::rolesInformation()
 {
     static QList<RoleInfo> rolesInfo;
@@ -658,7 +689,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);
     }
@@ -720,11 +751,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();
@@ -745,8 +779,6 @@ void KFileItemModel::slotNewItems(const KFileItemList& items)
         // 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.
@@ -754,22 +786,21 @@ void KFileItemModel::slotNewItems(const KFileItemList& items)
         }
     }
 
+    QList<ItemData*> 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()) {
@@ -784,7 +815,7 @@ void KFileItemModel::slotItemsDeleted(const KFileItemList& items)
     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));
@@ -793,38 +824,19 @@ void KFileItemModel::slotItemsDeleted(const KFileItemList& items)
 
     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<KUrl> urlsToRemove;
-            urlsToRemove.reserve(itemsToRemove.count());
-            foreach (const KFileItem& item, itemsToRemove) {
-                KUrl url = item.url();
-                url.adjustPath(KUrl::RemoveTrailingSlash);
-                urlsToRemove.insert(url);
+            QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.find(item);
+            if (it != m_filteredItems.end()) {
+                delete it.value();
+                m_filteredItems.erase(it);
             }
+        }
 
-            QSet<KFileItem>::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<QPair<KFileItem, KFileItem> >& items)
@@ -851,7 +863,7 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
 
             // 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));
+            QHashIterator<QByteArray, QVariant> it(retrieveData(newItem, m_itemData.at(index)->parent));
             while (it.hasNext()) {
                 it.next();
                 m_itemData[index]->values.insert(it.key(), it.value());
@@ -906,6 +918,7 @@ void KFileItemModel::slotClear()
     kDebug() << "Clearing all items";
 #endif
 
+    qDeleteAll(m_filteredItems.values());
     m_filteredItems.clear();
     m_groups.clear();
 
@@ -913,8 +926,6 @@ void KFileItemModel::slotClear()
     m_resortAllItemsTimer->stop();
     m_pendingItemsToInsert.clear();
 
-    m_expandedParentsCountRoot = UninitializedExpandedParentsCountRoot;
-
     const int removedCount = m_itemData.count();
     if (removedCount > 0) {
         qDeleteAll(m_itemData);
@@ -945,197 +956,212 @@ void KFileItemModel::dispatchPendingItemsToInsert()
     }
 }
 
-void KFileItemModel::insertItems(const KFileItemList& items)
+void KFileItemModel::insertItems(QList<ItemData*>& newItems)
 {
-    if (items.isEmpty()) {
+    if (newItems.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();
     kDebug() << "===========================================================";
-    kDebug() << "Inserting" << items.count() << "items";
+    kDebug() << "Inserting" << newItems.count() << "items";
 #endif
 
     m_groups.clear();
 
-    QList<ItemData*> sortedItems = createItemDataList(items);
-    KFileItemModelSortAlgorithm::sort(this, sortedItems.begin(), sortedItems.end());
+    sort(newItems.begin(), newItems.end());
 
 #ifdef KFILEITEMMODEL_DEBUG
     kDebug() << "[TIME] Sorting:" << timer.elapsed();
 #endif
 
     KItemRangeList itemRanges;
-    int targetIndex = 0;
-    int sourceIndex = 0;
-    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()) {
-        // 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))) {
-                break;
+    const int existingItemCount = m_itemData.count();
+    const int newItemCount = newItems.count();
+    const int totalItemCount = existingItemCount + newItemCount;
+
+    if (existingItemCount == 0) {
+        // Optimization for the common special case that there are no
+        // items in the model yet. Happens, e.g., when entering a folder.
+        m_itemData = newItems;
+        itemRanges << KItemRange(0, newItemCount);
+    } else {
+        m_itemData.reserve(totalItemCount);
+        for (int i = existingItemCount; i < totalItemCount; ++i) {
+            m_itemData.append(0);
+        }
+
+        // We build the new list m_items in reverse order to minimize
+        // the number of moves and guarantee O(N) complexity.
+        int targetIndex = totalItemCount - 1;
+        int sourceIndexExistingItems = existingItemCount - 1;
+        int sourceIndexNewItems = newItemCount - 1;
+
+        int rangeCount = 0;
+
+        while (sourceIndexNewItems >= 0) {
+            ItemData* newItem = newItems.at(sourceIndexNewItems);
+            if (sourceIndexExistingItems >= 0 && lessThan(newItem, m_itemData.at(sourceIndexExistingItems))) {
+                // Move an existing item to its new position. If any new items
+                // are behind it, push the item range to itemRanges.
+                if (rangeCount > 0) {
+                    itemRanges << KItemRange(sourceIndexExistingItems + 1, rangeCount);
+                    rangeCount = 0;
+                }
+
+                m_itemData[targetIndex] = m_itemData.at(sourceIndexExistingItems);
+                --sourceIndexExistingItems;
+            } else {
+                // Insert a new item into the list.
+                ++rangeCount;
+                m_itemData[targetIndex] = newItem;
+                --sourceIndexNewItems;
             }
-            ++targetIndex;
+            --targetIndex;
         }
 
-        if (targetIndex - previousTargetIndex > 0 && insertedAtIndex >= 0) {
-            itemRanges << KItemRange(insertedAtIndex, insertedCount);
-            previouslyInsertedCount += insertedCount;
-            insertedAtIndex = targetIndex - previouslyInsertedCount;
-            insertedCount = 0;
+        // Push the final item range to itemRanges.
+        if (rangeCount > 0) {
+            itemRanges << KItemRange(sourceIndexExistingItems + 1, rangeCount);
         }
 
-        // Insert item at the position targetIndex by transferring
-        // the ownership of the item-data from sortedItems to m_itemData.
-        // m_items will be inserted after the loop (see comment below)
-        m_itemData.insert(targetIndex, sortedItems.at(sourceIndex));
-        ++insertedCount;
-
-        if (insertedAtIndex < 0) {
-            insertedAtIndex = targetIndex;
-            Q_ASSERT(previouslyInsertedCount == 0);
-        }
-        ++targetIndex;
-        ++sourceIndex;
+        // Note that itemRanges is still sorted in reverse order.
+        std::reverse(itemRanges.begin(), itemRanges.end());
     }
 
-    // The indexes of all m_items must be adjusted, not only the index
-    // of the new items
-    const int itemDataCount = m_itemData.count();
-    for (int i = 0; i < itemDataCount; ++i) {
+    // 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);
     }
 
-    itemRanges << KItemRange(insertedAtIndex, insertedCount);
     emit itemsInserted(itemRanges);
 
 #ifdef KFILEITEMMODEL_DEBUG
-    kDebug() << "[TIME] Inserting of" << items.count() << "items:" << timer.elapsed();
+    kDebug() << "[TIME] Inserting of" << newItems.count() << "items:" << timer.elapsed();
 #endif
 }
 
-void KFileItemModel::removeItems(const KFileItemList& items)
+static KItemRangeList sortedIndexesToKItemRangeList(const QList<int>& sortedNumbers)
 {
-    if (items.isEmpty()) {
-        return;
+    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;
+    }
+
+    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<ItemData*> sortedItems;
-    sortedItems.reserve(items.count());
+    // 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 int index = m_items.value(item.url(), -1);
+        const KUrl url = item.url();
+        const int index = m_items.value(url, -1);
         if (index >= 0) {
-            sortedItems.append(m_itemData.at(index));
-        }
-    }
-    KFileItemModelSortAlgorithm::sort(this, sortedItems.begin(), sortedItems.end());
+            indexesToRemove.append(index);
 
-    QList<int> indexesToRemove;
-    indexesToRemove.reserve(items.count());
+            // 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);
 
-    // 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;
-
-        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, 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);
     }
 
-    itemRanges << KItemRange(removedAtIndex, removedCount);
     emit itemsRemoved(itemRanges);
 }
 
-QList<KFileItemModel::ItemData*> KFileItemModel::createItemDataList(const KFileItemList& items) const
+QList<KFileItemModel::ItemData*> 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<ItemData*> 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);
     }
 
@@ -1156,9 +1182,8 @@ void KFileItemModel::removeExpandedItems()
 
     // 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();
 }
 
@@ -1221,7 +1246,7 @@ QByteArray KFileItemModel::roleForType(RoleType roleType) const
     return roles.value(roleType);
 }
 
-QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item) const
+QHash<QByteArray, QVariant> 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
@@ -1302,37 +1327,17 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item)
         data.insert("path", path);
     }
 
-    if (m_requestRole[IsExpandedRole]) {
-        data.insert("isExpanded", false);
-    }
-
     if (m_requestRole[IsExpandableRole]) {
         data.insert("isExpandable", item.isDir() && item.url() == item.targetUrl());
     }
 
     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()) {
@@ -1350,11 +1355,34 @@ bool KFileItemModel::lessThan(const ItemData* a, const ItemData* b) const
 {
     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;
         }
     }
 
@@ -1373,6 +1401,44 @@ bool KFileItemModel::lessThan(const ItemData* a, const ItemData* b) const
     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<KFileItemModel::ItemData*>::iterator begin,
+                          QList<KFileItemModel::ItemData*>::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;
@@ -1497,91 +1563,14 @@ int KFileItemModel::stringCompare(const QString& a, const QString& b) const
                             : 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
+bool KFileItemModel::useMaximumUpdateInterval() const
 {
-    Q_ASSERT(isDir);
-    const int pathIndex = itemPath.indexOf('/', start + 1);
-    *isDir = (pathIndex > 0) || item.isDir();
-    return itemPath.mid(start, pathIndex - start);
+    return !m_dirLister->url().isLocalFile();
 }
 
-bool KFileItemModel::useMaximumUpdateInterval() const
+static bool localeAwareLessThan(const QChar& c1, const QChar& c2)
 {
-    return !m_dirLister->url().isLocalFile();
+    return QString::localeAwareCompare(c1, c2) < 0;
 }
 
 QList<QPair<int, QVariant> > KFileItemModel::nameRoleGroups() const
@@ -1593,7 +1582,6 @@ QList<QPair<int, QVariant> > KFileItemModel::nameRoleGroups() const
 
     QString groupValue;
     QChar firstChar;
-    bool isLetter = false;
     for (int i = 0; i <= maxIndex; ++i) {
         if (isChildItem(i)) {
             continue;
@@ -1609,31 +1597,31 @@ QList<QPair<int, QVariant> > KFileItemModel::nameRoleGroups() const
 
         if (firstChar != newFirstChar) {
             QString newGroupValue;
-            if (newFirstChar >= QLatin1Char('A') && newFirstChar <= QLatin1Char('Z')) {
-                // Apply group 'A' - 'Z'
-                newGroupValue = newFirstChar;
-                isLetter = true;
+            if (newFirstChar.isLetter()) {
+                // Try to find a matching group in the range 'A' to 'Z'.
+                static std::vector<QChar> lettersAtoZ;
+                if (lettersAtoZ.empty()) {
+                    for (char c = 'A'; c <= 'Z'; ++c) {
+                        lettersAtoZ.push_back(QLatin1Char(c));
+                    }
+                }
+
+                std::vector<QChar>::iterator it = std::lower_bound(lettersAtoZ.begin(), lettersAtoZ.end(), newFirstChar, localeAwareLessThan);
+                if (it != lettersAtoZ.end()) {
+                    if (localeAwareLessThan(newFirstChar, *it) && it != lettersAtoZ.begin()) {
+                        // newFirstChar belongs to the group preceding *it.
+                        // Example: for an umlaut 'A' in the German locale, *it would be 'B' now.
+                        --it;
+                    }
+                    newGroupValue = *it;
+                } else {
+                    newGroupValue = newFirstChar;
+                }
             } else if (newFirstChar >= QLatin1Char('0') && newFirstChar <= QLatin1Char('9')) {
                 // Apply group '0 - 9' for any name that starts with a digit
                 newGroupValue = i18nc("@title:group Groups that start with a digit", "0 - 9");
-                isLetter = false;
             } else {
-                if (isLetter) {
-                    // If the current group is 'A' - 'Z' check whether a locale character
-                    // fits into the existing group.
-                    // TODO: This does not work in the case if e.g. the group 'O' starts with
-                    // an umlaut 'O' -> provide unit-test to document this known issue
-                    const QChar prevChar(firstChar.unicode() - ushort(1));
-                    const QChar nextChar(firstChar.unicode() + ushort(1));
-                    const QString currChar(newFirstChar);
-                    const bool partOfCurrentGroup = currChar.localeAwareCompare(prevChar) > 0 &&
-                                                    currChar.localeAwareCompare(nextChar) < 0;
-                    if (partOfCurrentGroup) {
-                        continue;
-                    }
-                }
                 newGroupValue = i18nc("@title:group", "Others");
-                isLetter = false;
             }
 
             if (newGroupValue != groupValue) {
@@ -1691,12 +1679,6 @@ QList<QPair<int, QVariant> > KFileItemModel::dateRoleGroups() const
 
     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) {
@@ -1714,20 +1696,9 @@ QList<QPair<int, QVariant> > KFileItemModel::dateRoleGroups() const
 
         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;
@@ -1736,7 +1707,7 @@ QList<QPair<int, QVariant> > KFileItemModel::dateRoleGroups() const
                 }
                 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");
@@ -1759,7 +1730,7 @@ QList<QPair<int, QVariant> > KFileItemModel::dateRoleGroups() const
                 } 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) {
@@ -1980,7 +1951,7 @@ void KFileItemModel::determineMimeTypes(const KFileItemList& items, int timeout)
 {
     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
@@ -1990,4 +1961,51 @@ void KFileItemModel::determineMimeTypes(const KFileItemList& items, int timeout)
     }
 }
 
+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"