]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/kitemviews/kfileitemmodel.cpp
Merge remote-tracking branch 'origin/KDE/4.11'
[dolphin.git] / src / kitemviews / kfileitemmodel.cpp
index 7b039ec4adf0e24bae89770559244e60a2b41ea8..37a30519af74277a985d977cd2acbc0eb8bb3618 100644 (file)
@@ -35,6 +35,9 @@
 #include <QTimer>
 #include <QWidget>
 
+#include <algorithm>
+#include <vector>
+
 // #define KFILEITEMMODEL_DEBUG
 
 KFileItemModel::KFileItemModel(QObject* parent) :
@@ -111,6 +114,7 @@ KFileItemModel::~KFileItemModel()
 {
     qDeleteAll(m_itemData);
     qDeleteAll(m_filteredItems.values());
+    qDeleteAll(m_pendingItemsToInsert);
 }
 
 void KFileItemModel::loadDirectory(const KUrl& url)
@@ -121,8 +125,10 @@ 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);
+    QHashIterator<KUrl, KUrl> expandedDirs(m_expandedDirs);
+    while (expandedDirs.hasNext()) {
+        expandedDirs.next();
+        m_dirLister->openUrl(expandedDirs.value(), KDirLister::Reload);
     }
 
     m_dirLister->openUrl(url, KDirLister::Reload);
@@ -164,7 +170,7 @@ bool KFileItemModel::setData(int index, const QHash<QByteArray, QVariant>& value
     QHashIterator<QByteArray, QVariant> it(values);
     while (it.hasNext()) {
         it.next();
-        const QByteArray role = it.key();
+        const QByteArray role = sharedValue(it.key());
         const QVariant value = it.value();
 
         if (currentValues[role] != value) {
@@ -186,8 +192,30 @@ bool KFileItemModel::setData(int index, const QHash<QByteArray, QVariant>& value
 
     emit itemsChanged(KItemRangeList() << KItemRange(index, 1), changedRoles);
 
-    if (changedRoles.contains(sortRole())) {
-        m_resortAllItemsTimer->start();
+    // Trigger a resorting if the item's correct position has changed. 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))) {
+        // Compare the changed item with its neighbors to see
+        // if an expensive resorting is needed at all.
+        const ItemData* changedItem = m_itemData.at(index);
+        const ItemData* previousItem = (index == 0) ? 0 : m_itemData.at(index - 1);
+        const ItemData* nextItem = (index == m_itemData.count() - 1) ? 0 : m_itemData.at(index + 1);
+
+        if ((previousItem && lessThan(changedItem, previousItem))
+            || (nextItem && lessThan(nextItem, changedItem))) {
+            m_resortAllItemsTimer->start();
+        } else 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();
+        }
     }
 
     return true;
@@ -246,7 +274,7 @@ QMimeData* KFileItemModel::createMimeData(const QSet<int>& indexes) const
         const int index = it.next();
         const KFileItem item = fileItem(index);
         if (!item.isNull()) {
-            urls << item.url();
+            urls << item.targetUrl();
 
             bool isLocal;
             mostLocalUrls << item.mostLocalUrl(isLocal);
@@ -422,18 +450,19 @@ bool KFileItemModel::setExpanded(int index, bool expanded)
     }
 
     QHash<QByteArray, QVariant> values;
-    values.insert("isExpanded", expanded);
+    values.insert(sharedValue("isExpanded"), expanded);
     if (!setData(index, values)) {
         return false;
     }
 
     const KFileItem item = m_itemData.at(index)->item;
     const KUrl url = item.url();
+    const KUrl targetUrl = item.targetUrl();
     if (expanded) {
-        m_expandedDirs.insert(url);
+        m_expandedDirs.insert(targetUrl, url);
         m_dirLister->openUrl(url, KDirLister::Keep);
     } else {
-        m_expandedDirs.remove(url);
+        m_expandedDirs.remove(targetUrl);
         m_dirLister->stop(url);
 
         removeFilteredChildren(KFileItemList() << item);
@@ -475,7 +504,7 @@ int KFileItemModel::expandedParentsCount(int index) const
 
 QSet<KUrl> KFileItemModel::expandedDirectories() const
 {
-    return m_expandedDirs;
+    return m_expandedDirs.values().toSet();
 }
 
 void KFileItemModel::restoreExpandedDirectories(const QSet<KUrl>& urls)
@@ -641,11 +670,11 @@ void KFileItemModel::onSortRoleChanged(const QByteArray& current, const QByteArr
     Q_UNUSED(previous);
     m_sortRole = typeForRole(current);
 
-#ifdef KFILEITEMMODEL_DEBUG
     if (!m_requestRole[m_sortRole]) {
-        kWarning() << "The sort-role has been changed to a role that has not been received yet";
+        QSet<QByteArray> newRoles = m_roles;
+        newRoles << current;
+        setRoles(newRoles);
     }
-#endif
 
     resortAllItems();
 }
@@ -682,7 +711,6 @@ void KFileItemModel::resortAllItems()
         oldUrls.append(itemData->item.url());
     }
 
-    m_groups.clear();
     m_items.clear();
 
     // Resort the items
@@ -691,20 +719,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).url());
-        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();
@@ -752,14 +805,15 @@ void KFileItemModel::slotItemsAdded(const KUrl& directoryUrl, const KFileItemLis
 {
     Q_ASSERT(!items.isEmpty());
 
-    KUrl parentUrl = directoryUrl;
-    parentUrl.adjustPath(KUrl::RemoveTrailingSlash);
+    KUrl parentUrl;
+    if (m_expandedDirs.contains(directoryUrl)) {
+        parentUrl = m_expandedDirs.value(directoryUrl);
+    } else {
+        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();
-
         KFileItem item = items.first();
 
         // If the expanding of items is enabled, the call
@@ -773,6 +827,12 @@ void KFileItemModel::slotItemsAdded(const KUrl& directoryUrl, const KFileItemLis
             return;
         }
 
+        if (directoryUrl != directory()) {
+            // 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.
@@ -843,12 +903,12 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
     kDebug() << "Refreshing" << items.count() << "items";
 #endif
 
-    m_groups.clear();
-
     // Get the indexes of all items that have been refreshed
     QList<int> indexes;
     indexes.reserve(items.count());
 
+    QSet<QByteArray> changedRoles;
+
     QListIterator<QPair<KFileItem, KFileItem> > it(items);
     while (it.hasNext()) {
         const QPair<KFileItem, KFileItem>& itemPair = it.next();
@@ -861,9 +921,14 @@ 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, m_itemData.at(index)->parent));
+            QHash<QByteArray, QVariant>& values = m_itemData[index]->values;
             while (it.hasNext()) {
                 it.next();
-                m_itemData[index]->values.insert(it.key(), it.value());
+                const QByteArray& role = it.key();
+                if (values.value(role) != it.value()) {
+                    values.insert(role, it.value());
+                    changedRoles.insert(role);
+                }
             }
 
             m_items.remove(oldItem.url());
@@ -904,9 +969,11 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
         itemRangeList.append(KItemRange(rangeIndex, rangeCount));
     }
 
-    emit itemsChanged(itemRangeList, m_roles);
+    emit itemsChanged(itemRangeList, changedRoles);
 
-    resortAllItems();
+    if (changedRoles.contains(sortRole())) {
+        m_resortAllItemsTimer->start();
+    }
 }
 
 void KFileItemModel::slotClear()
@@ -921,6 +988,8 @@ void KFileItemModel::slotClear()
 
     m_maximumUpdateIntervalTimer->stop();
     m_resortAllItemsTimer->stop();
+
+    qDeleteAll(m_pendingItemsToInsert);
     m_pendingItemsToInsert.clear();
 
     const int removedCount = m_itemData.count();
@@ -953,9 +1022,9 @@ void KFileItemModel::dispatchPendingItemsToInsert()
     }
 }
 
-void KFileItemModel::insertItems(QList<ItemData*>& items)
+void KFileItemModel::insertItems(QList<ItemData*>& newItems)
 {
-    if (items.isEmpty()) {
+    if (newItems.isEmpty()) {
         return;
     }
 
@@ -963,68 +1032,81 @@ void KFileItemModel::insertItems(QList<ItemData*>& items)
     QElapsedTimer timer;
     timer.start();
     kDebug() << "===========================================================";
-    kDebug() << "Inserting" << items.count() << "items";
+    kDebug() << "Inserting" << newItems.count() << "items";
 #endif
 
     m_groups.clear();
 
-    sort(items.begin(), items.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 < 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), items.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 'items' to m_itemData.
-        // m_items will be inserted after the loop (see comment below)
-        m_itemData.insert(targetIndex, items.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();
-    m_items.reserve(itemDataCount);
-    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
 }
 
@@ -1116,10 +1198,10 @@ void KFileItemModel::removeItems(const KFileItemList& items, RemoveItemsBehavior
 
     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.
+    // 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 = 0; i < newItemDataCount; ++i) {
+    for (int i = itemRanges.front().index; i < newItemDataCount; ++i) {
         m_items.insert(m_itemData.at(i)->item.url(), i);
     }
 
@@ -1236,28 +1318,23 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item,
     // KFileItem::iconName() can be very expensive if the MIME-type is unknown
     // and hence will be retrieved asynchronously by KFileItemModelRolesUpdater.
     QHash<QByteArray, QVariant> data;
-    data.insert("url", item.url());
+    data.insert(sharedValue("url"), item.url());
 
     const bool isDir = item.isDir();
-    if (m_requestRole[IsDirRole]) {
-        data.insert("isDir", isDir);
+    if (m_requestRole[IsDirRole] && isDir) {
+        data.insert(sharedValue("isDir"), true);
     }
 
-    if (m_requestRole[IsLinkRole]) {
-        const bool isLink = item.isLink();
-        data.insert("isLink", isLink);
+    if (m_requestRole[IsLinkRole] && item.isLink()) {
+        data.insert(sharedValue("isLink"), true);
     }
 
     if (m_requestRole[NameRole]) {
-        data.insert("text", item.text());
+        data.insert(sharedValue("text"), item.text());
     }
 
-    if (m_requestRole[SizeRole]) {
-        if (isDir) {
-            data.insert("size", QVariant());
-        } else {
-            data.insert("size", item.size());
-        }
+    if (m_requestRole[SizeRole] && !isDir) {
+        data.insert(sharedValue("size"), item.size());
     }
 
     if (m_requestRole[DateRole]) {
@@ -1265,19 +1342,19 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item,
         // 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("date", dateTime.dateTime());
+        data.insert(sharedValue("date"), dateTime.dateTime());
     }
 
     if (m_requestRole[PermissionsRole]) {
-        data.insert("permissions", item.permissionsString());
+        data.insert(sharedValue("permissions"), item.permissionsString());
     }
 
     if (m_requestRole[OwnerRole]) {
-        data.insert("owner", item.user());
+        data.insert(sharedValue("owner"), item.user());
     }
 
     if (m_requestRole[GroupRole]) {
-        data.insert("group", item.group());
+        data.insert(sharedValue("group"), item.group());
     }
 
     if (m_requestRole[DestinationRole]) {
@@ -1285,7 +1362,7 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item,
         if (destination.isEmpty()) {
             destination = QLatin1String("-");
         }
-        data.insert("destination", destination);
+        data.insert(sharedValue("destination"), destination);
     }
 
     if (m_requestRole[PathRole]) {
@@ -1308,32 +1385,29 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item,
 
         const int index = path.lastIndexOf(item.text());
         path = path.mid(0, index - 1);
-        data.insert("path", path);
-    }
-
-    if (m_requestRole[IsExpandedRole]) {
-        data.insert("isExpanded", false);
+        data.insert(sharedValue("path"), path);
     }
 
-    if (m_requestRole[IsExpandableRole]) {
-        data.insert("isExpandable", item.isDir() && item.url() == item.targetUrl());
+    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 = parent->values["expandedParentsCount"].toInt() + 1;
+            data.insert(sharedValue("expandedParentsCount"), level);
         }
-
-        data.insert("expandedParentsCount", level);
     }
 
     if (item.isMimeTypeKnown()) {
-        data.insert("iconName", item.iconName());
+        data.insert(sharedValue("iconName"), item.iconName());
 
         if (m_requestRole[TypeRole]) {
-            data.insert("type", item.mimeComment());
+            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;
@@ -1556,6 +1630,11 @@ 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());
@@ -1565,7 +1644,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;
@@ -1581,31 +1659,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) {
@@ -1936,7 +2014,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.
@@ -1945,6 +2031,19 @@ void KFileItemModel::determineMimeTypes(const KFileItemList& items, int timeout)
     }
 }
 
+QByteArray KFileItemModel::sharedValue(const QByteArray& value)
+{
+    static QSet<QByteArray> pool;
+    const QSet<QByteArray>::const_iterator it = pool.constFind(value);
+
+    if (it != pool.constEnd()) {
+        return *it;
+    } else {
+        pool.insert(value);
+        return value;
+    }
+}
+
 bool KFileItemModel::isConsistent() const
 {
     if (m_items.count() != m_itemData.count()) {