]> cloud.milkyroute.net Git - dolphin.git/commitdiff
Merge remote-tracking branch 'origin/KDE/4.10'
authorLuca Beltrame <luca.beltrame@marionegri.it>
Wed, 27 Mar 2013 15:50:22 +0000 (16:50 +0100)
committerLuca Beltrame <luca.beltrame@marionegri.it>
Wed, 27 Mar 2013 15:50:22 +0000 (16:50 +0100)
Conflicts:
plasma/applets/folderview/folderview.cpp

1  2 
src/kitemviews/kfileitemmodel.cpp

index 7b039ec4adf0e24bae89770559244e60a2b41ea8,7ba78a436fa57bf74c8f7d7948e27b0deab421ec..9be891ad79609c69f88aeceb810babd8a59770c1
@@@ -1,23 -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"
  
@@@ -35,6 -33,9 +35,9 @@@
  #include <QTimer>
  #include <QWidget>
  
+ #include <algorithm>
+ #include <vector>
  // #define KFILEITEMMODEL_DEBUG
  
  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 +72,7 @@@
      connect(m_dirLister, SIGNAL(started(KUrl)), this, SIGNAL(directoryLoadingStarted()));
      connect(m_dirLister, SIGNAL(canceled()), this, SLOT(slotCanceled()));
      connect(m_dirLister, SIGNAL(completed(KUrl)), this, SLOT(slotCompleted()));
 -    connect(m_dirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList)));
 +    connect(m_dirLister, SIGNAL(itemsAdded(KUrl,KFileItemList)), this, SLOT(slotItemsAdded(KUrl,KFileItemList)));
      connect(m_dirLister, SIGNAL(itemsDeleted(KFileItemList)), this, SLOT(slotItemsDeleted(KFileItemList)));
      connect(m_dirLister, SIGNAL(refreshItems(QList<QPair<KFileItem,KFileItem> >)), this, SLOT(slotRefreshItems(QList<QPair<KFileItem,KFileItem> >)));
      connect(m_dirLister, SIGNAL(clear()), this, SLOT(slotClear()));
  KFileItemModel::~KFileItemModel()
  {
      qDeleteAll(m_itemData);
 -    m_itemData.clear();
 +    qDeleteAll(m_filteredItems.values());
  }
  
  void KFileItemModel::loadDirectory(const KUrl& url)
@@@ -402,7 -405,7 +405,7 @@@ void KFileItemModel::setRoles(const QSe
          // Update m_data with the changed requested roles
          const int maxIndex = count() - 1;
          for (int i = 0; i <= maxIndex; ++i) {
 -            m_itemData[i]->values = retrieveData(m_itemData.at(i)->item);
 +            m_itemData[i]->values = retrieveData(m_itemData.at(i)->item, m_itemData.at(i)->parent);
          }
  
          kWarning() << "TODO: Emitting itemsChanged() with no information what has changed!";
@@@ -427,8 -430,7 +430,8 @@@ bool KFileItemModel::setExpanded(int in
          return false;
      }
  
 -    const KUrl url = m_itemData.at(index)->item.url();
 +    const KFileItem item = m_itemData.at(index)->item;
 +    const KUrl url = item.url();
      if (expanded) {
          m_expandedDirs.insert(url);
          m_dirLister->openUrl(url, KDirLister::Keep);
          m_expandedDirs.remove(url);
          m_dirLister->stop(url);
  
 +        removeFilteredChildren(KFileItemList() << item);
  
 -        KFileItemList itemsToRemove;
 -        const int expandedParentsCount = data(index)["expandedParentsCount"].toInt();
 -        ++index;
 -        while (index < count() && data(index)["expandedParentsCount"].toInt() > expandedParentsCount) {
 -            itemsToRemove.append(m_itemData.at(index)->item);
 -            ++index;
 -        }
 -
 -        QSet<KUrl> urlsToRemove;
 -        urlsToRemove.reserve(itemsToRemove.count() + 1);
 -        urlsToRemove.insert(url);
 -        foreach (const KFileItem& item, itemsToRemove) {
 -            KUrl url = item.url();
 -            url.adjustPath(KUrl::RemoveTrailingSlash);
 -            urlsToRemove.insert(url);
 -        }
 -
 -        QSet<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;
 -            }
 -        }
 -
 -        removeItems(itemsToRemove);
 +        const KFileItemList itemsToRemove = childItems(item);
 +        removeFilteredChildren(itemsToRemove);
 +        removeItems(itemsToRemove, DeleteItemData);
      }
  
      return true;
@@@ -550,57 -579,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;
@@@ -686,7 -689,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);
      }
@@@ -748,14 -751,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();
          // 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.
          }
      }
  
 +    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()) {
@@@ -812,7 -815,7 +815,7 @@@ void KFileItemModel::slotItemsDeleted(c
      dispatchPendingItemsToInsert();
  
      KFileItemList itemsToRemove = items;
 -    if (m_requestRole[ExpandedParentsCountRole] && m_expandedParentsCountRoot >= 0) {
 +    if (m_requestRole[ExpandedParentsCountRole]) {
          // Assure that removing a parent item also results in removing all children
          foreach (const KFileItem& item, items) {
              itemsToRemove.append(childItems(item));
  
      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)
  
              // 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());
@@@ -915,7 -937,6 +918,7 @@@ void KFileItemModel::slotClear(
      kDebug() << "Clearing all items";
  #endif
  
 +    qDeleteAll(m_filteredItems.values());
      m_filteredItems.clear();
      m_groups.clear();
  
      m_resortAllItemsTimer->stop();
      m_pendingItemsToInsert.clear();
  
 -    m_expandedParentsCountRoot = UninitializedExpandedParentsCountRoot;
 -
      const int removedCount = m_itemData.count();
      if (removedCount > 0) {
          qDeleteAll(m_itemData);
@@@ -953,12 -976,19 +956,12 @@@ void KFileItemModel::dispatchPendingIte
      }
  }
  
 -void KFileItemModel::insertItems(const KFileItemList& items)
 +void KFileItemModel::insertItems(QList<ItemData*>& items)
  {
      if (items.isEmpty()) {
          return;
      }
  
 -    if (m_sortRole == TypeRole) {
 -        // Try to resolve the MIME-types synchronously to prevent a reordering of
 -        // the items when sorting by type (per default MIME-types are resolved
 -        // asynchronously by KFileItemModelRolesUpdater).
 -        determineMimeTypes(items, 200);
 -    }
 -
  #ifdef KFILEITEMMODEL_DEBUG
      QElapsedTimer timer;
      timer.start();
  
      m_groups.clear();
  
 -    QList<ItemData*> sortedItems = createItemDataList(items);
 -    KFileItemModelSortAlgorithm::sort(this, sortedItems.begin(), sortedItems.end());
 +    sort(items.begin(), items.end());
  
  #ifdef KFILEITEMMODEL_DEBUG
      kDebug() << "[TIME] Sorting:" << timer.elapsed();
      int insertedAtIndex = -1;        // Index for the current item-range
      int insertedCount = 0;           // Count for the current item-range
      int previouslyInsertedCount = 0; // Sum of previously inserted items for all ranges
 -    while (sourceIndex < sortedItems.count()) {
 +    while (sourceIndex < items.count()) {
          // Find target index from m_items to insert the current item
          // in a sorted order
          const int previousTargetIndex = targetIndex;
          while (targetIndex < m_itemData.count()) {
 -            if (!lessThan(m_itemData.at(targetIndex), sortedItems.at(sourceIndex))) {
 +            if (!lessThan(m_itemData.at(targetIndex), items.at(sourceIndex))) {
                  break;
              }
              ++targetIndex;
          }
  
          // Insert item at the position targetIndex by transferring
 -        // the ownership of the item-data from sortedItems to m_itemData.
 +        // the ownership of the item-data from 'items' to m_itemData.
          // m_items will be inserted after the loop (see comment below)
 -        m_itemData.insert(targetIndex, sortedItems.at(sourceIndex));
 +        m_itemData.insert(targetIndex, items.at(sourceIndex));
          ++insertedCount;
  
          if (insertedAtIndex < 0) {
      // The indexes of all m_items must be adjusted, not only the index
      // of the new items
      const int itemDataCount = m_itemData.count();
 +    m_items.reserve(itemDataCount);
      for (int i = 0; i < itemDataCount; ++i) {
          m_items.insert(m_itemData.at(i)->item.url(), i);
      }
  #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());
 -    foreach (const KFileItem& item, items) {
 -        const int index = m_items.value(item.url(), -1);
 -        if (index >= 0) {
 -            sortedItems.append(m_itemData.at(index));
 -        }
 -    }
 -    KFileItemModelSortAlgorithm::sort(this, sortedItems.begin(), sortedItems.end());
 -
 +    // Step 1: Determine the indexes of the removed items, remove them from
 +    //         the hash m_items, and free the ItemData.
      QList<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);
  
 -    // Calculate the item ranges that will get deleted
 -    KItemRangeList itemRanges;
 -    int removedAtIndex = -1;
 -    int removedCount = 0;
 -    int targetIndex = 0;
 -    foreach (const ItemData* itemData, sortedItems) {
 -        const KFileItem& itemToRemove = itemData->item;
 +            // Prevent repeated expensive rehashing by using QHash::erase(),
 +            // rather than QHash::remove().
 +            QHash<KUrl, int>::iterator it = m_items.find(url);
 +            m_items.erase(it);
  
 -        const int previousTargetIndex = targetIndex;
 -        while (targetIndex < m_itemData.count()) {
 -            if (m_itemData.at(targetIndex)->item.url() == itemToRemove.url()) {
 -                break;
 +            if (behavior == DeleteItemData) {
 +                delete m_itemData.at(index);
              }
 -            ++targetIndex;
 -        }
 -        if (targetIndex >= m_itemData.count()) {
 -            kWarning() << "Item that should be deleted has not been found!";
 -            return;
 -        }
  
 -        if (targetIndex - previousTargetIndex > 0 && removedAtIndex >= 0) {
 -            itemRanges << KItemRange(removedAtIndex, removedCount);
 -            removedAtIndex = targetIndex;
 -            removedCount = 0;
 +            m_itemData[index] = 0;
          }
 +    }
  
 -        indexesToRemove.append(targetIndex);
 -        if (removedAtIndex < 0) {
 -            removedAtIndex = targetIndex;
 -        }
 -        ++removedCount;
 -        ++targetIndex;
 +    if (indexesToRemove.isEmpty()) {
 +        return;
      }
  
 -    // Delete the items
 -    for (int i = indexesToRemove.count() - 1; i >= 0; --i) {
 -        const int indexToRemove = indexesToRemove.at(i);
 -        ItemData* data = m_itemData.at(indexToRemove);
 +    std::sort(indexesToRemove.begin(), indexesToRemove.end());
  
 -        m_items.remove(data->item.url());
 +    // Step 2: Remove the ItemData pointers from the list m_itemData.
 +    const KItemRangeList itemRanges = sortedIndexesToKItemRangeList(indexesToRemove);
 +    int target = itemRanges.at(0).index;
 +    int source = itemRanges.at(0).index + itemRanges.at(0).count;
 +    int nextRange = 1;
  
 -        delete data;
 -        m_itemData.removeAt(indexToRemove);
 -    }
 +    const int oldItemDataCount = m_itemData.count();
 +    while (source < oldItemDataCount) {
 +        m_itemData[target] = m_itemData[source];
 +        ++target;
 +        ++source;
  
 -    // The indexes of all m_items must be adjusted, not only the index
 -    // of the removed items
 -    const int itemDataCount = m_itemData.count();
 -    for (int i = 0; i < itemDataCount; ++i) {
 -        m_items.insert(m_itemData.at(i)->item.url(), i);
 +        if (nextRange < itemRanges.count() && source == itemRanges.at(nextRange).index) {
 +            // Skip the items in the next removed range.
 +            source += itemRanges.at(nextRange).count;
 +            ++nextRange;
 +        }
      }
  
 -    if (count() <= 0) {
 -        m_expandedParentsCountRoot = UninitializedExpandedParentsCountRoot;
 +    m_itemData.erase(m_itemData.end() - indexesToRemove.count(), m_itemData.end());
 +
 +    // Step 3: Adjust indexes in the hash m_items. Note that all indexes
 +    //         might have been changed by the removal of the items.
 +    const int newItemDataCount = m_itemData.count();
 +    for (int i = 0; i < newItemDataCount; ++i) {
 +        m_items.insert(m_itemData.at(i)->item.url(), i);
      }
  
 -    itemRanges << KItemRange(removedAtIndex, removedCount);
      emit itemsRemoved(itemRanges);
  }
  
 -QList<KFileItemModel::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);
      }
  
@@@ -1166,8 -1187,9 +1169,8 @@@ void KFileItemModel::removeExpandedItem
  
      // The m_expandedParentsCountRoot may not get reset before all items with
      // a bigger count have been removed.
 -    removeItems(expandedItems);
 +    removeItems(expandedItems, DeleteItemData);
  
 -    m_expandedParentsCountRoot = UninitializedExpandedParentsCountRoot;
      m_expandedDirs.clear();
  }
  
@@@ -1230,7 -1252,7 +1233,7 @@@ QByteArray KFileItemModel::roleForType(
      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
      }
  
      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()) {
@@@ -1343,34 -1381,11 +1346,34 @@@ bool KFileItemModel::lessThan(const Ite
  {
      int result = 0;
  
 -    if (m_expandedParentsCountRoot >= 0) {
 -        result = expandedParentsCountCompare(a, b);
 -        if (result != 0) {
 -            // The items have parents with different expansion levels
 -            return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0;
 +    if (a->parent != b->parent) {
 +        const int expansionLevelA = a->values.value("expandedParentsCount").toInt();
 +        const int expansionLevelB = b->values.value("expandedParentsCount").toInt();
 +
 +        // If b has a higher expansion level than a, check if a is a parent
 +        // of b, and make sure that both expansion levels are equal otherwise.
 +        for (int i = expansionLevelB; i > expansionLevelA; --i) {
 +            if (b->parent == a) {
 +                return true;
 +            }
 +            b = b->parent;
 +        }
 +
 +        // If a has a higher expansion level than a, check if b is a parent
 +        // of a, and make sure that both expansion levels are equal otherwise.
 +        for (int i = expansionLevelA; i > expansionLevelB; --i) {
 +            if (a->parent == b) {
 +                return false;
 +            }
 +            a = a->parent;
 +        }
 +
 +        Q_ASSERT(a->values.value("expandedParentsCount").toInt() == b->values.value("expandedParentsCount").toInt());
 +
 +        // Compare the last parents of a and b which are different.
 +        while (a->parent != b->parent) {
 +            a = a->parent;
 +            b = b->parent;
          }
      }
  
      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;
@@@ -1551,11 -1528,98 +1554,16 @@@ int KFileItemModel::stringCompare(cons
                              : QString::compare(a, b, Qt::CaseSensitive);
  }
  
 -int KFileItemModel::expandedParentsCountCompare(const ItemData* a, const ItemData* b) const
 -{
 -    const KUrl urlA = a->item.url();
 -    const KUrl urlB = b->item.url();
 -    if (urlA.directory() == urlB.directory()) {
 -        // Both items have the same directory as parent
 -        return 0;
 -    }
 -
 -    // Check whether one item is the parent of the other item
 -    if (urlA.isParentOf(urlB)) {
 -        return (sortOrder() == Qt::AscendingOrder) ? -1 : +1;
 -    } else if (urlB.isParentOf(urlA)) {
 -        return (sortOrder() == Qt::AscendingOrder) ? +1 : -1;
 -    }
 -
 -    // Determine the maximum common path of both items and
 -    // remember the index in 'index'
 -    const QString pathA = urlA.path();
 -    const QString pathB = urlB.path();
 -
 -    const int maxIndex = qMin(pathA.length(), pathB.length()) - 1;
 -    int index = 0;
 -    while (index <= maxIndex && pathA.at(index) == pathB.at(index)) {
 -        ++index;
 -    }
 -    if (index > maxIndex) {
 -        index = maxIndex;
 -    }
 -    while (index > 0 && (pathA.at(index) != QLatin1Char('/') || pathB.at(index) != QLatin1Char('/'))) {
 -        --index;
 -    }
 -
 -    // Determine the first sub-path after the common path and
 -    // check whether it represents a directory or already a file
 -    bool isDirA = true;
 -    const QString subPathA = subPath(a->item, pathA, index, &isDirA);
 -    bool isDirB = true;
 -    const QString subPathB = subPath(b->item, pathB, index, &isDirB);
 -
 -    if (m_sortDirsFirst || m_sortRole == SizeRole) {
 -        if (isDirA && !isDirB) {
 -            return (sortOrder() == Qt::AscendingOrder) ? -1 : +1;
 -        } else if (!isDirA && isDirB) {
 -            return (sortOrder() == Qt::AscendingOrder) ? +1 : -1;
 -        }
 -    }
 -
 -    // Compare the items of the parents that represent the first
 -    // different path after the common path.
 -    const QString parentPathA = pathA.left(index) + subPathA;
 -    const QString parentPathB = pathB.left(index) + subPathB;
 -
 -    const ItemData* parentA = a;
 -    while (parentA && parentA->item.url().path() != parentPathA) {
 -        parentA = parentA->parent;
 -    }
 -
 -    const ItemData* parentB = b;
 -    while (parentB && parentB->item.url().path() != parentPathB) {
 -        parentB = parentB->parent;
 -    }
 -
 -    if (parentA && parentB) {
 -        return sortRoleCompare(parentA, parentB);
 -    }
 -
 -    kWarning() << "Child items without parent detected:" << a->item.url() << b->item.url();
 -    return QString::compare(urlA.url(), urlB.url(), Qt::CaseSensitive);
 -}
 -
 -QString KFileItemModel::subPath(const KFileItem& item,
 -                                const QString& itemPath,
 -                                int start,
 -                                bool* isDir) const
 -{
 -    Q_ASSERT(isDir);
 -    const int pathIndex = itemPath.indexOf('/', start + 1);
 -    *isDir = (pathIndex > 0) || item.isDir();
 -    return itemPath.mid(start, pathIndex - start);
 -}
 -
  bool KFileItemModel::useMaximumUpdateInterval() const
  {
      return !m_dirLister->url().isLocalFile();
  }
  
+ static bool localeAwareLessThan(const QChar& c1, const QChar& c2)
+ {
+     return QString::localeAwareCompare(c1, c2) < 0;
+ }
  QList<QPair<int, QVariant> > KFileItemModel::nameRoleGroups() const
  {
      Q_ASSERT(!m_itemData.isEmpty());
  
      QString groupValue;
      QChar firstChar;
-     bool isLetter = false;
      for (int i = 0; i <= maxIndex; ++i) {
          if (isChildItem(i)) {
              continue;
  
          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) {
@@@ -1663,6 -1726,12 +1670,6 @@@ QList<QPair<int, QVariant> > KFileItemM
  
      const QDate currentDate = KDateTime::currentLocalDateTime().date();
  
 -    int yearForCurrentWeek = 0;
 -    int currentWeek = currentDate.weekNumber(&yearForCurrentWeek);
 -    if (yearForCurrentWeek == currentDate.year() + 1) {
 -        currentWeek = 53;
 -    }
 -
      QDate previousModifiedDate;
      QString groupValue;
      for (int i = 0; i <= maxIndex; ++i) {
  
          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;
                  }
                  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");
                  } 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) {
@@@ -1935,7 -2015,7 +1942,7 @@@ void KFileItemModel::determineMimeTypes
  {
      QElapsedTimer timer;
      timer.start();
 -    foreach (KFileItem item, items) { // krazy:exclude=foreach
 +    foreach (const KFileItem& item, items) { // krazy:exclude=foreach
          item.determineMimeType();
          if (timer.elapsed() > timeout) {
              // Don't block the user interface, let the remaining items
      }
  }
  
 +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"