-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()
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()
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
views/viewmodecontroller.cpp
views/viewproperties.cpp
views/zoomlevelinfo.cpp
+ dolphinremoveaction.cpp
)
if(HAVE_NEPOMUK)
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
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
#include "dolphinnewfilemenu.h"
#include "dolphinviewcontainer.h"
#include "dolphin_generalsettings.h"
+#include "dolphinremoveaction.h"
#include <KActionCollection>
#include <KDesktopFile>
#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),
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)
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);
}
}
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());
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());
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());
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());
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()) {
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();
}
}
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);
}
}
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()
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));
}
}
}
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"
#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
* - 'Actions': Contains all actions which can be applied to the
* given item.
*/
-class DolphinContextMenu : public QObject
+class DolphinContextMenu : public KMenu
{
Q_OBJECT
*/
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();
*/
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
{
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
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());
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);
+ }
+ }
}
}
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()) {
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);
}
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"));
*/
#include "dolphinpart.h"
+#include "dolphinremoveaction.h"
#include <KFileItemListProperties>
#include <konq_operations.h>
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);
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)
}
}
- 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.
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)
class KDirLister;
class DolphinView;
class KAboutData;
+class DolphinRemoveAction;
class DolphinPart : public KParts::ReadOnlyPart
{
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,
KAction* m_findFileAction;
KAction* m_openTerminalAction;
QString m_nameFilter;
+ DolphinRemoveAction* m_removeAction;
Q_DISABLE_COPY(DolphinPart)
};
--- /dev/null
+/***************************************************************************
+ * 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());
+ }
+}
--- /dev/null
+/***************************************************************************
+ * 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
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)));
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);
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);
// 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();
}
void DolphinViewContainer::closeFilterBar()
{
- m_filterBar->hide();
- m_filterBar->clear();
+ m_filterBar->closeFilterBar();
m_view->setFocus();
emit showFilterBarChanged(false);
}
void DolphinViewContainer::slotUrlNavigatorLocationChanged(const KUrl& url)
{
+ slotReturnPressed();
+
if (KProtocolManager::supportsListing(url)) {
setSearchModeEnabled(isSearchUrl(url));
m_view->setUrl(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);
}
}
}
+void DolphinViewContainer::slotReturnPressed()
+{
+ if (!GeneralSettings::editableUrl()) {
+ m_urlNavigator->setUrlEditable(false);
+ }
+}
+
void DolphinViewContainer::startSearching()
{
const KUrl url = m_searchBox->urlForSearching();
void slotHistoryChanged();
+ void slotReturnPressed();
+
/**
* Gets the search URL from the searchbox and starts searching.
*/
/***************************************************************************
* 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 *
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);
hLayout->addWidget(closeButton);
hLayout->addWidget(filterLabel);
hLayout->addWidget(m_filterInput);
+ hLayout->addWidget(m_lockButton);
filterLabel->setBuddy(m_filterInput);
}
{
}
+void FilterBar::closeFilterBar()
+{
+ hide();
+ clear();
+ if (m_lockButton) {
+ m_lockButton->setChecked(false);
+ }
+}
+
void FilterBar::selectAll()
{
m_filterInput->selectAll();
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()) {
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;
}
}
/***************************************************************************
* 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 *
#include <QWidget>
class KLineEdit;
+class QToolButton;
/**
* @brief Provides an input field for filtering the currently shown items.
explicit FilterBar(QWidget* parent = 0);
virtual ~FilterBar();
+ /** Called by view container to hide this **/
+ void closeFilterBar();
+
/**
* Selects the whole text of the filter bar.
*/
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:
/**
*/
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
-/***************************************************************************
- * 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"
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);
connect(m_dirLister, SIGNAL(started(KUrl)), this, SIGNAL(directoryLoadingStarted()));
connect(m_dirLister, SIGNAL(canceled()), this, SLOT(slotCanceled()));
connect(m_dirLister, SIGNAL(completed(KUrl)), this, SLOT(slotCompleted()));
- connect(m_dirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList)));
+ connect(m_dirLister, SIGNAL(itemsAdded(KUrl,KFileItemList)), this, SLOT(slotItemsAdded(KUrl,KFileItemList)));
connect(m_dirLister, SIGNAL(itemsDeleted(KFileItemList)), this, SLOT(slotItemsDeleted(KFileItemList)));
connect(m_dirLister, SIGNAL(refreshItems(QList<QPair<KFileItem,KFileItem> >)), this, SLOT(slotRefreshItems(QList<QPair<KFileItem,KFileItem> >)));
connect(m_dirLister, SIGNAL(clear()), this, SLOT(slotClear()));
KFileItemModel::~KFileItemModel()
{
qDeleteAll(m_itemData);
- m_itemData.clear();
+ qDeleteAll(m_filteredItems.values());
}
void KFileItemModel::loadDirectory(const KUrl& url)
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) {
// 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!";
}
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);
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;
// 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;
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);
}
emit directoryLoadingCanceled();
}
-void KFileItemModel::slotNewItems(const KFileItemList& items)
+void KFileItemModel::slotItemsAdded(const KUrl& directoryUrl, const KFileItemList& items)
{
Q_ASSERT(!items.isEmpty());
- if (m_requestRole[ExpandedParentsCountRole] && m_expandedParentsCountRoot >= 0) {
+ KUrl parentUrl = directoryUrl;
+ parentUrl.adjustPath(KUrl::RemoveTrailingSlash);
+
+ if (m_requestRole[ExpandedParentsCountRole]) {
// To be able to compare whether the new items may be inserted as children
// of a parent item the pending items must be added to the model first.
dispatchPendingItemsToInsert();
// KDirLister keeps the children of items that got expanded once even if
// they got collapsed again with KFileItemModel::setExpanded(false). So it must be
// checked whether the parent for new items is still expanded.
- KUrl parentUrl = item.url().upUrl();
- parentUrl.adjustPath(KUrl::RemoveTrailingSlash);
const int parentIndex = m_items.value(parentUrl, -1);
if (parentIndex >= 0 && !m_itemData[parentIndex]->values.value("isExpanded").toBool()) {
// The parent is not expanded.
}
}
+ QList<ItemData*> itemDataList = createItemDataList(parentUrl, items);
+
if (!m_filter.hasSetFilters()) {
- m_pendingItemsToInsert.append(items);
+ m_pendingItemsToInsert.append(itemDataList);
} else {
// The name or type filter is active. Hide filtered items
// before inserting them into the model and remember
// the filtered items in m_filteredItems.
- KFileItemList filteredItems;
- foreach (const KFileItem& item, items) {
- if (m_filter.matches(item)) {
- filteredItems.append(item);
+ foreach (ItemData* itemData, itemDataList) {
+ if (m_filter.matches(itemData->item)) {
+ m_pendingItemsToInsert.append(itemData);
} else {
- m_filteredItems.insert(item);
+ m_filteredItems.insert(itemData->item, itemData);
}
}
-
- m_pendingItemsToInsert.append(filteredItems);
}
if (useMaximumUpdateInterval() && !m_maximumUpdateIntervalTimer->isActive()) {
dispatchPendingItemsToInsert();
KFileItemList itemsToRemove = items;
- if (m_requestRole[ExpandedParentsCountRole] && m_expandedParentsCountRoot >= 0) {
+ if (m_requestRole[ExpandedParentsCountRole]) {
// Assure that removing a parent item also results in removing all children
foreach (const KFileItem& item, items) {
itemsToRemove.append(childItems(item));
if (!m_filteredItems.isEmpty()) {
foreach (const KFileItem& item, itemsToRemove) {
- m_filteredItems.remove(item);
- }
-
- if (m_requestRole[ExpandedParentsCountRole] && m_expandedParentsCountRoot >= 0) {
- // Remove all filtered children of deleted items. First, we put the
- // deleted URLs into a set to provide fast lookup while iterating
- // over m_filteredItems and prevent quadratic complexity if there
- // are N removed items and N filtered items.
- QSet<KUrl> urlsToRemove;
- urlsToRemove.reserve(itemsToRemove.count());
- foreach (const KFileItem& item, itemsToRemove) {
- KUrl url = item.url();
- url.adjustPath(KUrl::RemoveTrailingSlash);
- urlsToRemove.insert(url);
+ QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.find(item);
+ if (it != m_filteredItems.end()) {
+ delete it.value();
+ m_filteredItems.erase(it);
}
+ }
- QSet<KFileItem>::iterator it = m_filteredItems.begin();
- while (it != m_filteredItems.end()) {
- const KUrl url = it->url();
- KUrl parentUrl = url.upUrl();
- parentUrl.adjustPath(KUrl::RemoveTrailingSlash);
-
- if (urlsToRemove.contains(parentUrl)) {
- it = m_filteredItems.erase(it);
- } else {
- ++it;
- }
- }
+ if (m_requestRole[ExpandedParentsCountRole]) {
+ removeFilteredChildren(itemsToRemove);
}
}
- removeItems(itemsToRemove);
+ removeItems(itemsToRemove, DeleteItemData);
}
void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >& items)
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();
// 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());
itemRangeList.append(KItemRange(rangeIndex, rangeCount));
}
- emit itemsChanged(itemRangeList, m_roles);
+ emit itemsChanged(itemRangeList, changedRoles);
- resortAllItems();
+ if (changedRoles.contains(sortRole())) {
+ resortAllItems();
+ }
}
void KFileItemModel::slotClear()
kDebug() << "Clearing all items";
#endif
+ qDeleteAll(m_filteredItems.values());
m_filteredItems.clear();
m_groups.clear();
m_resortAllItemsTimer->stop();
m_pendingItemsToInsert.clear();
- m_expandedParentsCountRoot = UninitializedExpandedParentsCountRoot;
-
const int removedCount = m_itemData.count();
if (removedCount > 0) {
qDeleteAll(m_itemData);
}
}
-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);
}
// 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();
}
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());
}
}
// 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]) {
if (destination.isEmpty()) {
destination = QLatin1String("-");
}
- data.insert("destination", destination);
+ data.insert(sharedValue("destination"), destination);
}
if (m_requestRole[PathRole]) {
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());
}
}
{
int result = 0;
- if (m_expandedParentsCountRoot >= 0) {
- result = expandedParentsCountCompare(a, b);
- if (result != 0) {
- // The items have parents with different expansion levels
- return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0;
+ if (a->parent != b->parent) {
+ const int expansionLevelA = a->values.value("expandedParentsCount").toInt();
+ const int expansionLevelB = b->values.value("expandedParentsCount").toInt();
+
+ // If b has a higher expansion level than a, check if a is a parent
+ // of b, and make sure that both expansion levels are equal otherwise.
+ for (int i = expansionLevelB; i > expansionLevelA; --i) {
+ if (b->parent == a) {
+ return true;
+ }
+ b = b->parent;
+ }
+
+ // If a has a higher expansion level than a, check if b is a parent
+ // of a, and make sure that both expansion levels are equal otherwise.
+ for (int i = expansionLevelA; i > expansionLevelB; --i) {
+ if (a->parent == b) {
+ return false;
+ }
+ a = a->parent;
+ }
+
+ Q_ASSERT(a->values.value("expandedParentsCount").toInt() == b->values.value("expandedParentsCount").toInt());
+
+ // Compare the last parents of a and b which are different.
+ while (a->parent != b->parent) {
+ a = a->parent;
+ b = b->parent;
}
}
return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0;
}
+/**
+ * Helper class for KFileItemModel::sort().
+ */
+class KFileItemModelLessThan
+{
+public:
+ KFileItemModelLessThan(const KFileItemModel* model) :
+ m_model(model)
+ {
+ }
+
+ bool operator()(const KFileItemModel::ItemData* a, const KFileItemModel::ItemData* b) const
+ {
+ return m_model->lessThan(a, b);
+ }
+
+private:
+ const KFileItemModel* m_model;
+};
+
+void KFileItemModel::sort(QList<KFileItemModel::ItemData*>::iterator begin,
+ QList<KFileItemModel::ItemData*>::iterator end) const
+{
+ KFileItemModelLessThan lessThan(this);
+
+ if (m_sortRole == NameRole) {
+ // Sorting by name can be expensive, in particular if natural sorting is
+ // enabled. Use all CPU cores to speed up the sorting process.
+ static const int numberOfThreads = QThread::idealThreadCount();
+ parallelMergeSort(begin, end, lessThan, numberOfThreads);
+ } else {
+ // Sorting by other roles is quite fast. Use only one thread to prevent
+ // problems caused by non-reentrant comparison functions, see
+ // https://bugs.kde.org/show_bug.cgi?id=312679
+ mergeSort(begin, end, lessThan);
+ }
+}
+
int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b) const
{
const KFileItem& itemA = a->item;
: 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();
const QDate currentDate = KDateTime::currentLocalDateTime().date();
- int yearForCurrentWeek = 0;
- int currentWeek = currentDate.weekNumber(&yearForCurrentWeek);
- if (yearForCurrentWeek == currentDate.year() + 1) {
- currentWeek = 53;
- }
-
QDate previousModifiedDate;
QString groupValue;
for (int i = 0; i <= maxIndex; ++i) {
const int daysDistance = modifiedDate.daysTo(currentDate);
- int yearForModifiedWeek = 0;
- int modifiedWeek = modifiedDate.weekNumber(&yearForModifiedWeek);
- if (yearForModifiedWeek == modifiedDate.year() + 1) {
- modifiedWeek = 53;
- }
-
QString newGroupValue;
if (currentDate.year() == modifiedDate.year() && currentDate.month() == modifiedDate.month()) {
- if (modifiedWeek > currentWeek) {
- // Usecase: modified date = 2010-01-01, current date = 2010-01-22
- // modified week = 53, current week = 3
- modifiedWeek = 0;
- }
- switch (currentWeek - modifiedWeek) {
+ switch (daysDistance / 7) {
case 0:
switch (daysDistance) {
case 0: newGroupValue = i18nc("@title:group Date", "Today"); break;
}
break;
case 1:
- newGroupValue = i18nc("@title:group Date", "Last Week");
+ newGroupValue = i18nc("@title:group Date", "One Week Ago");
break;
case 2:
newGroupValue = i18nc("@title:group Date", "Two Weeks Ago");
} else if (daysDistance <= 7) {
newGroupValue = modifiedTime.toString(i18nc("@title:group The week day name: %A, %B is full month name in current locale, and %Y is full year number", "%A (%B, %Y)"));
} else if (daysDistance <= 7 * 2) {
- newGroupValue = modifiedTime.toString(i18nc("@title:group Date: %B is full month name in current locale, and %Y is full year number", "Last Week (%B, %Y)"));
+ newGroupValue = modifiedTime.toString(i18nc("@title:group Date: %B is full month name in current locale, and %Y is full year number", "One Week Ago (%B, %Y)"));
} else if (daysDistance <= 7 * 3) {
newGroupValue = modifiedTime.toString(i18nc("@title:group Date: %B is full month name in current locale, and %Y is full year number", "Two Weeks Ago (%B, %Y)"));
} else if (daysDistance <= 7 * 4) {
{
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
}
}
+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"
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();
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
* 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();
*/
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
*/
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
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;
*/
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.
*/
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;
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;
// 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
};
#include <QElapsedTimer>
#include <QTimer>
+#include <algorithm>
+
#ifdef HAVE_NEPOMUK
#include "private/knepomukrolesprovider.h"
#include <Nepomuk2/ResourceWatcher>
// 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()
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");
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();
}
}
}
m_firstVisibleIndex = index;
m_lastVisibleIndex = qMin(index + count - 1, m_model->count() - 1);
- if (hasPendingRoles() && !m_paused) {
- sortAndResolvePendingRoles();
- }
+ startUpdating();
}
void KFileItemModelRolesUpdater::setMaximumVisibleItems(int count)
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();
}
}
}
#endif
- updateSortProgress();
-
- if (m_paused) {
+ if (m_state == Paused) {
m_rolesChangedDuringPausing = true;
} else {
- sortAndResolveAllRoles();
+ startUpdating();
}
}
}
bool KFileItemModelRolesUpdater::isPaused() const
{
- return m_paused;
+ return m_state == Paused;
}
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)
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) {
}
#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,
{
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,
{
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) {
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)
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)
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;
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();
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"
* 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
{
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,
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);
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,
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.
*/
*/
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:
// 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;
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;
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;
}
}
+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;
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;
}
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);
+++ /dev/null
-/***************************************************************************
- * 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--);
- }
-}
-/***************************************************************************
- * 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
{
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();
}
{
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"),
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");
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);
}
return SearchForType;
}
+ if (protocol == QLatin1String("bluetooth")) {
+ return DevicesType;
+ }
+
return PlacesType;
}
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);
}
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 {
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)));
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)
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;
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);
}
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"
#include <KGlobal>
#include <KUrl>
+#include <QFileInfo>
+#include <QDir>
struct DolphinSearchInformationSingleton
{
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) {
#include "dolphin_generalsettings.h"
#include "dolphin_versioncontrolsettings.h"
+#include <kabstractfileitemactionplugin.h>
#include <KConfig>
#include <KConfigGroup>
#include <KDesktopFile>
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);
}
}
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);
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"
*/
void updateZoomSliderToolTip(int zoomLevel);
- void applyFixedWidgetSize(QWidget* widget, const QSize& size);
-
private:
QString m_text;
QString m_defaultText;
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
--- /dev/null
+/***************************************************************************
+ * 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"
#include <qtest_kde.h>
#include <KDirLister>
+#include <kio/job.h>
+
#include "kitemviews/kfileitemmodel.h"
#include "kitemviews/private/kfileitemmodeldirlister.h"
#include "testdir.h"
void testItemRangeConsistencyWhenInsertingItems();
void testExpandItems();
void testExpandParentItems();
+ void testMakeExpandedItemHidden();
void testSorting();
void testIndexForKeyboardSearch();
void testNameFilter();
void testRemoveHiddenItems();
void collapseParentOfHiddenItems();
void removeParentOfHiddenItems();
+ void testGeneralParentChildRelationships();
private:
- bool isModelConsistent() const;
QStringList itemsInModel() const;
private:
QCOMPARE(m_model->count(), 3);
- QVERIFY(isModelConsistent());
+ QVERIFY(m_model->isConsistent());
}
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()
QCOMPARE(itemsRemovedSpy.count(), 2);
QCOMPARE(m_model->count(), 4);
- QVERIFY(isModelConsistent());
+ QVERIFY(m_model->isConsistent());
}
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()
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()
QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
}
- QVERIFY(isModelConsistent());
+ QVERIFY(m_model->isConsistent());
}
QCOMPARE(m_model->count(), 201);
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();
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.
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()
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();
}
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
void testBasicKeyboardSearch();
void testAbortedKeyboardSearch();
void testRepeatedKeyPress();
+ void testPressShift();
private:
KItemListKeyboardSearchManager m_keyboardSearchManager;
void KItemListKeyboardSearchManagerTest::init()
{
// Make sure that the previous search string is cleared
- m_keyboardSearchManager.addKeys("");
+ m_keyboardSearchManager.cancelSearch();
}
void KItemListKeyboardSearchManagerTest::testBasicKeyboardSearch()
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"
#include <KMenu>
#include <KProtocolInfo>
#include <KProtocolManager>
-#include <KIO/SlaveConfig>
#include <KIO/Scheduler>
#include <KConfigGroup>
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;
#include <QTimer>
#include <QScrollBar>
+#include <KDesktopFile>
+#include <KProtocolManager>
#include <KActionCollection>
#include <KColorScheme>
#include <KDirModel>
m_restoredContentsPosition(),
m_selectedUrls(),
m_clearSelectionBeforeSelectingNewItems(false),
+ m_markFirstNewlySelectedItemAsCurrent(false),
m_versionControlObserver(0)
{
m_topLayout = new QVBoxLayout(this);
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)));
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);
}
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());
}
}
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)
}
}
+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();
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) {
m_view->scrollToItem(currentIndex);
m_scrollToCurrentItem = false;
}
+
+ m_currentItemUrl = KUrl();
} else {
selectionManager->setCurrentItem(0);
}
- m_currentItemUrl = KUrl();
}
if (!m_restoredContentsPosition.isNull()) {
}
}
+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
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)));
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)));
+ }
}
}
}
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
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;
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
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
*/
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().
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);
/**
*/
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
QList<KUrl> m_selectedUrls; // Used for making the view to remember selections after F5
bool m_clearSelectionBeforeSelectingNewItems;
+ bool m_markFirstNewlySelectedItemAsCurrent;
VersionControlObserver* m_versionControlObserver;
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()
#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();
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;
}
class KUrl;
class QDropEvent;
class QWidget;
+class KonqOperations;
class LIBDOLPHINPRIVATE_EXPORT DragAndDropHelper
{
* @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
#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
{
}
-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);
QList<VersionControlObserver::ItemState> UpdateItemStatesThread::itemStates() const
{
- QMutexLocker locker(&m_itemMutex);
return m_itemStates;
}
bool UpdateItemStatesThread::retrievedItems() const
{
- QMutexLocker locker(&m_itemMutex);
return m_retrievedItems;
}
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
* 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
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;
};
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.
}
}
- 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;
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);
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
}
}