diff options
Diffstat (limited to 'src')
22 files changed, 828 insertions, 541 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 41efa3589..ffb232ce2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,8 +1,17 @@ -macro_optional_find_package(Soprano) macro_optional_find_package(NepomukCore) +set_package_properties(NepomukCore PROPERTIES DESCRIPTION "Nepomuk Core libraries" + URL "http://www.kde.org" + TYPE OPTIONAL + PURPOSE "For adding desktop-wide tagging support to dolphin" + ) + macro_optional_find_package(NepomukWidgets) -macro_log_feature(NepomukCore_FOUND "Nepomuk Core" "Nepomuk Core functionality" "http://www.kde.org" FALSE "" "For fetching additional file metadata in dolphin") -macro_log_feature(NepomukWidgets_FOUND "Nepomuk Widgets" "Nepomuk Widgets" "http://www.kde.org" FALSE "" "For adding desktop-wide tagging support to dolphin") +set_package_properties(NepomukWidgets PROPERTIES DESCRIPTION "Nepomuk Widgets" + URL "http://www.kde.org" + TYPE OPTIONAL + PURPOSE "For adding desktop-wide tagging support to dolphin" + ) + if(NepomukCore_FOUND AND NepomukWidgets_FOUND) set(HAVE_NEPOMUK TRUE) endif() @@ -15,7 +24,13 @@ configure_file(config-X11.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-X11.h ) include_directories( ${KACTIVITIES_INCLUDE_DIRS} ) if(HAVE_NEPOMUK) - # Yes, Soprano includes is what we need here + find_package(Soprano 2.7.56) + set_package_properties(Soprano PROPERTIES DESCRIPTION "Qt-based RDF storage and parsing solution" + URL "http://soprano.sourceforge.net" + TYPE REQUIRED + PURPOSE "Required for everything (storage and general data management)" + ) + include_directories( ${SOPRANO_INCLUDE_DIR} ${NEPOMUK_CORE_INCLUDE_DIR} ${NEPOMUK_WIDGETS_INCLUDE_DIR} ) endif() @@ -45,7 +60,6 @@ set(dolphinprivate_LIB_SRCS kitemviews/kstandarditemmodel.cpp kitemviews/private/kfileitemclipboard.cpp kitemviews/private/kfileitemmodeldirlister.cpp - kitemviews/private/kfileitemmodelsortalgorithm.cpp kitemviews/private/kfileitemmodelfilter.cpp kitemviews/private/kitemlistheaderwidget.cpp kitemviews/private/kitemlistkeyboardsearchmanager.cpp diff --git a/src/dolphinmainwindow.cpp b/src/dolphinmainwindow.cpp index 9454c8c42..8ed31dea4 100644 --- a/src/dolphinmainwindow.cpp +++ b/src/dolphinmainwindow.cpp @@ -1276,7 +1276,8 @@ void DolphinMainWindow::tabDropEvent(int tab, QDropEvent* event) const ViewTab& viewTab = m_viewTab[tab]; const DolphinView* view = viewTab.isPrimaryViewActive ? viewTab.primaryView->view() : viewTab.secondaryView->view(); - const QString error = DragAndDropHelper::dropUrls(view->rootItem(), view->url(), event); + QString error; + DragAndDropHelper::dropUrls(view->rootItem(), view->url(), event, error); if (!error.isEmpty()) { activeViewContainer()->showMessage(error, DolphinViewContainer::Error); } diff --git a/src/dolphinviewcontainer.cpp b/src/dolphinviewcontainer.cpp index 8800a1732..b2c8605d8 100644 --- a/src/dolphinviewcontainer.cpp +++ b/src/dolphinviewcontainer.cpp @@ -128,6 +128,8 @@ DolphinViewContainer::DolphinViewContainer(const KUrl& url, QWidget* parent) : this, SLOT(slotUrlNavigatorLocationChanged(KUrl))); connect(m_urlNavigator, SIGNAL(historyChanged()), this, SLOT(slotHistoryChanged())); + connect(m_urlNavigator, SIGNAL(returnPressed()), + this, SLOT(slotReturnPressed())); // Initialize status bar m_statusBar = new DolphinStatusBar(this); @@ -574,6 +576,8 @@ void DolphinViewContainer::slotUrlNavigatorLocationAboutToBeChanged(const KUrl& void DolphinViewContainer::slotUrlNavigatorLocationChanged(const KUrl& url) { + slotReturnPressed(); + if (KProtocolManager::supportsListing(url)) { setSearchModeEnabled(isSearchUrl(url)); m_view->setUrl(url); @@ -616,7 +620,8 @@ void DolphinViewContainer::slotUrlNavigatorLocationChanged(const KUrl& url) void DolphinViewContainer::dropUrls(const KUrl& destination, QDropEvent* event) { - const QString error = DragAndDropHelper::dropUrls(KFileItem(), destination, event); + QString error; + DragAndDropHelper::dropUrls(KFileItem(), destination, event, error); if (!error.isEmpty()) { showMessage(error, Error); } @@ -657,6 +662,13 @@ void DolphinViewContainer::slotHistoryChanged() } } +void DolphinViewContainer::slotReturnPressed() +{ + if (!GeneralSettings::editableUrl()) { + m_urlNavigator->setUrlEditable(false); + } +} + void DolphinViewContainer::startSearching() { const KUrl url = m_searchBox->urlForSearching(); diff --git a/src/dolphinviewcontainer.h b/src/dolphinviewcontainer.h index e2d1b1875..bc58531a2 100644 --- a/src/dolphinviewcontainer.h +++ b/src/dolphinviewcontainer.h @@ -282,6 +282,8 @@ private slots: void slotHistoryChanged(); + void slotReturnPressed(); + /** * Gets the search URL from the searchbox and starts searching. */ diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index 6c015db37..60fc27546 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -1,5 +1,6 @@ /*************************************************************************** * Copyright (C) 2011 by Peter Penz <[email protected]> * + * Copyright (C) 2013 by Frank Reininghaus <[email protected]> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -58,7 +59,6 @@ KFileItemModel::KFileItemModel(QObject* parent) : m_urlsToExpand() { m_dirLister = new KFileItemModelDirLister(this); - m_dirLister->setAutoUpdate(true); m_dirLister->setDelayedMimeTypes(true); const QWidget* parentWidget = qobject_cast<QWidget*>(parent); @@ -658,7 +658,7 @@ void KFileItemModel::resortAllItems() m_items.clear(); // Resort the items - KFileItemModelSortAlgorithm::sort(this, m_itemData.begin(), m_itemData.end()); + sort(m_itemData.begin(), m_itemData.end()); for (int i = 0; i < itemCount; ++i) { m_items.insert(m_itemData.at(i)->item.url(), i); } @@ -941,7 +941,7 @@ void KFileItemModel::insertItems(const KFileItemList& items) m_groups.clear(); QList<ItemData*> sortedItems = createItemDataList(items); - KFileItemModelSortAlgorithm::sort(this, sortedItems.begin(), sortedItems.end()); + sort(sortedItems.begin(), sortedItems.end()); #ifdef KFILEITEMMODEL_DEBUG kDebug() << "[TIME] Sorting:" << timer.elapsed(); @@ -988,6 +988,7 @@ void KFileItemModel::insertItems(const KFileItemList& items) // The indexes of all m_items must be adjusted, not only the index // of the new items const int itemDataCount = m_itemData.count(); + m_items.reserve(itemDataCount); for (int i = 0; i < itemDataCount; ++i) { m_items.insert(m_itemData.at(i)->item.url(), i); } @@ -1000,80 +1001,96 @@ void KFileItemModel::insertItems(const KFileItemList& items) #endif } -void KFileItemModel::removeItems(const KFileItemList& items) +static KItemRangeList sortedIndexesToKItemRangeList(const QList<int>& sortedNumbers) { - if (items.isEmpty()) { - return; + if (sortedNumbers.empty()) { + return KItemRangeList(); } + KItemRangeList result; + + QList<int>::const_iterator it = sortedNumbers.begin(); + int index = *it; + int count = 1; + + ++it; + + QList<int>::const_iterator end = sortedNumbers.end(); + while (it != end) { + if (*it == index + count) { + ++count; + } else { + result << KItemRange(index, count); + index = *it; + count = 1; + } + ++it; + } + + result << KItemRange(index, count); + return result; +} + +void KFileItemModel::removeItems(const KFileItemList& items) +{ #ifdef KFILEITEMMODEL_DEBUG kDebug() << "Removing " << items.count() << "items"; #endif m_groups.clear(); - QList<ItemData*> sortedItems; - sortedItems.reserve(items.count()); + // Step 1: Determine the indexes of the removed items, remove them from + // the hash m_items, and free the ItemData. + QList<int> indexesToRemove; + indexesToRemove.reserve(items.count()); foreach (const KFileItem& item, items) { - const int index = m_items.value(item.url(), -1); + const KUrl url = item.url(); + const int index = m_items.value(url, -1); if (index >= 0) { - sortedItems.append(m_itemData.at(index)); + indexesToRemove.append(index); + + // Prevent repeated expensive rehashing by using QHash::erase(), + // rather than QHash::remove(). + QHash<KUrl, int>::iterator it = m_items.find(url); + m_items.erase(it); + + ItemData* data = m_itemData.at(index); + delete data; + m_itemData[index] = 0; } } - KFileItemModelSortAlgorithm::sort(this, sortedItems.begin(), sortedItems.end()); - QList<int> indexesToRemove; - indexesToRemove.reserve(items.count()); + if (indexesToRemove.isEmpty()) { + return; + } - // Calculate the item ranges that will get deleted - KItemRangeList itemRanges; - int removedAtIndex = -1; - int removedCount = 0; - int targetIndex = 0; - foreach (const ItemData* itemData, sortedItems) { - const KFileItem& itemToRemove = itemData->item; + std::sort(indexesToRemove.begin(), indexesToRemove.end()); - const int previousTargetIndex = targetIndex; - while (targetIndex < m_itemData.count()) { - if (m_itemData.at(targetIndex)->item.url() == itemToRemove.url()) { - break; - } - ++targetIndex; - } - if (targetIndex >= m_itemData.count()) { - kWarning() << "Item that should be deleted has not been found!"; - return; - } + // Step 2: Remove the ItemData pointers from the list m_itemData. + const KItemRangeList itemRanges = sortedIndexesToKItemRangeList(indexesToRemove); + int target = itemRanges.at(0).index; + int source = itemRanges.at(0).index + itemRanges.at(0).count; + int nextRange = 1; - if (targetIndex - previousTargetIndex > 0 && removedAtIndex >= 0) { - itemRanges << KItemRange(removedAtIndex, removedCount); - removedAtIndex = targetIndex; - removedCount = 0; - } + const int oldItemDataCount = m_itemData.count(); + while (source < oldItemDataCount) { + m_itemData[target] = m_itemData[source]; + ++target; + ++source; - indexesToRemove.append(targetIndex); - if (removedAtIndex < 0) { - removedAtIndex = targetIndex; + if (nextRange < itemRanges.count() && source == itemRanges.at(nextRange).index) { + // Skip the items in the next removed range. + source += itemRanges.at(nextRange).count; + ++nextRange; } - ++removedCount; - ++targetIndex; } - // Delete the items - for (int i = indexesToRemove.count() - 1; i >= 0; --i) { - const int indexToRemove = indexesToRemove.at(i); - ItemData* data = m_itemData.at(indexToRemove); - - m_items.remove(data->item.url()); + m_itemData.erase(m_itemData.end() - indexesToRemove.count(), m_itemData.end()); - delete data; - m_itemData.removeAt(indexToRemove); - } - - // The indexes of all m_items must be adjusted, not only the index - // of the removed items - const int itemDataCount = m_itemData.count(); - for (int i = 0; i < itemDataCount; ++i) { + // Step 3: Adjust indexes in the hash m_items. Note that all indexes + // might have been changed by the removal of the items. + const int newItemDataCount = m_itemData.count(); + for (int i = 0; i < newItemDataCount; ++i) { m_items.insert(m_itemData.at(i)->item.url(), i); } @@ -1081,7 +1098,6 @@ void KFileItemModel::removeItems(const KFileItemList& items) m_expandedParentsCountRoot = UninitializedExpandedParentsCountRoot; } - itemRanges << KItemRange(removedAtIndex, removedCount); emit itemsRemoved(itemRanges); } @@ -1323,11 +1339,34 @@ bool KFileItemModel::lessThan(const ItemData* a, const ItemData* b) const { int result = 0; - if (m_expandedParentsCountRoot >= 0) { - result = expandedParentsCountCompare(a, b); - if (result != 0) { - // The items have parents with different expansion levels - return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0; + if (m_expandedParentsCountRoot >= 0 && a->parent != b->parent) { + const int expansionLevelA = a->values.value("expandedParentsCount").toInt(); + const int expansionLevelB = b->values.value("expandedParentsCount").toInt(); + + // If b has a higher expansion level than a, check if a is a parent + // of b, and make sure that both expansion levels are equal otherwise. + for (int i = expansionLevelB; i > expansionLevelA; --i) { + if (b->parent == a) { + return true; + } + b = b->parent; + } + + // If a has a higher expansion level than a, check if b is a parent + // of a, and make sure that both expansion levels are equal otherwise. + for (int i = expansionLevelA; i > expansionLevelB; --i) { + if (a->parent == b) { + return false; + } + a = a->parent; + } + + Q_ASSERT(a->values.value("expandedParentsCount").toInt() == b->values.value("expandedParentsCount").toInt()); + + // Compare the last parents of a and b which are different. + while (a->parent != b->parent) { + a = a->parent; + b = b->parent; } } @@ -1346,6 +1385,44 @@ bool KFileItemModel::lessThan(const ItemData* a, const ItemData* b) const return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0; } +/** + * Helper class for KFileItemModel::sort(). + */ +class KFileItemModelLessThan +{ +public: + KFileItemModelLessThan(const KFileItemModel* model) : + m_model(model) + { + } + + bool operator()(const KFileItemModel::ItemData* a, const KFileItemModel::ItemData* b) const + { + return m_model->lessThan(a, b); + } + +private: + const KFileItemModel* m_model; +}; + +void KFileItemModel::sort(QList<KFileItemModel::ItemData*>::iterator begin, + QList<KFileItemModel::ItemData*>::iterator end) const +{ + KFileItemModelLessThan lessThan(this); + + if (m_sortRole == NameRole) { + // Sorting by name can be expensive, in particular if natural sorting is + // enabled. Use all CPU cores to speed up the sorting process. + static const int numberOfThreads = QThread::idealThreadCount(); + parallelMergeSort(begin, end, lessThan, numberOfThreads); + } else { + // Sorting by other roles is quite fast. Use only one thread to prevent + // problems caused by non-reentrant comparison functions, see + // https://bugs.kde.org/show_bug.cgi?id=312679 + mergeSort(begin, end, lessThan); + } +} + int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b) const { const KFileItem& itemA = a->item; @@ -1470,88 +1547,6 @@ int KFileItemModel::stringCompare(const QString& a, const QString& b) const : QString::compare(a, b, Qt::CaseSensitive); } -int KFileItemModel::expandedParentsCountCompare(const ItemData* a, const ItemData* b) const -{ - const KUrl urlA = a->item.url(); - const KUrl urlB = b->item.url(); - if (urlA.directory() == urlB.directory()) { - // Both items have the same directory as parent - return 0; - } - - // Check whether one item is the parent of the other item - if (urlA.isParentOf(urlB)) { - return (sortOrder() == Qt::AscendingOrder) ? -1 : +1; - } else if (urlB.isParentOf(urlA)) { - return (sortOrder() == Qt::AscendingOrder) ? +1 : -1; - } - - // Determine the maximum common path of both items and - // remember the index in 'index' - const QString pathA = urlA.path(); - const QString pathB = urlB.path(); - - const int maxIndex = qMin(pathA.length(), pathB.length()) - 1; - int index = 0; - while (index <= maxIndex && pathA.at(index) == pathB.at(index)) { - ++index; - } - if (index > maxIndex) { - index = maxIndex; - } - while (index > 0 && (pathA.at(index) != QLatin1Char('/') || pathB.at(index) != QLatin1Char('/'))) { - --index; - } - - // Determine the first sub-path after the common path and - // check whether it represents a directory or already a file - bool isDirA = true; - const QString subPathA = subPath(a->item, pathA, index, &isDirA); - bool isDirB = true; - const QString subPathB = subPath(b->item, pathB, index, &isDirB); - - if (m_sortDirsFirst || m_sortRole == SizeRole) { - if (isDirA && !isDirB) { - return (sortOrder() == Qt::AscendingOrder) ? -1 : +1; - } else if (!isDirA && isDirB) { - return (sortOrder() == Qt::AscendingOrder) ? +1 : -1; - } - } - - // Compare the items of the parents that represent the first - // different path after the common path. - const QString parentPathA = pathA.left(index) + subPathA; - const QString parentPathB = pathB.left(index) + subPathB; - - const ItemData* parentA = a; - while (parentA && parentA->item.url().path() != parentPathA) { - parentA = parentA->parent; - } - - const ItemData* parentB = b; - while (parentB && parentB->item.url().path() != parentPathB) { - parentB = parentB->parent; - } - - if (parentA && parentB) { - return sortRoleCompare(parentA, parentB); - } - - kWarning() << "Child items without parent detected:" << a->item.url() << b->item.url(); - return QString::compare(urlA.url(), urlB.url(), Qt::CaseSensitive); -} - -QString KFileItemModel::subPath(const KFileItem& item, - const QString& itemPath, - int start, - bool* isDir) const -{ - Q_ASSERT(isDir); - const int pathIndex = itemPath.indexOf('/', start + 1); - *isDir = (pathIndex > 0) || item.isDir(); - return itemPath.mid(start, pathIndex - start); -} - bool KFileItemModel::useMaximumUpdateInterval() const { return !m_dirLister->url().isLocalFile(); @@ -1963,4 +1958,51 @@ void KFileItemModel::determineMimeTypes(const KFileItemList& items, int timeout) } } +bool KFileItemModel::isConsistent() const +{ + if (m_items.count() != m_itemData.count()) { + return false; + } + + for (int i = 0; i < count(); ++i) { + // Check if m_items and m_itemData are consistent. + const KFileItem item = fileItem(i); + if (item.isNull()) { + qWarning() << "Item" << i << "is null"; + return false; + } + + const int itemIndex = index(item); + if (itemIndex != i) { + qWarning() << "Item" << i << "has a wrong index:" << itemIndex; + return false; + } + + // Check if the items are sorted correctly. + if (i > 0 && !lessThan(m_itemData.at(i - 1), m_itemData.at(i))) { + qWarning() << "The order of items" << i - 1 << "and" << i << "is wrong:" + << fileItem(i - 1) << fileItem(i); + return false; + } + + // Check if all parent-child relationships are consistent. + const ItemData* data = m_itemData.at(i); + const ItemData* parent = data->parent; + if (parent) { + if (data->values.value("expandedParentsCount").toInt() != parent->values.value("expandedParentsCount").toInt() + 1) { + qWarning() << "expandedParentsCount is inconsistent for parent" << parent->item << "and child" << data->item; + return false; + } + + const int parentIndex = index(parent->item); + if (parentIndex >= i) { + qWarning() << "Index" << parentIndex << "of parent" << parent->item << "is not smaller than index" << i << "of child" << data->item; + return false; + } + } + } + + return true; +} + #include "kfileitemmodel.moc" diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h index ef9dc98b9..a05d1f9d8 100644 --- a/src/kitemviews/kfileitemmodel.h +++ b/src/kitemviews/kfileitemmodel.h @@ -342,6 +342,12 @@ private: bool lessThan(const ItemData* a, const ItemData* b) const; /** + * Sorts the items between \a begin and \a end using the comparison + * function lessThan(). + */ + void sort(QList<ItemData*>::iterator begin, QList<ItemData*>::iterator end) const; + + /** * Helper method for lessThan() and expandedParentsCountCompare(): Compares * the passed item-data using m_sortRole as criteria. Both items must * have the same parent item, otherwise the comparison will be wrong. @@ -350,22 +356,6 @@ private: int stringCompare(const QString& a, const QString& b) const; - /** - * Compares the expansion level of both items. The "expansion level" is defined - * by the number of parent directories. However simply comparing just the numbers - * is not sufficient, it is also important to check the hierarchy for having - * a correct order like shown in a tree. - */ - int expandedParentsCountCompare(const ItemData* a, const ItemData* b) const; - - /** - * Helper method for expandedParentsCountCompare(). - */ - QString subPath(const KFileItem& item, - const QString& itemPath, - int start, - bool* isDir) const; - bool useMaximumUpdateInterval() const; QList<QPair<int, QVariant> > nameRoleGroups() const; @@ -428,6 +418,11 @@ private: */ static void determineMimeTypes(const KFileItemList& items, int timeout); + /** + * Checks if the model's internal data structures are consistent. + */ + bool isConsistent() const; + private: KFileItemModelDirLister* m_dirLister; @@ -476,9 +471,10 @@ private: // and done step after step in slotCompleted(). QSet<KUrl> m_urlsToExpand; - friend class KFileItemModelSortAlgorithm; // Accesses lessThan() method + friend class KFileItemModelLessThan; // Accesses lessThan() method friend class KFileItemModelRolesUpdater; // Accesses emitSortProgress() method friend class KFileItemModelTest; // For unit testing + friend class KFileItemModelBenchmark; // For unit testing friend class KFileItemListViewTest; // For unit testing friend class DolphinPart; // Accesses m_dirLister }; diff --git a/src/kitemviews/private/kfileitemmodelsortalgorithm.cpp b/src/kitemviews/private/kfileitemmodelsortalgorithm.cpp deleted file mode 100644 index ab650efea..000000000 --- a/src/kitemviews/private/kfileitemmodelsortalgorithm.cpp +++ /dev/null @@ -1,190 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2012 by Peter Penz <[email protected]> * - * * - * 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--); - } -} diff --git a/src/kitemviews/private/kfileitemmodelsortalgorithm.h b/src/kitemviews/private/kfileitemmodelsortalgorithm.h index 07e5d4a81..1d5689432 100644 --- a/src/kitemviews/private/kfileitemmodelsortalgorithm.h +++ b/src/kitemviews/private/kfileitemmodelsortalgorithm.h @@ -1,79 +1,143 @@ -/*************************************************************************** - * Copyright (C) 2012 by Peter Penz <[email protected]> * - * * - * 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 <[email protected]> * + * Copyright (C) 2012 by Emmanuel Pescosta <[email protected]> * + * Copyright (C) 2013 by Frank Reininghaus <[email protected]> * + * * + * 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); + // The implementation is based on qStableSortHelper() from qalgorithms.h + // Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). -private: - static void sequentialSort(KFileItemModel* model, - QList<KFileItemModel::ItemData*>::iterator begin, - QList<KFileItemModel::ItemData*>::iterator end); + const int span = end - begin; + if (span < 2) { + return; + } - static void parallelSort(KFileItemModel* model, - QList<KFileItemModel::ItemData*>::iterator begin, - QList<KFileItemModel::ItemData*>::iterator end, - const int numberOfThreads); + const RandomAccessIterator middle = begin + span / 2; + mergeSort(begin, middle, lessThan); + mergeSort(middle, end, lessThan); + merge(begin, middle, end, lessThan); +} - static void merge(KFileItemModel* model, - QList<KFileItemModel::ItemData*>::iterator begin, - QList<KFileItemModel::ItemData*>::iterator pivot, - QList<KFileItemModel::ItemData*>::iterator end); +/** + * 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. + */ - static QList<KFileItemModel::ItemData*>::iterator - lowerBound(KFileItemModel* model, - QList<KFileItemModel::ItemData*>::iterator begin, - QList<KFileItemModel::ItemData*>::iterator end, - const KFileItemModel::ItemData* value); +template <typename RandomAccessIterator, typename LessThan> +static void parallelMergeSort(RandomAccessIterator begin, + RandomAccessIterator end, + LessThan lessThan, + int numberOfThreads, + int parallelMergeSortingThreshold = 100) +{ + const int span = end - begin; - static QList<KFileItemModel::ItemData*>::iterator - upperBound(KFileItemModel* model, - QList<KFileItemModel::ItemData*>::iterator begin, - QList<KFileItemModel::ItemData*>::iterator end, - const KFileItemModel::ItemData* value); + if (numberOfThreads > 1 && span > parallelMergeSortingThreshold) { + const int newNumberOfThreads = numberOfThreads / 2; + const RandomAccessIterator middle = begin + span / 2; - static void reverse(QList<KFileItemModel::ItemData*>::iterator begin, - QList<KFileItemModel::ItemData*>::iterator end); -}; + QFuture<void> future = QtConcurrent::run(parallelMergeSort<RandomAccessIterator, LessThan>, begin, middle, lessThan, newNumberOfThreads, parallelMergeSortingThreshold); + parallelMergeSort(middle, end, lessThan, newNumberOfThreads, parallelMergeSortingThreshold); -#endif + 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 diff --git a/src/main.cpp b/src/main.cpp index 5addff194..59575fdaf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -50,6 +50,9 @@ KDE_EXPORT int kdemain(int argc, char **argv) about.addAuthor(ki18nc("@info:credit", "David Faure"), ki18nc("@info:credit", "Developer"), + about.addAuthor(ki18nc("@info:credit", "Emmanuel Pescosta"), + ki18nc("@info:credit", "Developer"), + "[email protected]"); about.addAuthor(ki18nc("@info:credit", "Aaron J. Seigo"), ki18nc("@info:credit", "Developer"), diff --git a/src/panels/folders/folderspanel.cpp b/src/panels/folders/folderspanel.cpp index 98c06fb35..46c1b3450 100644 --- a/src/panels/folders/folderspanel.cpp +++ b/src/panels/folders/folderspanel.cpp @@ -236,7 +236,8 @@ void FoldersPanel::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* eve event->buttons(), event->modifiers()); - const QString error = DragAndDropHelper::dropUrls(destItem, destItem.url(), &dropEvent); + QString error; + DragAndDropHelper::dropUrls(destItem, destItem.url(), &dropEvent, error); if (!error.isEmpty()) { emit errorMessage(error); } diff --git a/src/panels/places/placespanel.cpp b/src/panels/places/placespanel.cpp index 919f2ed45..e23732c97 100644 --- a/src/panels/places/placespanel.cpp +++ b/src/panels/places/placespanel.cpp @@ -352,7 +352,11 @@ void PlacesPanel::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* even event->buttons(), event->modifiers()); - DragAndDropHelper::dropUrls(KFileItem(), destUrl, &dropEvent); + QString error; + DragAndDropHelper::dropUrls(KFileItem(), destUrl, &dropEvent, error); + if (!error.isEmpty()) { + emit errorMessage(error); + } } void PlacesPanel::slotItemDropEventStorageSetupDone(int index, bool success) @@ -364,7 +368,11 @@ void PlacesPanel::slotItemDropEventStorageSetupDone(int index, bool success) if (success) { KUrl destUrl = m_model->placesItem(index)->url(); - DragAndDropHelper::dropUrls(KFileItem(), destUrl, m_itemDropEvent); + QString error; + DragAndDropHelper::dropUrls(KFileItem(), destUrl, m_itemDropEvent, error); + if (!error.isEmpty()) { + emit errorMessage(error); + } } delete m_itemDropEventMimeData; @@ -384,7 +392,8 @@ void PlacesPanel::slotAboveItemDropEvent(int index, QGraphicsSceneDragDropEvent* void PlacesPanel::slotUrlsDropped(const KUrl& dest, QDropEvent* event, QWidget* parent) { Q_UNUSED(parent); - const QString error = DragAndDropHelper::dropUrls(KFileItem(), dest, event); + QString error; + DragAndDropHelper::dropUrls(KFileItem(), dest, event, error); if (!error.isEmpty()) { emit errorMessage(error); } diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 6575eecde..543261eac 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -48,6 +48,16 @@ set(kfileitemmodeltest_SRCS kde4_add_unit_test(kfileitemmodeltest TEST ${kfileitemmodeltest_SRCS}) target_link_libraries(kfileitemmodeltest dolphinprivate ${KDE4_KIO_LIBS} ${QT_QTTEST_LIBRARY}) +# KFileItemModelBenchmark +set(kfileitemmodelbenchmark_SRCS + kfileitemmodelbenchmark.cpp + testdir.cpp + ../kitemviews/kfileitemmodel.cpp + ../kitemviews/kitemmodelbase.cpp +) +kde4_add_executable(kfileitemmodelbenchmark TEST ${kfileitemmodelbenchmark_SRCS}) +target_link_libraries(kfileitemmodelbenchmark dolphinprivate ${KDE4_KIO_LIBS} ${QT_QTTEST_LIBRARY}) + # KItemListKeyboardSearchManagerTest set(kitemlistkeyboardsearchmanagertest_SRCS kitemlistkeyboardsearchmanagertest.cpp diff --git a/src/tests/kfileitemmodelbenchmark.cpp b/src/tests/kfileitemmodelbenchmark.cpp new file mode 100644 index 000000000..b1e777c06 --- /dev/null +++ b/src/tests/kfileitemmodelbenchmark.cpp @@ -0,0 +1,328 @@ +/*************************************************************************** + * Copyright (C) 2011 by Peter Penz <[email protected]> * + * Copyright (C) 2013 by Frank Reininghaus <[email protected]> * + * * + * 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.slotNewItems(initialItems); + model.slotCompleted(); + QCOMPARE(model.count(), initialItems.count()); + + if (!newItems.isEmpty()) { + model.slotNewItems(newItems); + model.slotCompleted(); + } + QCOMPARE(model.count(), initialItems.count() + newItems.count()); + + if (!removedItems.isEmpty()) { + model.removeItems(removedItems); + } + 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() +{ + 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()); + } +} + +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" diff --git a/src/tests/kfileitemmodeltest.cpp b/src/tests/kfileitemmodeltest.cpp index 719459c6f..c9f8a3468 100644 --- a/src/tests/kfileitemmodeltest.cpp +++ b/src/tests/kfileitemmodeltest.cpp @@ -21,6 +21,8 @@ #include <qtest_kde.h> #include <KDirLister> +#include <kio/job.h> + #include "kitemviews/kfileitemmodel.h" #include "kitemviews/private/kfileitemmodeldirlister.h" #include "testdir.h" @@ -71,6 +73,7 @@ private slots: void testItemRangeConsistencyWhenInsertingItems(); void testExpandItems(); void testExpandParentItems(); + void testMakeExpandedItemHidden(); void testSorting(); void testIndexForKeyboardSearch(); void testNameFilter(); @@ -78,7 +81,6 @@ private slots: void testRemoveHiddenItems(); private: - bool isModelConsistent() const; QStringList itemsInModel() const; private: @@ -153,7 +155,7 @@ void KFileItemModelTest::testNewItems() QCOMPARE(m_model->count(), 3); - QVERIFY(isModelConsistent()); + QVERIFY(m_model->isConsistent()); } void KFileItemModelTest::testRemoveItems() @@ -163,13 +165,13 @@ void KFileItemModelTest::testRemoveItems() m_model->loadDirectory(m_testDir->url()); QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); QCOMPARE(m_model->count(), 2); - QVERIFY(isModelConsistent()); + QVERIFY(m_model->isConsistent()); m_testDir->removeFile("a.txt"); m_model->m_dirLister->updateDirectory(m_testDir->url()); QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsRemoved(KItemRangeList)), DefaultTimeout)); QCOMPARE(m_model->count(), 1); - QVERIFY(isModelConsistent()); + QVERIFY(m_model->isConsistent()); } void KFileItemModelTest::testDirLoadingCompleted() @@ -212,7 +214,7 @@ void KFileItemModelTest::testDirLoadingCompleted() QCOMPARE(itemsRemovedSpy.count(), 2); QCOMPARE(m_model->count(), 4); - QVERIFY(isModelConsistent()); + QVERIFY(m_model->isConsistent()); } void KFileItemModelTest::testSetData() @@ -233,7 +235,7 @@ void KFileItemModelTest::testSetData() values = m_model->data(0); QCOMPARE(values.value("customRole1").toString(), QString("Test1")); QCOMPARE(values.value("customRole2").toString(), QString("Test2")); - QVERIFY(isModelConsistent()); + QVERIFY(m_model->isConsistent()); } void KFileItemModelTest::testSetDataWithModifiedSortRole_data() @@ -314,7 +316,7 @@ void KFileItemModelTest::testSetDataWithModifiedSortRole() QCOMPARE(m_model->data(0).value("rating").toInt(), ratingIndex0); QCOMPARE(m_model->data(1).value("rating").toInt(), ratingIndex1); QCOMPARE(m_model->data(2).value("rating").toInt(), ratingIndex2); - QVERIFY(isModelConsistent()); + QVERIFY(m_model->isConsistent()); } void KFileItemModelTest::testModelConsistencyWhenInsertingItems() @@ -353,7 +355,7 @@ void KFileItemModelTest::testModelConsistencyWhenInsertingItems() QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); } - QVERIFY(isModelConsistent()); + QVERIFY(m_model->isConsistent()); } QCOMPARE(m_model->count(), 201); @@ -487,6 +489,7 @@ void KFileItemModelTest::testExpandItems() QCOMPARE(spyRemoved.count(), 1); itemRangeList = spyRemoved.takeFirst().at(0).value<KItemRangeList>(); QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 4)); // 4 items removed + QVERIFY(m_model->isConsistent()); // Clear the model, reload the folder and try to restore the expanded folders. m_model->clear(); @@ -503,6 +506,7 @@ void KFileItemModelTest::testExpandItems() QVERIFY(m_model->isExpanded(3)); QVERIFY(!m_model->isExpanded(4)); QCOMPARE(m_model->expandedDirectories(), allFolders); + QVERIFY(m_model->isConsistent()); // Move to a sub folder, then call restoreExpandedFolders() *before* going back. // This is how DolphinView restores the expanded folders when navigating in history. @@ -565,6 +569,56 @@ void KFileItemModelTest::testExpandParentItems() QVERIFY(m_model->isExpanded(2)); QVERIFY(m_model->isExpanded(3)); QVERIFY(!m_model->isExpanded(4)); + QVERIFY(m_model->isConsistent()); +} + +/** + * Renaming an expanded folder by prepending its name with a dot makes it + * hidden. Verify that this does not cause an inconsistent model state and + * a crash later on, see https://bugs.kde.org/show_bug.cgi?id=311947 + */ +void KFileItemModelTest::testMakeExpandedItemHidden() +{ + QSet<QByteArray> modelRoles = m_model->roles(); + modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount"; + m_model->setRoles(modelRoles); + + QStringList files; + m_testDir->createFile("1a/2a/3a"); + m_testDir->createFile("1a/2a/3b"); + m_testDir->createFile("1a/2b"); + m_testDir->createFile("1b"); + + m_model->loadDirectory(m_testDir->url()); + QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + + // So far, the model contains only "1a/" and "1b". + QCOMPARE(m_model->count(), 2); + m_model->setExpanded(0, true); + QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + + // Now "1a/2a" and "1a/2b" have appeared. + QCOMPARE(m_model->count(), 4); + m_model->setExpanded(1, true); + QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QCOMPARE(m_model->count(), 6); + + // Rename "1a/2" and make it hidden. + const QString oldPath = m_model->fileItem(0).url().path() + "/2a"; + const QString newPath = m_model->fileItem(0).url().path() + "/.2a"; + + KIO::SimpleJob* job = KIO::rename(oldPath, newPath, KIO::HideProgressInfo); + bool ok = job->exec(); + QVERIFY(ok); + QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsRemoved(KItemRangeList)), DefaultTimeout)); + + // "1a/2" and its subfolders have disappeared now. + QVERIFY(m_model->isConsistent()); + QCOMPARE(m_model->count(), 3); + + m_model->setExpanded(0, false); + QCOMPARE(m_model->count(), 2); + } void KFileItemModelTest::testSorting() @@ -849,29 +903,6 @@ void KFileItemModelTest::testRemoveHiddenItems() m_model->setShowHiddenFiles(false); } -bool KFileItemModelTest::isModelConsistent() const -{ - if (m_model->m_items.count() != m_model->m_itemData.count()) { - return false; - } - - 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; - } - - const int itemIndex = m_model->index(item); - if (itemIndex != i) { - qWarning() << "Item" << i << "has a wrong index:" << itemIndex; - return false; - } - } - - return true; -} - QStringList KFileItemModelTest::itemsInModel() const { QStringList items; diff --git a/src/views/dolphinremoteencoding.cpp b/src/views/dolphinremoteencoding.cpp index 375b3fd46..04b350eda 100644 --- a/src/views/dolphinremoteencoding.cpp +++ b/src/views/dolphinremoteencoding.cpp @@ -38,7 +38,6 @@ #include <KMenu> #include <KProtocolInfo> #include <KProtocolManager> -#include <KIO/SlaveConfig> #include <KIO/Scheduler> #include <KConfigGroup> @@ -132,9 +131,7 @@ void DolphinRemoteEncoding::updateMenu() m_menu->menu()->actions().at(i)->setChecked(false); } - QString charset = KGlobal::charsets()->descriptionForEncoding(KIO::SlaveConfig::self()->configData(m_currentURL.protocol(), - m_currentURL.host(), DATA_KEY)); - + const QString charset = KGlobal::charsets()->descriptionForEncoding(KProtocolManager::charsetFor(m_currentURL)); if (!charset.isEmpty()) { int id = 0; bool isFound = false; diff --git a/src/views/dolphinview.cpp b/src/views/dolphinview.cpp index 502ffd428..d1e154f68 100644 --- a/src/views/dolphinview.cpp +++ b/src/views/dolphinview.cpp @@ -1023,14 +1023,16 @@ void DolphinView::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* even event->buttons(), event->modifiers()); - const QString error = DragAndDropHelper::dropUrls(destItem, destUrl, &dropEvent); + QString error; + KonqOperations* op = DragAndDropHelper::dropUrls(destItem, destUrl, &dropEvent, error); if (!error.isEmpty()) { emit infoMessage(error); } - if (destUrl == url()) { + if (op && destUrl == url()) { // Mark the dropped urls as selected. - markPastedUrlsAsSelected(event->mimeData()); + m_clearSelectionBeforeSelectingNewItems = true; + connect(op, SIGNAL(urlPasted(KUrl)), this, SLOT(slotUrlPasted(KUrl))); } } @@ -1066,6 +1068,11 @@ void DolphinView::slotMouseButtonPressed(int itemIndex, Qt::MouseButtons buttons } } +void DolphinView::slotAboutToCreate(const KUrl::List& urls) +{ + m_selectedUrls << urls; +} + void DolphinView::slotSelectionChanged(const QSet<int>& current, const QSet<int>& previous) { const int currentCount = current.count(); @@ -1523,8 +1530,11 @@ void DolphinView::applyModeToView() void DolphinView::pasteToUrl(const KUrl& url) { - markPastedUrlsAsSelected(QApplication::clipboard()->mimeData()); - KonqOperations::doPaste(this, url); + KonqOperations* op = KonqOperations::doPasteV2(this, url); + if (op) { + m_clearSelectionBeforeSelectingNewItems = true; + connect(op, SIGNAL(aboutToCreate(KUrl::List)), this, SLOT(slotAboutToCreate(KUrl::List))); + } } KUrl::List DolphinView::simplifiedSelectedUrls() const @@ -1552,18 +1562,6 @@ QMimeData* DolphinView::selectionMimeData() const return m_model->createMimeData(selectedIndexes); } -void DolphinView::markPastedUrlsAsSelected(const QMimeData* mimeData) -{ - const KUrl::List sourceUrls = KUrl::List::fromMimeData(mimeData); - KUrl::List destUrls; - foreach (const KUrl& source, sourceUrls) { - KUrl destination(url().url() + '/' + source.fileName()); - destUrls << destination; - } - markUrlsAsSelected(destUrls); - m_clearSelectionBeforeSelectingNewItems = true; -} - void DolphinView::updateWritableState() { const bool wasFolderWritable = m_isFolderWritable; diff --git a/src/views/dolphinview.h b/src/views/dolphinview.h index a2fe9f62a..13cc66545 100644 --- a/src/views/dolphinview.h +++ b/src/views/dolphinview.h @@ -566,6 +566,11 @@ private slots: void slotModelChanged(KItemModelBase* current, KItemModelBase* previous); void slotMouseButtonPressed(int itemIndex, Qt::MouseButtons buttons); + /* + * Is called when new items get pasted or dropped. + */ + void slotAboutToCreate(const KUrl::List& urls); + /** * Emits the signal \a selectionChanged() with a small delay. This is * because getting all file items for the selection can be an expensive @@ -722,14 +727,6 @@ private: QMimeData* selectionMimeData() const; /** - * Is invoked after a paste operation or a drag & drop - * operation and URLs from \a mimeData as selected. - * This allows to select all newly pasted - * items in restoreViewState(). - */ - void markPastedUrlsAsSelected(const QMimeData* mimeData); - - /** * Updates m_isFolderWritable dependent on whether the folder represented by * the current URL is writable. If the state has changed, the signal * writeableStateChanged() will be emitted. diff --git a/src/views/draganddrophelper.cpp b/src/views/draganddrophelper.cpp index f81d4d0bf..f8ae0ad03 100644 --- a/src/views/draganddrophelper.cpp +++ b/src/views/draganddrophelper.cpp @@ -28,10 +28,13 @@ #include <QtDBus> #include <QDropEvent> -QString DragAndDropHelper::dropUrls(const KFileItem& destItem, const KUrl& destUrl, QDropEvent* event) +KonqOperations* DragAndDropHelper::dropUrls(const KFileItem& destItem, const KUrl& destUrl, QDropEvent* event, QString& error) { + error.clear(); + if (!destItem.isNull() && !destItem.isWritable()) { - return i18nc("@info:status", "Access denied. Could not write to <filename>%1</filename>", destUrl.pathOrUrl()); + error = i18nc("@info:status", "Access denied. Could not write to <filename>%1</filename>", destUrl.pathOrUrl()); + return 0; } const QMimeData* mimeData = event->mimeData(); @@ -49,15 +52,16 @@ QString DragAndDropHelper::dropUrls(const KFileItem& destItem, const KUrl& destU const KUrl::List urls = KUrl::List::fromMimeData(event->mimeData()); foreach (const KUrl& url, urls) { if (url == destUrl) { - return i18nc("@info:status", "A folder cannot be dropped into itself"); + error = i18nc("@info:status", "A folder cannot be dropped into itself"); + return 0; } } - KonqOperations::doDrop(destItem, destUrl, event, QApplication::activeWindow()); + return KonqOperations::doDrop(destItem, destUrl, event, QApplication::activeWindow(), QList<QAction*>()); } else { - KonqOperations::doDrop(KFileItem(), destUrl, event, QApplication::activeWindow()); + return KonqOperations::doDrop(KFileItem(), destUrl, event, QApplication::activeWindow(), QList<QAction*>()); } - return QString(); + return 0; } diff --git a/src/views/draganddrophelper.h b/src/views/draganddrophelper.h index ac16f7cf2..eda5fc5c2 100644 --- a/src/views/draganddrophelper.h +++ b/src/views/draganddrophelper.h @@ -29,6 +29,7 @@ class KFileItem; class KUrl; class QDropEvent; class QWidget; +class KonqOperations; class LIBDOLPHINPRIVATE_EXPORT DragAndDropHelper { @@ -46,13 +47,15 @@ public: * @param destUrl URL of the item destination. Is used only if destItem::isNull() * is true. * @param event Drop event. - * @return Error message intended to be shown for users if dropping is not + * @param error Error message intended to be shown for users if dropping is not * possible. If an empty string is returned, the dropping has been * successful. + * @return KonqOperations pointer */ - static QString dropUrls(const KFileItem& destItem, - const KUrl& destUrl, - QDropEvent* event); + static KonqOperations* dropUrls(const KFileItem& destItem, + const KUrl& destUrl, + QDropEvent* event, + QString& error); }; #endif diff --git a/src/views/versioncontrol/updateitemstatesthread.cpp b/src/views/versioncontrol/updateitemstatesthread.cpp index e07d72c76..fa005f8f1 100644 --- a/src/views/versioncontrol/updateitemstatesthread.cpp +++ b/src/views/versioncontrol/updateitemstatesthread.cpp @@ -23,13 +23,13 @@ #include <QMutexLocker> -UpdateItemStatesThread::UpdateItemStatesThread() : +UpdateItemStatesThread::UpdateItemStatesThread(KVersionControlPlugin* plugin, + const QList<VersionControlObserver::ItemState>& itemStates) : QThread(), m_globalPluginMutex(0), - m_plugin(0), - m_itemMutex(), + m_plugin(plugin), m_retrievedItems(false), - m_itemStates() + m_itemStates(itemStates) { // Several threads may share one instance of a plugin. A global // mutex is required to serialize the retrieval of version control @@ -42,32 +42,16 @@ UpdateItemStatesThread::~UpdateItemStatesThread() { } -void UpdateItemStatesThread::setData(KVersionControlPlugin* plugin, - const QList<VersionControlObserver::ItemState>& itemStates) -{ - // The locks are taken in the same order as in run() - // to avoid potential deadlock. - QMutexLocker pluginLocker(m_globalPluginMutex); - QMutexLocker itemLocker(&m_itemMutex); - - m_itemStates = itemStates; - m_plugin = plugin; -} - void UpdateItemStatesThread::run() { Q_ASSERT(!m_itemStates.isEmpty()); Q_ASSERT(m_plugin); - QMutexLocker itemLocker(&m_itemMutex); - const QString directory = m_itemStates.first().item.url().directory(KUrl::AppendTrailingSlash); m_retrievedItems = false; - itemLocker.unlock(); QMutexLocker pluginLocker(m_globalPluginMutex); if (m_plugin->beginRetrieval(directory)) { - itemLocker.relock(); const int count = m_itemStates.count(); KVersionControlPlugin2* pluginV2 = qobject_cast<KVersionControlPlugin2*>(m_plugin); @@ -99,13 +83,11 @@ void UpdateItemStatesThread::unlockPlugin() QList<VersionControlObserver::ItemState> UpdateItemStatesThread::itemStates() const { - QMutexLocker locker(&m_itemMutex); return m_itemStates; } bool UpdateItemStatesThread::retrievedItems() const { - QMutexLocker locker(&m_itemMutex); return m_retrievedItems; } diff --git a/src/views/versioncontrol/updateitemstatesthread.h b/src/views/versioncontrol/updateitemstatesthread.h index f0f91d7d2..a28169755 100644 --- a/src/views/versioncontrol/updateitemstatesthread.h +++ b/src/views/versioncontrol/updateitemstatesthread.h @@ -38,9 +38,6 @@ class LIBDOLPHINPRIVATE_EXPORT UpdateItemStatesThread : public QThread Q_OBJECT public: - UpdateItemStatesThread(); - virtual ~UpdateItemStatesThread(); - /** * @param plugin Version control plugin that is used to update the * state of the items. Whenever the plugin is accessed @@ -49,8 +46,9 @@ public: * UpdateItemStatesThread::unlockPlugin() must be used. * @param itemStates List of items, where the states get updated. */ - void setData(KVersionControlPlugin* plugin, - const QList<VersionControlObserver::ItemState>& itemStates); + UpdateItemStatesThread(KVersionControlPlugin* plugin, + const QList<VersionControlObserver::ItemState>& itemStates); + virtual ~UpdateItemStatesThread(); /** * Whenever the plugin is accessed by the thread creator, lockPlugin() must @@ -76,7 +74,6 @@ private: QMutex* m_globalPluginMutex; // Protects the m_plugin globally KVersionControlPlugin* m_plugin; - mutable QMutex m_itemMutex; // Protects m_retrievedItems and m_itemStates bool m_retrievedItems; QList<VersionControlObserver::ItemState> m_itemStates; }; diff --git a/src/views/versioncontrol/versioncontrolobserver.cpp b/src/views/versioncontrol/versioncontrolobserver.cpp index 64bc26867..402a2de54 100644 --- a/src/views/versioncontrol/versioncontrolobserver.cpp +++ b/src/views/versioncontrol/versioncontrolobserver.cpp @@ -108,12 +108,7 @@ QList<QAction*> VersionControlObserver::actions(const KFileItemList& items) cons if (pluginV2) { // Use version 2 of the KVersionControlPlugin which allows providing actions // also for non-versioned directories. - if (m_updateItemStatesThread && m_updateItemStatesThread->lockPlugin()) { - actions = pluginV2->actions(items); - m_updateItemStatesThread->unlockPlugin(); - } else { - actions = pluginV2->actions(items); - } + actions = pluginV2->actions(items); } else if (isVersioned()) { // Support deprecated interfaces from KVersionControlPlugin version 1. // Context menu actions where only available for versioned directories. @@ -125,14 +120,8 @@ QList<QAction*> VersionControlObserver::actions(const KFileItemList& items) cons } } - if (m_updateItemStatesThread && m_updateItemStatesThread->lockPlugin()) { - actions = directory.isEmpty() ? m_plugin->contextMenuActions(items) - : m_plugin->contextMenuActions(directory); - m_updateItemStatesThread->unlockPlugin(); - } else { - actions = directory.isEmpty() ? m_plugin->contextMenuActions(items) - : m_plugin->contextMenuActions(directory); - } + actions = directory.isEmpty() ? m_plugin->contextMenuActions(items) + : m_plugin->contextMenuActions(directory); } return actions; @@ -238,20 +227,12 @@ void VersionControlObserver::slotThreadFinished() void VersionControlObserver::updateItemStates() { Q_ASSERT(m_plugin); - if (!m_updateItemStatesThread) { - m_updateItemStatesThread = new UpdateItemStatesThread(); - connect(m_updateItemStatesThread, SIGNAL(finished()), - this, SLOT(slotThreadFinished())); - connect(m_updateItemStatesThread, SIGNAL(finished()), - m_updateItemStatesThread, SLOT(deleteLater())); - } - else { + if (m_updateItemStatesThread) { // An update is currently ongoing. Wait until the thread has finished // the update (see slotThreadFinished()). m_pendingItemStatesUpdate = true; return; } - QList<ItemState> itemStates; const int itemCount = m_model->count(); itemStates.reserve(itemCount); @@ -269,7 +250,12 @@ void VersionControlObserver::updateItemStates() if (!m_silentUpdate) { emit infoMessage(i18nc("@info:status", "Updating version information...")); } - m_updateItemStatesThread->setData(m_plugin, itemStates); + m_updateItemStatesThread = new UpdateItemStatesThread(m_plugin, itemStates); + connect(m_updateItemStatesThread, SIGNAL(finished()), + this, SLOT(slotThreadFinished())); + connect(m_updateItemStatesThread, SIGNAL(finished()), + m_updateItemStatesThread, SLOT(deleteLater())); + m_updateItemStatesThread->start(); // slotThreadFinished() is called when finished } } |
