]> cloud.milkyroute.net Git - dolphin.git/commitdiff
Merge remote-tracking branch 'origin/KDE/4.10'
authorFrank Reininghaus <frank78ac@googlemail.com>
Thu, 20 Jun 2013 17:44:06 +0000 (19:44 +0200)
committerFrank Reininghaus <frank78ac@googlemail.com>
Thu, 20 Jun 2013 17:44:06 +0000 (19:44 +0200)
46 files changed:
src/CMakeLists.txt
src/dolphin.desktop
src/dolphincontextmenu.cpp
src/dolphincontextmenu.h
src/dolphinmainwindow.cpp
src/dolphinpart.cpp
src/dolphinpart.h
src/dolphinremoveaction.cpp [new file with mode: 0644]
src/dolphinremoveaction.h [new file with mode: 0644]
src/dolphinviewcontainer.cpp
src/dolphinviewcontainer.h
src/filterbar/filterbar.cpp
src/filterbar/filterbar.h
src/kitemviews/kfileitemmodel.cpp
src/kitemviews/kfileitemmodel.h
src/kitemviews/kfileitemmodelrolesupdater.cpp
src/kitemviews/kfileitemmodelrolesupdater.h
src/kitemviews/kitemlistcontroller.cpp
src/kitemviews/kitemlistview.cpp
src/kitemviews/kitemlistview.h
src/kitemviews/private/kfileitemmodelsortalgorithm.cpp [deleted file]
src/kitemviews/private/kfileitemmodelsortalgorithm.h
src/kitemviews/private/kitemlistkeyboardsearchmanager.cpp
src/main.cpp
src/panels/folders/folderspanel.cpp
src/panels/places/placesitem.cpp
src/panels/places/placesitemmodel.cpp
src/panels/places/placespanel.cpp
src/search/dolphinsearchbox.cpp
src/search/dolphinsearchinformation.cpp
src/settings/services/servicessettingspage.cpp
src/statusbar/dolphinstatusbar.cpp
src/statusbar/dolphinstatusbar.h
src/tests/CMakeLists.txt
src/tests/kfileitemmodelbenchmark.cpp [new file with mode: 0644]
src/tests/kfileitemmodeltest.cpp
src/tests/kitemlistkeyboardsearchmanagertest.cpp
src/views/dolphinremoteencoding.cpp
src/views/dolphinview.cpp
src/views/dolphinview.h
src/views/dolphinviewactionhandler.cpp
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..6856991d5ef119d994414edbcfc8cba5c3046114 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
@@ -77,6 +91,7 @@ set(dolphinprivate_LIB_SRCS
     views/viewmodecontroller.cpp
     views/viewproperties.cpp
     views/zoomlevelinfo.cpp
+    dolphinremoveaction.cpp
 )
 
 if(HAVE_NEPOMUK)
index 13d66d657bae903c51b2ac26382cf9aa9ae48d92..9364ebbdf474edc41e32791c6abf6e35e792ec78 100755 (executable)
@@ -88,7 +88,7 @@ Name[wa]=Dolphin
 Name[x-test]=xxDolphinxx
 Name[zh_CN]=Dolphin
 Name[zh_TW]=Dolphin
-Exec=dolphin %i -caption "%c" %u
+Exec=dolphin %i -caption %c %u
 Icon=system-file-manager
 Type=Application
 X-DocPath=dolphin/index.html
@@ -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 bb26c7aaeda87ba77f9e241cfde6c4961ce90640..f66847334621f0d0deab3776ccb0c779ca3cabb9 100644 (file)
@@ -24,6 +24,7 @@
 #include "dolphinnewfilemenu.h"
 #include "dolphinviewcontainer.h"
 #include "dolphin_generalsettings.h"
+#include "dolphinremoveaction.h"
 
 #include <KActionCollection>
 #include <KDesktopFile>
@@ -38,7 +39,6 @@
 #include <KMenuBar>
 #include <KMessageBox>
 #include <KMimeTypeTrader>
-#include <KModifierKeyInfo>
 #include <KNewFileMenu>
 #include <konqmimedata.h>
 #include <konq_operations.h>
 #include "views/dolphinview.h"
 #include "views/viewmodecontroller.h"
 
-K_GLOBAL_STATIC(KModifierKeyInfo, m_keyInfo)
-
 DolphinContextMenu::DolphinContextMenu(DolphinMainWindow* parent,
                                        const QPoint& pos,
                                        const KFileItem& fileInfo,
                                        const KUrl& baseUrl) :
-    QObject(parent),
+    KMenu(parent),
     m_pos(pos),
     m_mainWindow(parent),
     m_fileInfo(fileInfo),
@@ -76,37 +74,20 @@ DolphinContextMenu::DolphinContextMenu(DolphinMainWindow* parent,
     m_context(NoContext),
     m_copyToMenu(parent),
     m_customActions(),
-    m_popup(0),
-    m_command(None),
-    m_shiftPressed(false),
-    m_removeAction(0)
+    m_command(None)
 {
     // The context menu either accesses the URLs of the selected items
     // or the items itself. To increase the performance both lists are cached.
     const DolphinView* view = m_mainWindow->activeViewContainer()->view();
     m_selectedItems = view->selectedItems();
 
-    if (m_keyInfo) {
-        if (m_keyInfo->isKeyPressed(Qt::Key_Shift) || m_keyInfo->isKeyLatched(Qt::Key_Shift)) {
-            m_shiftPressed = true;
-        }
-        connect(m_keyInfo, SIGNAL(keyPressed(Qt::Key,bool)),
-                this, SLOT(slotKeyModifierPressed(Qt::Key,bool)));
-    }
-
-    m_removeAction = new QAction(this);
-    connect(m_removeAction, SIGNAL(triggered()), this, SLOT(slotRemoveActionTriggered()));
-
-    m_popup = new KMenu(m_mainWindow);
+    m_removeAction = new DolphinRemoveAction(this, m_mainWindow->actionCollection());
 }
 
 DolphinContextMenu::~DolphinContextMenu()
 {
     delete m_selectedItemsProperties;
     m_selectedItemsProperties = 0;
-
-    delete m_popup;
-    m_popup = 0;
 }
 
 void DolphinContextMenu::setCustomActions(const QList<QAction*>& actions)
@@ -143,47 +124,39 @@ DolphinContextMenu::Command DolphinContextMenu::open()
     return m_command;
 }
 
-void DolphinContextMenu::initializeModifierKeyInfo()
-{
-    // Access m_keyInfo, so that it gets instantiated by
-    // K_GLOBAL_STATIC
-    KModifierKeyInfo* keyInfo = m_keyInfo;
-    Q_UNUSED(keyInfo);
-}
-
-void DolphinContextMenu::slotKeyModifierPressed(Qt::Key key, bool pressed)
+void DolphinContextMenu::keyPressEvent(QKeyEvent *ev)
 {
-    m_shiftPressed = (key == Qt::Key_Shift) && pressed;
-    updateRemoveAction();
+    if (ev->key() == Qt::Key_Shift) {
+        m_removeAction->update();
+    }
+    KMenu::keyPressEvent(ev);
 }
 
-void DolphinContextMenu::slotRemoveActionTriggered()
+void DolphinContextMenu::keyReleaseEvent(QKeyEvent *ev)
 {
-    const KActionCollection* collection = m_mainWindow->actionCollection();
-    if (moveToTrash()) {
-        collection->action("move_to_trash")->trigger();
-    } else {
-        collection->action("delete")->trigger();
+    if (ev->key() == Qt::Key_Shift) {
+        m_removeAction->update();
     }
+    KMenu::keyReleaseEvent(ev);
 }
 
 void DolphinContextMenu::openTrashContextMenu()
 {
     Q_ASSERT(m_context & TrashContext);
 
-    QAction* emptyTrashAction = new QAction(KIcon("trash-empty"), i18nc("@action:inmenu", "Empty Trash"), m_popup);
+    QAction* emptyTrashAction = new QAction(KIcon("trash-empty"), i18nc("@action:inmenu", "Empty Trash"), this);
     KConfig trashConfig("trashrc", KConfig::SimpleConfig);
     emptyTrashAction->setEnabled(!trashConfig.group("Status").readEntry("Empty", true));
-    m_popup->addAction(emptyTrashAction);
+    addAction(emptyTrashAction);
 
     addCustomActions();
 
     QAction* propertiesAction = m_mainWindow->actionCollection()->action("properties");
-    m_popup->addAction(propertiesAction);
+    addAction(propertiesAction);
 
     addShowMenuBarAction();
 
-    if (m_popup->exec(m_pos) == emptyTrashAction) {
+    if (exec(m_pos) == emptyTrashAction) {
         KonqOperations::emptyTrash(m_mainWindow);
     }
 }
@@ -194,15 +167,15 @@ void DolphinContextMenu::openTrashItemContextMenu()
     Q_ASSERT(m_context & ItemContext);
 
     QAction* restoreAction = new QAction(i18nc("@action:inmenu", "Restore"), m_mainWindow);
-    m_popup->addAction(restoreAction);
+    addAction(restoreAction);
 
     QAction* deleteAction = m_mainWindow->actionCollection()->action("delete");
-    m_popup->addAction(deleteAction);
+    addAction(deleteAction);
 
     QAction* propertiesAction = m_mainWindow->actionCollection()->action("properties");
-    m_popup->addAction(propertiesAction);
+    addAction(propertiesAction);
 
-    if (m_popup->exec(m_pos) == restoreAction) {
+    if (exec(m_pos) == restoreAction) {
         KUrl::List selectedUrls;
         foreach (const KFileItem &item, m_selectedItems) {
             selectedUrls.append(item.url());
@@ -234,41 +207,62 @@ void DolphinContextMenu::openItemContextMenu()
             KMenu* menu = newFileMenu->menu();
             menu->setTitle(i18nc("@title:menu Create new folder, file, link, etc.", "Create New"));
             menu->setIcon(KIcon("document-new"));
-            m_popup->addMenu(menu);
-            m_popup->addSeparator();
+            addMenu(menu);
+            addSeparator();
 
             // insert 'Open in new window' and 'Open in new tab' entries
-            m_popup->addAction(m_mainWindow->actionCollection()->action("open_in_new_window"));
-            m_popup->addAction(m_mainWindow->actionCollection()->action("open_in_new_tab"));
+            addAction(m_mainWindow->actionCollection()->action("open_in_new_window"));
+            addAction(m_mainWindow->actionCollection()->action("open_in_new_tab"));
 
             // insert 'Add to Places' entry
             if (!placeExists(m_fileInfo.url())) {
-                addToPlacesAction = m_popup->addAction(KIcon("bookmark-new"),
+                addToPlacesAction = addAction(KIcon("bookmark-new"),
                                                        i18nc("@action:inmenu Add selected folder to places",
                                                              "Add to Places"));
             }
 
-            m_popup->addSeparator();
+            addSeparator();
         } else if (m_baseUrl.protocol().contains("search")) {
             openParentInNewWindowAction = new QAction(KIcon("window-new"),
                                                     i18nc("@action:inmenu",
                                                           "Open Path in New Window"),
                                                     this);
-            m_popup->addAction(openParentInNewWindowAction);
+            addAction(openParentInNewWindowAction);
 
             openParentInNewTabAction = new QAction(KIcon("tab-new"),
                                                    i18nc("@action:inmenu",
                                                          "Open Path in New Tab"),
                                                    this);
-            m_popup->addAction(openParentInNewTabAction);
+            addAction(openParentInNewTabAction);
+
+            addSeparator();
+        } else if (!DolphinView::openItemAsFolderUrl(m_fileInfo).isEmpty()) {
+            // insert 'Open in new window' and 'Open in new tab' entries
+            addAction(m_mainWindow->actionCollection()->action("open_in_new_window"));
+            addAction(m_mainWindow->actionCollection()->action("open_in_new_tab"));
 
-            m_popup->addSeparator();
+            addSeparator();
+        }
+    } else {
+        bool selectionHasOnlyDirs = true;
+        foreach (const KFileItem& item, m_selectedItems) {
+            const KUrl& url = DolphinView::openItemAsFolderUrl(item);
+            if (url.isEmpty()) {
+                selectionHasOnlyDirs = false;
+                break;
+            }
+        }
+
+        if (selectionHasOnlyDirs) {
+            // insert 'Open in new tab' entry
+            addAction(m_mainWindow->actionCollection()->action("open_in_new_tabs"));
+            addSeparator();
         }
     }
 
     insertDefaultItemActions();
 
-    m_popup->addSeparator();
+    addSeparator();
 
     KFileItemActions fileItemActions;
     fileItemActions.setItemListProperties(selectedItemsProperties());
@@ -282,14 +276,14 @@ void DolphinContextMenu::openItemContextMenu()
     if (GeneralSettings::showCopyMoveMenu()) {
         m_copyToMenu.setItems(m_selectedItems);
         m_copyToMenu.setReadOnly(!selectedItemsProperties().supportsWriting());
-        m_copyToMenu.addActionsTo(m_popup);
+        m_copyToMenu.addActionsTo(this);
     }
 
     // insert 'Properties...' entry
     QAction* propertiesAction = m_mainWindow->actionCollection()->action("properties");
-    m_popup->addAction(propertiesAction);
+    addAction(propertiesAction);
 
-    QAction* activatedAction = m_popup->exec(m_pos);
+    QAction* activatedAction = exec(m_pos);
     if (activatedAction) {
         if (activatedAction == addToPlacesAction) {
             const KUrl selectedUrl(m_fileInfo.url());
@@ -315,26 +309,26 @@ void DolphinContextMenu::openViewportContextMenu()
     newFileMenu->setViewShowsHiddenFiles(view->hiddenFilesShown());
     newFileMenu->checkUpToDate();
     newFileMenu->setPopupFiles(m_baseUrl);
-    m_popup->addMenu(newFileMenu->menu());
-    m_popup->addSeparator();
+    addMenu(newFileMenu->menu());
+    addSeparator();
 
     // Insert 'New Window' and 'New Tab' entries. Don't use "open_in_new_window" and
     // "open_in_new_tab" here, as the current selection should get ignored.
-    m_popup->addAction(m_mainWindow->actionCollection()->action("new_window"));
-    m_popup->addAction(m_mainWindow->actionCollection()->action("new_tab"));
+    addAction(m_mainWindow->actionCollection()->action("new_window"));
+    addAction(m_mainWindow->actionCollection()->action("new_tab"));
 
     // Insert 'Add to Places' entry if exactly one item is selected
     QAction* addToPlacesAction = 0;
     if (!placeExists(m_mainWindow->activeViewContainer()->url())) {
-        addToPlacesAction = m_popup->addAction(KIcon("bookmark-new"),
+        addToPlacesAction = addAction(KIcon("bookmark-new"),
                                              i18nc("@action:inmenu Add current folder to places", "Add to Places"));
     }
 
-    m_popup->addSeparator();
+    addSeparator();
 
     QAction* pasteAction = createPasteAction();
-    m_popup->addAction(pasteAction);
-    m_popup->addSeparator();
+    addAction(pasteAction);
+    addSeparator();
 
     // Insert service actions
     const KFileItemListProperties baseUrlProperties(KFileItemList() << baseFileItem());
@@ -349,11 +343,11 @@ void DolphinContextMenu::openViewportContextMenu()
     addCustomActions();
 
     QAction* propertiesAction = m_mainWindow->actionCollection()->action("properties");
-    m_popup->addAction(propertiesAction);
+    addAction(propertiesAction);
 
     addShowMenuBarAction();
 
-    QAction* action = m_popup->exec(m_pos);
+    QAction* action = exec(m_pos);
     if (addToPlacesAction && (action == addToPlacesAction)) {
         const DolphinViewContainer* container =  m_mainWindow->activeViewContainer();
         if (container->url().isValid()) {
@@ -370,23 +364,23 @@ void DolphinContextMenu::insertDefaultItemActions()
     const KActionCollection* collection = m_mainWindow->actionCollection();
 
     // Insert 'Cut', 'Copy' and 'Paste'
-    m_popup->addAction(collection->action(KStandardAction::name(KStandardAction::Cut)));
-    m_popup->addAction(collection->action(KStandardAction::name(KStandardAction::Copy)));
-    m_popup->addAction(createPasteAction());
+    addAction(collection->action(KStandardAction::name(KStandardAction::Cut)));
+    addAction(collection->action(KStandardAction::name(KStandardAction::Copy)));
+    addAction(createPasteAction());
 
-    m_popup->addSeparator();
+    addSeparator();
 
     // Insert 'Rename'
     QAction* renameAction = collection->action("rename");
-    m_popup->addAction(renameAction);
+    addAction(renameAction);
 
     // Insert 'Move to Trash' and/or 'Delete'
     if (KGlobal::config()->group("KDE").readEntry("ShowDeleteCommand", false)) {
-        m_popup->addAction(collection->action("move_to_trash"));
-        m_popup->addAction(collection->action("delete"));
+        addAction(collection->action("move_to_trash"));
+        addAction(collection->action("delete"));
     } else {
-        m_popup->addAction(m_removeAction);
-        updateRemoveAction();
+        addAction(m_removeAction);
+        m_removeAction->update();
     }
 }
 
@@ -395,8 +389,8 @@ void DolphinContextMenu::addShowMenuBarAction()
     const KActionCollection* ac = m_mainWindow->actionCollection();
     QAction* showMenuBar = ac->action(KStandardAction::name(KStandardAction::ShowMenubar));
     if (!m_mainWindow->menuBar()->isVisible() && !m_mainWindow->toolBar()->isVisible()) {
-        m_popup->addSeparator();
-        m_popup->addAction(showMenuBar);
+        addSeparator();
+        addAction(showMenuBar);
     }
 }
 
@@ -453,10 +447,10 @@ void DolphinContextMenu::addServiceActions(KFileItemActions& fileItemActions)
     fileItemActions.setParentWidget(m_mainWindow);
 
     // insert 'Open With...' action or sub menu
-    fileItemActions.addOpenWithActionsTo(m_popup, "DesktopEntryName != 'dolphin'");
+    fileItemActions.addOpenWithActionsTo(this, "DesktopEntryName != 'dolphin'");
 
     // insert 'Actions' sub menu
-    fileItemActions.addServiceActionsTo(m_popup);
+    fileItemActions.addServiceActionsTo(this);
 }
 
 void DolphinContextMenu::addFileItemPluginActions()
@@ -482,22 +476,27 @@ void DolphinContextMenu::addFileItemPluginActions()
     const KConfigGroup showGroup = config.group("Show");
 
     foreach (const KSharedPtr<KService>& service, pluginServices) {
-        if (!showGroup.readEntry(service->desktopEntryName(), true)) {
-            // The plugin has been disabled
-            continue;
-        }
-
         // Old API (kdelibs-4.6.0 only)
         KFileItemActionPlugin* plugin = service->createInstance<KFileItemActionPlugin>();
         if (plugin) {
-            plugin->setParent(m_popup);
-            m_popup->addActions(plugin->actions(props, m_mainWindow));
+            if (!showGroup.readEntry(service->desktopEntryName(), true)) {
+                // The plugin has been disabled
+                continue;
+            }
+
+            plugin->setParent(this);
+            addActions(plugin->actions(props, m_mainWindow));
         }
         // New API (kdelibs >= 4.6.1)
         KAbstractFileItemActionPlugin* abstractPlugin = service->createInstance<KAbstractFileItemActionPlugin>();
         if (abstractPlugin) {
-            abstractPlugin->setParent(m_popup);
-            m_popup->addActions(abstractPlugin->actions(props, m_mainWindow));
+            if (!showGroup.readEntry(service->desktopEntryName(), abstractPlugin->enabledByDefault())) {
+                // The plugin has been disabled
+                continue;
+            }
+
+            abstractPlugin->setParent(this);
+            addActions(abstractPlugin->actions(props, m_mainWindow));
         }
     }
 }
@@ -508,41 +507,17 @@ void DolphinContextMenu::addVersionControlPluginActions()
     const QList<QAction*> versionControlActions = view->versionControlActions(m_selectedItems);
     if (!versionControlActions.isEmpty()) {
         foreach (QAction* action, versionControlActions) {
-            m_popup->addAction(action);
+            addAction(action);
         }
-        m_popup->addSeparator();
+        addSeparator();
     }
 }
 
 void DolphinContextMenu::addCustomActions()
 {
     foreach (QAction* action, m_customActions) {
-        m_popup->addAction(action);
+        addAction(action);
     }
 }
 
-void DolphinContextMenu::updateRemoveAction()
-{
-    const KActionCollection* collection = m_mainWindow->actionCollection();
-
-    // Using m_removeAction->setText(action->text()) does not apply the &-shortcut.
-    // This is only done until the original action has been shown at least once. To
-    // bypass this issue, the text and &-shortcut is applied manually.
-    const QAction* action = 0;
-    if (moveToTrash()) {
-        action = collection->action("move_to_trash");
-        m_removeAction->setText(i18nc("@action:inmenu", "&Move to Trash"));
-    } else {
-        action = collection->action("delete");
-        m_removeAction->setText(i18nc("@action:inmenu", "&Delete"));
-    }
-    m_removeAction->setIcon(action->icon());
-    m_removeAction->setShortcuts(action->shortcuts());
-}
-
-bool DolphinContextMenu::moveToTrash() const
-{
-    return !m_shiftPressed;
-}
-
 #include "dolphincontextmenu.moc"
index 3d0005d30a21810546dabd754ea166427ada5870..160f088040190ee3a2db4ebdb5957eee9f12fc6b 100644 (file)
@@ -24,6 +24,7 @@
 #include <KService>
 #include <KUrl>
 #include <konq_copytomenu.h>
+#include <KMenu>
 
 #include <QObject>
 
 
 #include <QScopedPointer>
 
-class KMenu;
-class KFileItem;
 class QAction;
 class DolphinMainWindow;
 class KFileItemActions;
 class KFileItemListProperties;
+class DolphinRemoveAction;
 
 /**
  * @brief Represents the context menu which appears when doing a right
@@ -50,7 +50,7 @@ class KFileItemListProperties;
  * - 'Actions':   Contains all actions which can be applied to the
  *                given item.
  */
-class DolphinContextMenu : public QObject
+class DolphinContextMenu : public KMenu
 {
     Q_OBJECT
 
@@ -91,30 +91,9 @@ public:
      */
     Command open();
 
-    /**
-     * TODO: This method is a workaround for a X11-issue in combination
-     * with KModifierKeyInfo: When constructing KModifierKeyInfo in the
-     * constructor of the context menu, the user interface might freeze.
-     * To bypass this, the KModifierKeyInfo is constructed in DolphinMainWindow
-     * directly after starting the application. Remove this method, if
-     * the X11-issue got fixed (contact the maintainer of KModifierKeyInfo for
-     * more details).
-     */
-    static void initializeModifierKeyInfo();
-
-private slots:
-    /**
-     * Is invoked if a key modifier has been pressed and updates the context
-     * menu to show the 'Delete' action instead of the 'Move To Trash' action
-     * if the shift-key has been pressed.
-     */
-    void slotKeyModifierPressed(Qt::Key key, bool pressed);
-
-    /**
-     * Triggers the 'Delete'-action if the shift-key has been pressed, otherwise
-     * the 'Move to Trash'-action gets triggered.
-     */
-    void slotRemoveActionTriggered();
+protected:
+    virtual void keyPressEvent(QKeyEvent *ev);
+    virtual void keyReleaseEvent(QKeyEvent *ev);
 
 private:
     void openTrashContextMenu();
@@ -163,20 +142,6 @@ private:
      */
     void addCustomActions();
 
-    /**
-     * Updates m_removeAction to represent the 'Delete'-action if the shift-key
-     * has been pressed or the selection is not local. Otherwise it represents
-     * the 'Move to Trash'-action.
-     */
-    void updateRemoveAction();
-
-    /**
-     * @return True if a moving to the trash should be done instead of
-     *         deleting the selected items.
-     * @see updateRemoveAction(), slotRemoveActionTriggered()
-     */
-    bool moveToTrash() const;
-
 private:
     struct Entry
     {
@@ -209,12 +174,10 @@ private:
     int m_context;
     KonqCopyToMenu m_copyToMenu;
     QList<QAction*> m_customActions;
-    KMenu* m_popup;
 
     Command m_command;
 
-    bool m_shiftPressed;
-    QAction* m_removeAction; // Action that represents either 'Move To Trash' or 'Delete'
+    DolphinRemoveAction* m_removeAction; // Action that represents either 'Move To Trash' or 'Delete'
 };
 
 #endif
index 9454c8c42cb7f9df775773508ad4aec156f55f28..73001bf54e957c967b8203fc06af7556005f8d12 100644 (file)
@@ -120,11 +120,6 @@ DolphinMainWindow::DolphinMainWindow() :
     m_updateToolBarTimer(0),
     m_lastHandleUrlStatJob(0)
 {
-    // Workaround for a X11-issue in combination with KModifierInfo
-    // (see DolphinContextMenu::initializeModifierKeyInfo() for
-    // more information):
-    DolphinContextMenu::initializeModifierKeyInfo();
-
     setObjectName("Dolphin#");
 
     m_viewTab.append(ViewTab());
@@ -525,11 +520,16 @@ void DolphinMainWindow::activatePrevTab()
 
 void DolphinMainWindow::openInNewTab()
 {
-    const KFileItemList list = m_activeViewContainer->view()->selectedItems();
+    const KFileItemList& list = m_activeViewContainer->view()->selectedItems();
     if (list.isEmpty()) {
         openNewTab(m_activeViewContainer->url());
-    } else if ((list.count() == 1) && list[0].isDir()) {
-        openNewTab(list[0].url());
+    } else {
+        foreach (const KFileItem& item, list) {
+            const KUrl& url = DolphinView::openItemAsFolderUrl(item);
+            if (!url.isEmpty()) {
+                openNewTab(url);
+            }
+        }
     }
 }
 
@@ -540,8 +540,9 @@ void DolphinMainWindow::openInNewWindow()
     const KFileItemList list = m_activeViewContainer->view()->selectedItems();
     if (list.isEmpty()) {
         newWindowUrl = m_activeViewContainer->url();
-    } else if ((list.count() == 1) && list[0].isDir()) {
-        newWindowUrl = list[0].url();
+    } else if (list.count() == 1) {
+        const KFileItem& item = list.first();
+        newWindowUrl = DolphinView::openItemAsFolderUrl(item);
     }
 
     if (!newWindowUrl.isEmpty()) {
@@ -1276,7 +1277,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);
         }
@@ -1649,6 +1651,11 @@ void DolphinMainWindow::setupActions()
     openInNewTab->setIcon(KIcon("tab-new"));
     connect(openInNewTab, SIGNAL(triggered()), this, SLOT(openInNewTab()));
 
+    KAction* openInNewTabs = actionCollection()->addAction("open_in_new_tabs");
+    openInNewTabs->setText(i18nc("@action:inmenu", "Open in New Tabs"));
+    openInNewTabs->setIcon(KIcon("tab-new"));
+    connect(openInNewTabs, SIGNAL(triggered()), this, SLOT(openInNewTab()));
+
     KAction* openInNewWindow = actionCollection()->addAction("open_in_new_window");
     openInNewWindow->setText(i18nc("@action:inmenu", "Open in New Window"));
     openInNewWindow->setIcon(KIcon("window-new"));
index 627ba79c56570e87e01d61c40288761aa8610294..642b1501365a1774a75ac25238e485e96efa29d8 100644 (file)
@@ -18,6 +18,7 @@
 */
 
 #include "dolphinpart.h"
+#include "dolphinremoveaction.h"
 
 #include <KFileItemListProperties>
 #include <konq_operations.h>
@@ -64,6 +65,7 @@ K_EXPORT_PLUGIN(DolphinPartFactory("dolphinpart", "dolphin"))
 DolphinPart::DolphinPart(QWidget* parentWidget, QObject* parent, const QVariantList& args)
     : KParts::ReadOnlyPart(parent)
       ,m_openTerminalAction(0)
+      ,m_removeAction(0)
 {
     Q_UNUSED(args)
     setComponentData(DolphinPartFactory::componentData(), false);
@@ -145,6 +147,10 @@ DolphinPart::DolphinPart(QWidget* parentWidget, QObject* parent, const QVariantL
     m_actionHandler->updateViewActions();
     slotSelectionChanged(KFileItemList()); // initially disable selection-dependent actions
 
+    // Listen to events from the app so we can update the remove key by
+    // checking for a Shift key press.
+    qApp->installEventFilter(this);
+
     // TODO there was a "always open a new window" (when clicking on a directory) setting in konqueror
     // (sort of spacial navigation)
 
@@ -447,10 +453,18 @@ void DolphinPart::slotOpenContextMenu(const QPoint& pos,
             }
         }
 
-        if (addTrash)
+        if (!addTrash || !addDel) {
+            if (!m_removeAction) {
+                m_removeAction = new DolphinRemoveAction(this, actionCollection());
+            }
+            editActions.append(m_removeAction);
+            m_removeAction->update();
+        } else {
+            delete m_removeAction;
+            m_removeAction = 0;
             editActions.append(actionCollection()->action("move_to_trash"));
-        if (addDel)
             editActions.append(actionCollection()->action("delete"));
+        }
 
         // Normally KonqPopupMenu only shows the "Create new" submenu in the current view
         // since otherwise the created file would not be visible.
@@ -593,6 +607,23 @@ void DolphinPart::setFilesToSelect(const KUrl::List& files)
     m_view->markUrlAsCurrent(files.at(0));
 }
 
+bool DolphinPart::eventFilter(QObject* obj, QEvent* event)
+{
+    const int type = event->type();
+
+    if ((type == QEvent::KeyPress || type == QEvent::KeyRelease) && m_removeAction) {
+        QMenu* menu = qobject_cast<QMenu*>(obj);
+        if (menu && menu->parent() == m_view) {
+            QKeyEvent* ev = static_cast<QKeyEvent*>(event);
+            if (ev->key() == Qt::Key_Shift) {
+                m_removeAction->update();
+            }
+        }
+    }
+
+    return KParts::ReadOnlyPart::eventFilter(obj, event);
+}
+
 ////
 
 void DolphinPartBrowserExtension::restoreState(QDataStream &stream)
index 7881ded43c15a1c1d9e4adeee11fe9427c81f272..172bfafc6d14b0dac5c7a5bc3b327902d9b43bbd 100644 (file)
@@ -39,6 +39,7 @@ class DolphinModel;
 class KDirLister;
 class DolphinView;
 class KAboutData;
+class DolphinRemoveAction;
 
 class DolphinPart : public KParts::ReadOnlyPart
 {
@@ -227,6 +228,8 @@ private Q_SLOTS:
     void setFilesToSelect(const KUrl::List& files);
     KUrl::List filesToSelect() const { return KUrl::List(); } // silence moc
 
+    virtual bool eventFilter(QObject*, QEvent*);
+
 private:
     void createActions();
     void createGoAction(const char* name, const char* iconName,
@@ -245,6 +248,7 @@ private:
     KAction* m_findFileAction;
     KAction* m_openTerminalAction;
     QString m_nameFilter;
+    DolphinRemoveAction* m_removeAction;
     Q_DISABLE_COPY(DolphinPart)
 };
 
diff --git a/src/dolphinremoveaction.cpp b/src/dolphinremoveaction.cpp
new file mode 100644 (file)
index 0000000..200fc40
--- /dev/null
@@ -0,0 +1,60 @@
+/***************************************************************************
+ *   Copyright (C) 2013 by Dawit Alemayehu <adawit@kde.org                 *
+ *                                                                         *
+ *   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 "dolphinremoveaction.h"
+
+#include <QApplication>
+
+#include <KLocalizedString>
+
+
+DolphinRemoveAction::DolphinRemoveAction(QObject* parent, KActionCollection* collection) :
+    QAction(parent),
+    m_collection(collection)
+{
+    update();
+    connect(this, SIGNAL(triggered()), this, SLOT(slotRemoveActionTriggered()));
+}
+
+void DolphinRemoveAction::slotRemoveActionTriggered()
+{
+    if (m_action) {
+        m_action->trigger();
+    }
+}
+
+void DolphinRemoveAction::update()
+{
+    Q_ASSERT(m_collection);
+    // Using setText(action->text()) does not apply the &-shortcut.
+    // This is only done until the original action has been shown at least once. To
+    // bypass this issue, the text and &-shortcut is applied manually.
+    if (qApp->keyboardModifiers() & Qt::ShiftModifier) {
+        m_action = m_collection ? m_collection->action("delete") : 0;
+        setText(i18nc("@action:inmenu", "&Delete"));
+    } else {
+        m_action = m_collection ? m_collection->action("move_to_trash") : 0;
+        setText(i18nc("@action:inmenu", "&Move to Trash"));
+    }
+
+   if (m_action) {
+        setIcon(m_action->icon());
+        setShortcuts(m_action->shortcuts());
+    }
+}
diff --git a/src/dolphinremoveaction.h b/src/dolphinremoveaction.h
new file mode 100644 (file)
index 0000000..1a123ac
--- /dev/null
@@ -0,0 +1,55 @@
+/***************************************************************************
+ *   Copyright (C) 2013 by Dawit Alemayehu <adawit@kde.org                 *
+ *                                                                         *
+ *   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 DOLPHINREMOVEACTION_H
+#define DOLPHINREMOVEACTION_H
+
+#include "libdolphin_export.h"
+
+#include <QAction>
+#include <QPointer>
+
+#include <KActionCollection>
+
+/**
+ * A QAction that manages the delete based on the current state of
+ * the Shift key or the parameter passed to update.
+ *
+ * This class expects the presence of both the "move_to_trash" and "delete"
+ * actions in @ref collection.
+ */
+class LIBDOLPHINPRIVATE_EXPORT DolphinRemoveAction : public QAction
+{
+  Q_OBJECT
+public:
+    DolphinRemoveAction(QObject* parent, KActionCollection* collection);
+    /**
+     * Updates this action key based on the state of the Shift key.
+     */
+    void update();
+
+private Q_SLOTS:
+    void slotRemoveActionTriggered();
+
+private:
+    QPointer<KActionCollection> m_collection;
+    QPointer<QAction> m_action;
+};
+
+#endif
index 8800a1732ebe6d3104f605f6aa90931747c9a369..71dc5fd7b50808731992cf1f0d7a3679497ea863 100644 (file)
@@ -106,6 +106,7 @@ DolphinViewContainer::DolphinViewContainer(const KUrl& url, QWidget* parent) :
 
     m_view = new DolphinView(url, this);
     connect(m_view, SIGNAL(urlChanged(KUrl)),                   m_urlNavigator, SLOT(setUrl(KUrl)));
+    connect(m_view, SIGNAL(urlChanged(KUrl)),                   m_messageWidget, SLOT(hide()));
     connect(m_view, SIGNAL(writeStateChanged(bool)),            this, SIGNAL(writeStateChanged(bool)));
     connect(m_view, SIGNAL(requestItemInfo(KFileItem)),         this, SLOT(showItemInfo(KFileItem)));
     connect(m_view, SIGNAL(itemActivated(KFileItem)),           this, SLOT(slotItemActivated(KFileItem)));
@@ -128,6 +129,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,8 +159,10 @@ 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()));
+            m_filterBar, SLOT(slotUrlChanged()));
 
     m_topLayout->addWidget(m_urlNavigator);
     m_topLayout->addWidget(m_searchBox);
@@ -477,37 +482,12 @@ void DolphinViewContainer::slotItemActivated(const KFileItem& item)
     // results in an active view.
     m_view->setActive(true);
 
-    KUrl url = item.targetUrl();
-
-    if (item.isDir()) {
+    const KUrl& url = DolphinView::openItemAsFolderUrl(item, GeneralSettings::browseThroughArchives());
+    if (!url.isEmpty()) {
         m_view->setUrl(url);
         return;
     }
 
-    if (GeneralSettings::browseThroughArchives() && item.isFile() && url.isLocalFile()) {
-        // Generic mechanism for redirecting to tar:/<path>/ when clicking on a tar file,
-        // zip:/<path>/ when clicking on a zip file, etc.
-        // The .protocol file specifies the mimetype that the kioslave handles.
-        // Note that we don't use mimetype inheritance since we don't want to
-        // open OpenDocument files as zip folders...
-        const QString protocol = KProtocolManager::protocolForArchiveMimetype(item.mimetype());
-        if (!protocol.isEmpty()) {
-            url.setProtocol(protocol);
-            m_view->setUrl(url);
-            return;
-        }
-    }
-
-    if (item.mimetype() == QLatin1String("application/x-desktop")) {
-        // Redirect to the URL in Type=Link desktop files
-        KDesktopFile desktopFile(url.toLocalFile());
-        if (desktopFile.hasLinkType()) {
-            url = desktopFile.readUrl();
-            m_view->setUrl(url);
-            return;
-        }
-    }
-
     item.run();
 }
 
@@ -531,8 +511,7 @@ void DolphinViewContainer::showItemInfo(const KFileItem& item)
 
 void DolphinViewContainer::closeFilterBar()
 {
-    m_filterBar->hide();
-    m_filterBar->clear();
+    m_filterBar->closeFilterBar();
     m_view->setFocus();
     emit showFilterBarChanged(false);
 }
@@ -574,6 +553,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 +597,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 +639,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..3fa9cc14706d9119759b3cce5f20f6d6212b93fa 100644 (file)
@@ -1,6 +1,7 @@
 /***************************************************************************
  *   Copyright (C) 2006-2010 by Peter Penz <peter.penz19@gmail.com>        *
  *   Copyright (C) 2006 by Gregor Kališnik <gregor@podnapisi.net>          *
+ *   Copyright (C) 2012 by Stuart Citrin <ctrn3e8@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  *
@@ -39,6 +40,13 @@ FilterBar::FilterBar(QWidget* parent) :
     closeButton->setToolTip(i18nc("@info:tooltip", "Hide Filter Bar"));
     connect(closeButton, SIGNAL(clicked()), this, SIGNAL(closeRequest()));
 
+    // Create button to lock text when changing folders
+    m_lockButton = new QToolButton(this);
+    m_lockButton->setCheckable(true);
+    m_lockButton->setIcon(KIcon("system-lock-screen.png"));
+    m_lockButton->setToolTip(i18nc("@info:tooltip", "Keep Filter When Changing Folders"));
+    connect(m_lockButton, SIGNAL(toggled(bool)), this, SLOT(slotToggleLockButton(bool)));
+
     // Create label
     QLabel* filterLabel = new QLabel(i18nc("@label:textbox", "Filter:"), this);
 
@@ -56,6 +64,7 @@ FilterBar::FilterBar(QWidget* parent) :
     hLayout->addWidget(closeButton);
     hLayout->addWidget(filterLabel);
     hLayout->addWidget(m_filterInput);
+    hLayout->addWidget(m_lockButton);
 
     filterLabel->setBuddy(m_filterInput);
 }
@@ -64,6 +73,15 @@ FilterBar::~FilterBar()
 {
 }
 
+void FilterBar::closeFilterBar()
+{
+    hide();
+    clear();
+    if (m_lockButton) {
+        m_lockButton->setChecked(false);
+    }
+}
+
 void FilterBar::selectAll()
 {
     m_filterInput->selectAll();
@@ -74,6 +92,20 @@ void FilterBar::clear()
     m_filterInput->clear();
 }
 
+void FilterBar::slotUrlChanged()
+{
+    if (!m_lockButton || !(m_lockButton->isChecked())) {
+        clear();
+    }
+}
+
+void FilterBar::slotToggleLockButton(bool checked)
+{
+    if (!checked) {
+        clear();
+    }
+}
+
 void FilterBar::showEvent(QShowEvent* event)
 {
     if (!event->spontaneous()) {
@@ -84,12 +116,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..5d5463a28e960e9e88baf440340e381e81c3944b 100644 (file)
@@ -1,6 +1,7 @@
 /***************************************************************************
  *   Copyright (C) 2006-2010 by Peter Penz <peter.penz19@gmail.com>        *
  *   Copyright (C) 2006 by Gregor Kališnik <gregor@podnapisi.net>          *
+ *   Copyright (C) 2012 by Stuart Citrin <ctrn3e8@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  *
@@ -24,6 +25,7 @@
 #include <QWidget>
 
 class KLineEdit;
+class QToolButton;
 
 /**
  * @brief Provides an input field for filtering the currently shown items.
@@ -38,6 +40,9 @@ public:
     explicit FilterBar(QWidget* parent = 0);
     virtual ~FilterBar();
 
+    /** Called by view container to hide this **/
+    void closeFilterBar();
+
     /**
      * Selects the whole text of the filter bar.
      */
@@ -46,6 +51,10 @@ public:
 public slots:
     /** Clears the input field. */
     void clear();
+    /** Clears the input field if the "lock button" is disabled. */
+    void slotUrlChanged();
+    /** The input field is cleared also if the "lock button" is released. */
+    void slotToggleLockButton(bool checked);
 
 signals:
     /**
@@ -59,12 +68,18 @@ 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);
 
 private:
     KLineEdit* m_filterInput;
+    QToolButton* m_lockButton;
 };
 
 #endif
index 400d29849e2518cc19f9e9f59098e28e5fe94d9e..7ea5e8018265aabf6a0e00e13eee7e176128a7e5 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)
@@ -167,7 +167,7 @@ bool KFileItemModel::setData(int index, const QHash<QByteArray, QVariant>& value
     QHashIterator<QByteArray, QVariant> it(values);
     while (it.hasNext()) {
         it.next();
-        const QByteArray role = it.key();
+        const QByteArray role = sharedValue(it.key());
         const QVariant value = it.value();
 
         if (currentValues[role] != value) {
@@ -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!";
@@ -425,12 +425,13 @@ bool KFileItemModel::setExpanded(int index, bool expanded)
     }
 
     QHash<QByteArray, QVariant> values;
-    values.insert("isExpanded", expanded);
+    values.insert(sharedValue("isExpanded"), expanded);
     if (!setData(index, values)) {
         return false;
     }
 
-    const 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)
@@ -865,12 +846,12 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
     kDebug() << "Refreshing" << items.count() << "items";
 #endif
 
-    m_groups.clear();
-
     // Get the indexes of all items that have been refreshed
     QList<int> indexes;
     indexes.reserve(items.count());
 
+    QSet<QByteArray> changedRoles;
+
     QListIterator<QPair<KFileItem, KFileItem> > it(items);
     while (it.hasNext()) {
         const QPair<KFileItem, KFileItem>& itemPair = it.next();
@@ -882,10 +863,15 @@ 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));
+            QHash<QByteArray, QVariant>& values = m_itemData[index]->values;
             while (it.hasNext()) {
                 it.next();
-                m_itemData[index]->values.insert(it.key(), it.value());
+                const QByteArray& role = it.key();
+                if (values.value(role) != it.value()) {
+                    values.insert(role, it.value());
+                    changedRoles.insert(role);
+                }
             }
 
             m_items.remove(oldItem.url());
@@ -926,9 +912,11 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
         itemRangeList.append(KItemRange(rangeIndex, rangeCount));
     }
 
-    emit itemsChanged(itemRangeList, m_roles);
+    emit itemsChanged(itemRangeList, changedRoles);
 
-    resortAllItems();
+    if (changedRoles.contains(sortRole())) {
+        resortAllItems();
+    }
 }
 
 void KFileItemModel::slotClear()
@@ -937,6 +925,7 @@ void KFileItemModel::slotClear()
     kDebug() << "Clearing all items";
 #endif
 
+    qDeleteAll(m_filteredItems.values());
     m_filteredItems.clear();
     m_groups.clear();
 
@@ -944,8 +933,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,197 +963,212 @@ void KFileItemModel::dispatchPendingItemsToInsert()
     }
 }
 
-void KFileItemModel::insertItems(const KFileItemList& items)
+void KFileItemModel::insertItems(QList<ItemData*>& newItems)
 {
-    if (items.isEmpty()) {
+    if (newItems.isEmpty()) {
         return;
     }
 
-    if (m_sortRole == TypeRole) {
-        // Try to resolve the MIME-types synchronously to prevent a reordering of
-        // the items when sorting by type (per default MIME-types are resolved
-        // asynchronously by KFileItemModelRolesUpdater).
-        determineMimeTypes(items, 200);
-    }
-
 #ifdef KFILEITEMMODEL_DEBUG
     QElapsedTimer timer;
     timer.start();
     kDebug() << "===========================================================";
-    kDebug() << "Inserting" << items.count() << "items";
+    kDebug() << "Inserting" << newItems.count() << "items";
 #endif
 
     m_groups.clear();
 
-    QList<ItemData*> sortedItems = createItemDataList(items);
-    KFileItemModelSortAlgorithm::sort(this, sortedItems.begin(), sortedItems.end());
+    sort(newItems.begin(), newItems.end());
 
 #ifdef KFILEITEMMODEL_DEBUG
     kDebug() << "[TIME] Sorting:" << timer.elapsed();
 #endif
 
     KItemRangeList itemRanges;
-    int targetIndex = 0;
-    int sourceIndex = 0;
-    int insertedAtIndex = -1;        // Index for the current item-range
-    int insertedCount = 0;           // Count for the current item-range
-    int previouslyInsertedCount = 0; // Sum of previously inserted items for all ranges
-    while (sourceIndex < sortedItems.count()) {
-        // Find target index from m_items to insert the current item
-        // in a sorted order
-        const int previousTargetIndex = targetIndex;
-        while (targetIndex < m_itemData.count()) {
-            if (!lessThan(m_itemData.at(targetIndex), sortedItems.at(sourceIndex))) {
-                break;
+    const int existingItemCount = m_itemData.count();
+    const int newItemCount = newItems.count();
+    const int totalItemCount = existingItemCount + newItemCount;
+
+    if (existingItemCount == 0) {
+        // Optimization for the common special case that there are no
+        // items in the model yet. Happens, e.g., when entering a folder.
+        m_itemData = newItems;
+        itemRanges << KItemRange(0, newItemCount);
+    } else {
+        m_itemData.reserve(totalItemCount);
+        for (int i = existingItemCount; i < totalItemCount; ++i) {
+            m_itemData.append(0);
+        }
+
+        // We build the new list m_items in reverse order to minimize
+        // the number of moves and guarantee O(N) complexity.
+        int targetIndex = totalItemCount - 1;
+        int sourceIndexExistingItems = existingItemCount - 1;
+        int sourceIndexNewItems = newItemCount - 1;
+
+        int rangeCount = 0;
+
+        while (sourceIndexNewItems >= 0) {
+            ItemData* newItem = newItems.at(sourceIndexNewItems);
+            if (sourceIndexExistingItems >= 0 && lessThan(newItem, m_itemData.at(sourceIndexExistingItems))) {
+                // Move an existing item to its new position. If any new items
+                // are behind it, push the item range to itemRanges.
+                if (rangeCount > 0) {
+                    itemRanges << KItemRange(sourceIndexExistingItems + 1, rangeCount);
+                    rangeCount = 0;
+                }
+
+                m_itemData[targetIndex] = m_itemData.at(sourceIndexExistingItems);
+                --sourceIndexExistingItems;
+            } else {
+                // Insert a new item into the list.
+                ++rangeCount;
+                m_itemData[targetIndex] = newItem;
+                --sourceIndexNewItems;
             }
-            ++targetIndex;
+            --targetIndex;
         }
 
-        if (targetIndex - previousTargetIndex > 0 && insertedAtIndex >= 0) {
-            itemRanges << KItemRange(insertedAtIndex, insertedCount);
-            previouslyInsertedCount += insertedCount;
-            insertedAtIndex = targetIndex - previouslyInsertedCount;
-            insertedCount = 0;
+        // Push the final item range to itemRanges.
+        if (rangeCount > 0) {
+            itemRanges << KItemRange(sourceIndexExistingItems + 1, rangeCount);
         }
 
-        // Insert item at the position targetIndex by transferring
-        // the ownership of the item-data from sortedItems to m_itemData.
-        // m_items will be inserted after the loop (see comment below)
-        m_itemData.insert(targetIndex, sortedItems.at(sourceIndex));
-        ++insertedCount;
-
-        if (insertedAtIndex < 0) {
-            insertedAtIndex = targetIndex;
-            Q_ASSERT(previouslyInsertedCount == 0);
-        }
-        ++targetIndex;
-        ++sourceIndex;
+        // Note that itemRanges is still sorted in reverse order.
+        std::reverse(itemRanges.begin(), itemRanges.end());
     }
 
-    // The indexes of all m_items must be adjusted, not only the index
-    // of the new items
-    const int itemDataCount = m_itemData.count();
-    for (int i = 0; i < itemDataCount; ++i) {
+    // The indexes starting from the first inserted item must be adjusted.
+    m_items.reserve(totalItemCount);
+    for (int i = itemRanges.front().index; i < totalItemCount; ++i) {
         m_items.insert(m_itemData.at(i)->item.url(), i);
     }
 
-    itemRanges << KItemRange(insertedAtIndex, insertedCount);
     emit itemsInserted(itemRanges);
 
 #ifdef KFILEITEMMODEL_DEBUG
-    kDebug() << "[TIME] Inserting of" << items.count() << "items:" << timer.elapsed();
+    kDebug() << "[TIME] Inserting of" << newItems.count() << "items:" << timer.elapsed();
 #endif
 }
 
-void KFileItemModel::removeItems(const KFileItemList& items)
+static KItemRangeList sortedIndexesToKItemRangeList(const QList<int>& sortedNumbers)
 {
-    if (items.isEmpty()) {
-        return;
+    if (sortedNumbers.empty()) {
+        return KItemRangeList();
+    }
+
+    KItemRangeList result;
+
+    QList<int>::const_iterator it = sortedNumbers.begin();
+    int index = *it;
+    int count = 1;
+
+    ++it;
+
+    QList<int>::const_iterator end = sortedNumbers.end();
+    while (it != end) {
+        if (*it == index + count) {
+            ++count;
+        } else {
+            result << KItemRange(index, count);
+            index = *it;
+            count = 1;
+        }
+        ++it;
     }
 
+    result << KItemRange(index, count);
+    return result;
+}
+
+void KFileItemModel::removeItems(const KFileItemList& items, RemoveItemsBehavior behavior)
+{
 #ifdef KFILEITEMMODEL_DEBUG
     kDebug() << "Removing " << items.count() << "items";
 #endif
 
     m_groups.clear();
 
-    QList<ItemData*> sortedItems;
-    sortedItems.reserve(items.count());
+    // Step 1: Determine the indexes of the removed items, remove them from
+    //         the hash m_items, and free the ItemData.
+    QList<int> indexesToRemove;
+    indexesToRemove.reserve(items.count());
     foreach (const KFileItem& item, items) {
-        const int index = m_items.value(item.url(), -1);
+        const KUrl url = item.url();
+        const int index = m_items.value(url, -1);
         if (index >= 0) {
-            sortedItems.append(m_itemData.at(index));
-        }
-    }
-    KFileItemModelSortAlgorithm::sort(this, sortedItems.begin(), sortedItems.end());
+            indexesToRemove.append(index);
 
-    QList<int> indexesToRemove;
-    indexesToRemove.reserve(items.count());
+            // Prevent repeated expensive rehashing by using QHash::erase(),
+            // rather than QHash::remove().
+            QHash<KUrl, int>::iterator it = m_items.find(url);
+            m_items.erase(it);
 
-    // Calculate the item ranges that will get deleted
-    KItemRangeList itemRanges;
-    int removedAtIndex = -1;
-    int removedCount = 0;
-    int targetIndex = 0;
-    foreach (const ItemData* itemData, sortedItems) {
-        const KFileItem& itemToRemove = itemData->item;
-
-        const int previousTargetIndex = targetIndex;
-        while (targetIndex < m_itemData.count()) {
-            if (m_itemData.at(targetIndex)->item.url() == itemToRemove.url()) {
-                break;
+            if (behavior == DeleteItemData) {
+                delete m_itemData.at(index);
             }
-            ++targetIndex;
-        }
-        if (targetIndex >= m_itemData.count()) {
-            kWarning() << "Item that should be deleted has not been found!";
-            return;
-        }
 
-        if (targetIndex - previousTargetIndex > 0 && removedAtIndex >= 0) {
-            itemRanges << KItemRange(removedAtIndex, removedCount);
-            removedAtIndex = targetIndex;
-            removedCount = 0;
+            m_itemData[index] = 0;
         }
+    }
 
-        indexesToRemove.append(targetIndex);
-        if (removedAtIndex < 0) {
-            removedAtIndex = targetIndex;
-        }
-        ++removedCount;
-        ++targetIndex;
+    if (indexesToRemove.isEmpty()) {
+        return;
     }
 
-    // Delete the items
-    for (int i = indexesToRemove.count() - 1; i >= 0; --i) {
-        const int indexToRemove = indexesToRemove.at(i);
-        ItemData* data = m_itemData.at(indexToRemove);
+    std::sort(indexesToRemove.begin(), indexesToRemove.end());
 
-        m_items.remove(data->item.url());
+    // Step 2: Remove the ItemData pointers from the list m_itemData.
+    const KItemRangeList itemRanges = sortedIndexesToKItemRangeList(indexesToRemove);
+    int target = itemRanges.at(0).index;
+    int source = itemRanges.at(0).index + itemRanges.at(0).count;
+    int nextRange = 1;
 
-        delete data;
-        m_itemData.removeAt(indexToRemove);
-    }
+    const int oldItemDataCount = m_itemData.count();
+    while (source < oldItemDataCount) {
+        m_itemData[target] = m_itemData[source];
+        ++target;
+        ++source;
 
-    // The indexes of all m_items must be adjusted, not only the index
-    // of the removed items
-    const int itemDataCount = m_itemData.count();
-    for (int i = 0; i < itemDataCount; ++i) {
-        m_items.insert(m_itemData.at(i)->item.url(), i);
+        if (nextRange < itemRanges.count() && source == itemRanges.at(nextRange).index) {
+            // Skip the items in the next removed range.
+            source += itemRanges.at(nextRange).count;
+            ++nextRange;
+        }
     }
 
-    if (count() <= 0) {
-        m_expandedParentsCountRoot = UninitializedExpandedParentsCountRoot;
+    m_itemData.erase(m_itemData.end() - indexesToRemove.count(), m_itemData.end());
+
+    // Step 3: Adjust indexes in the hash m_items, starting from the
+    //         index of the first removed item.
+    const int newItemDataCount = m_itemData.count();
+    for (int i = itemRanges.front().index; i < newItemDataCount; ++i) {
+        m_items.insert(m_itemData.at(i)->item.url(), i);
     }
 
-    itemRanges << KItemRange(removedAtIndex, removedCount);
     emit itemsRemoved(itemRanges);
 }
 
-QList<KFileItemModel::ItemData*> KFileItemModel::createItemDataList(const KFileItemList& items) const
+QList<KFileItemModel::ItemData*> KFileItemModel::createItemDataList(const KUrl& parentUrl, const KFileItemList& items) const
 {
+    if (m_sortRole == TypeRole) {
+        // Try to resolve the MIME-types synchronously to prevent a reordering of
+        // the items when sorting by type (per default MIME-types are resolved
+        // asynchronously by KFileItemModelRolesUpdater).
+        determineMimeTypes(items, 200);
+    }
+
+    const int parentIndex = m_items.value(parentUrl, -1);
+    ItemData* parentItem = parentIndex < 0 ? 0 : m_itemData.at(parentIndex);
+
     QList<ItemData*> itemDataList;
     itemDataList.reserve(items.count());
 
     foreach (const KFileItem& item, items) {
         ItemData* itemData = new ItemData();
         itemData->item = item;
-        itemData->values = retrieveData(item);
-        itemData->parent = 0;
-
-        const bool determineParent = m_requestRole[ExpandedParentsCountRole]
-                                     && itemData->values["expandedParentsCount"].toInt() > 0;
-        if (determineParent) {
-            KUrl parentUrl = item.url().upUrl();
-            parentUrl.adjustPath(KUrl::RemoveTrailingSlash);
-            const int parentIndex = m_items.value(parentUrl, -1);
-            if (parentIndex >= 0) {
-                itemData->parent = m_itemData.at(parentIndex);
-            } else {
-                kWarning() << "Parent item not found for" << item.url();
-            }
-        }
-
+        itemData->values = retrieveData(item, parentItem);
+        itemData->parent = parentItem;
         itemDataList.append(itemData);
     }
 
@@ -1187,9 +1189,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,33 +1253,33 @@ 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
     // and hence will be retrieved asynchronously by KFileItemModelRolesUpdater.
     QHash<QByteArray, QVariant> data;
-    data.insert("url", item.url());
+    data.insert(sharedValue("url"), item.url());
 
     const bool isDir = item.isDir();
     if (m_requestRole[IsDirRole]) {
-        data.insert("isDir", isDir);
+        data.insert(sharedValue("isDir"), isDir);
     }
 
     if (m_requestRole[IsLinkRole]) {
         const bool isLink = item.isLink();
-        data.insert("isLink", isLink);
+        data.insert(sharedValue("isLink"), isLink);
     }
 
     if (m_requestRole[NameRole]) {
-        data.insert("text", item.text());
+        data.insert(sharedValue("text"), item.text());
     }
 
     if (m_requestRole[SizeRole]) {
         if (isDir) {
-            data.insert("size", QVariant());
+            data.insert(sharedValue("size"), QVariant());
         } else {
-            data.insert("size", item.size());
+            data.insert(sharedValue("size"), item.size());
         }
     }
 
@@ -1287,19 +1288,19 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item)
         // having several thousands of items. Instead the formatting of the
         // date-time will be done on-demand by the view when the date will be shown.
         const KDateTime dateTime = item.time(KFileItem::ModificationTime);
-        data.insert("date", dateTime.dateTime());
+        data.insert(sharedValue("date"), dateTime.dateTime());
     }
 
     if (m_requestRole[PermissionsRole]) {
-        data.insert("permissions", item.permissionsString());
+        data.insert(sharedValue("permissions"), item.permissionsString());
     }
 
     if (m_requestRole[OwnerRole]) {
-        data.insert("owner", item.user());
+        data.insert(sharedValue("owner"), item.user());
     }
 
     if (m_requestRole[GroupRole]) {
-        data.insert("group", item.group());
+        data.insert(sharedValue("group"), item.group());
     }
 
     if (m_requestRole[DestinationRole]) {
@@ -1307,7 +1308,7 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item)
         if (destination.isEmpty()) {
             destination = QLatin1String("-");
         }
-        data.insert("destination", destination);
+        data.insert(sharedValue("destination"), destination);
     }
 
     if (m_requestRole[PathRole]) {
@@ -1330,43 +1331,27 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item)
 
         const int index = path.lastIndexOf(item.text());
         path = path.mid(0, index - 1);
-        data.insert("path", path);
+        data.insert(sharedValue("path"), path);
     }
 
     if (m_requestRole[IsExpandableRole]) {
-        data.insert("isExpandable", item.isDir() && item.url() == item.targetUrl());
+        data.insert(sharedValue("isExpandable"), item.isDir() && item.url() == item.targetUrl());
     }
 
     if (m_requestRole[ExpandedParentsCountRole]) {
-        if (m_expandedParentsCountRoot == UninitializedExpandedParentsCountRoot) {
-            const KUrl rootUrl = m_dirLister->url();
-            const QString protocol = rootUrl.protocol();
-            const bool forceExpandedParentsCountRoot = (protocol == QLatin1String("trash") ||
-                                                        protocol == QLatin1String("nepomuk") ||
-                                                        protocol == QLatin1String("remote") ||
-                                                        protocol.contains(QLatin1String("search")));
-            if (forceExpandedParentsCountRoot) {
-                m_expandedParentsCountRoot = ForceExpandedParentsCountRoot;
-            } else {
-                const QString rootDir = rootUrl.path(KUrl::AddTrailingSlash);
-                m_expandedParentsCountRoot = rootDir.count('/');
-            }
+        int level = 0;
+        if (parent) {
+            level = parent->values["expandedParentsCount"].toInt() + 1;
         }
 
-        if (m_expandedParentsCountRoot == ForceExpandedParentsCountRoot) {
-            data.insert("expandedParentsCount", -1);
-        } else {
-            const QString dir = item.url().directory(KUrl::AppendTrailingSlash);
-            const int level = dir.count('/') - m_expandedParentsCountRoot;
-            data.insert("expandedParentsCount", level);
-        }
+        data.insert(sharedValue("expandedParentsCount"), level);
     }
 
     if (item.isMimeTypeKnown()) {
-        data.insert("iconName", item.iconName());
+        data.insert(sharedValue("iconName"), item.iconName());
 
         if (m_requestRole[TypeRole]) {
-            data.insert("type", item.mimeComment());
+            data.insert(sharedValue("type"), item.mimeComment());
         }
     }
 
@@ -1377,11 +1362,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;
         }
     }
 
@@ -1400,6 +1408,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;
@@ -1524,88 +1570,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();
@@ -1722,12 +1686,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) {
@@ -1745,20 +1703,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;
@@ -1767,7 +1714,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");
@@ -1790,7 +1737,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) {
@@ -2011,7 +1958,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
@@ -2021,4 +1968,64 @@ void KFileItemModel::determineMimeTypes(const KFileItemList& items, int timeout)
     }
 }
 
+QByteArray KFileItemModel::sharedValue(const QByteArray& value)
+{
+    static QSet<QByteArray> pool;
+    const QSet<QByteArray>::const_iterator it = pool.constFind(value);
+
+    if (it != pool.constEnd()) {
+        return *it;
+    } else {
+        pool.insert(value);
+        return value;
+    }
+}
+
+bool KFileItemModel::isConsistent() const
+{
+    if (m_items.count() != m_itemData.count()) {
+        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..1d2d8c1722e43dc577096347a4db7a5c3210c591 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,17 @@ private:
      */
     static void determineMimeTypes(const KFileItemList& items, int timeout);
 
+    /**
+     * @return Returns a copy of \a value that is implicitly shared
+     * with other users to save memory.
+     */
+    static QByteArray sharedValue(const QByteArray& value);
+
+    /**
+     * Checks if the model's internal data structures are consistent.
+     */
+    bool isConsistent() const;
+
 private:
     KFileItemModelDirLister* m_dirLister;
 
@@ -443,32 +455,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 +473,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
 };
index 7cade10f5f6a7f8de4e67196aa021d31beb50150..294778621cd2e8a23f061c512c4a77d68ef2672f 100644 (file)
@@ -38,6 +38,8 @@
 #include <QElapsedTimer>
 #include <QTimer>
 
+#include <algorithm>
+
 #ifdef HAVE_NEPOMUK
     #include "private/knepomukrolesprovider.h"
     #include <Nepomuk2/ResourceWatcher>
@@ -58,34 +60,41 @@ namespace {
     // may perform a blocking operation
     const int MaxBlockTimeout = 200;
 
-    // Maximum number of items that will get resolved synchronously.
-    // The value should roughly represent the number of maximum visible
-    // items, as it does not make sense to resolve more items synchronously
-    // and probably reach the MaxBlockTimeout because of invisible items.
-    const int MaxResolveItemsCount = 100;
+    // If the number of items is smaller than ResolveAllItemsLimit,
+    // the roles of all items will be resolved.
+    const int ResolveAllItemsLimit = 500;
+
+    // Not only the visible area, but up to ReadAheadPages before and after
+    // this area will be resolved.
+    const int ReadAheadPages = 5;
 }
 
 KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QObject* parent) :
     QObject(parent),
-    m_paused(false),
+    m_state(Idle),
     m_previewChangedDuringPausing(false),
     m_iconSizeChangedDuringPausing(false),
     m_rolesChangedDuringPausing(false),
     m_previewShown(false),
     m_enlargeSmallPreviews(true),
     m_clearPreviews(false),
-    m_sortingProgress(-1),
+    m_finishedItems(),
     m_model(model),
     m_iconSize(),
     m_firstVisibleIndex(0),
     m_lastVisibleIndex(-1),
-    m_maximumVisibleItems(100),
+    m_maximumVisibleItems(50),
     m_roles(),
+    m_resolvableRoles(),
     m_enabledPlugins(),
-    m_pendingVisibleItems(),
-    m_pendingInvisibleItems(),
-    m_previewJobs(),
-    m_changedItemsTimer(0),
+    m_pendingSortRoleItems(),
+    m_hasUnknownIcons(false),
+    m_firstIndexWithoutIcon(0),
+    m_pendingIndexes(),
+    m_pendingPreviewItems(),
+    m_previewJob(),
+    m_recentlyChangedItemsTimer(0),
+    m_recentlyChangedItems(),
     m_changedItems(),
     m_dirWatcher(0),
     m_watchedDirs()
@@ -108,15 +117,17 @@ KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QO
             this,    SLOT(slotItemsRemoved(KItemRangeList)));
     connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
             this,    SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
+    connect(m_model, SIGNAL(itemsMoved(KItemRange,QList<int>)),
+            this,    SLOT(slotItemsMoved(KItemRange,QList<int>)));
     connect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)),
             this,    SLOT(slotSortRoleChanged(QByteArray,QByteArray)));
 
     // Use a timer to prevent that each call of slotItemsChanged() results in a synchronous
     // resolving of the roles. Postpone the resolving until no update has been done for 1 second.
-    m_changedItemsTimer = new QTimer(this);
-    m_changedItemsTimer->setInterval(1000);
-    m_changedItemsTimer->setSingleShot(true);
-    connect(m_changedItemsTimer, SIGNAL(timeout()), this, SLOT(resolveChangedItems()));
+    m_recentlyChangedItemsTimer = new QTimer(this);
+    m_recentlyChangedItemsTimer->setInterval(1000);
+    m_recentlyChangedItemsTimer->setSingleShot(true);
+    connect(m_recentlyChangedItemsTimer, SIGNAL(timeout()), this, SLOT(resolveRecentlyChangedItems()));
 
     m_resolvableRoles.insert("size");
     m_resolvableRoles.insert("type");
@@ -133,21 +144,20 @@ KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QO
 
 KFileItemModelRolesUpdater::~KFileItemModelRolesUpdater()
 {
-    resetPendingRoles();
+    killPreviewJob();
 }
 
 void KFileItemModelRolesUpdater::setIconSize(const QSize& size)
 {
     if (size != m_iconSize) {
         m_iconSize = size;
-        if (m_paused) {
+        if (m_state == Paused) {
             m_iconSizeChangedDuringPausing = true;
         } else if (m_previewShown) {
             // An icon size change requires the regenerating of
             // all previews
-            sortAndResolveAllRoles();
-        } else {
-            sortAndResolvePendingRoles();
+            m_finishedItems.clear();
+            startUpdating();
         }
     }
 }
@@ -174,9 +184,7 @@ void KFileItemModelRolesUpdater::setVisibleIndexRange(int index, int count)
     m_firstVisibleIndex = index;
     m_lastVisibleIndex = qMin(index + count - 1, m_model->count() - 1);
 
-    if (hasPendingRoles() && !m_paused) {
-        sortAndResolvePendingRoles();
-    }
+    startUpdating();
 }
 
 void KFileItemModelRolesUpdater::setMaximumVisibleItems(int count)
@@ -230,31 +238,33 @@ void KFileItemModelRolesUpdater::setEnabledPlugins(const QStringList& list)
 
 void KFileItemModelRolesUpdater::setPaused(bool paused)
 {
-    if (paused == m_paused) {
+    if (paused == (m_state == Paused)) {
         return;
     }
 
-    m_paused = paused;
     if (paused) {
-        if (hasPendingRoles()) {
-            foreach (KJob* job, m_previewJobs) {
-                job->kill();
-            }
-            Q_ASSERT(m_previewJobs.isEmpty());
-        }
+        m_state = Paused;
+        killPreviewJob();
     } else {
-        const bool resolveAll = (m_iconSizeChangedDuringPausing && m_previewShown) ||
-                                m_previewChangedDuringPausing ||
-                                m_rolesChangedDuringPausing;
+        const bool updatePreviews = (m_iconSizeChangedDuringPausing && m_previewShown) ||
+                                    m_previewChangedDuringPausing;
+        const bool resolveAll = updatePreviews || m_rolesChangedDuringPausing;
         if (resolveAll) {
-            sortAndResolveAllRoles();
-        } else {
-            sortAndResolvePendingRoles();
+            m_finishedItems.clear();
         }
 
         m_iconSizeChangedDuringPausing = false;
         m_previewChangedDuringPausing = false;
         m_rolesChangedDuringPausing = false;
+
+        if (!m_pendingSortRoleItems.isEmpty()) {
+            m_state = ResolvingSortRole;
+            resolveNextSortRole();
+        } else {
+            m_state = Idle;
+        }
+
+        startUpdating();
     }
 }
 
@@ -292,12 +302,10 @@ void KFileItemModelRolesUpdater::setRoles(const QSet<QByteArray>& roles)
         }
 #endif
 
-        updateSortProgress();
-
-        if (m_paused) {
+        if (m_state == Paused) {
             m_rolesChangedDuringPausing = true;
         } else {
-            sortAndResolveAllRoles();
+            startUpdating();
         }
     }
 }
@@ -309,7 +317,7 @@ QSet<QByteArray> KFileItemModelRolesUpdater::roles() const
 
 bool KFileItemModelRolesUpdater::isPaused() const
 {
-    return m_paused;
+    return m_state == Paused;
 }
 
 QStringList KFileItemModelRolesUpdater::enabledPlugins() const
@@ -319,7 +327,41 @@ QStringList KFileItemModelRolesUpdater::enabledPlugins() const
 
 void KFileItemModelRolesUpdater::slotItemsInserted(const KItemRangeList& itemRanges)
 {
-    startUpdating(itemRanges);
+    QElapsedTimer timer;
+    timer.start();
+
+    const int firstInsertedIndex = itemRanges.first().index;
+    m_firstIndexWithoutIcon = qMin(m_firstIndexWithoutIcon, firstInsertedIndex);
+    m_hasUnknownIcons = true;
+
+    // Determine the sort role synchronously for as many items as possible.
+    if (m_resolvableRoles.contains(m_model->sortRole())) {
+        int insertedCount = 0;
+        foreach (const KItemRange& range, itemRanges) {
+            const int lastIndex = insertedCount + range.index + range.count - 1;
+            for (int i = insertedCount + range.index; i <= lastIndex; ++i) {
+                if (timer.elapsed() < MaxBlockTimeout) {
+                    applySortRole(i);
+                } else {
+                    m_pendingSortRoleItems.insert(m_model->fileItem(i));
+                }
+            }
+            insertedCount += range.count;
+        }
+
+        applySortProgressToModel();
+
+        // If there are still items whose sort role is unknown, check if the
+        // asynchronous determination of the sort role is already in progress,
+        // and start it if that is not the case.
+        if (!m_pendingSortRoleItems.isEmpty() && m_state != ResolvingSortRole) {
+            killPreviewJob();
+            m_state = ResolvingSortRole;
+            resolveNextSortRole();
+        }
+    }
+
+    startUpdating();
 }
 
 void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList& itemRanges)
@@ -328,6 +370,11 @@ void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList& itemRang
 
     const bool allItemsRemoved = (m_model->count() == 0);
 
+    if (m_hasUnknownIcons) {
+        const int firstRemovedIndex = itemRanges.first().index;
+        m_firstIndexWithoutIcon = qMin(m_firstIndexWithoutIcon, firstRemovedIndex);
+    }
+
     if (!m_watchedDirs.isEmpty()) {
         // Don't let KDirWatch watch for removed items
         if (allItemsRemoved) {
@@ -375,35 +422,47 @@ void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList& itemRang
     }
 #endif
 
-    m_firstVisibleIndex = 0;
-    m_lastVisibleIndex = -1;
-    if (!hasPendingRoles()) {
-        return;
-    }
-
     if (allItemsRemoved) {
-        // Most probably a directory change is done. Clear all pending items
-        // and also kill all ongoing preview-jobs.
-        resetPendingRoles();
-
+        m_state = Idle;
+
+        m_finishedItems.clear();
+        m_pendingSortRoleItems.clear();
+        m_pendingIndexes.clear();
+        m_pendingPreviewItems.clear();
+        m_recentlyChangedItems.clear();
+        m_recentlyChangedItemsTimer->stop();
         m_changedItems.clear();
-        m_changedItemsTimer->stop();
+
+        killPreviewJob();
     } else {
-        // Remove all items from m_pendingVisibleItems and m_pendingInvisibleItems
-        // that are not part of the model anymore. The items from m_changedItems
-        // don't need to be handled here, removed items are just skipped in
-        // resolveChangedItems().
-        for (int i = 0; i <= 1; ++i) {
-            QSet<KFileItem>& pendingItems = (i == 0) ? m_pendingVisibleItems : m_pendingInvisibleItems;
-            QMutableSetIterator<KFileItem> it(pendingItems);
-            while (it.hasNext()) {
-                const KFileItem item = it.next();
-                if (m_model->index(item) < 0) {
-                    pendingItems.remove(item);
-                }
+        // Only remove the items from m_finishedItems. They will be removed
+        // from the other sets later on.
+        QSet<KFileItem>::iterator it = m_finishedItems.begin();
+        while (it != m_finishedItems.end()) {
+            if (m_model->index(*it) < 0) {
+                it = m_finishedItems.erase(it);
+            } else {
+                ++it;
             }
         }
+
+        // The visible items might have changed.
+        startUpdating();
+    }
+}
+
+void KFileItemModelRolesUpdater::slotItemsMoved(const KItemRange& itemRange, QList<int> movedToIndexes)
+{
+    Q_UNUSED(itemRange);
+    Q_UNUSED(movedToIndexes);
+
+    if (m_hasUnknownIcons) {
+        const int firstMovedIndex = itemRange.index;
+        m_firstIndexWithoutIcon = qMin(m_firstIndexWithoutIcon, firstMovedIndex);
     }
+
+    // The visible items might have changed.
+    startUpdating();
 }
 
 void KFileItemModelRolesUpdater::slotItemsChanged(const KItemRangeList& itemRanges,
@@ -411,21 +470,27 @@ void KFileItemModelRolesUpdater::slotItemsChanged(const KItemRangeList& itemRang
 {
     Q_UNUSED(roles);
 
-    if (m_changedItemsTimer->isActive()) {
-        // A call of slotItemsChanged() has been done recently. Postpone the resolving
-        // of the roles until the timer has exceeded.
-        foreach (const KItemRange& itemRange, itemRanges) {
-            int index = itemRange.index;
-            for (int count = itemRange.count; count > 0; --count) {
-                m_changedItems.insert(m_model->fileItem(index));
-                ++index;
-            }
+    // Find out if slotItemsChanged() has been done recently. If that is the
+    // case, resolving the roles is postponed until a timer has exceeded
+    // to prevent expensive repeated updates if files are updated frequently.
+    const bool itemsChangedRecently = m_recentlyChangedItemsTimer->isActive();
+
+    QSet<KFileItem>& targetSet = itemsChangedRecently ? m_recentlyChangedItems : m_changedItems;
+
+    foreach (const KItemRange& itemRange, itemRanges) {
+        int index = itemRange.index;
+        for (int count = itemRange.count; count > 0; --count) {
+            const KFileItem item = m_model->fileItem(index);
+            targetSet.insert(item);
+            ++index;
         }
-    } else {
-        // No call of slotItemsChanged() has been done recently, resolve the roles now.
-        startUpdating(itemRanges);
     }
-    m_changedItemsTimer->start();
+
+    m_recentlyChangedItemsTimer->start();
+
+    if (!itemsChangedRecently) {
+        updateChangedItems();
+    }
 }
 
 void KFileItemModelRolesUpdater::slotSortRoleChanged(const QByteArray& current,
@@ -433,13 +498,46 @@ void KFileItemModelRolesUpdater::slotSortRoleChanged(const QByteArray& current,
 {
     Q_UNUSED(current);
     Q_UNUSED(previous);
-    updateSortProgress();
+
+    if (m_resolvableRoles.contains(current)) {
+        m_pendingSortRoleItems.clear();
+        m_finishedItems.clear();
+
+        const int count = m_model->count();
+        QElapsedTimer timer;
+        timer.start();
+
+        // Determine the sort role synchronously for as many items as possible.
+        for (int index = 0; index < count; ++index) {
+            if (timer.elapsed() < MaxBlockTimeout) {
+                applySortRole(index);
+            } else {
+                m_pendingSortRoleItems.insert(m_model->fileItem(index));
+            }
+        }
+
+        applySortProgressToModel();
+
+        if (!m_pendingSortRoleItems.isEmpty()) {
+            // Trigger the asynchronous determination of the sort role.
+            killPreviewJob();
+            m_state = ResolvingSortRole;
+            resolveNextSortRole();
+        }
+    } else {
+        m_state = Idle;
+        m_pendingSortRoleItems.clear();
+        applySortProgressToModel();
+    }
 }
 
 void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem& item, const QPixmap& pixmap)
 {
-    m_pendingVisibleItems.remove(item);
-    m_pendingInvisibleItems.remove(item);
+    if (m_state != PreviewJobRunning) {
+        return;
+    }
+
+    m_changedItems.remove(item);
 
     const int index = m_model->index(item);
     if (index < 0) {
@@ -493,99 +591,147 @@ void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem& item, const QPi
     connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
             this,    SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
 
-    applySortProgressToModel();
+    m_finishedItems.insert(item);
 }
 
 void KFileItemModelRolesUpdater::slotPreviewFailed(const KFileItem& item)
 {
-    m_pendingVisibleItems.remove(item);
-    m_pendingInvisibleItems.remove(item);
+    if (m_state != PreviewJobRunning) {
+        return;
+    }
+
+    m_changedItems.remove(item);
+
+    const int index = m_model->index(item);
+    if (index >= 0) {
+        QHash<QByteArray, QVariant> data;
+        data.insert("iconPixmap", QPixmap());
 
-    const bool clearPreviews = m_clearPreviews;
-    m_clearPreviews = true;
-    applyResolvedRoles(item, ResolveAll);
-    m_clearPreviews = clearPreviews;
+        disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+                   this,    SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
+        m_model->setData(index, data);
+        connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+                this,    SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
 
-    applySortProgressToModel();
+        applyResolvedRoles(item, ResolveAll);
+        m_finishedItems.insert(item);
+    }
 }
 
-void KFileItemModelRolesUpdater::slotPreviewJobFinished(KJob* job)
+void KFileItemModelRolesUpdater::slotPreviewJobFinished()
 {
-#ifdef KFILEITEMMODELROLESUPDATER_DEBUG
-    kDebug() << "Preview job finished. Pending visible:" << m_pendingVisibleItems.count() << "invisible:" << m_pendingInvisibleItems.count();
-#endif
+    m_previewJob = 0;
 
-    m_previewJobs.removeOne(job);
-    if (!m_previewJobs.isEmpty() || !hasPendingRoles()) {
+    if (m_state != PreviewJobRunning) {
         return;
     }
 
-    const KFileItemList visibleItems = sortedItems(m_pendingVisibleItems);
-    startPreviewJob(visibleItems + m_pendingInvisibleItems.toList());
+    m_state = Idle;
+
+    if (!m_pendingPreviewItems.isEmpty()) {
+        startPreviewJob();
+    } else {
+        if (!m_changedItems.isEmpty()) {
+            updateChangedItems();
+        }
+    }
 }
 
-void KFileItemModelRolesUpdater::resolveNextPendingRoles()
+void KFileItemModelRolesUpdater::resolveNextSortRole()
 {
-    if (m_paused) {
+    if (m_state != ResolvingSortRole) {
         return;
     }
 
-    if (m_previewShown) {
-        // The preview has been turned on since the last run. Skip
-        // resolving further pending roles as this is done as soon
-        // as a preview has been received.
-        return;
-    }
+    QSet<KFileItem>::iterator it = m_pendingSortRoleItems.begin();
+    while (it != m_pendingSortRoleItems.end()) {
+        const KFileItem item = *it;
+        const int index = m_model->index(item);
 
-    int resolvedCount = 0;
-    bool changed = false;
-    for (int i = 0; i <= 1; ++i) {
-        QSet<KFileItem>& pendingItems = (i == 0) ? m_pendingVisibleItems : m_pendingInvisibleItems;
-        QSet<KFileItem>::iterator it = pendingItems.begin();
-        while (it != pendingItems.end() && !changed && resolvedCount < MaxResolveItemsCount) {
-            changed = applyResolvedRoles(*it, ResolveAll);
-            it = pendingItems.erase(it);
-            ++resolvedCount;
+        // Continue if the sort role has already been determined for the
+        // item, and the item has not been changed recently.
+        if (!m_changedItems.contains(item) && m_model->data(index).contains(m_model->sortRole())) {
+            it = m_pendingSortRoleItems.erase(it);
+            continue;
         }
-    }
 
-    if (hasPendingRoles()) {
-        QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
-    } else {
-        m_clearPreviews = false;
+        applySortRole(index);
+        m_pendingSortRoleItems.erase(it);
+        break;
     }
 
-    applySortProgressToModel();
+    if (!m_pendingSortRoleItems.isEmpty()) {
+        applySortProgressToModel();
+        QTimer::singleShot(0, this, SLOT(resolveNextSortRole()));
+    } else {
+        m_state = Idle;
 
-#ifdef KFILEITEMMODELROLESUPDATER_DEBUG
-    static int callCount = 0;
-    ++callCount;
-    if (callCount % 100 == 0) {
-        kDebug() << "Remaining visible roles to resolve:" << m_pendingVisibleItems.count()
-                 << "invisible:" << m_pendingInvisibleItems.count();
+        // Prevent that we try to update the items twice.
+        disconnect(m_model, SIGNAL(itemsMoved(KItemRange,QList<int>)),
+                   this,    SLOT(slotItemsMoved(KItemRange,QList<int>)));
+        applySortProgressToModel();
+        connect(m_model, SIGNAL(itemsMoved(KItemRange,QList<int>)),
+                this,    SLOT(slotItemsMoved(KItemRange,QList<int>)));
+        startUpdating();
     }
-#endif
 }
 
-void KFileItemModelRolesUpdater::resolveChangedItems()
+void KFileItemModelRolesUpdater::resolveNextPendingRoles()
 {
-    if (m_changedItems.isEmpty()) {
+    if (m_state != ResolvingAllRoles) {
         return;
     }
 
-    KItemRangeList itemRanges;
+    while (!m_pendingIndexes.isEmpty()) {
+        const int index = m_pendingIndexes.takeFirst();
+        const KFileItem item = m_model->fileItem(index);
 
-    QSetIterator<KFileItem> it(m_changedItems);
-    while (it.hasNext()) {
-        const KFileItem& item = it.next();
-        const int index = m_model->index(item);
-        if (index >= 0) {
-            itemRanges.append(KItemRange(index, 1));
+        if (m_finishedItems.contains(item)) {
+            continue;
         }
+
+        applyResolvedRoles(item, ResolveAll);
+        m_finishedItems.insert(item);
+        m_changedItems.remove(item);
+        break;
     }
-    m_changedItems.clear();
 
-    startUpdating(itemRanges);
+    if (!m_pendingIndexes.isEmpty()) {
+        QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
+    } else {
+        m_state = Idle;
+
+        if (m_clearPreviews) {
+            // Only go through the list if there are items which might still have previews.
+            if (m_finishedItems.count() != m_model->count()) {
+                QHash<QByteArray, QVariant> data;
+                data.insert("iconPixmap", QPixmap());
+
+                disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+                           this,    SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
+                for (int index = 0; index <= m_model->count(); ++index) {
+                    if (m_model->data(index).contains("iconPixmap")) {
+                        m_model->setData(index, data);
+                    }
+                }
+                connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+                        this,    SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
+
+            }
+            m_clearPreviews = false;
+        }
+
+        if (!m_changedItems.isEmpty()) {
+            updateChangedItems();
+        }
+    }
+}
+
+void KFileItemModelRolesUpdater::resolveRecentlyChangedItems()
+{
+    m_changedItems += m_recentlyChangedItems;
+    m_recentlyChangedItems.clear();
+    updateChangedItems();
 }
 
 void KFileItemModelRolesUpdater::applyChangedNepomukRoles(const Nepomuk2::Resource& resource)
@@ -647,366 +793,310 @@ void KFileItemModelRolesUpdater::slotDirWatchDirty(const QString& path)
                 data.insert("isExpandable", count > 0);
             }
 
+            // Note that we do not block the itemsChanged signal here.
+            // This ensures that a new preview will be generated.
             m_model->setData(index, data);
         }
     }
 }
 
-void KFileItemModelRolesUpdater::startUpdating(const KItemRangeList& itemRanges)
+void KFileItemModelRolesUpdater::startUpdating()
 {
-    // If no valid index range is given assume that all items are visible.
-    // A cleanup will be done later as soon as the index range has been set.
-    const bool hasValidIndexRange = (m_lastVisibleIndex >= 0);
-
-    if (hasValidIndexRange) {
-        // Move all current pending visible items that are not visible anymore
-        // to the pending invisible items.
-        QSet<KFileItem>::iterator it = m_pendingVisibleItems.begin();
-        while (it != m_pendingVisibleItems.end()) {
-            const KFileItem item = *it;
-            const int index = m_model->index(item);
-            if (index < m_firstVisibleIndex || index > m_lastVisibleIndex) {
-                it = m_pendingVisibleItems.erase(it);
-                m_pendingInvisibleItems.insert(item);
-            } else {
-                ++it;
-            }
-        }
-    }
-
-    int rangesCount = 0;
-
-    foreach (const KItemRange& range, itemRanges) {
-        rangesCount += range.count;
-
-        // Add the inserted items to the pending visible and invisible items
-        const int lastIndex = range.index + range.count - 1;
-        for (int i = range.index; i <= lastIndex; ++i) {
-            const KFileItem item = m_model->fileItem(i);
-            bool visible;
-            if (hasValidIndexRange) {
-                visible = (i >= m_firstVisibleIndex && i <= m_lastVisibleIndex);
-            } else {
-                // If the view has not informed us about the visible range yet,
-                // just assume that the first items are visible.
-                visible = (i < m_maximumVisibleItems);
-            }
-
-            if (visible) {
-                m_pendingVisibleItems.insert(item);
-            } else {
-                m_pendingInvisibleItems.insert(item);
-            }
-        }
+    if (m_state == Paused) {
+        return;
     }
 
-    resolvePendingRoles();
-}
-
-void KFileItemModelRolesUpdater::startPreviewJob(const KFileItemList& items)
-{
-    if (items.isEmpty() || m_paused) {
+    if (m_finishedItems.count() == m_model->count()) {
+        // All roles have been resolved already.
+        m_state = Idle;
         return;
     }
 
-    // PreviewJob internally caches items always with the size of
-    // 128 x 128 pixels or 256 x 256 pixels. A (slow) downscaling is done
-    // by PreviewJob if a smaller size is requested. For images KFileItemModelRolesUpdater must
-    // do a downscaling anyhow because of the frame, so in this case only the provided
-    // cache sizes are requested.
-    const QSize cacheSize = (m_iconSize.width() > 128) || (m_iconSize.height() > 128)
-                            ? QSize(256, 256) : QSize(128, 128);
+    // Terminate all updates that are currently active.
+    killPreviewJob();
+    m_pendingIndexes.clear();
 
-    // KIO::filePreview() will request the MIME-type of all passed items, which (in the
-    // worst case) might block the application for several seconds. To prevent such
-    // a blocking the MIME-type of the items will determined until the MaxBlockTimeout
-    // has been reached and only those items will get passed. As soon as the MIME-type
-    // has been resolved once KIO::PreviewJob() can already access the resolved
-    // MIME-type in a fast way.
     QElapsedTimer timer;
     timer.start();
 
-    KFileItemList itemSubSet;
-    const int count = items.count();
-    itemSubSet.reserve(count);
-    for (int i = 0; i < count; ++i) {
-        KFileItem item = items.at(i);
-        item.determineMimeType();
-        itemSubSet.append(item);
-        if (timer.elapsed() > MaxBlockTimeout) {
-#ifdef KFILEITEMMODELROLESUPDATER_DEBUG
-            kDebug() << "Maximum time of" << MaxBlockTimeout << "ms exceeded, creating only previews for"
-                     << (i + 1) << "items," << (items.count() - (i + 1)) << "will be resolved later";
-#endif
-            break;
-        }
+    // Determine the icons for the visible items synchronously.
+    updateVisibleIcons();
+
+    // Try to do at least a fast icon loading (without determining the
+    // mime type) for all items, to reduce the risk that the user sees
+    // "unknown" icons when scrolling.
+    if (m_hasUnknownIcons) {
+        updateAllIconsFast(MaxBlockTimeout - timer.elapsed());
     }
-    KIO::PreviewJob* job = new KIO::PreviewJob(itemSubSet, cacheSize, &m_enabledPlugins);
-    job->setIgnoreMaximumSize(items.first().isLocalFile());
-    if (job->ui()) {
-        job->ui()->setWindow(qApp->activeWindow());
+
+    // A detailed update of the items in and near the visible area
+    // only makes sense if sorting is finished.
+    if (m_state == ResolvingSortRole) {
+        return;
     }
 
-    connect(job,  SIGNAL(gotPreview(KFileItem,QPixmap)),
-            this, SLOT(slotGotPreview(KFileItem,QPixmap)));
-    connect(job,  SIGNAL(failed(KFileItem)),
-            this, SLOT(slotPreviewFailed(KFileItem)));
-    connect(job,  SIGNAL(finished(KJob*)),
-            this, SLOT(slotPreviewJobFinished(KJob*)));
+    // Start the preview job or the asynchronous resolving of all roles.
+    QList<int> indexes = indexesToResolve();
 
-    m_previewJobs.append(job);
-}
+    if (m_previewShown) {
+        m_pendingPreviewItems.clear();
+        m_pendingPreviewItems.reserve(indexes.count());
 
+        foreach (int index, indexes) {
+            const KFileItem item = m_model->fileItem(index);
+            if (!m_finishedItems.contains(item)) {
+                m_pendingPreviewItems.append(item);
+            }
+        }
 
-bool KFileItemModelRolesUpdater::hasPendingRoles() const
-{
-    return !m_pendingVisibleItems.isEmpty() || !m_pendingInvisibleItems.isEmpty();
+        startPreviewJob();
+    } else {
+        m_pendingIndexes = indexes;
+        // Trigger the asynchronous resolving of all roles.
+        m_state = ResolvingAllRoles;
+        QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
+    }
 }
 
-void KFileItemModelRolesUpdater::resolvePendingRoles()
+void KFileItemModelRolesUpdater::updateVisibleIcons()
 {
-    int resolvedCount = 0;
-
-    bool hasSlowRoles = m_previewShown;
-    if (!hasSlowRoles) {
-        QSetIterator<QByteArray> it(m_roles);
-        while (it.hasNext()) {
-            if (m_resolvableRoles.contains(it.next())) {
-                hasSlowRoles = true;
-                break;
-            }
+    int lastVisibleIndex = m_lastVisibleIndex;
+    if (lastVisibleIndex <= 0) {
+        // Guess a reasonable value for the last visible index if the view
+        // has not told us about the real value yet.
+        lastVisibleIndex = qMin(m_firstVisibleIndex + m_maximumVisibleItems, m_model->count() - 1);
+        if (lastVisibleIndex <= 0) {
+            lastVisibleIndex = qMin(200, m_model->count() - 1);
         }
     }
 
-    const ResolveHint resolveHint = hasSlowRoles ? ResolveFast : ResolveAll;
-
-    // Resolving the MIME type can be expensive. Assure that not more than MaxBlockTimeout ms are
-    // spend for resolving them synchronously. Usually this is more than enough to determine
-    // all visible items, but there are corner cases where this limit gets easily exceeded.
     QElapsedTimer timer;
     timer.start();
 
-    // Resolve the MIME type of all visible items
-    QSet<KFileItem>::iterator visibleIt = m_pendingVisibleItems.begin();
-    while (visibleIt != m_pendingVisibleItems.end()) {
-        const KFileItem item = *visibleIt;
-        if (!hasSlowRoles) {
-            Q_ASSERT(!m_pendingInvisibleItems.contains(item));
-            // All roles will be resolved by applyResolvedRoles()
-            visibleIt = m_pendingVisibleItems.erase(visibleIt);
-        } else {
-            ++visibleIt;
-        }
-        applyResolvedRoles(item, resolveHint);
-        ++resolvedCount;
-
-        if (timer.elapsed() > MaxBlockTimeout) {
-            break;
-        }
+    // Try to determine the final icons for all visible items.
+    int index;
+    for (index = m_firstVisibleIndex; index <= lastVisibleIndex && timer.elapsed() < MaxBlockTimeout; ++index) {
+        const KFileItem item = m_model->fileItem(index);
+        applyResolvedRoles(item, ResolveFast);
     }
 
-    // Resolve the MIME type of the invisible items at least until the timeout
-    // has been exceeded or the maximum number of items has been reached
-    KFileItemList invisibleItems;
-    if (m_lastVisibleIndex >= 0) {
-        // The visible range is valid, don't care about the order how the MIME
-        // type of invisible items get resolved
-        invisibleItems = m_pendingInvisibleItems.toList();
-    } else {
-        // The visible range is temporary invalid (e.g. happens when loading
-        // a directory) so take care to sort the currently invisible items where
-        // a part will get visible later
-        invisibleItems = sortedItems(m_pendingInvisibleItems);
+    if (index > lastVisibleIndex) {
+        return;
     }
 
-    int index = 0;
-    while (resolvedCount < MaxResolveItemsCount && index < invisibleItems.count() && timer.elapsed() <= MaxBlockTimeout) {
-        const KFileItem item = invisibleItems.at(index);
-        applyResolvedRoles(item, resolveHint);
+    // If this didn't work before MaxBlockTimeout was reached, at least
+    // prevent that the user sees 'unknown' icons.
+    disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+               this,    SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
 
-        if (!hasSlowRoles) {
-            // All roles have been resolved already by applyResolvedRoles()
-            m_pendingInvisibleItems.remove(item);
+    while (index <= lastVisibleIndex) {
+        if (!m_model->data(index).contains("iconName")) {
+            const KFileItem item = m_model->fileItem(index);
+            QHash<QByteArray, QVariant> data;
+            data.insert("iconName", item.iconName());
+            m_model->setData(index, data);
         }
         ++index;
-        ++resolvedCount;
     }
 
-    if (m_previewShown) {
-        KFileItemList items = sortedItems(m_pendingVisibleItems);
-        items += invisibleItems;
-        startPreviewJob(items);
-    } else {
-        QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
-    }
+    connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+            this,    SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
+}
 
-#ifdef KFILEITEMMODELROLESUPDATER_DEBUG
-    if (timer.elapsed() > MaxBlockTimeout) {
-        kDebug() << "Maximum time of" << MaxBlockTimeout
-                 << "ms exceeded, skipping items... Remaining visible:" << m_pendingVisibleItems.count()
-                 << "invisible:" << m_pendingInvisibleItems.count();
+void KFileItemModelRolesUpdater::updateAllIconsFast(int timeout)
+{
+    if (timeout <= 0) {
+        return;
     }
-    kDebug() << "[TIME] Resolved pending roles:" << timer.elapsed();
-#endif
 
-    applySortProgressToModel();
-}
+    QElapsedTimer timer;
+    timer.start();
 
-void KFileItemModelRolesUpdater::resetPendingRoles()
-{
-    m_pendingVisibleItems.clear();
-    m_pendingInvisibleItems.clear();
+    disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+               this,    SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
+
+    const int count = m_model->count();
+    while (m_firstIndexWithoutIcon < count && timer.elapsed() < timeout) {
+        if (!m_model->data(m_firstIndexWithoutIcon).contains("iconName")) {
+            const KFileItem item = m_model->fileItem(m_firstIndexWithoutIcon);
+            QHash<QByteArray, QVariant> data;
+            data.insert("iconName", item.iconName());
+            m_model->setData(m_firstIndexWithoutIcon, data);
+        }
+        ++m_firstIndexWithoutIcon;
+    }
 
-    foreach (KJob* job, m_previewJobs) {
-        job->kill();
+    if (m_firstIndexWithoutIcon == count) {
+        m_hasUnknownIcons = false;
     }
-    Q_ASSERT(m_previewJobs.isEmpty());
+
+    connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+            this,    SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
 }
 
-void KFileItemModelRolesUpdater::sortAndResolveAllRoles()
+void KFileItemModelRolesUpdater::startPreviewJob()
 {
-    if (m_paused) {
+    m_state = PreviewJobRunning;
+
+    if (m_pendingPreviewItems.isEmpty()) {
+        QTimer::singleShot(0, this, SLOT(slotPreviewJobFinished()));
         return;
     }
 
-    resetPendingRoles();
-    Q_ASSERT(m_pendingVisibleItems.isEmpty());
-    Q_ASSERT(m_pendingInvisibleItems.isEmpty());
+    // PreviewJob internally caches items always with the size of
+    // 128 x 128 pixels or 256 x 256 pixels. A (slow) downscaling is done
+    // by PreviewJob if a smaller size is requested. For images KFileItemModelRolesUpdater must
+    // do a downscaling anyhow because of the frame, so in this case only the provided
+    // cache sizes are requested.
+    const QSize cacheSize = (m_iconSize.width() > 128) || (m_iconSize.height() > 128)
+                             ? QSize(256, 256) : QSize(128, 128);
+
+    // KIO::filePreview() will request the MIME-type of all passed items, which (in the
+    // worst case) might block the application for several seconds. To prevent such
+    // a blocking, we only pass items with known mime type to the preview job.
+    const int count = m_pendingPreviewItems.count();
+    KFileItemList itemSubSet;
+    itemSubSet.reserve(count);
 
-    if (m_model->count() == 0) {
-        return;
-    }
+    if (m_pendingPreviewItems.first().isMimeTypeKnown()) {
+        // Some mime types are known already, probably because they were
+        // determined when loading the icons for the visible items. Start
+        // a preview job for all items at the beginning of the list which
+        // have a known mime type.
+        do {
+            itemSubSet.append(m_pendingPreviewItems.takeFirst());
+        } while (!m_pendingPreviewItems.isEmpty() && m_pendingPreviewItems.first().isMimeTypeKnown());
+    } else {
+        // Determine mime types for MaxBlockTimeout ms, and start a preview
+        // job for the corresponding items.
+        QElapsedTimer timer;
+        timer.start();
 
-    // Determine all visible items
-    Q_ASSERT(m_firstVisibleIndex >= 0);
-    for (int i = m_firstVisibleIndex; i <= m_lastVisibleIndex; ++i) {
-        const KFileItem item = m_model->fileItem(i);
-        if (!item.isNull()) {
-            m_pendingVisibleItems.insert(item);
-        }
+        do {
+            const KFileItem item = m_pendingPreviewItems.takeFirst();
+            item.determineMimeType();
+            itemSubSet.append(item);
+        } while (!m_pendingPreviewItems.isEmpty() && timer.elapsed() < MaxBlockTimeout);
     }
 
-    // Determine all invisible items
-    for (int i = 0; i < m_firstVisibleIndex; ++i) {
-        const KFileItem item = m_model->fileItem(i);
-        if (!item.isNull()) {
-            m_pendingInvisibleItems.insert(item);
-        }
-    }
-    const int count = m_model->count();
-    for (int i = m_lastVisibleIndex + 1; i < count; ++i) {
-        const KFileItem item = m_model->fileItem(i);
-        if (!item.isNull()) {
-            m_pendingInvisibleItems.insert(item);
-        }
+    KIO::PreviewJob* job = new KIO::PreviewJob(itemSubSet, cacheSize, &m_enabledPlugins);
+
+    job->setIgnoreMaximumSize(itemSubSet.first().isLocalFile());
+    if (job->ui()) {
+        job->ui()->setWindow(qApp->activeWindow());
     }
 
-    resolvePendingRoles();
+    connect(job,  SIGNAL(gotPreview(KFileItem,QPixmap)),
+            this, SLOT(slotGotPreview(KFileItem,QPixmap)));
+    connect(job,  SIGNAL(failed(KFileItem)),
+            this, SLOT(slotPreviewFailed(KFileItem)));
+    connect(job,  SIGNAL(finished(KJob*)),
+            this, SLOT(slotPreviewJobFinished()));
+
+    m_previewJob = job;
 }
 
-void KFileItemModelRolesUpdater::sortAndResolvePendingRoles()
+void KFileItemModelRolesUpdater::updateChangedItems()
 {
-    Q_ASSERT(!m_paused);
-    if (m_model->count() == 0) {
+    if (m_state == Paused) {
         return;
     }
 
-    // If no valid index range is given assume that all items are visible.
-    // A cleanup will be done later as soon as the index range has been set.
-    const bool hasValidIndexRange = (m_lastVisibleIndex >= 0);
+    if (m_changedItems.isEmpty()) {
+        return;
+    }
 
-    // Trigger a preview generation of all pending items. Assure that the visible
-    // pending items get generated first.
+    m_finishedItems -= m_changedItems;
 
-    // Step 1: Check if any items in m_pendingVisibleItems are not visible any more
-    //         and move them to m_pendingInvisibleItems.
-    QSet<KFileItem>::iterator itVisible = m_pendingVisibleItems.begin();
-    while (itVisible != m_pendingVisibleItems.end()) {
-        const KFileItem item = *itVisible;
-        if (item.isNull()) {
-            itVisible = m_pendingVisibleItems.erase(itVisible);
-            continue;
-        }
+    if (m_resolvableRoles.contains(m_model->sortRole())) {
+        m_pendingSortRoleItems += m_changedItems;
 
-        const int index = m_model->index(item);
-        if (!hasValidIndexRange || (index >= m_firstVisibleIndex && index <= m_lastVisibleIndex)) {
-            ++itVisible;
-        } else {
-            itVisible = m_pendingVisibleItems.erase(itVisible);
-            m_pendingInvisibleItems.insert(item);
+        if (m_state != ResolvingSortRole) {
+            // Stop the preview job if necessary, and trigger the
+            // asynchronous determination of the sort role.
+            killPreviewJob();
+            m_state = ResolvingSortRole;
+            QTimer::singleShot(0, this, SLOT(resolveNextSortRole()));
         }
+
+        return;
     }
 
-    // Step 2: Check if any items in m_pendingInvisibleItems have become visible
-    //         and move them to m_pendingVisibleItems.
-    QSet<KFileItem>::iterator itInvisible = m_pendingInvisibleItems.begin();
-    while (itInvisible != m_pendingInvisibleItems.end()) {
-        const KFileItem item = *itInvisible;
-        if (item.isNull()) {
-            itInvisible = m_pendingInvisibleItems.erase(itInvisible);
+    QList<int> visibleChangedIndexes;
+    QList<int> invisibleChangedIndexes;
+
+    foreach (const KFileItem& item, m_changedItems) {
+        const int index = m_model->index(item);
+
+        if (index < 0) {
+            m_changedItems.remove(item);
             continue;
         }
 
-        const int index = m_model->index(item);
-        if (!hasValidIndexRange || (index >= m_firstVisibleIndex && index <= m_lastVisibleIndex)) {
-            itInvisible = m_pendingInvisibleItems.erase(itInvisible);
-            m_pendingVisibleItems.insert(item);
+        if (index >= m_firstVisibleIndex && index <= m_lastVisibleIndex) {
+            visibleChangedIndexes.append(index);
         } else {
-            ++itInvisible;
+            invisibleChangedIndexes.append(index);
         }
     }
 
-    resolvePendingRoles();
-}
+    std::sort(visibleChangedIndexes.begin(), visibleChangedIndexes.end());
 
-void KFileItemModelRolesUpdater::applySortProgressToModel()
-{
-    if (m_sortingProgress < 0) {
-        return;
-    }
+    if (m_previewShown) {
+        foreach (int index, visibleChangedIndexes) {
+            m_pendingPreviewItems.append(m_model->fileItem(index));
+        }
 
-    // Inform the model about the progress of the resolved items,
-    // so that it can give an indication when the sorting has been finished.
-    const int resolvedCount = m_model->count()
-                              - m_pendingVisibleItems.count()
-                              - m_pendingInvisibleItems.count();
-    if (resolvedCount > 0) {
-        m_model->emitSortProgress(resolvedCount);
-        if (resolvedCount == m_model->count()) {
-            m_sortingProgress = -1;
+        foreach (int index, invisibleChangedIndexes) {
+            m_pendingPreviewItems.append(m_model->fileItem(index));
+        }
+
+        if (!m_previewJob) {
+            startPreviewJob();
+        }
+    } else {
+        const bool resolvingInProgress = !m_pendingIndexes.isEmpty();
+        m_pendingIndexes = visibleChangedIndexes + m_pendingIndexes + invisibleChangedIndexes;
+        if (!resolvingInProgress) {
+            // Trigger the asynchronous resolving of the changed roles.
+            m_state = ResolvingAllRoles;
+            QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
         }
     }
 }
 
-void KFileItemModelRolesUpdater::updateSortProgress()
+void KFileItemModelRolesUpdater::applySortRole(int index)
 {
-    const QByteArray sortRole = m_model->sortRole();
+    QHash<QByteArray, QVariant> data;
+    const KFileItem item = m_model->fileItem(index);
 
-    // Optimization if the sorting is done by type: In case if all MIME-types
-    // are known, the types have been resolved already by KFileItemModel and
-    // no sort-progress feedback is required.
-    const bool showProgress = (sortRole == "type")
-                              ? hasUnknownMimeTypes()
-                              : m_resolvableRoles.contains(sortRole);
+    if (m_model->sortRole() == "type") {
+        if (!item.isMimeTypeKnown()) {
+            item.determineMimeType();
+        }
 
-    if (m_sortingProgress >= 0) {
-        // Mark the current sorting as finished
-        m_model->emitSortProgress(m_model->count());
+        data.insert("type", item.mimeComment());
+    } else if (m_model->sortRole() == "size" && item.isLocalFile() && item.isDir()) {
+        const QString path = item.localPath();
+        data.insert("size", subItemsCount(path));
+    } else {
+        // Probably the sort role is a Nepomuk role - just determine all roles.
+        data = rolesData(item);
     }
-    m_sortingProgress = showProgress ? 0 : -1;
+
+    disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+               this,    SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
+    m_model->setData(index, data);
+    connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+            this,    SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
 }
 
-bool KFileItemModelRolesUpdater::hasUnknownMimeTypes() const
+void KFileItemModelRolesUpdater::applySortProgressToModel()
 {
-    const int count = m_model->count();
-    for (int i = 0; i < count; ++i) {
-        const KFileItem item = m_model->fileItem(i);
-        if (!item.isMimeTypeKnown()) {
-            return true;
-        }
-    }
-
-    return false;
+    // Inform the model about the progress of the resolved items,
+    // so that it can give an indication when the sorting has been finished.
+    const int resolvedCount = m_model->count() - m_pendingSortRoleItems.count();
+    m_model->emitSortProgress(resolvedCount);
 }
 
 bool KFileItemModelRolesUpdater::applyResolvedRoles(const KFileItem& item, ResolveHint hint)
@@ -1017,13 +1107,18 @@ bool KFileItemModelRolesUpdater::applyResolvedRoles(const KFileItem& item, Resol
 
     const bool resolveAll = (hint == ResolveAll);
 
-    bool mimeTypeChanged = false;
+    bool iconChanged = false;
     if (!item.isMimeTypeKnown() || !item.isFinalIconKnown()) {
         item.determineMimeType();
-        mimeTypeChanged = true;
+        iconChanged = true;
+    } else {
+        const int index = m_model->index(item);
+        if (!m_model->data(index).contains("iconName")) {
+            iconChanged = true;
+        }
     }
 
-    if (mimeTypeChanged || resolveAll || m_clearPreviews) {
+    if (iconChanged || resolveAll || m_clearPreviews) {
         const int index = m_model->index(item);
         if (index < 0) {
             return false;
@@ -1116,42 +1211,6 @@ QHash<QByteArray, QVariant> KFileItemModelRolesUpdater::rolesData(const KFileIte
     return data;
 }
 
-KFileItemList KFileItemModelRolesUpdater::sortedItems(const QSet<KFileItem>& items) const
-{
-    KFileItemList itemList;
-    if (items.isEmpty()) {
-        return itemList;
-    }
-
-#ifdef KFILEITEMMODELROLESUPDATER_DEBUG
-    QElapsedTimer timer;
-    timer.start();
-#endif
-
-    QList<int> indexes;
-    indexes.reserve(items.count());
-
-    QSetIterator<KFileItem> it(items);
-    while (it.hasNext()) {
-        const KFileItem item = it.next();
-        const int index = m_model->index(item);
-        if (index >= 0) {
-            indexes.append(index);
-        }
-    }
-    qSort(indexes);
-
-    itemList.reserve(items.count());
-    foreach (int index, indexes) {
-        itemList.append(m_model->fileItem(index));
-    }
-
-#ifdef KFILEITEMMODELROLESUPDATER_DEBUG
-    kDebug() << "[TIME] Sorting of items:" << timer.elapsed();
-#endif
-    return itemList;
-}
-
 int KFileItemModelRolesUpdater::subItemsCount(const QString& path) const
 {
     const bool countHiddenFiles = m_model->showHiddenFiles();
@@ -1209,11 +1268,84 @@ int KFileItemModelRolesUpdater::subItemsCount(const QString& path) const
 
 void KFileItemModelRolesUpdater::updateAllPreviews()
 {
-    if (m_paused) {
+    if (m_state == Paused) {
         m_previewChangedDuringPausing = true;
     } else {
-        sortAndResolveAllRoles();
+        m_finishedItems.clear();
+        startUpdating();
+    }
+}
+
+void KFileItemModelRolesUpdater::killPreviewJob()
+{
+    if (m_previewJob) {
+        disconnect(m_previewJob,  SIGNAL(gotPreview(KFileItem,QPixmap)),
+                   this, SLOT(slotGotPreview(KFileItem,QPixmap)));
+        disconnect(m_previewJob,  SIGNAL(failed(KFileItem)),
+                   this, SLOT(slotPreviewFailed(KFileItem)));
+        disconnect(m_previewJob,  SIGNAL(finished(KJob*)),
+                   this, SLOT(slotPreviewJobFinished()));
+        m_previewJob->kill();
+        m_previewJob = 0;
+        m_pendingPreviewItems.clear();
     }
 }
 
+QList<int> KFileItemModelRolesUpdater::indexesToResolve() const
+{
+    const int count = m_model->count();
+
+    QList<int> result;
+    result.reserve(ResolveAllItemsLimit);
+
+    // Add visible items.
+    for (int i = m_firstVisibleIndex; i <= m_lastVisibleIndex; ++i) {
+        result.append(i);
+    }
+
+    // We need a reasonable upper limit for number of items to resolve after
+    // and before the visible range. m_maximumVisibleItems can be quite large
+    // when using Compace View.
+    const int readAheadItems = qMin(ReadAheadPages * m_maximumVisibleItems, ResolveAllItemsLimit / 2);
+
+    // Add items after the visible range.
+    const int endExtendedVisibleRange = qMin(m_lastVisibleIndex + readAheadItems, count - 1);
+    for (int i = m_lastVisibleIndex + 1; i <= endExtendedVisibleRange; ++i) {
+        result.append(i);
+    }
+
+    // Add items before the visible range in reverse order.
+    const int beginExtendedVisibleRange = qMax(0, m_firstVisibleIndex - readAheadItems);
+    for (int i = m_firstVisibleIndex - 1; i >= beginExtendedVisibleRange; --i) {
+        result.append(i);
+    }
+
+    // Add items on the last page.
+    const int beginLastPage = qMax(qMin(endExtendedVisibleRange + 1, count - 1), count - m_maximumVisibleItems);
+    for (int i = beginLastPage; i < count; ++i) {
+        result.append(i);
+    }
+
+    // Add items on the first page.
+    const int endFirstPage = qMin(qMax(beginExtendedVisibleRange - 1, 0), m_maximumVisibleItems);
+    for (int i = 0; i <= endFirstPage; ++i) {
+        result.append(i);
+    }
+
+    // Continue adding items until ResolveAllItemsLimit is reached.
+    int remainingItems = ResolveAllItemsLimit - result.count();
+
+    for (int i = endExtendedVisibleRange + 1; i < beginLastPage && remainingItems > 0; ++i) {
+        result.append(i);
+        --remainingItems;
+    }
+
+    for (int i = beginExtendedVisibleRange - 1; i > endFirstPage && remainingItems > 0; --i) {
+        result.append(i);
+        --remainingItems;
+    }
+
+    return result;
+}
+
 #include "kfileitemmodelrolesupdater.moc"
index b837e8c7f6b2e24fad226ef7da34edf63973bc14..20ce21cfa4093f0500e5ad36cbd96c3d01f4fdb3 100644 (file)
@@ -59,6 +59,38 @@ class QTimer;
  * KFileItemModel only resolves roles that are inexpensive like e.g. the file name or
  * the permissions. Creating previews or determining the MIME-type can be quite expensive
  * and KFileItemModelRolesUpdater takes care to update such roles asynchronously.
+ *
+ * To prevent a huge CPU and I/O load, these roles are not updated for all
+ * items, but only for the visible items, some items around the visible area,
+ * and the items on the first and last pages of the view. This is a compromise
+ * that aims to minimize the risk that the user sees items with unknown icons
+ * in the view when scrolling or pressing Home or End.
+ *
+ * Determining the roles is done in several phases:
+ *
+ * 1.   If the sort role is "slow", it is determined for all items. If this
+ *      cannot be finished synchronously in 200 ms, the remaining items are
+ *      handled asynchronously by \a resolveNextSortRole().
+ *
+ * 2.   The function startUpdating(), which is called if either the sort role
+ *      has been successfully determined for all items, or items are inserted
+ *      in the view, or the visible items might have changed because items
+ *      were removed or moved, tries to determine the icons for all visible
+ *      items synchronously for 200 ms. Then:
+ *
+ *      (a) If previews are disabled, icons and all other roles are determined
+ *          asynchronously for the interesting items. This is done by the
+ *          function \a resolveNextPendingRoles().
+ *
+ *      (b) If previews are enabled, a \a KIO::PreviewJob is started that loads
+ *          the previews for the interesting items. At the same time, the icons
+ *          for these items are determined asynchronously as fast as possible
+ *          by \a resolveNextPendingRoles(). This minimizes the risk that the
+ *          user sees "unknown" icons when scrolling before the previews have
+ *          arrived.
+ *
+ * 3.   Finally, the entire process is repeated for any items that might have
+ *      changed in the mean time.
  */
 class LIBDOLPHINPRIVATE_EXPORT KFileItemModelRolesUpdater : public QObject
 {
@@ -129,6 +161,7 @@ public:
 private slots:
     void slotItemsInserted(const KItemRangeList& itemRanges);
     void slotItemsRemoved(const KItemRangeList& itemRanges);
+    void slotItemsMoved(const KItemRange& itemRange, QList<int> movedToIndexes);
     void slotItemsChanged(const KItemRangeList& itemRanges,
                           const QSet<QByteArray>& roles);
     void slotSortRoleChanged(const QByteArray& current,
@@ -147,20 +180,33 @@ private slots:
     void slotPreviewFailed(const KFileItem& item);
 
     /**
-     * Is invoked when the preview job has been finished and
-     * removes the job from the m_previewJobs list.
+     * Is invoked when the preview job has been finished. Starts a new preview
+     * job if there are any interesting items without previews left, or updates
+     * the changed items otherwise.     *
      * @see startPreviewJob()
      */
-    void slotPreviewJobFinished(KJob* job);
+    void slotPreviewJobFinished();
+
+    /**
+     * Resolves the sort role of the next item in m_pendingSortRole, applies it
+     * to the model, and invokes itself if there are any pending items left. If
+     * that is not the case, \a startUpdating() is called.
+     */
+    void resolveNextSortRole();
 
+    /**
+     * Resolves the icon name and (if previews are disabled) all other roles
+     * for the next interesting item. If there are no pending items left, any
+     * changed items are updated.
+     */
     void resolveNextPendingRoles();
 
     /**
      * Resolves items that have not been resolved yet after the change has been
      * notified by slotItemsChanged(). Is invoked if the m_changedItemsTimer
-     * exceeds.
+     * expires.
      */
-    void resolveChangedItems();
+    void resolveRecentlyChangedItems();
 
     void applyChangedNepomukRoles(const Nepomuk2::Resource& resource);
 
@@ -173,41 +219,46 @@ private slots:
 
 private:
     /**
-     * Updates the roles for the given item ranges. The roles for the currently
-     * visible items will get updated first.
+     * Starts the updating of all roles. The visible items are handled first.
+     */
+    void startUpdating();
+
+    /**
+     * Loads the icons for the visible items. After 200 ms, the function
+     * stops determining mime types and only loads preliminary icons.
+     * This is a compromise that prevents that
+     * (a) the GUI is blocked for more than 200 ms, and
+     * (b) "unknown" icons could be shown in the view.
+     */
+    void updateVisibleIcons();
+
+    /**
+     * Tries to load at least preliminary icons (without determining the
+     * mime type) for all items for \a timeout milliseconds.
      */
-    void startUpdating(const KItemRangeList& itemRanges);
+    void updateAllIconsFast(int timeout);
 
     /**
-     * Creates previews for the items starting from the first item of the
-     * given list.
+     * Creates previews for the items starting from the first item in
+     * m_pendingPreviewItems.
      * @see slotGotPreview()
      * @see slotPreviewFailed()
      * @see slotPreviewJobFinished()
      */
-    void startPreviewJob(const KFileItemList& items);
-
-    bool hasPendingRoles() const;
-    void resolvePendingRoles();
-    void resetPendingRoles();
-    void sortAndResolveAllRoles();
-    void sortAndResolvePendingRoles();
-    void applySortProgressToModel();
+    void startPreviewJob();
 
     /**
-     * Updates m_sortProgress to be 0 if the sort-role
-     * needs to get resolved asynchronously and hence a
-     * progress is required. Otherwise m_sortProgress
-     * will be set to -1 which means that no progress
-     * will be provided.
+     * Ensures that icons, previews, and other roles are determined for any
+     * items that have been changed.
      */
-    void updateSortProgress();
+    void updateChangedItems();
 
     /**
-     * @return True, if at least one item from the model
-     *         has an unknown MIME-type.
+     * Resolves the sort role of the item and applies it to the model.
      */
-    bool hasUnknownMimeTypes() const;
+    void applySortRole(int index);
+
+    void applySortProgressToModel();
 
     enum ResolveHint {
         ResolveFast,
@@ -216,8 +267,6 @@ private:
     bool applyResolvedRoles(const KFileItem& item, ResolveHint hint);
     QHash<QByteArray, QVariant> rolesData(const KFileItem& item) const;
 
-    KFileItemList sortedItems(const QSet<KFileItem>& items) const;
-
     /**
      * @return The number of items of the path \a path.
      */
@@ -229,9 +278,20 @@ private:
      */
     void updateAllPreviews();
 
+    void killPreviewJob();
+
+    QList<int> indexesToResolve() const;
+
 private:
-    // Property for setPaused()/isPaused().
-    bool m_paused;
+    enum State {
+        Idle,
+        Paused,
+        ResolvingSortRole,
+        ResolvingAllRoles,
+        PreviewJobRunning
+    };
+
+    State m_state;
 
     // Property changes during pausing must be remembered to be able
     // to react when unpausing again:
@@ -250,7 +310,9 @@ private:
     // during the roles-updater has been paused by setPaused().
     bool m_clearPreviews;
 
-    int m_sortingProgress;
+    // Remembers which items have been handled already, to prevent that
+    // previews and other expensive roles are determined again.
+    QSet<KFileItem> m_finishedItems;
 
     KFileItemModel* m_model;
     QSize m_iconSize;
@@ -261,16 +323,33 @@ private:
     QSet<QByteArray> m_resolvableRoles;
     QStringList m_enabledPlugins;
 
-    QSet<KFileItem> m_pendingVisibleItems;
-    QSet<KFileItem> m_pendingInvisibleItems;
-    QList<KJob*> m_previewJobs;
+    // Items for which the sort role still has to be determined.
+    QSet<KFileItem> m_pendingSortRoleItems;
+
+    // Determines if the next call of startUpdating() will try to do a fast
+    // icon loading (i.e., without determining the mime type) for all items.
+    bool m_hasUnknownIcons;
+    int m_firstIndexWithoutIcon;
+
+    // Indexes of items which still have to be handled by
+    // resolveNextPendingRoles().
+    QList<int> m_pendingIndexes;
+
+    // Items which have been left over from the last call of startPreviewJob().
+    // A new preview job will be started from them once the first one finishes.
+    KFileItemList m_pendingPreviewItems;
+
+    KJob* m_previewJob;
 
     // When downloading or copying large files, the slot slotItemsChanged()
     // will be called periodically within a quite short delay. To prevent
     // a high CPU-load by generating e.g. previews for each notification, the update
     // will be postponed until no file change has been done within a longer period
     // of time.
-    QTimer* m_changedItemsTimer;
+    QTimer* m_recentlyChangedItemsTimer;
+    QSet<KFileItem> m_recentlyChangedItems;
+
+    // Items which have not been changed repeatedly recently.
     QSet<KFileItem> m_changedItems;
 
     KDirWatch* m_dirWatcher;
index c6239df94150560a5e24fe6e85d3e85155c88d17..4629b29f1b3a13f30e1a1ef731a786a0b6a73607 100644 (file)
@@ -838,27 +838,39 @@ bool KItemListController::dragMoveEvent(QGraphicsSceneDragDropEvent* event, cons
             oldHoveredWidget->setHovered(false);
             emit itemUnhovered(oldHoveredWidget->index());
         }
+    }
 
-        if (newHoveredWidget) {
-            bool droppingBetweenItems = false;
-            if (m_model->sortRole().isEmpty()) {
-                // The model supports inserting items between other items.
-                droppingBetweenItems = (m_view->showDropIndicator(pos) >= 0);
-            }
+    if (newHoveredWidget) {
+        bool droppingBetweenItems = false;
+        if (m_model->sortRole().isEmpty()) {
+            // The model supports inserting items between other items.
+            droppingBetweenItems = (m_view->showDropIndicator(pos) >= 0);
+        }
 
-            const int index = newHoveredWidget->index();
-            if (!droppingBetweenItems && m_model->supportsDropping(index)) {
+        const int index = newHoveredWidget->index();
+        if (!droppingBetweenItems) {
+            if (m_model->supportsDropping(index)) {
                 // Something has been dragged on an item.
                 m_view->hideDropIndicator();
-                newHoveredWidget->setHovered(true);
-                emit itemHovered(index);
+                if (!newHoveredWidget->isHovered()) {
+                    newHoveredWidget->setHovered(true);
+                    emit itemHovered(index);
+                }
 
-                if (m_autoActivationTimer->interval() >= 0) {
+                if (!m_autoActivationTimer->isActive() && m_autoActivationTimer->interval() >= 0) {
                     m_autoActivationTimer->setProperty("index", index);
                     m_autoActivationTimer->start();
                 }
             }
+        } else {
+            m_autoActivationTimer->stop();
+            if (newHoveredWidget && newHoveredWidget->isHovered()) {
+                newHoveredWidget->setHovered(false);
+                emit itemUnhovered(index);
+            }
         }
+    } else {
+        m_view->hideDropIndicator();
     }
 
     return false;
index a2629c5654dc0511a9c860ee241a42cc1f82132a..b5e105843375c7c3fb54829408d986a10de31e10 100644 (file)
@@ -678,6 +678,16 @@ void KItemListView::paint(QPainter* painter, const QStyleOptionGraphicsItem* opt
     }
 }
 
+QVariant KItemListView::itemChange(GraphicsItemChange change, const QVariant &value)
+{
+    if (change == QGraphicsItem::ItemSceneHasChanged && scene()) {
+        if (!scene()->views().isEmpty()) {
+            m_styleOption.palette = scene()->views().at(0)->palette();
+        }
+    }
+    return QGraphicsItem::itemChange(change, value);
+}
+
 void KItemListView::setItemSize(const QSizeF& size)
 {
     const QSizeF previousSize = m_itemSize;
@@ -2365,7 +2375,8 @@ int KItemListView::showDropIndicator(const QPointF& pos)
         const QRectF rect = itemRect(widget->index());
         if (mappedPos.y() >= 0 && mappedPos.y() <= rect.height()) {
             if (m_model->supportsDropping(widget->index())) {
-                const int gap = qMax(4, m_styleOption.padding);
+                // Keep 30% of the rectangle as the gap instead of always having a fixed gap
+                const int gap = qMax(4.0, 0.3 * rect.height());
                 if (mappedPos.y() >= gap && mappedPos.y() <= rect.height() - gap) {
                     return -1;
                 }
index 6d609a9df5d5e15f363b1a82c4697f59716647bd..6467b8c915cc252c56bfafbdd46b3534d91d33d7 100644 (file)
@@ -321,6 +321,7 @@ signals:
     void roleEditingFinished(int index, const QByteArray& role, const QVariant& value);
 
 protected:
+    virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value);
     void setItemSize(const QSizeF& size);
     void setStyleOption(const KItemListStyleOption& option);
 
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 da8f72b7ee67995c7e66d2cc0988a9a4ba30dc58..38154864bba558eed3bbb0435eb3feda31c1f39c 100644 (file)
@@ -40,7 +40,7 @@ void KItemListKeyboardSearchManager::addKeys(const QString& keys)
 {
     const bool keyboardTimeWasValid = m_keyboardInputTime.isValid();
     const qint64 keyboardInputTimeElapsed = m_keyboardInputTime.restart();
-    if (keyboardInputTimeElapsed > m_timeout || !keyboardTimeWasValid || keys.isEmpty()) {
+    if (keyboardInputTimeElapsed > m_timeout || !keyboardTimeWasValid) {
         m_searchedString.clear();
     }
 
index 5addff19444b36dac0f968605d8820b38efe251f..e5ac4351c902e574534c2947eee3b5fad65356ae 100644 (file)
@@ -33,10 +33,10 @@ 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"));
+                     ki18nc("@info:credit", "(C) 2006-2013 Peter Penz and Frank Reininghaus"));
     about.setHomepage("http://dolphin.kde.org");
     about.addAuthor(ki18nc("@info:credit", "Frank Reininghaus"),
                     ki18nc("@info:credit", "Maintainer (since 2012) and developer"),
@@ -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 75e14d0fb01140d4099c42db025a661c9df934f4..5723b80a274cce09f9a752d37e101cd9db9adab7 100644 (file)
@@ -176,6 +176,10 @@ PlacesItem::GroupType PlacesItem::groupType() const
             return SearchForType;
         }
 
+        if (protocol == QLatin1String("bluetooth")) {
+            return DevicesType;
+        }
+
         return PlacesType;
     }
 
index caf6b75662c4953fce522d86faf4f8dbfba0fe1e..eae2095c994ff37fe78fb8dc9a82a1be2cdda644 100644 (file)
@@ -444,6 +444,11 @@ void PlacesItemModel::dropMimeDataBefore(int index, const QMimeData* mimeData)
                 text = url.host();
             }
 
+            if (url.isLocalFile() && !QFileInfo(url.toLocalFile()).isDir()) {
+                // Only directories are allowed
+                continue;
+            }
+
             PlacesItem* newItem = createPlacesItem(text, url);
             const int dropIndex = groupedDropIndex(index, newItem);
             insertItem(dropIndex, newItem);
@@ -612,17 +617,13 @@ void PlacesItemModel::slotStorageSetupDone(Solid::ErrorType error,
     }
 
     if (error) {
-        // TODO: Request message-freeze exception
         if (errorData.isValid()) {
-        //    emit errorMessage(i18nc("@info", "An error occurred while accessing '%1', the system responded: %2",
-        //                            item->text(),
-        //                            errorData.toString()));
-            emit errorMessage(QString("An error occurred while accessing '%1', the system responded: %2")
-                              .arg(item->text()).arg(errorData.toString()));
+            emit errorMessage(i18nc("@info", "An error occurred while accessing '%1', the system responded: %2",
+                                    item->text(),
+                                    errorData.toString()));
         } else {
-        //    emit errorMessage(i18nc("@info", "An error occurred while accessing '%1'",
-        //                            item->text()));
-            emit errorMessage(QString("An error occurred while accessing '%1'").arg(item->text()));
+            emit errorMessage(i18nc("@info", "An error occurred while accessing '%1'",
+                                    item->text()));
         }
         emit storageSetupDone(index, false);
     } else {
index 56042d8564f842f4854e54be5aa7f3d09bd9fcf0..d5308eabe1ccf366978d00a2b800cd9f5be60199 100644 (file)
@@ -332,6 +332,12 @@ void PlacesPanel::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* even
         return;
     }
 
+    const PlacesItem* destItem = m_model->placesItem(index);
+    const PlacesItem::GroupType group = destItem->groupType();
+    if (group == PlacesItem::SearchForType || group == PlacesItem::RecentlyAccessedType) {
+        return;
+    }
+
     if (m_model->storageSetupNeeded(index)) {
         connect(m_model, SIGNAL(storageSetupDone(int,bool)),
                 this, SLOT(slotItemDropEventStorageSetupDone(int,bool)));
@@ -356,14 +362,18 @@ void PlacesPanel::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* even
         return;
     }
 
-    KUrl destUrl = m_model->placesItem(index)->url();
+    KUrl destUrl = destItem->url();
     QDropEvent dropEvent(event->pos().toPoint(),
                          event->possibleActions(),
                          event->mimeData(),
                          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 +385,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 +409,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 1792b201761f683b8559a4b815805645d953ac1f..ef9c2bfcf627b7712f6dbc6e1136fe9bf0160171 100644 (file)
@@ -473,7 +473,7 @@ void DolphinSearchBox::updateFacetsToggleButton()
     const bool facetsIsVisible = SearchSettings::showFacetsWidget();
     m_facetsToggleButton->setChecked(facetsIsVisible ? true : false);
     m_facetsToggleButton->setIcon(KIcon(facetsIsVisible ? "arrow-up-double" : "arrow-down-double"));
-    m_facetsToggleButton->setText(facetsIsVisible ? i18nc("action:button", "Less Options") : i18nc("action:button", "More Options"));
+    m_facetsToggleButton->setText(facetsIsVisible ? i18nc("action:button", "Fewer Options") : i18nc("action:button", "More Options"));
 }
 
 #include "dolphinsearchbox.moc"
index 31f6fcc7bdfa8289a9140fab14cea695c2eaf095..b723f1ec00a4547b99760ede1a6fe8919f6895b6 100644 (file)
@@ -28,6 +28,8 @@
 
 #include <KGlobal>
 #include <KUrl>
+#include <QFileInfo>
+#include <QDir>
 
 struct DolphinSearchInformationSingleton
 {
@@ -50,12 +52,35 @@ bool DolphinSearchInformation::isIndexingEnabled() const
     return m_indexingEnabled;
 }
 
+namespace {
+    /// recursively check if a folder is hidden
+    bool isDirHidden( QDir& dir ) {
+        if (QFileInfo(dir.path()).isHidden()) {
+            return true;
+        } else if (dir.cdUp()) {
+            return isDirHidden(dir);
+        } else {
+            return false;
+        }
+    }
+
+    bool isDirHidden(const QString& path) {
+        QDir dir(path);
+        return isDirHidden(dir);
+    }
+}
+
 bool DolphinSearchInformation::isPathIndexed(const KUrl& url) const
 {
 #ifdef HAVE_NEPOMUK
     const KConfig strigiConfig("nepomukstrigirc");
     const QStringList indexedFolders = strigiConfig.group("General").readPathEntry("folders", QStringList());
 
+    // Nepomuk does not index hidden folders
+    if (isDirHidden(url.toLocalFile())) {
+        return false;
+    }
+
     // Check whether the path is part of an indexed folder
     bool isIndexed = false;
     foreach (const QString& indexedFolder, indexedFolders) {
index 48e816be7dd4476082df52dd6e730b32575d48db..9adca9baf595366f26caa2217a3992ce22558f63 100644 (file)
@@ -22,6 +22,7 @@
 #include "dolphin_generalsettings.h"
 #include "dolphin_versioncontrolsettings.h"
 
+#include <kabstractfileitemactionplugin.h>
 #include <KConfig>
 #include <KConfigGroup>
 #include <KDesktopFile>
@@ -223,7 +224,15 @@ void ServicesSettingsPage::loadServices()
     foreach (const KSharedPtr<KService>& service, pluginServices) {
         const QString desktopEntryName = service->desktopEntryName();
         if (!isInServicesList(desktopEntryName)) {
-            const bool checked = showGroup.readEntry(desktopEntryName, true);
+            bool checked;
+
+            KAbstractFileItemActionPlugin* abstractPlugin = service->createInstance<KAbstractFileItemActionPlugin>();
+            if (abstractPlugin) {
+                checked = showGroup.readEntry(desktopEntryName, abstractPlugin->enabledByDefault());
+            } else {
+                checked = showGroup.readEntry(desktopEntryName, true);
+            }
+
             addRow(service->icon(), service->name(), desktopEntryName, checked);
         }
     }
index 068b6325a19e2e671a843c6d8093a79387e21fc1..6f734ed4d544478ada354ad41f3a679c85427a2c 100644 (file)
@@ -107,14 +107,18 @@ DolphinStatusBar::DolphinStatusBar(QWidget* parent) :
     const int zoomSliderHeight = m_zoomSlider->minimumSizeHint().height();
     const int contentHeight = qMax(fontHeight, zoomSliderHeight);
 
-    m_label->setMinimumHeight(contentHeight);
-    m_label->setMaximumHeight(contentHeight);
+    m_label->setFixedHeight(contentHeight);
     m_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
 
-    const QSize size(150, contentHeight);
-    applyFixedWidgetSize(m_spaceInfo, size);
-    applyFixedWidgetSize(m_progressBar, size);
-    applyFixedWidgetSize(m_zoomSlider, size);
+    m_zoomSlider->setFixedHeight(contentHeight);
+    m_zoomSlider->setMaximumWidth(150);
+
+    m_spaceInfo->setFixedHeight(contentHeight);
+    m_spaceInfo->setMaximumWidth(150);
+    m_spaceInfo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+
+    m_progressBar->setFixedHeight(contentHeight);
+    m_progressBar->setMaximumWidth(150);
 
     QHBoxLayout* topLayout = new QHBoxLayout(this);
     topLayout->setMargin(0);
@@ -349,11 +353,4 @@ void DolphinStatusBar::updateZoomSliderToolTip(int zoomLevel)
     m_zoomSlider->setToolTip(i18ncp("@info:tooltip", "Size: 1 pixel", "Size: %1 pixels", size));
 }
 
-void DolphinStatusBar::applyFixedWidgetSize(QWidget* widget, const QSize& size)
-{
-    widget->setMinimumSize(size);
-    widget->setMaximumSize(size);
-    widget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
-}
-
 #include "dolphinstatusbar.moc"
index d7e37f584c2204513895a5d018edc07318f83b62..b2afe2eb9f6da930658d62a6c0b04af9426a8d28 100644 (file)
@@ -137,8 +137,6 @@ private:
      */
     void updateZoomSliderToolTip(int zoomLevel);
 
-    void applyFixedWidgetSize(QWidget* widget, const QSize& size);
-
 private:
     QString m_text;
     QString m_defaultText;
index 84c2e5ae51bbb5dd1cefc3dd28c4074784c46dc5..dd761fc90b0113f81359e30a2fd2531a146e314a 100644 (file)
@@ -57,6 +57,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 fd6c2be90a8df0f30ffed4fd6aa8647f6e0b235c..0ad7a378dd9870895aba143c75fb873b96fbb86f 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"
@@ -72,6 +74,7 @@ private slots:
     void testItemRangeConsistencyWhenInsertingItems();
     void testExpandItems();
     void testExpandParentItems();
+    void testMakeExpandedItemHidden();
     void testSorting();
     void testIndexForKeyboardSearch();
     void testNameFilter();
@@ -80,9 +83,9 @@ private slots:
     void testRemoveHiddenItems();
     void collapseParentOfHiddenItems();
     void removeParentOfHiddenItems();
+    void testGeneralParentChildRelationships();
 
 private:
-    bool isModelConsistent() const;
     QStringList itemsInModel() const;
 
 private:
@@ -157,7 +160,7 @@ void KFileItemModelTest::testNewItems()
 
     QCOMPARE(m_model->count(), 3);
 
-    QVERIFY(isModelConsistent());
+    QVERIFY(m_model->isConsistent());
 }
 
 void KFileItemModelTest::testRemoveItems()
@@ -167,13 +170,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()
@@ -216,7 +219,7 @@ void KFileItemModelTest::testDirLoadingCompleted()
     QCOMPARE(itemsRemovedSpy.count(), 2);
     QCOMPARE(m_model->count(), 4);
 
-    QVERIFY(isModelConsistent());
+    QVERIFY(m_model->isConsistent());
 }
 
 void KFileItemModelTest::testSetData()
@@ -237,7 +240,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()
@@ -319,7 +322,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::testChangeSortRole()
@@ -397,7 +400,7 @@ void KFileItemModelTest::testModelConsistencyWhenInsertingItems()
             QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
         }
 
-        QVERIFY(isModelConsistent());
+        QVERIFY(m_model->isConsistent());
     }
 
     QCOMPARE(m_model->count(), 201);
@@ -531,6 +534,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();
@@ -547,6 +551,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.
@@ -609,6 +614,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()
@@ -834,12 +889,12 @@ void KFileItemModelTest::testEmptyPath()
 
     const KUrl emptyUrl;
     QVERIFY(emptyUrl.path().isEmpty());
-    
+
     const KUrl url("file:///test/");
-    
+
     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();
 }
 
@@ -1025,27 +1080,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 cf15324e2789fa3e292c9c29a99aa46797c0158e..7d5fc3b9ab94e9dfb142fd001251a644f16a5322 100644 (file)
@@ -31,6 +31,7 @@ private slots:
     void testBasicKeyboardSearch();
     void testAbortedKeyboardSearch();
     void testRepeatedKeyPress();
+    void testPressShift();
 
 private:
     KItemListKeyboardSearchManager m_keyboardSearchManager;
@@ -39,7 +40,7 @@ private:
 void KItemListKeyboardSearchManagerTest::init()
 {
     // Make sure that the previous search string is cleared
-    m_keyboardSearchManager.addKeys("");
+    m_keyboardSearchManager.cancelSearch();
 }
 
 void KItemListKeyboardSearchManagerTest::testBasicKeyboardSearch()
@@ -120,6 +121,32 @@ void KItemListKeyboardSearchManagerTest::testRepeatedKeyPress()
     QCOMPARE(spy.takeFirst(), QList<QVariant>() << "pppq" << false);
 }
 
+void KItemListKeyboardSearchManagerTest::testPressShift()
+{
+    // If the user presses Shift, i.e., to get a character like '_',
+    // KItemListController calls the addKeys(QString) method with an empty
+    // string. Make sure that this does not reset the current search. See
+    // https://bugs.kde.org/show_bug.cgi?id=321286
+
+    QSignalSpy spy(&m_keyboardSearchManager, SIGNAL(changeCurrentItem(QString,bool)));
+
+    // Simulate that the user enters "a_b".
+    m_keyboardSearchManager.addKeys("a");
+    QCOMPARE(spy.count(), 1);
+    QCOMPARE(spy.takeFirst(), QList<QVariant>() << "a" << true);
+
+    m_keyboardSearchManager.addKeys("");
+    QCOMPARE(spy.count(), 0);
+
+    m_keyboardSearchManager.addKeys("_");
+    QCOMPARE(spy.count(), 1);
+    QCOMPARE(spy.takeFirst(), QList<QVariant>() << "a_" << false);
+
+    m_keyboardSearchManager.addKeys("b");
+    QCOMPARE(spy.count(), 1);
+    QCOMPARE(spy.takeFirst(), QList<QVariant>() << "a_b" << false);
+}
+
 QTEST_KDEMAIN(KItemListKeyboardSearchManagerTest, NoGUI)
 
 #include "kitemlistkeyboardsearchmanagertest.moc"
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 d69d664afaa5bc776d92db11235e9341800cfaa2..303ee34af2dbf60edf5f0900d7935cc28fd135b9 100644 (file)
@@ -33,6 +33,8 @@
 #include <QTimer>
 #include <QScrollBar>
 
+#include <KDesktopFile>
+#include <KProtocolManager>
 #include <KActionCollection>
 #include <KColorScheme>
 #include <KDirModel>
@@ -102,6 +104,7 @@ DolphinView::DolphinView(const KUrl& url, QWidget* parent) :
     m_restoredContentsPosition(),
     m_selectedUrls(),
     m_clearSelectionBeforeSelectingNewItems(false),
+    m_markFirstNewlySelectedItemAsCurrent(false),
     m_versionControlObserver(0)
 {
     m_topLayout = new QVBoxLayout(this);
@@ -175,7 +178,7 @@ DolphinView::DolphinView(const KUrl& url, QWidget* parent) :
     connect(m_view, SIGNAL(visibleRolesChanged(QList<QByteArray>,QList<QByteArray>)),
             this, SLOT(slotVisibleRolesChangedByHeader(QList<QByteArray>,QList<QByteArray>)));
     connect(m_view, SIGNAL(roleEditingCanceled(int,QByteArray,QVariant)),
-            this, SLOT(slotRoleEditingCanceled(int,QByteArray,QVariant)));
+            this, SLOT(slotRoleEditingCanceled()));
     connect(m_view->header(), SIGNAL(columnWidthChanged(QByteArray,qreal,qreal)),
             this, SLOT(slotHeaderColumnWidthChanged(QByteArray,qreal,qreal)));
 
@@ -815,9 +818,10 @@ void DolphinView::slotItemsActivated(const QSet<int>& indexes)
     while (it.hasNext()) {
         const int index = it.next();
         KFileItem item = m_model->fileItem(index);
+        const KUrl& url = openItemAsFolderUrl(item);
 
-        if (item.isDir()) { // Open folders in new tabs
-            emit tabRequested(item.url());
+        if (!url.isEmpty()) { // Open folders in new tabs
+            emit tabRequested(url);
         } else {
             items.append(item);
         }
@@ -832,8 +836,11 @@ void DolphinView::slotItemsActivated(const QSet<int>& indexes)
 
 void DolphinView::slotItemMiddleClicked(int index)
 {
-    const KFileItem item = m_model->fileItem(index);
-    if (item.isDir() || isTabsForFilesEnabled()) {
+    const KFileItem& item = m_model->fileItem(index);
+    const KUrl& url = openItemAsFolderUrl(item);
+    if (!url.isEmpty()) {
+        emit tabRequested(url);
+    } else if (isTabsForFilesEnabled()) {
         emit tabRequested(item.url());
     }
 }
@@ -1029,15 +1036,19 @@ 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)));
     }
+
+    setActive(true);
 }
 
 void DolphinView::slotModelChanged(KItemModelBase* current, KItemModelBase* previous)
@@ -1072,6 +1083,17 @@ void DolphinView::slotMouseButtonPressed(int itemIndex, Qt::MouseButtons buttons
     }
 }
 
+void DolphinView::slotAboutToCreate(const KUrl::List& urls)
+{
+    if (!urls.isEmpty()) {
+        if (m_markFirstNewlySelectedItemAsCurrent) {
+            markUrlAsCurrent(urls.first());
+            m_markFirstNewlySelectedItemAsCurrent = false;
+        }
+        m_selectedUrls << urls;
+    }
+}
+
 void DolphinView::slotSelectionChanged(const QSet<int>& current, const QSet<int>& previous)
 {
     const int currentCount = current.count();
@@ -1194,6 +1216,46 @@ QString DolphinView::viewPropertiesContext() const
     return m_viewPropertiesContext;
 }
 
+KUrl DolphinView::openItemAsFolderUrl(const KFileItem& item, const bool browseThroughArchives)
+{
+    if (item.isNull()) {
+        return KUrl();
+    }
+
+    KUrl url = item.targetUrl();
+
+    if (item.isDir()) {
+        return url;
+    }
+
+    if (item.isMimeTypeKnown()) {
+        const QString& mimetype = item.mimetype();
+
+        if (browseThroughArchives && item.isFile() && url.isLocalFile()) {
+            // Generic mechanism for redirecting to tar:/<path>/ when clicking on a tar file,
+            // zip:/<path>/ when clicking on a zip file, etc.
+            // The .protocol file specifies the mimetype that the kioslave handles.
+            // Note that we don't use mimetype inheritance since we don't want to
+            // open OpenDocument files as zip folders...
+            const QString& protocol = KProtocolManager::protocolForArchiveMimetype(mimetype);
+            if (!protocol.isEmpty()) {
+                url.setProtocol(protocol);
+                return url;
+            }
+        }
+
+        if (mimetype == QLatin1String("application/x-desktop")) {
+            // Redirect to the URL in Type=Link desktop files
+            KDesktopFile desktopFile(url.toLocalFile());
+            if (desktopFile.hasLinkType()) {
+                return desktopFile.readUrl();
+            }
+        }
+    }
+
+    return KUrl();
+}
+
 void DolphinView::observeCreatedItem(const KUrl& url)
 {
     if (m_active) {
@@ -1224,10 +1286,11 @@ void DolphinView::updateViewState()
                 m_view->scrollToItem(currentIndex);
                 m_scrollToCurrentItem = false;
             }
+
+            m_currentItemUrl = KUrl();
         } else {
             selectionManager->setCurrentItem(0);
         }
-        m_currentItemUrl = KUrl();
     }
 
     if (!m_restoredContentsPosition.isNull()) {
@@ -1306,6 +1369,16 @@ void DolphinView::slotDeleteFileFinished(KJob* job)
     }
 }
 
+void DolphinView::slotRenamingFailed(const KUrl& oldUrl, const KUrl& newUrl)
+{
+    const int index = m_model->index(newUrl);
+    if (index >= 0) {
+        QHash<QByteArray, QVariant> data;
+        data.insert("text", oldUrl.fileName());
+        m_model->setData(index, data);
+    }
+}
+
 void DolphinView::slotDirectoryLoadingStarted()
 {
     // Disable the writestate temporary until it can be determined in a fast way
@@ -1372,7 +1445,7 @@ void DolphinView::slotVisibleRolesChangedByHeader(const QList<QByteArray>& curre
     emit visibleRolesChanged(m_visibleRoles, previousVisibleRoles);
 }
 
-void DolphinView::slotRoleEditingCanceled(int index, const QByteArray& role, const QVariant& value)
+void DolphinView::slotRoleEditingCanceled()
 {
     disconnect(m_view, SIGNAL(roleEditingFinished(int,QByteArray,QVariant)),
                this, SLOT(slotRoleEditingFinished(int,QByteArray,QVariant)));
@@ -1406,7 +1479,10 @@ void DolphinView::slotRoleEditingFinished(int index, const QByteArray& role, con
                 m_model->setData(index, data);
             }
 
-            KonqOperations::rename(this, oldUrl, newName);
+            KonqOperations* op = KonqOperations::renameV2(this, oldUrl, newName);
+            if (op) {
+                connect(op, SIGNAL(renamingFailed(KUrl,KUrl)), SLOT(slotRenamingFailed(KUrl,KUrl)));
+            }
         }
     }
 }
@@ -1538,8 +1614,12 @@ 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;
+        m_markFirstNewlySelectedItemAsCurrent = true;
+        connect(op, SIGNAL(aboutToCreate(KUrl::List)), this, SLOT(slotAboutToCreate(KUrl::List)));
+    }
 }
 
 KUrl::List DolphinView::simplifiedSelectedUrls() const
@@ -1567,18 +1647,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 62b5df7c77997444210c5c46aff0da6af58a62c8..e5e9834b948cc55bb593c3b86784c5f2c3e55e67 100644 (file)
@@ -304,6 +304,14 @@ public:
     void setViewPropertiesContext(const QString& context);
     QString viewPropertiesContext() const;
 
+    /**
+     * Checks if the given \a item can be opened as folder (e.g. archives).
+     * This function will also adjust the \a url (e.g. change the protocol).
+     * @return a valid and adjusted url if the item can be opened as folder,
+     * otherwise return an empty url.
+     */
+    static KUrl openItemAsFolderUrl(const KFileItem& item, const bool browseThroughArchives = true);
+
 public slots:
     /**
      * Changes the directory to \a url. If the current directory is equal to
@@ -566,6 +574,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
@@ -619,6 +632,8 @@ private slots:
      */
     void slotDeleteFileFinished(KJob* job);
 
+    void slotRenamingFailed(const KUrl& oldUrl, const KUrl& newUrl);
+
     /**
      * Invoked when the file item model has started the loading
      * of the directory specified by DolphinView::url().
@@ -655,7 +670,7 @@ private slots:
     void slotVisibleRolesChangedByHeader(const QList<QByteArray>& current,
                                          const QList<QByteArray>& previous);
 
-    void slotRoleEditingCanceled(int index, const QByteArray& role, const QVariant& value);
+    void slotRoleEditingCanceled();
     void slotRoleEditingFinished(int index, const QByteArray& role, const QVariant& value);
 
     /**
@@ -722,14 +737,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
@@ -773,6 +780,7 @@ private:
 
     QList<KUrl> m_selectedUrls; // Used for making the view to remember selections after F5
     bool m_clearSelectionBeforeSelectingNewItems;
+    bool m_markFirstNewlySelectedItemAsCurrent;
 
     VersionControlObserver* m_versionControlObserver;
 
index 730723785e78fbd423c1ab33454c64c1a5409483..9a9718c3336869693105866c5622200ad0c508b4 100644 (file)
@@ -314,14 +314,7 @@ void DolphinViewActionHandler::slotRename()
 void DolphinViewActionHandler::slotTrashActivated(Qt::MouseButtons, Qt::KeyboardModifiers modifiers)
 {
     emit actionBeingHandled();
-    // Note: kde3's konq_mainwindow.cpp used to check
-    // reason == KAction::PopupMenuActivation && ...
-    // but this isn't supported anymore
-    if (modifiers & Qt::ShiftModifier) {
-        m_currentView->deleteSelectedItems();
-    } else {
-        m_currentView->trashSelectedItems();
-    }
+    m_currentView->trashSelectedItems();
 }
 
 void DolphinViewActionHandler::slotDeleteItems()
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
     }
 }