-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
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
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);
}
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()));
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.
*/
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;
}
}
*/
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);
-/***************************************************************************
- * 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)
// 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!";
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)
// Keep old values as long as possible if they could not retrieved synchronously yet.
// The update of the values will be done asynchronously by KFileItemModelRolesUpdater.
- QHashIterator<QByteArray, QVariant> it(retrieveData(newItem));
+ QHashIterator<QByteArray, QVariant> it(retrieveData(newItem, m_itemData.at(index)->parent));
while (it.hasNext()) {
it.next();
m_itemData[index]->values.insert(it.key(), it.value());
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*>& items)
{
if (items.isEmpty()) {
return;
}
- if (m_sortRole == TypeRole) {
- // Try to resolve the MIME-types synchronously to prevent a reordering of
- // the items when sorting by type (per default MIME-types are resolved
- // asynchronously by KFileItemModelRolesUpdater).
- determineMimeTypes(items, 200);
- }
-
#ifdef KFILEITEMMODEL_DEBUG
QElapsedTimer timer;
timer.start();
m_groups.clear();
- QList<ItemData*> sortedItems = createItemDataList(items);
- KFileItemModelSortAlgorithm::sort(this, sortedItems.begin(), sortedItems.end());
+ sort(items.begin(), items.end());
#ifdef KFILEITEMMODEL_DEBUG
kDebug() << "[TIME] Sorting:" << timer.elapsed();
int insertedAtIndex = -1; // Index for the current item-range
int insertedCount = 0; // Count for the current item-range
int previouslyInsertedCount = 0; // Sum of previously inserted items for all ranges
- while (sourceIndex < sortedItems.count()) {
+ while (sourceIndex < items.count()) {
// Find target index from m_items to insert the current item
// in a sorted order
const int previousTargetIndex = targetIndex;
while (targetIndex < m_itemData.count()) {
- if (!lessThan(m_itemData.at(targetIndex), sortedItems.at(sourceIndex))) {
+ if (!lessThan(m_itemData.at(targetIndex), items.at(sourceIndex))) {
break;
}
++targetIndex;
}
// Insert item at the position targetIndex by transferring
- // the ownership of the item-data from sortedItems to m_itemData.
+ // the ownership of the item-data from 'items' to m_itemData.
// m_items will be inserted after the loop (see comment below)
- m_itemData.insert(targetIndex, sortedItems.at(sourceIndex));
+ m_itemData.insert(targetIndex, items.at(sourceIndex));
++insertedCount;
if (insertedAtIndex < 0) {
// 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);
}
#endif
}
-void KFileItemModel::removeItems(const KFileItemList& items)
+static KItemRangeList sortedIndexesToKItemRangeList(const QList<int>& sortedNumbers)
{
- if (items.isEmpty()) {
- return;
+ if (sortedNumbers.empty()) {
+ return KItemRangeList();
+ }
+
+ KItemRangeList result;
+
+ QList<int>::const_iterator it = sortedNumbers.begin();
+ int index = *it;
+ int count = 1;
+
+ ++it;
+
+ QList<int>::const_iterator end = sortedNumbers.end();
+ while (it != end) {
+ if (*it == index + count) {
+ ++count;
+ } else {
+ result << KItemRange(index, count);
+ index = *it;
+ count = 1;
+ }
+ ++it;
}
+ result << KItemRange(index, count);
+ return result;
+}
+
+void KFileItemModel::removeItems(const KFileItemList& items, RemoveItemsBehavior behavior)
+{
#ifdef KFILEITEMMODEL_DEBUG
kDebug() << "Removing " << items.count() << "items";
#endif
m_groups.clear();
- QList<ItemData*> sortedItems;
- sortedItems.reserve(items.count());
- foreach (const KFileItem& item, items) {
- const int index = m_items.value(item.url(), -1);
- if (index >= 0) {
- sortedItems.append(m_itemData.at(index));
- }
- }
- KFileItemModelSortAlgorithm::sort(this, sortedItems.begin(), sortedItems.end());
-
+ // Step 1: Determine the indexes of the removed items, remove them from
+ // the hash m_items, and free the ItemData.
QList<int> indexesToRemove;
indexesToRemove.reserve(items.count());
+ foreach (const KFileItem& item, items) {
+ const KUrl url = item.url();
+ const int index = m_items.value(url, -1);
+ if (index >= 0) {
+ indexesToRemove.append(index);
- // Calculate the item ranges that will get deleted
- KItemRangeList itemRanges;
- int removedAtIndex = -1;
- int removedCount = 0;
- int targetIndex = 0;
- foreach (const ItemData* itemData, sortedItems) {
- const KFileItem& itemToRemove = itemData->item;
+ // Prevent repeated expensive rehashing by using QHash::erase(),
+ // rather than QHash::remove().
+ QHash<KUrl, int>::iterator it = m_items.find(url);
+ m_items.erase(it);
- const int previousTargetIndex = targetIndex;
- while (targetIndex < m_itemData.count()) {
- if (m_itemData.at(targetIndex)->item.url() == itemToRemove.url()) {
- break;
+ if (behavior == DeleteItemData) {
+ delete m_itemData.at(index);
}
- ++targetIndex;
- }
- if (targetIndex >= m_itemData.count()) {
- kWarning() << "Item that should be deleted has not been found!";
- return;
- }
- if (targetIndex - previousTargetIndex > 0 && removedAtIndex >= 0) {
- itemRanges << KItemRange(removedAtIndex, removedCount);
- removedAtIndex = targetIndex;
- removedCount = 0;
+ m_itemData[index] = 0;
}
+ }
- indexesToRemove.append(targetIndex);
- if (removedAtIndex < 0) {
- removedAtIndex = targetIndex;
- }
- ++removedCount;
- ++targetIndex;
+ if (indexesToRemove.isEmpty()) {
+ return;
}
- // Delete the items
- for (int i = indexesToRemove.count() - 1; i >= 0; --i) {
- const int indexToRemove = indexesToRemove.at(i);
- ItemData* data = m_itemData.at(indexToRemove);
+ std::sort(indexesToRemove.begin(), indexesToRemove.end());
- m_items.remove(data->item.url());
+ // Step 2: Remove the ItemData pointers from the list m_itemData.
+ const KItemRangeList itemRanges = sortedIndexesToKItemRangeList(indexesToRemove);
+ int target = itemRanges.at(0).index;
+ int source = itemRanges.at(0).index + itemRanges.at(0).count;
+ int nextRange = 1;
- delete data;
- m_itemData.removeAt(indexToRemove);
- }
+ const int oldItemDataCount = m_itemData.count();
+ while (source < oldItemDataCount) {
+ m_itemData[target] = m_itemData[source];
+ ++target;
+ ++source;
- // The indexes of all m_items must be adjusted, not only the index
- // of the removed items
- const int itemDataCount = m_itemData.count();
- for (int i = 0; i < itemDataCount; ++i) {
- m_items.insert(m_itemData.at(i)->item.url(), i);
+ if (nextRange < itemRanges.count() && source == itemRanges.at(nextRange).index) {
+ // Skip the items in the next removed range.
+ source += itemRanges.at(nextRange).count;
+ ++nextRange;
+ }
}
- if (count() <= 0) {
- m_expandedParentsCountRoot = UninitializedExpandedParentsCountRoot;
+ m_itemData.erase(m_itemData.end() - indexesToRemove.count(), m_itemData.end());
+
+ // Step 3: Adjust indexes in the hash m_items. Note that all indexes
+ // might have been changed by the removal of the items.
+ const int newItemDataCount = m_itemData.count();
+ for (int i = 0; i < newItemDataCount; ++i) {
+ m_items.insert(m_itemData.at(i)->item.url(), i);
}
- itemRanges << KItemRange(removedAtIndex, removedCount);
emit itemsRemoved(itemRanges);
}
-QList<KFileItemModel::ItemData*> KFileItemModel::createItemDataList(const KFileItemList& items) const
+QList<KFileItemModel::ItemData*> KFileItemModel::createItemDataList(const KUrl& parentUrl, const KFileItemList& items) const
{
+ if (m_sortRole == TypeRole) {
+ // Try to resolve the MIME-types synchronously to prevent a reordering of
+ // the items when sorting by type (per default MIME-types are resolved
+ // asynchronously by KFileItemModelRolesUpdater).
+ determineMimeTypes(items, 200);
+ }
+
+ const int parentIndex = m_items.value(parentUrl, -1);
+ ItemData* parentItem = parentIndex < 0 ? 0 : m_itemData.at(parentIndex);
+
QList<ItemData*> itemDataList;
itemDataList.reserve(items.count());
foreach (const KFileItem& item, items) {
ItemData* itemData = new ItemData();
itemData->item = item;
- itemData->values = retrieveData(item);
- itemData->parent = 0;
-
- const bool determineParent = m_requestRole[ExpandedParentsCountRole]
- && itemData->values["expandedParentsCount"].toInt() > 0;
- if (determineParent) {
- KUrl parentUrl = item.url().upUrl();
- parentUrl.adjustPath(KUrl::RemoveTrailingSlash);
- const int parentIndex = m_items.value(parentUrl, -1);
- if (parentIndex >= 0) {
- itemData->parent = m_itemData.at(parentIndex);
- } else {
- kWarning() << "Parent item not found for" << item.url();
- }
- }
-
+ itemData->values = retrieveData(item, parentItem);
+ itemData->parent = parentItem;
itemDataList.append(itemData);
}
// 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
}
if (m_requestRole[ExpandedParentsCountRole]) {
- if (m_expandedParentsCountRoot == UninitializedExpandedParentsCountRoot) {
- const KUrl rootUrl = m_dirLister->url();
- const QString protocol = rootUrl.protocol();
- const bool forceExpandedParentsCountRoot = (protocol == QLatin1String("trash") ||
- protocol == QLatin1String("nepomuk") ||
- protocol == QLatin1String("remote") ||
- protocol.contains(QLatin1String("search")));
- if (forceExpandedParentsCountRoot) {
- m_expandedParentsCountRoot = ForceExpandedParentsCountRoot;
- } else {
- const QString rootDir = rootUrl.path(KUrl::AddTrailingSlash);
- m_expandedParentsCountRoot = rootDir.count('/');
- }
+ int level = 0;
+ if (parent) {
+ level = parent->values["expandedParentsCount"].toInt() + 1;
}
- if (m_expandedParentsCountRoot == ForceExpandedParentsCountRoot) {
- data.insert("expandedParentsCount", -1);
- } else {
- const QString dir = item.url().directory(KUrl::AppendTrailingSlash);
- const int level = dir.count('/') - m_expandedParentsCountRoot;
- data.insert("expandedParentsCount", level);
- }
+ data.insert("expandedParentsCount", level);
}
if (item.isMimeTypeKnown()) {
{
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
}
}
+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);
+ /**
+ * 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
};
+++ /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
{
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"));
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);
}
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);
}
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::testModelConsistencyWhenInsertingItems()
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()
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
#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;
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)));
}
}
}
}
+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();
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
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 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
*/
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
#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
}
}