]> 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

25 files changed:
src/CMakeLists.txt
src/dolphin.desktop
src/dolphinmainwindow.cpp
src/dolphinviewcontainer.cpp
src/dolphinviewcontainer.h
src/filterbar/filterbar.cpp
src/filterbar/filterbar.h
src/kitemviews/kfileitemmodel.cpp
src/kitemviews/kfileitemmodel.h
src/kitemviews/private/kfileitemmodelsortalgorithm.cpp [deleted file]
src/kitemviews/private/kfileitemmodelsortalgorithm.h
src/main.cpp
src/panels/folders/folderspanel.cpp
src/panels/places/placespanel.cpp
src/tests/CMakeLists.txt
src/tests/kfileitemmodelbenchmark.cpp [new file with mode: 0644]
src/tests/kfileitemmodeltest.cpp
src/views/dolphinremoteencoding.cpp
src/views/dolphinview.cpp
src/views/dolphinview.h
src/views/draganddrophelper.cpp
src/views/draganddrophelper.h
src/views/versioncontrol/updateitemstatesthread.cpp
src/views/versioncontrol/updateitemstatesthread.h
src/views/versioncontrol/versioncontrolobserver.cpp

index 41efa3589dbb2494c38f1faa4c6832bfff6a79c8..ffb232ce286ad6dc646cd17f611606a17eb8ad35 100644 (file)
@@ -1,8 +1,17 @@
-macro_optional_find_package(Soprano)
 macro_optional_find_package(NepomukCore)
+set_package_properties(NepomukCore PROPERTIES DESCRIPTION "Nepomuk Core libraries"
+                       URL "http://www.kde.org"
+                       TYPE OPTIONAL
+                       PURPOSE "For adding desktop-wide tagging support to dolphin"
+                      )
+
 macro_optional_find_package(NepomukWidgets)
-macro_log_feature(NepomukCore_FOUND "Nepomuk Core" "Nepomuk Core functionality" "http://www.kde.org" FALSE "" "For fetching additional file metadata in dolphin")
-macro_log_feature(NepomukWidgets_FOUND "Nepomuk Widgets" "Nepomuk Widgets" "http://www.kde.org" FALSE "" "For adding desktop-wide tagging support to dolphin")
+set_package_properties(NepomukWidgets PROPERTIES DESCRIPTION "Nepomuk Widgets"
+                       URL "http://www.kde.org"
+                       TYPE OPTIONAL
+                       PURPOSE "For adding desktop-wide tagging support to dolphin"
+                      )
+
 if(NepomukCore_FOUND AND NepomukWidgets_FOUND)
     set(HAVE_NEPOMUK TRUE)
 endif()
@@ -15,7 +24,13 @@ configure_file(config-X11.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-X11.h )
 include_directories( ${KACTIVITIES_INCLUDE_DIRS} )
 
 if(HAVE_NEPOMUK)
-  # Yes, Soprano includes is what we need here
+  find_package(Soprano 2.7.56)
+  set_package_properties(Soprano PROPERTIES DESCRIPTION "Qt-based RDF storage and parsing solution"
+                         URL "http://soprano.sourceforge.net"
+                         TYPE REQUIRED
+                         PURPOSE "Required for everything (storage and general data management)"
+                        )
+
   include_directories( ${SOPRANO_INCLUDE_DIR} ${NEPOMUK_CORE_INCLUDE_DIR} ${NEPOMUK_WIDGETS_INCLUDE_DIR} )
 endif()
 
@@ -45,7 +60,6 @@ set(dolphinprivate_LIB_SRCS
     kitemviews/kstandarditemmodel.cpp
     kitemviews/private/kfileitemclipboard.cpp
     kitemviews/private/kfileitemmodeldirlister.cpp
-    kitemviews/private/kfileitemmodelsortalgorithm.cpp
     kitemviews/private/kfileitemmodelfilter.cpp
     kitemviews/private/kitemlistheaderwidget.cpp
     kitemviews/private/kitemlistkeyboardsearchmanager.cpp
index 13d66d657bae903c51b2ac26382cf9aa9ae48d92..db73ed49180ede7797befae64706ec0c208aab0e 100755 (executable)
@@ -182,7 +182,7 @@ GenericName[vi]=Bộ quản lý tập tin
 GenericName[wa]=Manaedjeu di fitchîs
 GenericName[x-test]=xxFile Managerxx
 GenericName[zh_CN]=文件管理器
-GenericName[zh_TW]=檔案管理程式
+GenericName[zh_TW]=檔案管理
 Terminal=false
 MimeType=inode/directory;
 InitialPreference=10
index 9454c8c42cb7f9df775773508ad4aec156f55f28..8ed31dea437cff8803d0893a65b51171d6841aa3 100644 (file)
@@ -1276,7 +1276,8 @@ void DolphinMainWindow::tabDropEvent(int tab, QDropEvent* event)
         const ViewTab& viewTab = m_viewTab[tab];
         const DolphinView* view = viewTab.isPrimaryViewActive ? viewTab.primaryView->view()
                                                               : viewTab.secondaryView->view();
-        const QString error = DragAndDropHelper::dropUrls(view->rootItem(), view->url(), event);
+        QString error;
+        DragAndDropHelper::dropUrls(view->rootItem(), view->url(), event, error);
         if (!error.isEmpty()) {
             activeViewContainer()->showMessage(error, DolphinViewContainer::Error);
         }
index 8800a1732ebe6d3104f605f6aa90931747c9a369..de1ae4b4d0fab05d91d7e7c0a021e4dd712346d5 100644 (file)
@@ -128,6 +128,8 @@ DolphinViewContainer::DolphinViewContainer(const KUrl& url, QWidget* parent) :
             this, SLOT(slotUrlNavigatorLocationChanged(KUrl)));
     connect(m_urlNavigator, SIGNAL(historyChanged()),
             this, SLOT(slotHistoryChanged()));
+    connect(m_urlNavigator, SIGNAL(returnPressed()),
+            this, SLOT(slotReturnPressed()));
 
     // Initialize status bar
     m_statusBar = new DolphinStatusBar(this);
@@ -156,6 +158,8 @@ DolphinViewContainer::DolphinViewContainer(const KUrl& url, QWidget* parent) :
             this, SLOT(setNameFilter(QString)));
     connect(m_filterBar, SIGNAL(closeRequest()),
             this, SLOT(closeFilterBar()));
+    connect(m_filterBar, SIGNAL(focusViewRequest()),
+            this, SLOT(requestFocus()));
     connect(m_view, SIGNAL(urlChanged(KUrl)),
             m_filterBar, SLOT(clear()));
 
@@ -574,6 +578,8 @@ void DolphinViewContainer::slotUrlNavigatorLocationAboutToBeChanged(const KUrl&
 
 void DolphinViewContainer::slotUrlNavigatorLocationChanged(const KUrl& url)
 {
+    slotReturnPressed();
+
     if (KProtocolManager::supportsListing(url)) {
         setSearchModeEnabled(isSearchUrl(url));
         m_view->setUrl(url);
@@ -616,7 +622,8 @@ void DolphinViewContainer::slotUrlNavigatorLocationChanged(const KUrl& url)
 
 void DolphinViewContainer::dropUrls(const KUrl& destination, QDropEvent* event)
 {
-    const QString error = DragAndDropHelper::dropUrls(KFileItem(), destination, event);
+    QString error;
+    DragAndDropHelper::dropUrls(KFileItem(), destination, event, error);
     if (!error.isEmpty()) {
         showMessage(error, Error);
     }
@@ -657,6 +664,13 @@ void DolphinViewContainer::slotHistoryChanged()
     }
 }
 
+void DolphinViewContainer::slotReturnPressed()
+{
+    if (!GeneralSettings::editableUrl()) {
+        m_urlNavigator->setUrlEditable(false);
+    }
+}
+
 void DolphinViewContainer::startSearching()
 {
     const KUrl url = m_searchBox->urlForSearching();
index e2d1b18752ba36e318c3357d6d89da0c3d459309..bc58531a2700d6e50a1c32a4afccb77472255eff 100644 (file)
@@ -282,6 +282,8 @@ private slots:
 
     void slotHistoryChanged();
 
+    void slotReturnPressed();
+
     /**
      * Gets the search URL from the searchbox and starts searching.
      */
index f3076f01070455ef8691f11b8ebdc16fa0b24ee2..7a8951743bc4a5d8c8274dd410de9cc1d1e0e9ef 100644 (file)
@@ -84,12 +84,23 @@ void FilterBar::showEvent(QShowEvent* event)
 void FilterBar::keyReleaseEvent(QKeyEvent* event)
 {
     QWidget::keyReleaseEvent(event);
-    if (event->key() == Qt::Key_Escape) {
+
+    switch (event->key()) {
+    case Qt::Key_Escape:
         if (m_filterInput->text().isEmpty()) {
             emit closeRequest();
         } else {
             m_filterInput->clear();
         }
+        break;
+
+    case Qt::Key_Enter:
+    case Qt::Key_Return:
+        emit focusViewRequest();
+        break;
+
+    default:
+        break;
     }
 }
 
index 9546c6371084cd56b1416c10941b80fcfcd4713c..539d1e23f08b2ddc04f83e0a6a9a948eb92798bb 100644 (file)
@@ -59,6 +59,11 @@ signals:
      */
     void closeRequest();
 
+    /*
+     * Emitted as soon as the focus should be returned back to the view.
+     */
+    void focusViewRequest();
+
 protected:
     virtual void showEvent(QShowEvent* event);
     virtual void keyReleaseEvent(QKeyEvent* event);
index 7ba78a436fa57bf74c8f7d7948e27b0deab421ec..9be891ad79609c69f88aeceb810babd8a59770c1 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"
 
@@ -56,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);
@@ -72,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()));
@@ -113,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)
@@ -405,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!";
@@ -430,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);
@@ -438,38 +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;
-        }
-
-        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;
@@ -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;
@@ -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);
     }
@@ -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();
@@ -776,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.
@@ -785,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()) {
@@ -815,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));
@@ -824,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)
@@ -882,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());
@@ -937,6 +918,7 @@ void KFileItemModel::slotClear()
     kDebug() << "Clearing all items";
 #endif
 
+    qDeleteAll(m_filteredItems.values());
     m_filteredItems.clear();
     m_groups.clear();
 
@@ -944,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);
@@ -976,19 +956,12 @@ void KFileItemModel::dispatchPendingItemsToInsert()
     }
 }
 
-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();
@@ -998,8 +971,7 @@ void KFileItemModel::insertItems(const KFileItemList& items)
 
     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();
@@ -1011,12 +983,12 @@ void KFileItemModel::insertItems(const KFileItemList& items)
     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;
@@ -1030,9 +1002,9 @@ void KFileItemModel::insertItems(const KFileItemList& items)
         }
 
         // 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) {
@@ -1046,6 +1018,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);
     }
@@ -1058,115 +1031,124 @@ 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, 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);
     }
 
@@ -1187,9 +1169,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();
 }
 
@@ -1252,7 +1233,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
@@ -1342,28 +1323,12 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item)
     }
 
     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()) {
@@ -1381,11 +1346,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;
         }
     }
 
@@ -1404,6 +1392,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;
@@ -1528,88 +1554,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();
@@ -1726,12 +1670,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) {
@@ -1749,20 +1687,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;
@@ -1771,7 +1698,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");
@@ -1794,7 +1721,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) {
@@ -2015,7 +1942,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
@@ -2025,4 +1952,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"
index ef9dc98b9a58554f99b4afa0c64b639804ea7215..1d837cb2a5c38938201d9cb914e233e869ac9254 100644 (file)
@@ -272,7 +272,7 @@ private slots:
 
     void slotCompleted();
     void slotCanceled();
-    void slotNewItems(const KFileItemList& items);
+    void slotItemsAdded(const KUrl& directoryUrl, const KFileItemList& items);
     void slotItemsDeleted(const KFileItemList& items);
     void slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >& items);
     void slotClear();
@@ -303,8 +303,13 @@ private:
         ItemData* parent;
     };
 
-    void insertItems(const KFileItemList& items);
-    void removeItems(const KFileItemList& items);
+    enum RemoveItemsBehavior {
+        KeepItemData,
+        DeleteItemData
+    };
+
+    void insertItems(QList<ItemData*>& items);
+    void removeItems(const KFileItemList& items, RemoveItemsBehavior behavior);
 
     /**
      * Helper method for insertItems() and removeItems(): Creates
@@ -312,7 +317,7 @@ private:
      * Note that the ItemData instances are created dynamically and
      * must be deleted by the caller.
      */
-    QList<ItemData*> createItemDataList(const KFileItemList& items) const;
+    QList<ItemData*> createItemDataList(const KUrl& parentUrl, const KFileItemList& items) const;
 
     void removeExpandedItems();
 
@@ -333,7 +338,7 @@ private:
      */
     QByteArray roleForType(RoleType roleType) const;
 
-    QHash<QByteArray, QVariant> retrieveData(const KFileItem& item) const;
+    QHash<QByteArray, QVariant> retrieveData(const KFileItem& item, const ItemData* parent) const;
 
     /**
      * @return True if the item-data \a a should be ordered before the item-data
@@ -341,6 +346,12 @@ private:
      */
     bool lessThan(const ItemData* a, const ItemData* b) const;
 
+    /**
+     * Sorts the items between \a begin and \a end using the comparison
+     * function lessThan().
+     */
+    void sort(QList<ItemData*>::iterator begin, QList<ItemData*>::iterator end) const;
+
     /**
      * Helper method for lessThan() and expandedParentsCountCompare(): Compares
      * the passed item-data using m_sortRole as criteria. Both items must
@@ -350,22 +361,6 @@ private:
 
     int stringCompare(const QString& a, const QString& b) const;
 
-    /**
-     * Compares the expansion level of both items. The "expansion level" is defined
-     * by the number of parent directories. However simply comparing just the numbers
-     * is not sufficient, it is also important to check the hierarchy for having
-     * a correct order like shown in a tree.
-     */
-    int expandedParentsCountCompare(const ItemData* a, const ItemData* b) const;
-
-    /**
-     * Helper method for expandedParentsCountCompare().
-     */
-    QString subPath(const KFileItem& item,
-                    const QString& itemPath,
-                    int start,
-                    bool* isDir) const;
-
     bool useMaximumUpdateInterval() const;
 
     QList<QPair<int, QVariant> > nameRoleGroups() const;
@@ -401,6 +396,12 @@ private:
      */
     void applyFilters();
 
+    /**
+     * Removes filtered items whose expanded parents have been deleted
+     * or collapsed via setExpanded(parentIndex, false).
+     */
+    void removeFilteredChildren(const KFileItemList& parentsList);
+
     /**
      * Maps the QByteArray-roles to RoleTypes and provides translation- and
      * group-contexts.
@@ -428,6 +429,11 @@ private:
      */
     static void determineMimeTypes(const KFileItemList& items, int timeout);
 
+    /**
+     * Checks if the model's internal data structures are consistent.
+     */
+    bool isConsistent() const;
+
 private:
     KFileItemModelDirLister* m_dirLister;
 
@@ -443,32 +449,17 @@ private:
     QHash<KUrl, int> m_items; // Allows O(1) access for KFileItemModel::index(const KFileItem& item)
 
     KFileItemModelFilter m_filter;
-    QSet<KFileItem> m_filteredItems; // Items that got hidden by KFileItemModel::setNameFilter()
+    QHash<KFileItem, ItemData*> m_filteredItems; // Items that got hidden by KFileItemModel::setNameFilter()
 
     bool m_requestRole[RolesCount];
 
     QTimer* m_maximumUpdateIntervalTimer;
     QTimer* m_resortAllItemsTimer;
-    KFileItemList m_pendingItemsToInsert;
+    QList<ItemData*> m_pendingItemsToInsert;
 
     // Cache for KFileItemModel::groups()
     mutable QList<QPair<int, QVariant> > m_groups;
 
-    // Stores the smallest expansion level of the root-URL. Is required to calculate
-    // the "expandedParentsCount" role in an efficient way. A value < 0 indicates a
-    // special meaning:
-    enum ExpandedParentsCountRootTypes
-    {
-        // m_expandedParentsCountRoot is uninitialized and must be determined by checking
-        // the root URL from the KDirLister.
-        UninitializedExpandedParentsCountRoot = -1,
-        // All items should be forced to get an expanded parents count of 0 even if they
-        // represent child items. This is useful for slaves that provide no parent items
-        // for child items like e.g. the search IO slaves.
-        ForceExpandedParentsCountRoot = -2
-    };
-    mutable int m_expandedParentsCountRoot;
-
     // Stores the URLs of the expanded directories.
     QSet<KUrl> m_expandedDirs;
 
@@ -476,9 +467,10 @@ private:
     // and done step after step in slotCompleted().
     QSet<KUrl> m_urlsToExpand;
 
-    friend class KFileItemModelSortAlgorithm;  // Accesses lessThan() method
+    friend class KFileItemModelLessThan;       // Accesses lessThan() method
     friend class KFileItemModelRolesUpdater;   // Accesses emitSortProgress() method
     friend class KFileItemModelTest;           // For unit testing
+    friend class KFileItemModelBenchmark;      // For unit testing
     friend class KFileItemListViewTest;        // For unit testing
     friend class DolphinPart;                  // Accesses m_dirLister
 };
diff --git a/src/kitemviews/private/kfileitemmodelsortalgorithm.cpp b/src/kitemviews/private/kfileitemmodelsortalgorithm.cpp
deleted file mode 100644 (file)
index ab650ef..0000000
+++ /dev/null
@@ -1,190 +0,0 @@
-/***************************************************************************
- *   Copyright (C) 2012 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            *
- ***************************************************************************/
-
-#include "kfileitemmodelsortalgorithm.h"
-
-#include <QThread>
-#include <QtCore>
-
-void KFileItemModelSortAlgorithm::sort(KFileItemModel* model,
-                                       QList<KFileItemModel::ItemData*>::iterator begin,
-                                       QList<KFileItemModel::ItemData*>::iterator end)
-{
-    if (model->sortRole() == model->roleForType(KFileItemModel::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();
-        parallelSort(model, begin, end, 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
-        sequentialSort(model, begin, end);
-    }
-}
-
-void KFileItemModelSortAlgorithm::sequentialSort(KFileItemModel* model,
-                                                 QList< KFileItemModel::ItemData* >::iterator begin,
-                                                 QList< KFileItemModel::ItemData* >::iterator end)
-{
-    // The implementation is based on qStableSortHelper() from qalgorithms.h
-    // Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
-
-    const int span = end - begin;
-    if (span < 2) {
-        return;
-    }
-
-    const QList<KFileItemModel::ItemData*>::iterator middle = begin + span / 2;
-    sequentialSort(model, begin, middle);
-    sequentialSort(model, middle, end);
-    merge(model, begin, middle, end);
-}
-
-void KFileItemModelSortAlgorithm::parallelSort(KFileItemModel* model,
-                                               QList< KFileItemModel::ItemData* >::iterator begin,
-                                               QList< KFileItemModel::ItemData* >::iterator end,
-                                               const int numberOfThreads)
-{
-    const int span = end - begin;
-
-    if (numberOfThreads > 1 && span > 100) {
-        const int newNumberOfThreads = numberOfThreads / 2;
-        const QList<KFileItemModel::ItemData*>::iterator middle = begin + span / 2;
-
-        QFuture<void> future = QtConcurrent::run(parallelSort, model, begin, middle, newNumberOfThreads);
-        parallelSort(model, middle, end, newNumberOfThreads);
-
-        future.waitForFinished();
-
-        merge(model, begin, middle, end);
-    } else {
-        sequentialSort(model, begin, end);
-    }
-}
-
-void KFileItemModelSortAlgorithm::merge(KFileItemModel* model,
-                                        QList<KFileItemModel::ItemData*>::iterator begin,
-                                        QList<KFileItemModel::ItemData*>::iterator pivot,
-                                        QList<KFileItemModel::ItemData*>::iterator end)
-{
-    // The implementation is based on qMerge() from qalgorithms.h
-    // Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
-
-    const int len1 = pivot - begin;
-    const int len2 = end - pivot;
-
-    if (len1 == 0 || len2 == 0) {
-        return;
-    }
-
-    if (len1 + len2 == 2) {
-        if (model->lessThan(*(begin + 1), *(begin))) {
-            qSwap(*begin, *(begin + 1));
-        }
-        return;
-    }
-
-    QList<KFileItemModel::ItemData*>::iterator firstCut;
-    QList<KFileItemModel::ItemData*>::iterator secondCut;
-    int len2Half;
-    if (len1 > len2) {
-        const int len1Half = len1 / 2;
-        firstCut = begin + len1Half;
-        secondCut = lowerBound(model, pivot, end, *firstCut);
-        len2Half = secondCut - pivot;
-    } else {
-        len2Half = len2 / 2;
-        secondCut = pivot + len2Half;
-        firstCut = upperBound(model, begin, pivot, *secondCut);
-    }
-
-    reverse(firstCut, pivot);
-    reverse(pivot, secondCut);
-    reverse(firstCut, secondCut);
-
-    const QList<KFileItemModel::ItemData*>::iterator newPivot = firstCut + len2Half;
-    merge(model, begin, firstCut, newPivot);
-    merge(model, newPivot, secondCut, end);
-}
-
-
-QList<KFileItemModel::ItemData*>::iterator
-KFileItemModelSortAlgorithm::lowerBound(KFileItemModel* model,
-                                        QList<KFileItemModel::ItemData*>::iterator begin,
-                                        QList<KFileItemModel::ItemData*>::iterator end,
-                                        const KFileItemModel::ItemData* value)
-{
-    // The implementation is based on qLowerBound() from qalgorithms.h
-    // Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
-
-    QList<KFileItemModel::ItemData*>::iterator middle;
-    int n = int(end - begin);
-    int half;
-
-    while (n > 0) {
-        half = n >> 1;
-        middle = begin + half;
-        if (model->lessThan(*middle, value)) {
-            begin = middle + 1;
-            n -= half + 1;
-        } else {
-            n = half;
-        }
-    }
-    return begin;
-}
-
-QList<KFileItemModel::ItemData*>::iterator
-KFileItemModelSortAlgorithm::upperBound(KFileItemModel* model,
-                                        QList<KFileItemModel::ItemData*>::iterator begin,
-                                        QList<KFileItemModel::ItemData*>::iterator end,
-                                        const KFileItemModel::ItemData* value)
-{
-    // The implementation is based on qUpperBound() from qalgorithms.h
-    // Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
-
-    QList<KFileItemModel::ItemData*>::iterator middle;
-    int n = end - begin;
-    int half;
-
-    while (n > 0) {
-        half = n >> 1;
-        middle = begin + half;
-        if (model->lessThan(value, *middle)) {
-            n = half;
-        } else {
-            begin = middle + 1;
-            n -= half + 1;
-        }
-    }
-    return begin;
-}
-
-void KFileItemModelSortAlgorithm::reverse(QList<KFileItemModel::ItemData*>::iterator begin,
-                                          QList<KFileItemModel::ItemData*>::iterator end)
-{
-    // The implementation is based on qReverse() from qalgorithms.h
-    // Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
-
-    --end;
-    while (begin < end) {
-        qSwap(*begin++, *end--);
-    }
-}
index 07e5d4a814d02031cb1667fecaf48e42bddea1df..1d56894325ed968979cb31b92f5d44c0474dca4b 100644 (file)
-/***************************************************************************
- *   Copyright (C) 2012 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) 2012 by Peter Penz <peter.penz19@gmail.com>               *
+ *   Copyright (C) 2012 by Emmanuel Pescosta <emmanuelpescosta099@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    *
+ *   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              *
+ *****************************************************************************/
 
 #ifndef KFILEITEMMODELSORTALGORITHM_H
 #define KFILEITEMMODELSORTALGORITHM_H
 
-#include <libdolphin_export.h>
+#include <QtCore>
 
-#include <kitemviews/kfileitemmodel.h>
+#include <algorithm>
 
 /**
- * @brief Sort algorithm for sorting items of KFileItemModel.
- *
- * Sorts the items by using KFileItemModel::lessThan() as comparison criteria.
- * The merge sort algorithm is used to assure a worst-case
- * of O(n * log(n)) and to keep the number of comparisons low.
+ * Sorts the items using the merge sort algorithm is used to assure a
+ * worst-case of O(n * log(n)) and to keep the number of comparisons low.
  *
  * The implementation is based on qStableSortHelper() from qalgorithms.h
  * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
- * The sorting implementations of qAlgorithms could not be used as they
- * don't support having a member-function as comparison criteria.
  */
-class LIBDOLPHINPRIVATE_EXPORT KFileItemModelSortAlgorithm
+
+template <typename RandomAccessIterator, typename LessThan>
+static void mergeSort(RandomAccessIterator begin,
+                      RandomAccessIterator end,
+                      LessThan lessThan)
 {
-public:
-    static void sort(KFileItemModel* model,
-                     QList<KFileItemModel::ItemData*>::iterator begin,
-                     QList<KFileItemModel::ItemData*>::iterator end);
-
-private:
-    static void sequentialSort(KFileItemModel* model,
-                               QList<KFileItemModel::ItemData*>::iterator begin,
-                               QList<KFileItemModel::ItemData*>::iterator end);
-
-    static void parallelSort(KFileItemModel* model,
-                             QList<KFileItemModel::ItemData*>::iterator begin,
-                             QList<KFileItemModel::ItemData*>::iterator end,
-                             const int numberOfThreads);
-
-    static void merge(KFileItemModel* model,
-                      QList<KFileItemModel::ItemData*>::iterator begin,
-                      QList<KFileItemModel::ItemData*>::iterator pivot,
-                      QList<KFileItemModel::ItemData*>::iterator end);
-
-    static QList<KFileItemModel::ItemData*>::iterator
-                lowerBound(KFileItemModel* model,
-                           QList<KFileItemModel::ItemData*>::iterator begin,
-                           QList<KFileItemModel::ItemData*>::iterator end,
-                           const KFileItemModel::ItemData* value);
-
-    static QList<KFileItemModel::ItemData*>::iterator
-                upperBound(KFileItemModel* model,
-                           QList<KFileItemModel::ItemData*>::iterator begin,
-                           QList<KFileItemModel::ItemData*>::iterator end,
-                           const KFileItemModel::ItemData* value);
-
-    static void reverse(QList<KFileItemModel::ItemData*>::iterator begin,
-                        QList<KFileItemModel::ItemData*>::iterator end);
-};
+    // The implementation is based on qStableSortHelper() from qalgorithms.h
+    // Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
 
-#endif
+    const int span = end - begin;
+    if (span < 2) {
+        return;
+    }
+
+    const RandomAccessIterator middle = begin + span / 2;
+    mergeSort(begin, middle, lessThan);
+    mergeSort(middle, end, lessThan);
+    merge(begin, middle, end, lessThan);
+}
+
+/**
+ * Uses up to \a numberOfThreads threads to sort the items between
+ * \a begin and \a end. Only item ranges longer than
+ * \a parallelMergeSortingThreshold are split to be sorted by two different
+ * threads.
+ *
+ * The comparison function \a lessThan must be reentrant.
+ */
+
+template <typename RandomAccessIterator, typename LessThan>
+static void parallelMergeSort(RandomAccessIterator begin,
+                              RandomAccessIterator end,
+                              LessThan lessThan,
+                              int numberOfThreads,
+                              int parallelMergeSortingThreshold = 100)
+{
+    const int span = end - begin;
+
+    if (numberOfThreads > 1 && span > parallelMergeSortingThreshold) {
+        const int newNumberOfThreads = numberOfThreads / 2;
+        const RandomAccessIterator middle = begin + span / 2;
+
+        QFuture<void> future = QtConcurrent::run(parallelMergeSort<RandomAccessIterator, LessThan>, begin, middle, lessThan, newNumberOfThreads, parallelMergeSortingThreshold);
+        parallelMergeSort(middle, end, lessThan, newNumberOfThreads, parallelMergeSortingThreshold);
+
+        future.waitForFinished();
+
+        merge(begin, middle, end, lessThan);
+    } else {
+        mergeSort(begin, end, lessThan);
+    }
+}
 
+/**
+ * Merges the sorted item ranges between \a begin and \a pivot and
+ * between \a pivot and \a end into a single sorted range between
+ * \a begin and \a end.
+ *
+ * The implementation is based on qMerge() from qalgorithms.h
+ * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+ */
+
+template <typename RandomAccessIterator, typename LessThan>
+static void merge(RandomAccessIterator begin,
+                  RandomAccessIterator pivot,
+                  RandomAccessIterator end,
+                  LessThan lessThan)
+{
+    // The implementation is based on qMerge() from qalgorithms.h
+    // Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+
+    const int len1 = pivot - begin;
+    const int len2 = end - pivot;
+
+    if (len1 == 0 || len2 == 0) {
+        return;
+    }
+
+    if (len1 + len2 == 2) {
+        if (lessThan(*(begin + 1), *(begin))) {
+            qSwap(*begin, *(begin + 1));
+        }
+        return;
+    }
+
+    RandomAccessIterator firstCut;
+    RandomAccessIterator secondCut;
+    int len2Half;
+    if (len1 > len2) {
+        const int len1Half = len1 / 2;
+        firstCut = begin + len1Half;
+        secondCut = std::lower_bound(pivot, end, *firstCut, lessThan);
+        len2Half = secondCut - pivot;
+    } else {
+        len2Half = len2 / 2;
+        secondCut = pivot + len2Half;
+        firstCut = std::upper_bound(begin, pivot, *secondCut, lessThan);
+    }
+
+    std::rotate(firstCut, pivot, secondCut);
+
+    RandomAccessIterator newPivot = firstCut + len2Half;
+    merge(begin, firstCut, newPivot, lessThan);
+    merge(newPivot, secondCut, end, lessThan);
+}
+
+#endif
 
index 5addff19444b36dac0f968605d8820b38efe251f..2a47fe65590edec298742fb2acf1b8f3112976b8 100644 (file)
@@ -33,7 +33,7 @@ KDE_EXPORT int kdemain(int argc, char **argv)
 {
     KAboutData about("dolphin", 0,
                      ki18nc("@title", "Dolphin"),
-                     "2.2",
+                     "2.2.60",
                      ki18nc("@title", "File Manager"),
                      KAboutData::License_GPL,
                      ki18nc("@info:credit", "(C) 2006-2012 Peter Penz and Frank Reininghaus"));
@@ -50,6 +50,9 @@ KDE_EXPORT int kdemain(int argc, char **argv)
     about.addAuthor(ki18nc("@info:credit", "David Faure"),
                     ki18nc("@info:credit", "Developer"),
                     "faure@kde.org");
+    about.addAuthor(ki18nc("@info:credit", "Emmanuel Pescosta"),
+                    ki18nc("@info:credit", "Developer"),
+                    "emmanuelpescosta099@gmail.com");
     about.addAuthor(ki18nc("@info:credit", "Aaron J. Seigo"),
                     ki18nc("@info:credit", "Developer"),
                     "aseigo@kde.org");
index 98c06fb35ecf672fbdcbf752ceae6469bb141cc2..46c1b34507c382dbfa396c9cd5b4edd3e9a1d3dc 100644 (file)
@@ -236,7 +236,8 @@ void FoldersPanel::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* eve
                              event->buttons(),
                              event->modifiers());
 
-        const QString error = DragAndDropHelper::dropUrls(destItem, destItem.url(), &dropEvent);
+        QString error;
+        DragAndDropHelper::dropUrls(destItem, destItem.url(), &dropEvent, error);
         if (!error.isEmpty()) {
             emit errorMessage(error);
         }
index 56042d8564f842f4854e54be5aa7f3d09bd9fcf0..0f5b83ad5fdca2193da412de31c02c05dcc6c273 100644 (file)
@@ -363,7 +363,11 @@ void PlacesPanel::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* even
                          event->buttons(),
                          event->modifiers());
 
-    DragAndDropHelper::dropUrls(KFileItem(), destUrl, &dropEvent);
+    QString error;
+    DragAndDropHelper::dropUrls(KFileItem(), destUrl, &dropEvent, error);
+    if (!error.isEmpty()) {
+        emit errorMessage(error);
+    }
 }
 
 void PlacesPanel::slotItemDropEventStorageSetupDone(int index, bool success)
@@ -375,7 +379,11 @@ void PlacesPanel::slotItemDropEventStorageSetupDone(int index, bool success)
         if (success) {
             KUrl destUrl = m_model->placesItem(index)->url();
 
-            DragAndDropHelper::dropUrls(KFileItem(), destUrl, m_itemDropEvent);
+            QString error;
+            DragAndDropHelper::dropUrls(KFileItem(), destUrl, m_itemDropEvent, error);
+            if (!error.isEmpty()) {
+                emit errorMessage(error);
+            }
         }
 
         delete m_itemDropEventMimeData;
@@ -395,7 +403,8 @@ void PlacesPanel::slotAboveItemDropEvent(int index, QGraphicsSceneDragDropEvent*
 void PlacesPanel::slotUrlsDropped(const KUrl& dest, QDropEvent* event, QWidget* parent)
 {
     Q_UNUSED(parent);
-    const QString error = DragAndDropHelper::dropUrls(KFileItem(), dest, event);
+    QString error;
+    DragAndDropHelper::dropUrls(KFileItem(), dest, event, error);
     if (!error.isEmpty()) {
         emit errorMessage(error);
     }
index 6575eecde1d40da11d7f6df4045b5ffef10177a5..543261eac1ca88765c929a435b12a885b856ae9a 100644 (file)
@@ -48,6 +48,16 @@ set(kfileitemmodeltest_SRCS
 kde4_add_unit_test(kfileitemmodeltest TEST ${kfileitemmodeltest_SRCS})
 target_link_libraries(kfileitemmodeltest dolphinprivate ${KDE4_KIO_LIBS} ${QT_QTTEST_LIBRARY})
 
+# KFileItemModelBenchmark
+set(kfileitemmodelbenchmark_SRCS
+    kfileitemmodelbenchmark.cpp
+    testdir.cpp
+    ../kitemviews/kfileitemmodel.cpp
+    ../kitemviews/kitemmodelbase.cpp
+)
+kde4_add_executable(kfileitemmodelbenchmark TEST ${kfileitemmodelbenchmark_SRCS})
+target_link_libraries(kfileitemmodelbenchmark dolphinprivate ${KDE4_KIO_LIBS} ${QT_QTTEST_LIBRARY})
+
 # KItemListKeyboardSearchManagerTest
 set(kitemlistkeyboardsearchmanagertest_SRCS
     kitemlistkeyboardsearchmanagertest.cpp
diff --git a/src/tests/kfileitemmodelbenchmark.cpp b/src/tests/kfileitemmodelbenchmark.cpp
new file mode 100644 (file)
index 0000000..f72e43e
--- /dev/null
@@ -0,0 +1,334 @@
+/***************************************************************************
+ *   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  *
+ *   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 <qtest_kde.h>
+
+#include "kitemviews/kfileitemmodel.h"
+#include "kitemviews/private/kfileitemmodelsortalgorithm.h"
+
+#include "testdir.h"
+
+#include <KRandomSequence>
+
+void myMessageOutput(QtMsgType type, const char* msg)
+{
+    switch (type) {
+    case QtDebugMsg:
+        break;
+    case QtWarningMsg:
+        break;
+    case QtCriticalMsg:
+        fprintf(stderr, "Critical: %s\n", msg);
+        break;
+    case QtFatalMsg:
+        fprintf(stderr, "Fatal: %s\n", msg);
+        abort();
+    default:
+       break;
+    }
+}
+
+namespace {
+    const int DefaultTimeout = 5000;
+};
+
+Q_DECLARE_METATYPE(KFileItemList)
+Q_DECLARE_METATYPE(KItemRangeList)
+
+class KFileItemModelBenchmark : public QObject
+{
+    Q_OBJECT
+
+public:
+    KFileItemModelBenchmark();
+
+private slots:
+    void insertAndRemoveManyItems_data();
+    void insertAndRemoveManyItems();
+    void insertManyChildItems();
+
+private:
+    static KFileItemList createFileItemList(const QStringList& fileNames, const QString& urlPrefix = QLatin1String("file:///"));
+};
+
+KFileItemModelBenchmark::KFileItemModelBenchmark()
+{
+}
+
+void KFileItemModelBenchmark::insertAndRemoveManyItems_data()
+{
+    QTest::addColumn<KFileItemList>("initialItems");
+    QTest::addColumn<KFileItemList>("newItems");
+    QTest::addColumn<KFileItemList>("removedItems");
+    QTest::addColumn<KFileItemList>("expectedFinalItems");
+    QTest::addColumn<KItemRangeList>("expectedItemsInserted");
+    QTest::addColumn<KItemRangeList>("expectedItemsRemoved");
+
+    QList<int> sizes;
+    sizes << 1000 << 4000 << 16000 << 64000 << 256000;
+    //sizes << 50000 << 100000 << 150000 << 200000 << 250000;
+
+    foreach (int n, sizes) {
+        QStringList allStrings;
+        for (int i = 0; i < n; ++i) {
+            allStrings << QString::number(i);
+        }
+
+        // We want to keep the sorting overhead in the benchmark low.
+        // Therefore, we do not use natural sorting. However, this
+        // means that our list is currently not sorted.
+        allStrings.sort();
+
+        KFileItemList all = createFileItemList(allStrings);
+
+        KFileItemList firstHalf, secondHalf, even, odd;
+        for (int i = 0; i < n; ++i) {
+            if (i < n / 2) {
+                firstHalf << all.at(i);
+            } else {
+                secondHalf << all.at(i);
+            }
+
+            if (i % 2 == 0) {
+                even << all.at(i);
+            } else {
+                odd << all.at(i);
+            }
+        }
+
+        KItemRangeList itemRangeListFirstHalf;
+        itemRangeListFirstHalf << KItemRange(0, firstHalf.count());
+
+        KItemRangeList itemRangeListSecondHalf;
+        itemRangeListSecondHalf << KItemRange(firstHalf.count(), secondHalf.count());
+
+        KItemRangeList itemRangeListOddInserted, itemRangeListOddRemoved;
+        for (int i = 0; i < odd.count(); ++i) {
+            // Note that the index in the KItemRange is the index of
+            // the model *before* the items have been inserted.
+            itemRangeListOddInserted << KItemRange(i + 1, 1);
+            itemRangeListOddRemoved << KItemRange(2 * i + 1, 1);
+        }
+
+        const int bufferSize = 128;
+        char buffer[bufferSize];
+
+        snprintf(buffer, bufferSize, "all--n=%i", n);
+        QTest::newRow(buffer) << all << KFileItemList() << KFileItemList() << all << KItemRangeList() << KItemRangeList();
+
+        snprintf(buffer, bufferSize, "1st half + 2nd half--n=%i", n);
+        QTest::newRow(buffer) << firstHalf << secondHalf << KFileItemList() << all << itemRangeListSecondHalf << KItemRangeList();
+
+        snprintf(buffer, bufferSize, "2nd half + 1st half--n=%i", n);
+        QTest::newRow(buffer) << secondHalf << firstHalf << KFileItemList() << all << itemRangeListFirstHalf << KItemRangeList();
+
+        snprintf(buffer, bufferSize, "even + odd--n=%i", n);
+        QTest::newRow(buffer) << even << odd << KFileItemList() << all << itemRangeListOddInserted << KItemRangeList();
+
+        snprintf(buffer, bufferSize, "all - 2nd half--n=%i", n);
+        QTest::newRow(buffer) << all << KFileItemList() << secondHalf << firstHalf << KItemRangeList() << itemRangeListSecondHalf;
+
+        snprintf(buffer, bufferSize, "all - 1st half--n=%i", n);
+        QTest::newRow(buffer) << all << KFileItemList() << firstHalf << secondHalf << KItemRangeList() << itemRangeListFirstHalf;
+
+        snprintf(buffer, bufferSize, "all - odd--n=%i", n);
+        QTest::newRow(buffer) << all << KFileItemList() << odd << even << KItemRangeList() << itemRangeListOddRemoved;
+    }
+}
+
+void KFileItemModelBenchmark::insertAndRemoveManyItems()
+{
+    QFETCH(KFileItemList, initialItems);
+    QFETCH(KFileItemList, newItems);
+    QFETCH(KFileItemList, removedItems);
+    QFETCH(KFileItemList, expectedFinalItems);
+    QFETCH(KItemRangeList, expectedItemsInserted);
+    QFETCH(KItemRangeList, expectedItemsRemoved);
+
+    KFileItemModel model;
+
+    // Avoid overhead caused by natural sorting
+    // and determining the isDir/isLink roles.
+    model.m_naturalSorting = false;
+    model.setRoles(QSet<QByteArray>() << "text");
+
+    QSignalSpy spyItemsInserted(&model, SIGNAL(itemsInserted(KItemRangeList)));
+    QSignalSpy spyItemsRemoved(&model, SIGNAL(itemsRemoved(KItemRangeList)));
+
+    QBENCHMARK {
+        model.slotClear();
+        model.slotItemsAdded(model.directory(), initialItems);
+        model.slotCompleted();
+        QCOMPARE(model.count(), initialItems.count());
+
+        if (!newItems.isEmpty()) {
+            model.slotItemsAdded(model.directory(), newItems);
+            model.slotCompleted();
+        }
+        QCOMPARE(model.count(), initialItems.count() + newItems.count());
+
+        if (!removedItems.isEmpty()) {
+            model.removeItems(removedItems, KFileItemModel::DeleteItemData);
+        }
+        QCOMPARE(model.count(), initialItems.count() + newItems.count() - removedItems.count());
+    }
+
+    QVERIFY(model.isConsistent());
+
+    for (int i = 0; i < model.count(); ++i) {
+        QCOMPARE(model.fileItem(i), expectedFinalItems.at(i));
+    }
+
+    if (!expectedItemsInserted.empty()) {
+        QVERIFY(!spyItemsInserted.empty());
+        const KItemRangeList actualItemsInserted = spyItemsInserted.last().first().value<KItemRangeList>();
+        QCOMPARE(actualItemsInserted, expectedItemsInserted);
+    }
+
+    if (!expectedItemsRemoved.empty()) {
+        QVERIFY(!spyItemsRemoved.empty());
+        const KItemRangeList actualItemsRemoved = spyItemsRemoved.last().first().value<KItemRangeList>();
+        QCOMPARE(actualItemsRemoved, expectedItemsRemoved);
+    }
+}
+
+void KFileItemModelBenchmark::insertManyChildItems()
+{
+    // TODO: this function needs to be adjusted to the changes in KFileItemModel
+    // (replacement of slotNewItems(KFileItemList) by slotItemsAdded(KUrl,KFileItemList))
+    // Currently, this function tries to insert child items of multiple
+    // directories by invoking the slot only once.
+#if 0
+    qInstallMsgHandler(myMessageOutput);
+
+    KFileItemModel model;
+
+    // Avoid overhead caused by natural sorting.
+    model.m_naturalSorting = false;
+
+    QSet<QByteArray> modelRoles = model.roles();
+    modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount";
+    model.setRoles(modelRoles);
+    model.setSortDirectoriesFirst(false);
+
+    // Create a test folder with a 3-level tree structure of folders.
+    TestDir testFolder;
+    int numberOfFolders = 0;
+
+    QStringList subFolderNames;
+    subFolderNames << "a/" << "b/" << "c/" << "d/";
+
+    foreach (const QString& s1, subFolderNames) {
+        ++numberOfFolders;
+        foreach (const QString& s2, subFolderNames) {
+            ++numberOfFolders;
+            foreach (const QString& s3, subFolderNames) {
+                testFolder.createDir("level-1-" + s1 + "level-2-" + s2 + "level-3-" + s3);
+                ++numberOfFolders;
+            }
+        }
+    }
+
+    // Open the folder in the model and expand all subfolders.
+    model.loadDirectory(testFolder.url());
+    QVERIFY(QTest::kWaitForSignal(&model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
+
+    int index = 0;
+    while (index < model.count()) {
+        if (model.isExpandable(index)) {
+            model.setExpanded(index, true);
+
+            if (!model.data(index).value("text").toString().startsWith("level-3")) {
+                // New subfolders will appear unless we are on the final level already.
+                QVERIFY(QTest::kWaitForSignal(&model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
+            }
+
+            QVERIFY(model.isExpanded(index));
+        }
+        ++index;
+    }
+
+    QCOMPARE(model.count(), numberOfFolders);
+
+    // Create a list of many file items, which will be added to each of the
+    // "level 1", "level 2", and "level 3" folders.
+    const int filesPerDirectory = 500;
+    QStringList allStrings;
+    for (int i = 0; i < filesPerDirectory; ++i) {
+        allStrings << QString::number(i);
+    }
+    allStrings.sort();
+
+    KFileItemList newItems;
+
+    // Also keep track of all expected items, including the existing
+    // folders, to verify the final state of the model.
+    KFileItemList allExpectedItems;
+
+    for (int i = 0; i < model.count(); ++i) {
+        const KFileItem folderItem = model.fileItem(i);
+        allExpectedItems << folderItem;
+
+        const KUrl folderUrl = folderItem.url();
+        KFileItemList itemsInFolder = createFileItemList(allStrings, folderUrl.url(KUrl::AddTrailingSlash));
+
+        newItems.append(itemsInFolder);
+        allExpectedItems.append(itemsInFolder);
+    }
+
+    // Bring the items into random order.
+    KRandomSequence randomSequence(0);
+    randomSequence.randomize(newItems);
+
+    // Measure how long it takes to insert and then remove all files.
+    QBENCHMARK {
+        model.slotNewItems(newItems);
+        model.slotCompleted();
+
+        QCOMPARE(model.count(), allExpectedItems.count());
+        QVERIFY(model.isConsistent());
+        for (int i = 0; i < model.count(); ++i) {
+            QCOMPARE(model.fileItem(i), allExpectedItems.at(i));
+        }
+
+        model.slotItemsDeleted(newItems);
+        QCOMPARE(model.count(), numberOfFolders);
+        QVERIFY(model.isConsistent());
+    }
+#endif
+}
+
+KFileItemList KFileItemModelBenchmark::createFileItemList(const QStringList& fileNames, const QString& prefix)
+{
+    // Suppress 'file does not exist anymore' messages from KFileItemPrivate::init().
+    qInstallMsgHandler(myMessageOutput);
+
+    KFileItemList result;
+    foreach (const QString& name, fileNames) {
+        const KUrl url(prefix + name);
+        const KFileItem item(url, QString(), KFileItem::Unknown);
+        result << item;
+    }
+    return result;
+}
+
+QTEST_KDEMAIN(KFileItemModelBenchmark, NoGUI)
+
+#include "kfileitemmodelbenchmark.moc"
index 58e83ac68cf469f7e9e169ec24ee861f1681c0ed..484ddee110ce816bf3146e19ca300c5bdab51d21 100644 (file)
@@ -21,6 +21,8 @@
 #include <qtest_kde.h>
 
 #include <KDirLister>
+#include <kio/job.h>
+
 #include "kitemviews/kfileitemmodel.h"
 #include "kitemviews/private/kfileitemmodeldirlister.h"
 #include "testdir.h"
@@ -71,6 +73,7 @@ private slots:
     void testItemRangeConsistencyWhenInsertingItems();
     void testExpandItems();
     void testExpandParentItems();
+    void testMakeExpandedItemHidden();
     void testSorting();
     void testIndexForKeyboardSearch();
     void testNameFilter();
@@ -78,9 +81,9 @@ private slots:
     void testRemoveHiddenItems();
     void collapseParentOfHiddenItems();
     void removeParentOfHiddenItems();
+    void testGeneralParentChildRelationships();
 
 private:
-    bool isModelConsistent() const;
     QStringList itemsInModel() const;
 
 private:
@@ -155,7 +158,7 @@ void KFileItemModelTest::testNewItems()
 
     QCOMPARE(m_model->count(), 3);
 
-    QVERIFY(isModelConsistent());
+    QVERIFY(m_model->isConsistent());
 }
 
 void KFileItemModelTest::testRemoveItems()
@@ -165,13 +168,13 @@ void KFileItemModelTest::testRemoveItems()
     m_model->loadDirectory(m_testDir->url());
     QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
     QCOMPARE(m_model->count(), 2);
-    QVERIFY(isModelConsistent());
+    QVERIFY(m_model->isConsistent());
 
     m_testDir->removeFile("a.txt");
     m_model->m_dirLister->updateDirectory(m_testDir->url());
     QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsRemoved(KItemRangeList)), DefaultTimeout));
     QCOMPARE(m_model->count(), 1);
-    QVERIFY(isModelConsistent());
+    QVERIFY(m_model->isConsistent());
 }
 
 void KFileItemModelTest::testDirLoadingCompleted()
@@ -214,7 +217,7 @@ void KFileItemModelTest::testDirLoadingCompleted()
     QCOMPARE(itemsRemovedSpy.count(), 2);
     QCOMPARE(m_model->count(), 4);
 
-    QVERIFY(isModelConsistent());
+    QVERIFY(m_model->isConsistent());
 }
 
 void KFileItemModelTest::testSetData()
@@ -235,7 +238,7 @@ void KFileItemModelTest::testSetData()
     values = m_model->data(0);
     QCOMPARE(values.value("customRole1").toString(), QString("Test1"));
     QCOMPARE(values.value("customRole2").toString(), QString("Test2"));
-    QVERIFY(isModelConsistent());
+    QVERIFY(m_model->isConsistent());
 }
 
 void KFileItemModelTest::testSetDataWithModifiedSortRole_data()
@@ -316,7 +319,7 @@ void KFileItemModelTest::testSetDataWithModifiedSortRole()
     QCOMPARE(m_model->data(0).value("rating").toInt(), ratingIndex0);
     QCOMPARE(m_model->data(1).value("rating").toInt(), ratingIndex1);
     QCOMPARE(m_model->data(2).value("rating").toInt(), ratingIndex2);
-    QVERIFY(isModelConsistent());
+    QVERIFY(m_model->isConsistent());
 }
 
 void KFileItemModelTest::testModelConsistencyWhenInsertingItems()
@@ -355,7 +358,7 @@ void KFileItemModelTest::testModelConsistencyWhenInsertingItems()
             QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
         }
 
-        QVERIFY(isModelConsistent());
+        QVERIFY(m_model->isConsistent());
     }
 
     QCOMPARE(m_model->count(), 201);
@@ -489,6 +492,7 @@ void KFileItemModelTest::testExpandItems()
     QCOMPARE(spyRemoved.count(), 1);
     itemRangeList = spyRemoved.takeFirst().at(0).value<KItemRangeList>();
     QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 4)); // 4 items removed
+    QVERIFY(m_model->isConsistent());
 
     // Clear the model, reload the folder and try to restore the expanded folders.
     m_model->clear();
@@ -505,6 +509,7 @@ void KFileItemModelTest::testExpandItems()
     QVERIFY(m_model->isExpanded(3));
     QVERIFY(!m_model->isExpanded(4));
     QCOMPARE(m_model->expandedDirectories(), allFolders);
+    QVERIFY(m_model->isConsistent());
 
     // Move to a sub folder, then call restoreExpandedFolders() *before* going back.
     // This is how DolphinView restores the expanded folders when navigating in history.
@@ -567,6 +572,56 @@ void KFileItemModelTest::testExpandParentItems()
     QVERIFY(m_model->isExpanded(2));
     QVERIFY(m_model->isExpanded(3));
     QVERIFY(!m_model->isExpanded(4));
+    QVERIFY(m_model->isConsistent());
+}
+
+/**
+ * Renaming an expanded folder by prepending its name with a dot makes it
+ * hidden. Verify that this does not cause an inconsistent model state and
+ * a crash later on, see https://bugs.kde.org/show_bug.cgi?id=311947
+ */
+void KFileItemModelTest::testMakeExpandedItemHidden()
+{
+    QSet<QByteArray> modelRoles = m_model->roles();
+    modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount";
+    m_model->setRoles(modelRoles);
+
+    QStringList files;
+    m_testDir->createFile("1a/2a/3a");
+    m_testDir->createFile("1a/2a/3b");
+    m_testDir->createFile("1a/2b");
+    m_testDir->createFile("1b");
+
+    m_model->loadDirectory(m_testDir->url());
+    QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
+
+    // So far, the model contains only "1a/" and "1b".
+    QCOMPARE(m_model->count(), 2);
+    m_model->setExpanded(0, true);
+    QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
+
+    // Now "1a/2a" and "1a/2b" have appeared.
+    QCOMPARE(m_model->count(), 4);
+    m_model->setExpanded(1, true);
+    QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
+    QCOMPARE(m_model->count(), 6);
+
+    // Rename "1a/2" and make it hidden.
+    const QString oldPath = m_model->fileItem(0).url().path() + "/2a";
+    const QString newPath = m_model->fileItem(0).url().path() + "/.2a";
+
+    KIO::SimpleJob* job = KIO::rename(oldPath, newPath, KIO::HideProgressInfo);
+    bool ok = job->exec();
+    QVERIFY(ok);
+    QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsRemoved(KItemRangeList)), DefaultTimeout));
+
+    // "1a/2" and its subfolders have disappeared now.
+    QVERIFY(m_model->isConsistent());
+    QCOMPARE(m_model->count(), 3);
+
+    m_model->setExpanded(0, false);
+    QCOMPARE(m_model->count(), 2);
+
 }
 
 void KFileItemModelTest::testSorting()
@@ -797,7 +852,7 @@ void KFileItemModelTest::testEmptyPath()
     
     KFileItemList items;
     items << KFileItem(emptyUrl, QString(), KFileItem::Unknown) << KFileItem(url, QString(), KFileItem::Unknown);
-    m_model->slotNewItems(items);
+    m_model->slotItemsAdded(emptyUrl, items);
     m_model->slotCompleted();
 }
 
@@ -950,27 +1005,96 @@ void KFileItemModelTest::removeParentOfHiddenItems()
     QCOMPARE(itemsInModel(), QStringList() << "a" << "1");
 }
 
-bool KFileItemModelTest::isModelConsistent() const
+/**
+ * Create a tree structure where parent-child relationships can not be
+ * determined by parsing the URLs, and verify that KFileItemModel
+ * handles them correctly.
+ */
+void KFileItemModelTest::testGeneralParentChildRelationships()
 {
-    if (m_model->m_items.count() != m_model->m_itemData.count()) {
-        return false;
-    }
+    QSet<QByteArray> modelRoles = m_model->roles();
+    modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount";
+    m_model->setRoles(modelRoles);
 
-    for (int i = 0; i < m_model->count(); ++i) {
-        const KFileItem item = m_model->fileItem(i);
-        if (item.isNull()) {
-            qWarning() << "Item" << i << "is null";
-            return false;
-        }
+    QStringList files;
+    files << "parent1/realChild1/realGrandChild1" << "parent2/realChild2/realGrandChild2";
+    m_testDir->createFiles(files);
 
-        const int itemIndex = m_model->index(item);
-        if (itemIndex != i) {
-            qWarning() << "Item" << i << "has a wrong index:" << itemIndex;
-            return false;
-        }
-    }
+    m_model->loadDirectory(m_testDir->url());
+    QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
+    QCOMPARE(itemsInModel(), QStringList() << "parent1" << "parent2");
+
+    // Expand all folders.
+    m_model->setExpanded(0, true);
+    QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
+    QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "parent2");
+
+    m_model->setExpanded(1, true);
+    QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
+    QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "realGrandChild1" << "parent2");
+
+    m_model->setExpanded(3, true);
+    QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
+    QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "realGrandChild1" << "parent2" << "realChild2");
+
+    m_model->setExpanded(4, true);
+    QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
+    QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "realGrandChild1" << "parent2" << "realChild2" << "realGrandChild2");
+
+    // Add some more children and grand-children.
+    const KUrl parent1 = m_model->fileItem(0).url();
+    const KUrl parent2 = m_model->fileItem(3).url();
+    const KUrl realChild1 = m_model->fileItem(1).url();
+    const KUrl realChild2 = m_model->fileItem(4).url();
+
+    m_model->slotItemsAdded(parent1, KFileItemList() << KFileItem(KUrl("child1"), QString(), KFileItem::Unknown));
+    m_model->slotCompleted();
+    QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "realGrandChild1" << "child1" << "parent2" << "realChild2" << "realGrandChild2");
+
+    m_model->slotItemsAdded(parent2, KFileItemList() << KFileItem(KUrl("child2"), QString(), KFileItem::Unknown));
+    m_model->slotCompleted();
+    QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "realGrandChild1" << "child1" << "parent2" << "realChild2" << "realGrandChild2" << "child2");
 
-    return true;
+    m_model->slotItemsAdded(realChild1, KFileItemList() << KFileItem(KUrl("grandChild1"), QString(), KFileItem::Unknown));
+    m_model->slotCompleted();
+    QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "grandChild1" << "realGrandChild1" << "child1" << "parent2" << "realChild2" << "realGrandChild2" << "child2");
+
+    m_model->slotItemsAdded(realChild1, KFileItemList() << KFileItem(KUrl("grandChild1"), QString(), KFileItem::Unknown));
+    m_model->slotCompleted();
+    QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "grandChild1" << "realGrandChild1" << "child1" << "parent2" << "realChild2" << "realGrandChild2" << "child2");
+
+    m_model->slotItemsAdded(realChild2, KFileItemList() << KFileItem(KUrl("grandChild2"), QString(), KFileItem::Unknown));
+    m_model->slotCompleted();
+    QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "grandChild1" << "realGrandChild1" << "child1" << "parent2" << "realChild2" << "grandChild2" << "realGrandChild2" << "child2");
+
+    // Set a name filter that matches nothing -> only expanded folders remain.
+    QSignalSpy itemsRemovedSpy(m_model, SIGNAL(itemsRemoved(KItemRangeList)));
+    m_model->setNameFilter("xyz");
+    QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "parent2" << "realChild2");
+    QCOMPARE(itemsRemovedSpy.count(), 1);
+    QList<QVariant> arguments = itemsRemovedSpy.takeFirst();
+    KItemRangeList itemRangeList = arguments.at(0).value<KItemRangeList>();
+    QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(2, 3) << KItemRange(7, 3));
+
+    // Collapse "parent1".
+    m_model->setExpanded(0, false);
+    QCOMPARE(itemsInModel(), QStringList() << "parent1" << "parent2" << "realChild2");
+    QCOMPARE(itemsRemovedSpy.count(), 1);
+    arguments = itemsRemovedSpy.takeFirst();
+    itemRangeList = arguments.at(0).value<KItemRangeList>();
+    QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 1));
+
+    // Remove "parent2".
+    m_model->slotItemsDeleted(KFileItemList() << m_model->fileItem(1));
+    QCOMPARE(itemsInModel(), QStringList() << "parent1");
+    QCOMPARE(itemsRemovedSpy.count(), 1);
+    arguments = itemsRemovedSpy.takeFirst();
+    itemRangeList = arguments.at(0).value<KItemRangeList>();
+    QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 2));
+
+    // Clear filter, verify that no items reappear.
+    m_model->setNameFilter(QString());
+    QCOMPARE(itemsInModel(), QStringList() << "parent1");
 }
 
 QStringList KFileItemModelTest::itemsInModel() const
index 375b3fd46aac7fbcc12792d47356223a14c60296..04b350eda78c26b3832bf1dd54a2ab6bec567bd3 100644 (file)
@@ -38,7 +38,6 @@
 #include <KMenu>
 #include <KProtocolInfo>
 #include <KProtocolManager>
-#include <KIO/SlaveConfig>
 #include <KIO/Scheduler>
 #include <KConfigGroup>
 
@@ -132,9 +131,7 @@ void DolphinRemoteEncoding::updateMenu()
         m_menu->menu()->actions().at(i)->setChecked(false);
     }
 
-    QString charset = KGlobal::charsets()->descriptionForEncoding(KIO::SlaveConfig::self()->configData(m_currentURL.protocol(),
-                                                                  m_currentURL.host(), DATA_KEY));
-
+    const QString charset = KGlobal::charsets()->descriptionForEncoding(KProtocolManager::charsetFor(m_currentURL));
     if (!charset.isEmpty()) {
         int id = 0;
         bool isFound = false;
index 502ffd428b5ee62d081c391cfdc3bafb7ae41abc..d1e154f68a5d1c9b5e3a8a1cd5f34dd2290c11f3 100644 (file)
@@ -1023,14 +1023,16 @@ void DolphinView::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* even
                          event->buttons(),
                          event->modifiers());
 
-    const QString error = DragAndDropHelper::dropUrls(destItem, destUrl, &dropEvent);
+    QString error;
+    KonqOperations* op = DragAndDropHelper::dropUrls(destItem, destUrl, &dropEvent, error);
     if (!error.isEmpty()) {
         emit infoMessage(error);
     }
 
-    if (destUrl == url()) {
+    if (op && destUrl == url()) {
         // Mark the dropped urls as selected.
-        markPastedUrlsAsSelected(event->mimeData());
+        m_clearSelectionBeforeSelectingNewItems = true;
+        connect(op, SIGNAL(urlPasted(KUrl)), this, SLOT(slotUrlPasted(KUrl)));
     }
 }
 
@@ -1066,6 +1068,11 @@ void DolphinView::slotMouseButtonPressed(int itemIndex, Qt::MouseButtons buttons
     }
 }
 
+void DolphinView::slotAboutToCreate(const KUrl::List& urls)
+{
+    m_selectedUrls << urls;
+}
+
 void DolphinView::slotSelectionChanged(const QSet<int>& current, const QSet<int>& previous)
 {
     const int currentCount = current.count();
@@ -1523,8 +1530,11 @@ void DolphinView::applyModeToView()
 
 void DolphinView::pasteToUrl(const KUrl& url)
 {
-    markPastedUrlsAsSelected(QApplication::clipboard()->mimeData());
-    KonqOperations::doPaste(this, url);
+    KonqOperations* op = KonqOperations::doPasteV2(this, url);
+    if (op) {
+        m_clearSelectionBeforeSelectingNewItems = true;
+        connect(op, SIGNAL(aboutToCreate(KUrl::List)), this, SLOT(slotAboutToCreate(KUrl::List)));
+    }
 }
 
 KUrl::List DolphinView::simplifiedSelectedUrls() const
@@ -1552,18 +1562,6 @@ QMimeData* DolphinView::selectionMimeData() const
     return m_model->createMimeData(selectedIndexes);
 }
 
-void DolphinView::markPastedUrlsAsSelected(const QMimeData* mimeData)
-{
-    const KUrl::List sourceUrls = KUrl::List::fromMimeData(mimeData);
-    KUrl::List destUrls;
-    foreach (const KUrl& source, sourceUrls) {
-        KUrl destination(url().url() + '/' + source.fileName());
-        destUrls << destination;
-    }
-    markUrlsAsSelected(destUrls);
-    m_clearSelectionBeforeSelectingNewItems = true;
-}
-
 void DolphinView::updateWritableState()
 {
     const bool wasFolderWritable = m_isFolderWritable;
index a2fe9f62a13fe8629b52cb8b24efa15b898a1011..13cc66545533b3a34fc419889a699f26e3ad254e 100644 (file)
@@ -566,6 +566,11 @@ private slots:
     void slotModelChanged(KItemModelBase* current, KItemModelBase* previous);
     void slotMouseButtonPressed(int itemIndex, Qt::MouseButtons buttons);
 
+    /*
+     * Is called when new items get pasted or dropped.
+     */
+    void slotAboutToCreate(const KUrl::List& urls);
+
     /**
      * Emits the signal \a selectionChanged() with a small delay. This is
      * because getting all file items for the selection can be an expensive
@@ -721,14 +726,6 @@ private:
      */
     QMimeData* selectionMimeData() const;
 
-    /**
-     * Is invoked after a paste operation or a drag & drop
-     * operation and URLs from \a mimeData as selected.
-     * This allows to select all newly pasted
-     * items in restoreViewState().
-     */
-    void markPastedUrlsAsSelected(const QMimeData* mimeData);
-
     /**
      * Updates m_isFolderWritable dependent on whether the folder represented by
      * the current URL is writable. If the state has changed, the signal
index f81d4d0bfe9d3b63e70745c772fc30878f4201b2..f8ae0ad03473d78d5e43febd9338e533ac855c68 100644 (file)
 #include <QtDBus>
 #include <QDropEvent>
 
-QString DragAndDropHelper::dropUrls(const KFileItem& destItem, const KUrl& destUrl, QDropEvent* event)
+KonqOperations* DragAndDropHelper::dropUrls(const KFileItem& destItem, const KUrl& destUrl, QDropEvent* event, QString& error)
 {
+    error.clear();
+
     if (!destItem.isNull() && !destItem.isWritable()) {
-        return i18nc("@info:status", "Access denied. Could not write to <filename>%1</filename>", destUrl.pathOrUrl());
+        error = i18nc("@info:status", "Access denied. Could not write to <filename>%1</filename>", destUrl.pathOrUrl());
+        return 0;
     }
 
     const QMimeData* mimeData = event->mimeData();
@@ -49,15 +52,16 @@ QString DragAndDropHelper::dropUrls(const KFileItem& destItem, const KUrl& destU
         const KUrl::List urls = KUrl::List::fromMimeData(event->mimeData());
         foreach (const KUrl& url, urls) {
             if (url == destUrl) {
-                return i18nc("@info:status", "A folder cannot be dropped into itself");
+                error = i18nc("@info:status", "A folder cannot be dropped into itself");
+                return 0;
             }
         }
 
-        KonqOperations::doDrop(destItem, destUrl, event, QApplication::activeWindow());
+        return KonqOperations::doDrop(destItem, destUrl, event, QApplication::activeWindow(), QList<QAction*>());
     } else {
-        KonqOperations::doDrop(KFileItem(), destUrl, event, QApplication::activeWindow());
+        return KonqOperations::doDrop(KFileItem(), destUrl, event, QApplication::activeWindow(), QList<QAction*>());
     }
 
-    return QString();
+    return 0;
 }
 
index ac16f7cf2718ba9044f1f219b420293dea479387..eda5fc5c268fb1daa7322cdf5e88ffe778892f6c 100644 (file)
@@ -29,6 +29,7 @@ class KFileItem;
 class KUrl;
 class QDropEvent;
 class QWidget;
+class KonqOperations;
 
 class LIBDOLPHINPRIVATE_EXPORT DragAndDropHelper
 {
@@ -46,13 +47,15 @@ public:
      * @param destUrl   URL of the item destination. Is used only if destItem::isNull()
      *                  is true.
      * @param event     Drop event.
-     * @return          Error message intended to be shown for users if dropping is not
+     * @param error     Error message intended to be shown for users if dropping is not
      *                  possible. If an empty string is returned, the dropping has been
      *                  successful.
+     * @return          KonqOperations pointer
      */
-    static QString dropUrls(const KFileItem& destItem,
-                            const KUrl& destUrl,
-                            QDropEvent* event);
+    static KonqOperations* dropUrls(const KFileItem& destItem,
+                                    const KUrl& destUrl,
+                                    QDropEvent* event,
+                                    QString& error);
 };
 
 #endif
index e07d72c76d380fb6dc1b112a3d2e95c1593e60ad..fa005f8f1a35b79fca7949a27ba3d86ce3f148b6 100644 (file)
 
 #include <QMutexLocker>
 
-UpdateItemStatesThread::UpdateItemStatesThread() :
+UpdateItemStatesThread::UpdateItemStatesThread(KVersionControlPlugin* plugin,
+                                     const QList<VersionControlObserver::ItemState>& itemStates) :
     QThread(),
     m_globalPluginMutex(0),
-    m_plugin(0),
-    m_itemMutex(),
+    m_plugin(plugin),
     m_retrievedItems(false),
-    m_itemStates()
+    m_itemStates(itemStates)
 {
     // Several threads may share one instance of a plugin. A global
     // mutex is required to serialize the retrieval of version control
@@ -42,32 +42,16 @@ UpdateItemStatesThread::~UpdateItemStatesThread()
 {
 }
 
-void UpdateItemStatesThread::setData(KVersionControlPlugin* plugin,
-                                     const QList<VersionControlObserver::ItemState>& itemStates)
-{
-    // The locks are taken in the same order as in run()
-    // to avoid potential deadlock.
-    QMutexLocker pluginLocker(m_globalPluginMutex);
-    QMutexLocker itemLocker(&m_itemMutex);
-
-    m_itemStates = itemStates;
-    m_plugin = plugin;
-}
-
 void UpdateItemStatesThread::run()
 {
     Q_ASSERT(!m_itemStates.isEmpty());
     Q_ASSERT(m_plugin);
 
-    QMutexLocker itemLocker(&m_itemMutex);
-
     const QString directory = m_itemStates.first().item.url().directory(KUrl::AppendTrailingSlash);
     m_retrievedItems = false;
-    itemLocker.unlock();
 
     QMutexLocker pluginLocker(m_globalPluginMutex);
     if (m_plugin->beginRetrieval(directory)) {
-        itemLocker.relock();
         const int count = m_itemStates.count();
 
         KVersionControlPlugin2* pluginV2 = qobject_cast<KVersionControlPlugin2*>(m_plugin);
@@ -99,13 +83,11 @@ void UpdateItemStatesThread::unlockPlugin()
 
 QList<VersionControlObserver::ItemState> UpdateItemStatesThread::itemStates() const
 {
-    QMutexLocker locker(&m_itemMutex);
     return m_itemStates;
 }
 
 bool UpdateItemStatesThread::retrievedItems() const
 {
-    QMutexLocker locker(&m_itemMutex);
     return m_retrievedItems;
 }
 
index f0f91d7d2d969b82c0a1d8fb5476b7f451826e92..a28169755a1b5bdc765f937b353895fec43b56a4 100644 (file)
@@ -38,9 +38,6 @@ class LIBDOLPHINPRIVATE_EXPORT UpdateItemStatesThread : public QThread
     Q_OBJECT
 
 public:
-    UpdateItemStatesThread();
-    virtual ~UpdateItemStatesThread();
-
     /**
      * @param plugin     Version control plugin that is used to update the
      *                   state of the items. Whenever the plugin is accessed
@@ -49,8 +46,9 @@ public:
      *                   UpdateItemStatesThread::unlockPlugin() must be used.
      * @param itemStates List of items, where the states get updated.
      */
-    void setData(KVersionControlPlugin* plugin,
-                 const QList<VersionControlObserver::ItemState>& itemStates);
+    UpdateItemStatesThread(KVersionControlPlugin* plugin,
+                           const QList<VersionControlObserver::ItemState>& itemStates);
+    virtual ~UpdateItemStatesThread();
 
     /**
      * Whenever the plugin is accessed by the thread creator, lockPlugin() must
@@ -76,7 +74,6 @@ private:
     QMutex* m_globalPluginMutex; // Protects the m_plugin globally
     KVersionControlPlugin* m_plugin;
 
-    mutable QMutex m_itemMutex; // Protects m_retrievedItems and m_itemStates
     bool m_retrievedItems;
     QList<VersionControlObserver::ItemState> m_itemStates;
 };
index 64bc268679df0c7218f38c1a11ae4b9a7f9e115a..402a2de54c1644169ceae455486050015461ff7e 100644 (file)
@@ -108,12 +108,7 @@ QList<QAction*> VersionControlObserver::actions(const KFileItemList& items) cons
     if (pluginV2) {
         // Use version 2 of the KVersionControlPlugin which allows providing actions
         // also for non-versioned directories.
-        if (m_updateItemStatesThread && m_updateItemStatesThread->lockPlugin()) {
-            actions = pluginV2->actions(items);
-            m_updateItemStatesThread->unlockPlugin();
-        } else {
-            actions = pluginV2->actions(items);
-        }
+        actions = pluginV2->actions(items);
     } else if (isVersioned()) {
         // Support deprecated interfaces from KVersionControlPlugin version 1.
         // Context menu actions where only available for versioned directories.
@@ -125,14 +120,8 @@ QList<QAction*> VersionControlObserver::actions(const KFileItemList& items) cons
             }
         }
 
-        if (m_updateItemStatesThread && m_updateItemStatesThread->lockPlugin()) {
-            actions = directory.isEmpty() ? m_plugin->contextMenuActions(items)
-                                          : m_plugin->contextMenuActions(directory);
-            m_updateItemStatesThread->unlockPlugin();
-        } else {
-            actions = directory.isEmpty() ? m_plugin->contextMenuActions(items)
-                                          : m_plugin->contextMenuActions(directory);
-        }
+        actions = directory.isEmpty() ? m_plugin->contextMenuActions(items)
+                                      : m_plugin->contextMenuActions(directory);
     }
 
     return actions;
@@ -238,20 +227,12 @@ void VersionControlObserver::slotThreadFinished()
 void VersionControlObserver::updateItemStates()
 {
     Q_ASSERT(m_plugin);
-    if (!m_updateItemStatesThread) {
-        m_updateItemStatesThread = new UpdateItemStatesThread();
-        connect(m_updateItemStatesThread, SIGNAL(finished()),
-                this, SLOT(slotThreadFinished()));
-        connect(m_updateItemStatesThread, SIGNAL(finished()),
-                m_updateItemStatesThread, SLOT(deleteLater()));
-    }
-    else {
+    if (m_updateItemStatesThread) {
         // An update is currently ongoing. Wait until the thread has finished
         // the update (see slotThreadFinished()).
         m_pendingItemStatesUpdate = true;
         return;
     }
-
     QList<ItemState> itemStates;
     const int itemCount = m_model->count();
     itemStates.reserve(itemCount);
@@ -269,7 +250,12 @@ void VersionControlObserver::updateItemStates()
         if (!m_silentUpdate) {
             emit infoMessage(i18nc("@info:status", "Updating version information..."));
         }
-        m_updateItemStatesThread->setData(m_plugin, itemStates);
+        m_updateItemStatesThread = new UpdateItemStatesThread(m_plugin, itemStates);
+        connect(m_updateItemStatesThread, SIGNAL(finished()),
+                this, SLOT(slotThreadFinished()));
+        connect(m_updateItemStatesThread, SIGNAL(finished()),
+                m_updateItemStatesThread, SLOT(deleteLater()));
+
         m_updateItemStatesThread->start(); // slotThreadFinished() is called when finished
     }
 }