]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/kitemviews/kfileitemmodel.cpp
Re-organize the code that compares expanded items
[dolphin.git] / src / kitemviews / kfileitemmodel.cpp
index 6c015db372cdb1df85995e56c3d79560cc21b97a..60fc27546bb435e5d73f7378be41334cd50eec49 100644 (file)
@@ -1,5 +1,6 @@
 /***************************************************************************
  *   Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com>             *
+ *   Copyright (C) 2013 by Frank Reininghaus <frank78ac@googlemail.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  *
@@ -58,7 +59,6 @@ KFileItemModel::KFileItemModel(QObject* parent) :
     m_urlsToExpand()
 {
     m_dirLister = new KFileItemModelDirLister(this);
-    m_dirLister->setAutoUpdate(true);
     m_dirLister->setDelayedMimeTypes(true);
 
     const QWidget* parentWidget = qobject_cast<QWidget*>(parent);
@@ -658,7 +658,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);
     }
@@ -941,7 +941,7 @@ void KFileItemModel::insertItems(const KFileItemList& items)
     m_groups.clear();
 
     QList<ItemData*> sortedItems = createItemDataList(items);
-    KFileItemModelSortAlgorithm::sort(this, sortedItems.begin(), sortedItems.end());
+    sort(sortedItems.begin(), sortedItems.end());
 
 #ifdef KFILEITEMMODEL_DEBUG
     kDebug() << "[TIME] Sorting:" << timer.elapsed();
@@ -988,6 +988,7 @@ void KFileItemModel::insertItems(const KFileItemList& items)
     // 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);
     }
@@ -1000,80 +1001,96 @@ void KFileItemModel::insertItems(const KFileItemList& items)
 #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)
+{
 #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));
+            indexesToRemove.append(index);
+
+            // Prevent repeated expensive rehashing by using QHash::erase(),
+            // rather than QHash::remove().
+            QHash<KUrl, int>::iterator it = m_items.find(url);
+            m_items.erase(it);
+
+            ItemData* data = m_itemData.at(index);
+            delete data;
+            m_itemData[index] = 0;
         }
     }
-    KFileItemModelSortAlgorithm::sort(this, sortedItems.begin(), sortedItems.end());
 
-    QList<int> indexesToRemove;
-    indexesToRemove.reserve(items.count());
+    if (indexesToRemove.isEmpty()) {
+        return;
+    }
 
-    // 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;
+    std::sort(indexesToRemove.begin(), indexesToRemove.end());
 
-        const int previousTargetIndex = targetIndex;
-        while (targetIndex < m_itemData.count()) {
-            if (m_itemData.at(targetIndex)->item.url() == itemToRemove.url()) {
-                break;
-            }
-            ++targetIndex;
-        }
-        if (targetIndex >= m_itemData.count()) {
-            kWarning() << "Item that should be deleted has not been found!";
-            return;
-        }
+    // 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;
 
-        if (targetIndex - previousTargetIndex > 0 && removedAtIndex >= 0) {
-            itemRanges << KItemRange(removedAtIndex, removedCount);
-            removedAtIndex = targetIndex;
-            removedCount = 0;
-        }
+    const int oldItemDataCount = m_itemData.count();
+    while (source < oldItemDataCount) {
+        m_itemData[target] = m_itemData[source];
+        ++target;
+        ++source;
 
-        indexesToRemove.append(targetIndex);
-        if (removedAtIndex < 0) {
-            removedAtIndex = targetIndex;
+        if (nextRange < itemRanges.count() && source == itemRanges.at(nextRange).index) {
+            // Skip the items in the next removed range.
+            source += itemRanges.at(nextRange).count;
+            ++nextRange;
         }
-        ++removedCount;
-        ++targetIndex;
     }
 
-    // Delete the items
-    for (int i = indexesToRemove.count() - 1; i >= 0; --i) {
-        const int indexToRemove = indexesToRemove.at(i);
-        ItemData* data = m_itemData.at(indexToRemove);
-
-        m_items.remove(data->item.url());
+    m_itemData.erase(m_itemData.end() - indexesToRemove.count(), m_itemData.end());
 
-        delete data;
-        m_itemData.removeAt(indexToRemove);
-    }
-
-    // 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) {
+    // 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);
     }
 
@@ -1081,7 +1098,6 @@ void KFileItemModel::removeItems(const KFileItemList& items)
         m_expandedParentsCountRoot = UninitializedExpandedParentsCountRoot;
     }
 
-    itemRanges << KItemRange(removedAtIndex, removedCount);
     emit itemsRemoved(itemRanges);
 }
 
@@ -1323,11 +1339,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 (m_expandedParentsCountRoot >= 0 && 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;
         }
     }
 
@@ -1346,6 +1385,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;
@@ -1470,88 +1547,6 @@ 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
-{
-    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();
@@ -1963,4 +1958,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"