┌   ┐
54
└   ┘

summaryrefslogtreecommitdiff
path: root/src/kitemviews
diff options
context:
space:
mode:
Diffstat (limited to 'src/kitemviews')
-rw-r--r--src/kitemviews/kfileitemmodel.cpp783
-rw-r--r--src/kitemviews/kfileitemmodel.h76
-rw-r--r--src/kitemviews/kfileitemmodelrolesupdater.cpp1083
-rw-r--r--src/kitemviews/kfileitemmodelrolesupdater.h151
-rw-r--r--src/kitemviews/kitemlistcontroller.cpp34
-rw-r--r--src/kitemviews/kitemlistview.cpp13
-rw-r--r--src/kitemviews/kitemlistview.h1
-rw-r--r--src/kitemviews/private/kfileitemmodelsortalgorithm.cpp190
-rw-r--r--src/kitemviews/private/kfileitemmodelsortalgorithm.h180
-rw-r--r--src/kitemviews/private/kitemlistkeyboardsearchmanager.cpp2
-rw-r--r--src/kitemviews/private/knepomukdatamanagement_export.h40
-rw-r--r--src/kitemviews/private/knepomukrolesprovider.cpp7
12 files changed, 1325 insertions, 1235 deletions
diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp
index 787d36ae6..698314c2f 100644
--- a/src/kitemviews/kfileitemmodel.cpp
+++ b/src/kitemviews/kfileitemmodel.cpp
@@ -1,21 +1,23 @@
-/***************************************************************************
- * Copyright (C) 2011 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) 2011 by Peter Penz <[email protected]> *
+ * Copyright (C) 2013 by Frank Reininghaus <[email protected]> *
+ * Copyright (C) 2013 by Emmanuel Pescosta <[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 "kfileitemmodel.h"
@@ -56,12 +58,10 @@ KFileItemModel::KFileItemModel(QObject* parent) :
m_resortAllItemsTimer(0),
m_pendingItemsToInsert(),
m_groups(),
- m_expandedParentsCountRoot(UninitializedExpandedParentsCountRoot),
m_expandedDirs(),
m_urlsToExpand()
{
m_dirLister = new KFileItemModelDirLister(this);
- m_dirLister->setAutoUpdate(true);
m_dirLister->setDelayedMimeTypes(true);
const QWidget* parentWidget = qobject_cast<QWidget*>(parent);
@@ -72,7 +72,7 @@ KFileItemModel::KFileItemModel(QObject* parent) :
connect(m_dirLister, SIGNAL(started(KUrl)), this, SIGNAL(directoryLoadingStarted()));
connect(m_dirLister, SIGNAL(canceled()), this, SLOT(slotCanceled()));
connect(m_dirLister, SIGNAL(completed(KUrl)), this, SLOT(slotCompleted()));
- connect(m_dirLister, SIGNAL(newItems(KFileItemList)), this, SLOT(slotNewItems(KFileItemList)));
+ connect(m_dirLister, SIGNAL(itemsAdded(KUrl,KFileItemList)), this, SLOT(slotItemsAdded(KUrl,KFileItemList)));
connect(m_dirLister, SIGNAL(itemsDeleted(KFileItemList)), this, SLOT(slotItemsDeleted(KFileItemList)));
connect(m_dirLister, SIGNAL(refreshItems(QList<QPair<KFileItem,KFileItem> >)), this, SLOT(slotRefreshItems(QList<QPair<KFileItem,KFileItem> >)));
connect(m_dirLister, SIGNAL(clear()), this, SLOT(slotClear()));
@@ -113,7 +113,7 @@ KFileItemModel::KFileItemModel(QObject* parent) :
KFileItemModel::~KFileItemModel()
{
qDeleteAll(m_itemData);
- m_itemData.clear();
+ qDeleteAll(m_filteredItems.values());
}
void KFileItemModel::loadDirectory(const KUrl& url)
@@ -167,7 +167,7 @@ bool KFileItemModel::setData(int index, const QHash<QByteArray, QVariant>& value
QHashIterator<QByteArray, QVariant> it(values);
while (it.hasNext()) {
it.next();
- const QByteArray role = it.key();
+ const QByteArray role = sharedValue(it.key());
const QVariant value = it.value();
if (currentValues[role] != value) {
@@ -405,7 +405,7 @@ void KFileItemModel::setRoles(const QSet<QByteArray>& roles)
// Update m_data with the changed requested roles
const int maxIndex = count() - 1;
for (int i = 0; i <= maxIndex; ++i) {
- m_itemData[i]->values = retrieveData(m_itemData.at(i)->item);
+ m_itemData[i]->values = retrieveData(m_itemData.at(i)->item, m_itemData.at(i)->parent);
}
kWarning() << "TODO: Emitting itemsChanged() with no information what has changed!";
@@ -425,12 +425,13 @@ bool KFileItemModel::setExpanded(int index, bool expanded)
}
QHash<QByteArray, QVariant> values;
- values.insert("isExpanded", expanded);
+ values.insert(sharedValue("isExpanded"), expanded);
if (!setData(index, values)) {
return false;
}
- const KUrl url = m_itemData.at(index)->item.url();
+ const KFileItem item = m_itemData.at(index)->item;
+ const KUrl url = item.url();
if (expanded) {
m_expandedDirs.insert(url);
m_dirLister->openUrl(url, KDirLister::Keep);
@@ -438,38 +439,11 @@ bool KFileItemModel::setExpanded(int index, bool expanded)
m_expandedDirs.remove(url);
m_dirLister->stop(url);
+ removeFilteredChildren(KFileItemList() << item);
- KFileItemList itemsToRemove;
- const int expandedParentsCount = data(index)["expandedParentsCount"].toInt();
- ++index;
- while (index < count() && data(index)["expandedParentsCount"].toInt() > expandedParentsCount) {
- itemsToRemove.append(m_itemData.at(index)->item);
- ++index;
- }
-
- QSet<KUrl> urlsToRemove;
- urlsToRemove.reserve(itemsToRemove.count() + 1);
- urlsToRemove.insert(url);
- foreach (const KFileItem& item, itemsToRemove) {
- KUrl url = item.url();
- url.adjustPath(KUrl::RemoveTrailingSlash);
- urlsToRemove.insert(url);
- }
-
- QSet<KFileItem>::iterator it = m_filteredItems.begin();
- while (it != m_filteredItems.end()) {
- const KUrl url = it->url();
- KUrl parentUrl = url.upUrl();
- parentUrl.adjustPath(KUrl::RemoveTrailingSlash);
-
- if (urlsToRemove.contains(parentUrl)) {
- it = m_filteredItems.erase(it);
- } else {
- ++it;
- }
- }
-
- removeItems(itemsToRemove);
+ const KFileItemList itemsToRemove = childItems(item);
+ removeFilteredChildren(itemsToRemove);
+ removeItems(itemsToRemove, DeleteItemData);
}
return true;
@@ -579,31 +553,57 @@ void KFileItemModel::applyFilters()
// Only filter non-expanded items as child items may never
// exist without a parent item
if (!itemData->values.value("isExpanded").toBool()) {
- if (!m_filter.matches(itemData->item)) {
- newFilteredItems.append(itemData->item);
- m_filteredItems.insert(itemData->item);
+ const KFileItem item = itemData->item;
+ if (!m_filter.matches(item)) {
+ newFilteredItems.append(item);
+ m_filteredItems.insert(item, itemData);
}
}
}
- removeItems(newFilteredItems);
+ removeItems(newFilteredItems, KeepItemData);
// Check which hidden items from m_filteredItems should
// get visible again and hence removed from m_filteredItems.
- KFileItemList newVisibleItems;
+ QList<ItemData*> newVisibleItems;
- QMutableSetIterator<KFileItem> it(m_filteredItems);
- while (it.hasNext()) {
- const KFileItem item = it.next();
- if (m_filter.matches(item)) {
- newVisibleItems.append(item);
- it.remove();
+ QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.begin();
+ while (it != m_filteredItems.end()) {
+ if (m_filter.matches(it.key())) {
+ newVisibleItems.append(it.value());
+ it = m_filteredItems.erase(it);
+ } else {
+ ++it;
}
}
insertItems(newVisibleItems);
}
+void KFileItemModel::removeFilteredChildren(const KFileItemList& parentsList)
+{
+ if (m_filteredItems.isEmpty()) {
+ return;
+ }
+
+ // First, we put the parent items into a set to provide fast lookup
+ // while iterating over m_filteredItems and prevent quadratic
+ // complexity if there are N parents and N filtered items.
+ const QSet<KFileItem> parents = parentsList.toSet();
+
+ QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.begin();
+ while (it != m_filteredItems.end()) {
+ const ItemData* parent = it.value()->parent;
+
+ if (parent && parents.contains(parent->item)) {
+ delete it.value();
+ it = m_filteredItems.erase(it);
+ } else {
+ ++it;
+ }
+ }
+}
+
QList<KFileItemModel::RoleInfo> KFileItemModel::rolesInformation()
{
static QList<RoleInfo> rolesInfo;
@@ -689,7 +689,7 @@ void KFileItemModel::resortAllItems()
m_items.clear();
// Resort the items
- KFileItemModelSortAlgorithm::sort(this, m_itemData.begin(), m_itemData.end());
+ sort(m_itemData.begin(), m_itemData.end());
for (int i = 0; i < itemCount; ++i) {
m_items.insert(m_itemData.at(i)->item.url(), i);
}
@@ -751,15 +751,14 @@ void KFileItemModel::slotCanceled()
emit directoryLoadingCanceled();
}
-void KFileItemModel::slotNewItems(const KFileItemList& items)
+void KFileItemModel::slotItemsAdded(const KUrl& directoryUrl, const KFileItemList& items)
{
Q_ASSERT(!items.isEmpty());
- if (m_requestRole[ExpandedParentsCountRole] && m_expandedParentsCountRoot >= 0) {
- // 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();
+ KUrl parentUrl = directoryUrl;
+ parentUrl.adjustPath(KUrl::RemoveTrailingSlash);
+ if (m_requestRole[ExpandedParentsCountRole]) {
KFileItem item = items.first();
// If the expanding of items is enabled, the call
@@ -773,11 +772,15 @@ void KFileItemModel::slotNewItems(const KFileItemList& items)
return;
}
+ if (directoryUrl != directory()) {
+ // 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.
@@ -785,22 +788,21 @@ void KFileItemModel::slotNewItems(const KFileItemList& items)
}
}
+ QList<ItemData*> itemDataList = createItemDataList(parentUrl, items);
+
if (!m_filter.hasSetFilters()) {
- m_pendingItemsToInsert.append(items);
+ m_pendingItemsToInsert.append(itemDataList);
} else {
// The name or type filter is active. Hide filtered items
// before inserting them into the model and remember
// the filtered items in m_filteredItems.
- KFileItemList filteredItems;
- foreach (const KFileItem& item, items) {
- if (m_filter.matches(item)) {
- filteredItems.append(item);
+ foreach (ItemData* itemData, itemDataList) {
+ if (m_filter.matches(itemData->item)) {
+ m_pendingItemsToInsert.append(itemData);
} else {
- m_filteredItems.insert(item);
+ m_filteredItems.insert(itemData->item, itemData);
}
}
-
- m_pendingItemsToInsert.append(filteredItems);
}
if (useMaximumUpdateInterval() && !m_maximumUpdateIntervalTimer->isActive()) {
@@ -815,7 +817,7 @@ void KFileItemModel::slotItemsDeleted(const KFileItemList& items)
dispatchPendingItemsToInsert();
KFileItemList itemsToRemove = items;
- if (m_requestRole[ExpandedParentsCountRole] && m_expandedParentsCountRoot >= 0) {
+ if (m_requestRole[ExpandedParentsCountRole]) {
// Assure that removing a parent item also results in removing all children
foreach (const KFileItem& item, items) {
itemsToRemove.append(childItems(item));
@@ -824,38 +826,19 @@ void KFileItemModel::slotItemsDeleted(const KFileItemList& items)
if (!m_filteredItems.isEmpty()) {
foreach (const KFileItem& item, itemsToRemove) {
- m_filteredItems.remove(item);
- }
-
- if (m_requestRole[ExpandedParentsCountRole] && m_expandedParentsCountRoot >= 0) {
- // Remove all filtered children of deleted items. First, we put the
- // deleted URLs into a set to provide fast lookup while iterating
- // over m_filteredItems and prevent quadratic complexity if there
- // are N removed items and N filtered items.
- QSet<KUrl> urlsToRemove;
- urlsToRemove.reserve(itemsToRemove.count());
- foreach (const KFileItem& item, itemsToRemove) {
- KUrl url = item.url();
- url.adjustPath(KUrl::RemoveTrailingSlash);
- urlsToRemove.insert(url);
+ QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.find(item);
+ if (it != m_filteredItems.end()) {
+ delete it.value();
+ m_filteredItems.erase(it);
}
+ }
- QSet<KFileItem>::iterator it = m_filteredItems.begin();
- while (it != m_filteredItems.end()) {
- const KUrl url = it->url();
- KUrl parentUrl = url.upUrl();
- parentUrl.adjustPath(KUrl::RemoveTrailingSlash);
-
- if (urlsToRemove.contains(parentUrl)) {
- it = m_filteredItems.erase(it);
- } else {
- ++it;
- }
- }
+ if (m_requestRole[ExpandedParentsCountRole]) {
+ removeFilteredChildren(itemsToRemove);
}
}
- removeItems(itemsToRemove);
+ removeItems(itemsToRemove, DeleteItemData);
}
void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >& items)
@@ -865,12 +848,12 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
kDebug() << "Refreshing" << items.count() << "items";
#endif
- m_groups.clear();
-
// Get the indexes of all items that have been refreshed
QList<int> indexes;
indexes.reserve(items.count());
+ QSet<QByteArray> changedRoles;
+
QListIterator<QPair<KFileItem, KFileItem> > it(items);
while (it.hasNext()) {
const QPair<KFileItem, KFileItem>& itemPair = it.next();
@@ -882,10 +865,15 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
// Keep old values as long as possible if they could not retrieved synchronously yet.
// The update of the values will be done asynchronously by KFileItemModelRolesUpdater.
- QHashIterator<QByteArray, QVariant> it(retrieveData(newItem));
+ QHashIterator<QByteArray, QVariant> it(retrieveData(newItem, m_itemData.at(index)->parent));
+ QHash<QByteArray, QVariant>& values = m_itemData[index]->values;
while (it.hasNext()) {
it.next();
- m_itemData[index]->values.insert(it.key(), it.value());
+ const QByteArray& role = it.key();
+ if (values.value(role) != it.value()) {
+ values.insert(role, it.value());
+ changedRoles.insert(role);
+ }
}
m_items.remove(oldItem.url());
@@ -926,9 +914,11 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
itemRangeList.append(KItemRange(rangeIndex, rangeCount));
}
- emit itemsChanged(itemRangeList, m_roles);
+ emit itemsChanged(itemRangeList, changedRoles);
- resortAllItems();
+ if (changedRoles.contains(sortRole())) {
+ resortAllItems();
+ }
}
void KFileItemModel::slotClear()
@@ -937,6 +927,7 @@ void KFileItemModel::slotClear()
kDebug() << "Clearing all items";
#endif
+ qDeleteAll(m_filteredItems.values());
m_filteredItems.clear();
m_groups.clear();
@@ -944,8 +935,6 @@ void KFileItemModel::slotClear()
m_resortAllItemsTimer->stop();
m_pendingItemsToInsert.clear();
- m_expandedParentsCountRoot = UninitializedExpandedParentsCountRoot;
-
const int removedCount = m_itemData.count();
if (removedCount > 0) {
qDeleteAll(m_itemData);
@@ -976,197 +965,212 @@ void KFileItemModel::dispatchPendingItemsToInsert()
}
}
-void KFileItemModel::insertItems(const KFileItemList& items)
+void KFileItemModel::insertItems(QList<ItemData*>& newItems)
{
- if (items.isEmpty()) {
+ if (newItems.isEmpty()) {
return;
}
- if (m_sortRole == TypeRole) {
- // Try to resolve the MIME-types synchronously to prevent a reordering of
- // the items when sorting by type (per default MIME-types are resolved
- // asynchronously by KFileItemModelRolesUpdater).
- determineMimeTypes(items, 200);
- }
-
#ifdef KFILEITEMMODEL_DEBUG
QElapsedTimer timer;
timer.start();
kDebug() << "===========================================================";
- kDebug() << "Inserting" << items.count() << "items";
+ kDebug() << "Inserting" << newItems.count() << "items";
#endif
m_groups.clear();
- QList<ItemData*> sortedItems = createItemDataList(items);
- KFileItemModelSortAlgorithm::sort(this, sortedItems.begin(), sortedItems.end());
+ sort(newItems.begin(), newItems.end());
#ifdef KFILEITEMMODEL_DEBUG
kDebug() << "[TIME] Sorting:" << timer.elapsed();
#endif
KItemRangeList itemRanges;
- int targetIndex = 0;
- int sourceIndex = 0;
- int insertedAtIndex = -1; // Index for the current item-range
- int insertedCount = 0; // Count for the current item-range
- int previouslyInsertedCount = 0; // Sum of previously inserted items for all ranges
- while (sourceIndex < sortedItems.count()) {
- // Find target index from m_items to insert the current item
- // in a sorted order
- const int previousTargetIndex = targetIndex;
- while (targetIndex < m_itemData.count()) {
- if (!lessThan(m_itemData.at(targetIndex), sortedItems.at(sourceIndex))) {
- break;
- }
- ++targetIndex;
- }
+ const int existingItemCount = m_itemData.count();
+ const int newItemCount = newItems.count();
+ const int totalItemCount = existingItemCount + newItemCount;
- if (targetIndex - previousTargetIndex > 0 && insertedAtIndex >= 0) {
- itemRanges << KItemRange(insertedAtIndex, insertedCount);
- previouslyInsertedCount += insertedCount;
- insertedAtIndex = targetIndex - previouslyInsertedCount;
- insertedCount = 0;
+ if (existingItemCount == 0) {
+ // Optimization for the common special case that there are no
+ // items in the model yet. Happens, e.g., when entering a folder.
+ m_itemData = newItems;
+ itemRanges << KItemRange(0, newItemCount);
+ } else {
+ m_itemData.reserve(totalItemCount);
+ for (int i = existingItemCount; i < totalItemCount; ++i) {
+ m_itemData.append(0);
}
- // Insert item at the position targetIndex by transferring
- // the ownership of the item-data from sortedItems to m_itemData.
- // m_items will be inserted after the loop (see comment below)
- m_itemData.insert(targetIndex, sortedItems.at(sourceIndex));
- ++insertedCount;
+ // We build the new list m_items in reverse order to minimize
+ // the number of moves and guarantee O(N) complexity.
+ int targetIndex = totalItemCount - 1;
+ int sourceIndexExistingItems = existingItemCount - 1;
+ int sourceIndexNewItems = newItemCount - 1;
+
+ int rangeCount = 0;
+
+ while (sourceIndexNewItems >= 0) {
+ ItemData* newItem = newItems.at(sourceIndexNewItems);
+ if (sourceIndexExistingItems >= 0 && lessThan(newItem, m_itemData.at(sourceIndexExistingItems))) {
+ // Move an existing item to its new position. If any new items
+ // are behind it, push the item range to itemRanges.
+ if (rangeCount > 0) {
+ itemRanges << KItemRange(sourceIndexExistingItems + 1, rangeCount);
+ rangeCount = 0;
+ }
- if (insertedAtIndex < 0) {
- insertedAtIndex = targetIndex;
- Q_ASSERT(previouslyInsertedCount == 0);
+ m_itemData[targetIndex] = m_itemData.at(sourceIndexExistingItems);
+ --sourceIndexExistingItems;
+ } else {
+ // Insert a new item into the list.
+ ++rangeCount;
+ m_itemData[targetIndex] = newItem;
+ --sourceIndexNewItems;
+ }
+ --targetIndex;
+ }
+
+ // Push the final item range to itemRanges.
+ if (rangeCount > 0) {
+ itemRanges << KItemRange(sourceIndexExistingItems + 1, rangeCount);
}
- ++targetIndex;
- ++sourceIndex;
+
+ // Note that itemRanges is still sorted in reverse order.
+ std::reverse(itemRanges.begin(), itemRanges.end());
}
- // The indexes of all m_items must be adjusted, not only the index
- // of the new items
- const int itemDataCount = m_itemData.count();
- for (int i = 0; i < itemDataCount; ++i) {
+ // The indexes starting from the first inserted item must be adjusted.
+ m_items.reserve(totalItemCount);
+ for (int i = itemRanges.front().index; i < totalItemCount; ++i) {
m_items.insert(m_itemData.at(i)->item.url(), i);
}
- itemRanges << KItemRange(insertedAtIndex, insertedCount);
emit itemsInserted(itemRanges);
#ifdef KFILEITEMMODEL_DEBUG
- kDebug() << "[TIME] Inserting of" << items.count() << "items:" << timer.elapsed();
+ kDebug() << "[TIME] Inserting of" << newItems.count() << "items:" << timer.elapsed();
#endif
}
-void KFileItemModel::removeItems(const KFileItemList& items)
+static KItemRangeList sortedIndexesToKItemRangeList(const QList<int>& sortedNumbers)
{
- if (items.isEmpty()) {
- return;
+ if (sortedNumbers.empty()) {
+ return KItemRangeList();
+ }
+
+ KItemRangeList result;
+
+ QList<int>::const_iterator it = sortedNumbers.begin();
+ int index = *it;
+ int count = 1;
+
+ ++it;
+
+ QList<int>::const_iterator end = sortedNumbers.end();
+ while (it != end) {
+ if (*it == index + count) {
+ ++count;
+ } else {
+ result << KItemRange(index, count);
+ index = *it;
+ count = 1;
+ }
+ ++it;
}
+ result << KItemRange(index, count);
+ return result;
+}
+
+void KFileItemModel::removeItems(const KFileItemList& items, RemoveItemsBehavior behavior)
+{
#ifdef KFILEITEMMODEL_DEBUG
kDebug() << "Removing " << items.count() << "items";
#endif
m_groups.clear();
- QList<ItemData*> sortedItems;
- sortedItems.reserve(items.count());
- 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, starting from the
+ // index of the first removed item.
+ const int newItemDataCount = m_itemData.count();
+ for (int i = itemRanges.front().index; i < newItemDataCount; ++i) {
+ m_items.insert(m_itemData.at(i)->item.url(), i);
}
- itemRanges << KItemRange(removedAtIndex, removedCount);
emit itemsRemoved(itemRanges);
}
-QList<KFileItemModel::ItemData*> KFileItemModel::createItemDataList(const KFileItemList& items) const
+QList<KFileItemModel::ItemData*> KFileItemModel::createItemDataList(const KUrl& parentUrl, const KFileItemList& items) const
{
+ if (m_sortRole == TypeRole) {
+ // Try to resolve the MIME-types synchronously to prevent a reordering of
+ // the items when sorting by type (per default MIME-types are resolved
+ // asynchronously by KFileItemModelRolesUpdater).
+ determineMimeTypes(items, 200);
+ }
+
+ const int parentIndex = m_items.value(parentUrl, -1);
+ ItemData* parentItem = parentIndex < 0 ? 0 : m_itemData.at(parentIndex);
+
QList<ItemData*> itemDataList;
itemDataList.reserve(items.count());
foreach (const KFileItem& item, items) {
ItemData* itemData = new ItemData();
itemData->item = item;
- itemData->values = retrieveData(item);
- itemData->parent = 0;
-
- const bool determineParent = m_requestRole[ExpandedParentsCountRole]
- && itemData->values["expandedParentsCount"].toInt() > 0;
- if (determineParent) {
- KUrl parentUrl = item.url().upUrl();
- parentUrl.adjustPath(KUrl::RemoveTrailingSlash);
- const int parentIndex = m_items.value(parentUrl, -1);
- if (parentIndex >= 0) {
- itemData->parent = m_itemData.at(parentIndex);
- } else {
- kWarning() << "Parent item not found for" << item.url();
- }
- }
-
+ itemData->values = retrieveData(item, parentItem);
+ itemData->parent = parentItem;
itemDataList.append(itemData);
}
@@ -1187,9 +1191,8 @@ void KFileItemModel::removeExpandedItems()
// The m_expandedParentsCountRoot may not get reset before all items with
// a bigger count have been removed.
- removeItems(expandedItems);
+ removeItems(expandedItems, DeleteItemData);
- m_expandedParentsCountRoot = UninitializedExpandedParentsCountRoot;
m_expandedDirs.clear();
}
@@ -1252,33 +1255,33 @@ QByteArray KFileItemModel::roleForType(RoleType roleType) const
return roles.value(roleType);
}
-QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item) const
+QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item, const ItemData* parent) const
{
// It is important to insert only roles that are fast to retrieve. E.g.
// KFileItem::iconName() can be very expensive if the MIME-type is unknown
// and hence will be retrieved asynchronously by KFileItemModelRolesUpdater.
QHash<QByteArray, QVariant> data;
- data.insert("url", item.url());
+ data.insert(sharedValue("url"), item.url());
const bool isDir = item.isDir();
if (m_requestRole[IsDirRole]) {
- data.insert("isDir", isDir);
+ data.insert(sharedValue("isDir"), isDir);
}
if (m_requestRole[IsLinkRole]) {
const bool isLink = item.isLink();
- data.insert("isLink", isLink);
+ data.insert(sharedValue("isLink"), isLink);
}
if (m_requestRole[NameRole]) {
- data.insert("text", item.text());
+ data.insert(sharedValue("text"), item.text());
}
if (m_requestRole[SizeRole]) {
if (isDir) {
- data.insert("size", QVariant());
+ data.insert(sharedValue("size"), QVariant());
} else {
- data.insert("size", item.size());
+ data.insert(sharedValue("size"), item.size());
}
}
@@ -1287,19 +1290,19 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item)
// having several thousands of items. Instead the formatting of the
// date-time will be done on-demand by the view when the date will be shown.
const KDateTime dateTime = item.time(KFileItem::ModificationTime);
- data.insert("date", dateTime.dateTime());
+ data.insert(sharedValue("date"), dateTime.dateTime());
}
if (m_requestRole[PermissionsRole]) {
- data.insert("permissions", item.permissionsString());
+ data.insert(sharedValue("permissions"), item.permissionsString());
}
if (m_requestRole[OwnerRole]) {
- data.insert("owner", item.user());
+ data.insert(sharedValue("owner"), item.user());
}
if (m_requestRole[GroupRole]) {
- data.insert("group", item.group());
+ data.insert(sharedValue("group"), item.group());
}
if (m_requestRole[DestinationRole]) {
@@ -1307,7 +1310,7 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item)
if (destination.isEmpty()) {
destination = QLatin1String("-");
}
- data.insert("destination", destination);
+ data.insert(sharedValue("destination"), destination);
}
if (m_requestRole[PathRole]) {
@@ -1330,43 +1333,27 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem& item)
const int index = path.lastIndexOf(item.text());
path = path.mid(0, index - 1);
- data.insert("path", path);
+ data.insert(sharedValue("path"), path);
}
if (m_requestRole[IsExpandableRole]) {
- data.insert("isExpandable", item.isDir() && item.url() == item.targetUrl());
+ data.insert(sharedValue("isExpandable"), item.isDir() && item.url() == item.targetUrl());
}
if (m_requestRole[ExpandedParentsCountRole]) {
- if (m_expandedParentsCountRoot == UninitializedExpandedParentsCountRoot) {
- const KUrl rootUrl = m_dirLister->url();
- const QString protocol = rootUrl.protocol();
- const bool forceExpandedParentsCountRoot = (protocol == QLatin1String("trash") ||
- protocol == QLatin1String("nepomuk") ||
- protocol == QLatin1String("remote") ||
- protocol.contains(QLatin1String("search")));
- if (forceExpandedParentsCountRoot) {
- m_expandedParentsCountRoot = ForceExpandedParentsCountRoot;
- } else {
- const QString rootDir = rootUrl.path(KUrl::AddTrailingSlash);
- m_expandedParentsCountRoot = rootDir.count('/');
- }
+ int level = 0;
+ if (parent) {
+ level = parent->values["expandedParentsCount"].toInt() + 1;
}
- if (m_expandedParentsCountRoot == ForceExpandedParentsCountRoot) {
- data.insert("expandedParentsCount", -1);
- } else {
- const QString dir = item.url().directory(KUrl::AppendTrailingSlash);
- const int level = dir.count('/') - m_expandedParentsCountRoot;
- data.insert("expandedParentsCount", level);
- }
+ data.insert(sharedValue("expandedParentsCount"), level);
}
if (item.isMimeTypeKnown()) {
- data.insert("iconName", item.iconName());
+ data.insert(sharedValue("iconName"), item.iconName());
if (m_requestRole[TypeRole]) {
- data.insert("type", item.mimeComment());
+ data.insert(sharedValue("type"), item.mimeComment());
}
}
@@ -1377,11 +1364,34 @@ bool KFileItemModel::lessThan(const ItemData* a, const ItemData* b) const
{
int result = 0;
- if (m_expandedParentsCountRoot >= 0) {
- result = expandedParentsCountCompare(a, b);
- if (result != 0) {
- // The items have parents with different expansion levels
- return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0;
+ if (a->parent != b->parent) {
+ const int expansionLevelA = a->values.value("expandedParentsCount").toInt();
+ const int expansionLevelB = b->values.value("expandedParentsCount").toInt();
+
+ // If b has a higher expansion level than a, check if a is a parent
+ // of b, and make sure that both expansion levels are equal otherwise.
+ for (int i = expansionLevelB; i > expansionLevelA; --i) {
+ if (b->parent == a) {
+ return true;
+ }
+ b = b->parent;
+ }
+
+ // If a has a higher expansion level than a, check if b is a parent
+ // of a, and make sure that both expansion levels are equal otherwise.
+ for (int i = expansionLevelA; i > expansionLevelB; --i) {
+ if (a->parent == b) {
+ return false;
+ }
+ a = a->parent;
+ }
+
+ Q_ASSERT(a->values.value("expandedParentsCount").toInt() == b->values.value("expandedParentsCount").toInt());
+
+ // Compare the last parents of a and b which are different.
+ while (a->parent != b->parent) {
+ a = a->parent;
+ b = b->parent;
}
}
@@ -1400,6 +1410,44 @@ bool KFileItemModel::lessThan(const ItemData* a, const ItemData* b) const
return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0;
}
+/**
+ * Helper class for KFileItemModel::sort().
+ */
+class KFileItemModelLessThan
+{
+public:
+ KFileItemModelLessThan(const KFileItemModel* model) :
+ m_model(model)
+ {
+ }
+
+ bool operator()(const KFileItemModel::ItemData* a, const KFileItemModel::ItemData* b) const
+ {
+ return m_model->lessThan(a, b);
+ }
+
+private:
+ const KFileItemModel* m_model;
+};
+
+void KFileItemModel::sort(QList<KFileItemModel::ItemData*>::iterator begin,
+ QList<KFileItemModel::ItemData*>::iterator end) const
+{
+ KFileItemModelLessThan lessThan(this);
+
+ if (m_sortRole == NameRole) {
+ // Sorting by name can be expensive, in particular if natural sorting is
+ // enabled. Use all CPU cores to speed up the sorting process.
+ static const int numberOfThreads = QThread::idealThreadCount();
+ parallelMergeSort(begin, end, lessThan, numberOfThreads);
+ } else {
+ // Sorting by other roles is quite fast. Use only one thread to prevent
+ // problems caused by non-reentrant comparison functions, see
+ // https://bugs.kde.org/show_bug.cgi?id=312679
+ mergeSort(begin, end, lessThan);
+ }
+}
+
int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b) const
{
const KFileItem& itemA = a->item;
@@ -1524,88 +1572,6 @@ int KFileItemModel::stringCompare(const QString& a, const QString& b) const
: QString::compare(a, b, Qt::CaseSensitive);
}
-int KFileItemModel::expandedParentsCountCompare(const ItemData* a, const ItemData* b) const
-{
- const KUrl urlA = a->item.url();
- const KUrl urlB = b->item.url();
- if (urlA.directory() == urlB.directory()) {
- // Both items have the same directory as parent
- return 0;
- }
-
- // Check whether one item is the parent of the other item
- if (urlA.isParentOf(urlB)) {
- return (sortOrder() == Qt::AscendingOrder) ? -1 : +1;
- } else if (urlB.isParentOf(urlA)) {
- return (sortOrder() == Qt::AscendingOrder) ? +1 : -1;
- }
-
- // Determine the maximum common path of both items and
- // remember the index in 'index'
- const QString pathA = urlA.path();
- const QString pathB = urlB.path();
-
- const int maxIndex = qMin(pathA.length(), pathB.length()) - 1;
- int index = 0;
- while (index <= maxIndex && pathA.at(index) == pathB.at(index)) {
- ++index;
- }
- if (index > maxIndex) {
- index = maxIndex;
- }
- while (index > 0 && (pathA.at(index) != QLatin1Char('/') || pathB.at(index) != QLatin1Char('/'))) {
- --index;
- }
-
- // Determine the first sub-path after the common path and
- // check whether it represents a directory or already a file
- bool isDirA = true;
- const QString subPathA = subPath(a->item, pathA, index, &isDirA);
- bool isDirB = true;
- const QString subPathB = subPath(b->item, pathB, index, &isDirB);
-
- if (m_sortDirsFirst || m_sortRole == SizeRole) {
- if (isDirA && !isDirB) {
- return (sortOrder() == Qt::AscendingOrder) ? -1 : +1;
- } else if (!isDirA && isDirB) {
- return (sortOrder() == Qt::AscendingOrder) ? +1 : -1;
- }
- }
-
- // Compare the items of the parents that represent the first
- // different path after the common path.
- const QString parentPathA = pathA.left(index) + subPathA;
- const QString parentPathB = pathB.left(index) + subPathB;
-
- const ItemData* parentA = a;
- while (parentA && parentA->item.url().path() != parentPathA) {
- parentA = parentA->parent;
- }
-
- const ItemData* parentB = b;
- while (parentB && parentB->item.url().path() != parentPathB) {
- parentB = parentB->parent;
- }
-
- if (parentA && parentB) {
- return sortRoleCompare(parentA, parentB);
- }
-
- kWarning() << "Child items without parent detected:" << a->item.url() << b->item.url();
- return QString::compare(urlA.url(), urlB.url(), Qt::CaseSensitive);
-}
-
-QString KFileItemModel::subPath(const KFileItem& item,
- const QString& itemPath,
- int start,
- bool* isDir) const
-{
- Q_ASSERT(isDir);
- const int pathIndex = itemPath.indexOf('/', start + 1);
- *isDir = (pathIndex > 0) || item.isDir();
- return itemPath.mid(start, pathIndex - start);
-}
-
bool KFileItemModel::useMaximumUpdateInterval() const
{
return !m_dirLister->url().isLocalFile();
@@ -1722,12 +1688,6 @@ QList<QPair<int, QVariant> > KFileItemModel::dateRoleGroups() const
const QDate currentDate = KDateTime::currentLocalDateTime().date();
- int yearForCurrentWeek = 0;
- int currentWeek = currentDate.weekNumber(&yearForCurrentWeek);
- if (yearForCurrentWeek == currentDate.year() + 1) {
- currentWeek = 53;
- }
-
QDate previousModifiedDate;
QString groupValue;
for (int i = 0; i <= maxIndex; ++i) {
@@ -1745,20 +1705,9 @@ QList<QPair<int, QVariant> > KFileItemModel::dateRoleGroups() const
const int daysDistance = modifiedDate.daysTo(currentDate);
- int yearForModifiedWeek = 0;
- int modifiedWeek = modifiedDate.weekNumber(&yearForModifiedWeek);
- if (yearForModifiedWeek == modifiedDate.year() + 1) {
- modifiedWeek = 53;
- }
-
QString newGroupValue;
if (currentDate.year() == modifiedDate.year() && currentDate.month() == modifiedDate.month()) {
- if (modifiedWeek > currentWeek) {
- // Usecase: modified date = 2010-01-01, current date = 2010-01-22
- // modified week = 53, current week = 3
- modifiedWeek = 0;
- }
- switch (currentWeek - modifiedWeek) {
+ switch (daysDistance / 7) {
case 0:
switch (daysDistance) {
case 0: newGroupValue = i18nc("@title:group Date", "Today"); break;
@@ -1767,7 +1716,7 @@ QList<QPair<int, QVariant> > KFileItemModel::dateRoleGroups() const
}
break;
case 1:
- newGroupValue = i18nc("@title:group Date", "Last Week");
+ newGroupValue = i18nc("@title:group Date", "One Week Ago");
break;
case 2:
newGroupValue = i18nc("@title:group Date", "Two Weeks Ago");
@@ -1790,7 +1739,7 @@ QList<QPair<int, QVariant> > KFileItemModel::dateRoleGroups() const
} else if (daysDistance <= 7) {
newGroupValue = modifiedTime.toString(i18nc("@title:group The week day name: %A, %B is full month name in current locale, and %Y is full year number", "%A (%B, %Y)"));
} else if (daysDistance <= 7 * 2) {
- newGroupValue = modifiedTime.toString(i18nc("@title:group Date: %B is full month name in current locale, and %Y is full year number", "Last Week (%B, %Y)"));
+ newGroupValue = modifiedTime.toString(i18nc("@title:group Date: %B is full month name in current locale, and %Y is full year number", "One Week Ago (%B, %Y)"));
} else if (daysDistance <= 7 * 3) {
newGroupValue = modifiedTime.toString(i18nc("@title:group Date: %B is full month name in current locale, and %Y is full year number", "Two Weeks Ago (%B, %Y)"));
} else if (daysDistance <= 7 * 4) {
@@ -2011,7 +1960,7 @@ void KFileItemModel::determineMimeTypes(const KFileItemList& items, int timeout)
{
QElapsedTimer timer;
timer.start();
- foreach (KFileItem item, items) { // krazy:exclude=foreach
+ foreach (const KFileItem& item, items) { // krazy:exclude=foreach
item.determineMimeType();
if (timer.elapsed() > timeout) {
// Don't block the user interface, let the remaining items
@@ -2021,4 +1970,64 @@ void KFileItemModel::determineMimeTypes(const KFileItemList& items, int timeout)
}
}
+QByteArray KFileItemModel::sharedValue(const QByteArray& value)
+{
+ static QSet<QByteArray> pool;
+ const QSet<QByteArray>::const_iterator it = pool.constFind(value);
+
+ if (it != pool.constEnd()) {
+ return *it;
+ } else {
+ pool.insert(value);
+ return value;
+ }
+}
+
+bool KFileItemModel::isConsistent() const
+{
+ if (m_items.count() != m_itemData.count()) {
+ return false;
+ }
+
+ for (int i = 0; i < count(); ++i) {
+ // Check if m_items and m_itemData are consistent.
+ const KFileItem item = fileItem(i);
+ if (item.isNull()) {
+ qWarning() << "Item" << i << "is null";
+ return false;
+ }
+
+ const int itemIndex = index(item);
+ if (itemIndex != i) {
+ qWarning() << "Item" << i << "has a wrong index:" << itemIndex;
+ return false;
+ }
+
+ // Check if the items are sorted correctly.
+ if (i > 0 && !lessThan(m_itemData.at(i - 1), m_itemData.at(i))) {
+ qWarning() << "The order of items" << i - 1 << "and" << i << "is wrong:"
+ << fileItem(i - 1) << fileItem(i);
+ return false;
+ }
+
+ // Check if all parent-child relationships are consistent.
+ const ItemData* data = m_itemData.at(i);
+ const ItemData* parent = data->parent;
+ if (parent) {
+ if (data->values.value("expandedParentsCount").toInt() != parent->values.value("expandedParentsCount").toInt() + 1) {
+ qWarning() << "expandedParentsCount is inconsistent for parent" << parent->item << "and child" << data->item;
+ return false;
+ }
+
+ const int parentIndex = index(parent->item);
+ if (parentIndex >= i) {
+ qWarning() << "Index" << parentIndex << "of parent" << parent->item << "is not smaller than index" << i << "of child" << data->item;
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
#include "kfileitemmodel.moc"
diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h
index ef9dc98b9..1d2d8c172 100644
--- a/src/kitemviews/kfileitemmodel.h
+++ b/src/kitemviews/kfileitemmodel.h
@@ -272,7 +272,7 @@ private slots:
void slotCompleted();
void slotCanceled();
- void slotNewItems(const KFileItemList& items);
+ void slotItemsAdded(const KUrl& directoryUrl, const KFileItemList& items);
void slotItemsDeleted(const KFileItemList& items);
void slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >& items);
void slotClear();
@@ -303,8 +303,13 @@ private:
ItemData* parent;
};
- void insertItems(const KFileItemList& items);
- void removeItems(const KFileItemList& items);
+ enum RemoveItemsBehavior {
+ KeepItemData,
+ DeleteItemData
+ };
+
+ void insertItems(QList<ItemData*>& items);
+ void removeItems(const KFileItemList& items, RemoveItemsBehavior behavior);
/**
* Helper method for insertItems() and removeItems(): Creates
@@ -312,7 +317,7 @@ private:
* Note that the ItemData instances are created dynamically and
* must be deleted by the caller.
*/
- QList<ItemData*> createItemDataList(const KFileItemList& items) const;
+ QList<ItemData*> createItemDataList(const KUrl& parentUrl, const KFileItemList& items) const;
void removeExpandedItems();
@@ -333,7 +338,7 @@ private:
*/
QByteArray roleForType(RoleType roleType) const;
- QHash<QByteArray, QVariant> retrieveData(const KFileItem& item) const;
+ QHash<QByteArray, QVariant> retrieveData(const KFileItem& item, const ItemData* parent) const;
/**
* @return True if the item-data \a a should be ordered before the item-data
@@ -342,6 +347,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 +361,6 @@ private:
int stringCompare(const QString& a, const QString& b) const;
- /**
- * Compares the expansion level of both items. The "expansion level" is defined
- * by the number of parent directories. However simply comparing just the numbers
- * is not sufficient, it is also important to check the hierarchy for having
- * a correct order like shown in a tree.
- */
- int expandedParentsCountCompare(const ItemData* a, const ItemData* b) const;
-
- /**
- * Helper method for expandedParentsCountCompare().
- */
- QString subPath(const KFileItem& item,
- const QString& itemPath,
- int start,
- bool* isDir) const;
-
bool useMaximumUpdateInterval() const;
QList<QPair<int, QVariant> > nameRoleGroups() const;
@@ -402,6 +397,12 @@ private:
void applyFilters();
/**
+ * Removes filtered items whose expanded parents have been deleted
+ * or collapsed via setExpanded(parentIndex, false).
+ */
+ void removeFilteredChildren(const KFileItemList& parentsList);
+
+ /**
* Maps the QByteArray-roles to RoleTypes and provides translation- and
* group-contexts.
*/
@@ -428,6 +429,17 @@ private:
*/
static void determineMimeTypes(const KFileItemList& items, int timeout);
+ /**
+ * @return Returns a copy of \a value that is implicitly shared
+ * with other users to save memory.
+ */
+ static QByteArray sharedValue(const QByteArray& value);
+
+ /**
+ * Checks if the model's internal data structures are consistent.
+ */
+ bool isConsistent() const;
+
private:
KFileItemModelDirLister* m_dirLister;
@@ -443,32 +455,17 @@ private:
QHash<KUrl, int> m_items; // Allows O(1) access for KFileItemModel::index(const KFileItem& item)
KFileItemModelFilter m_filter;
- QSet<KFileItem> m_filteredItems; // Items that got hidden by KFileItemModel::setNameFilter()
+ QHash<KFileItem, ItemData*> m_filteredItems; // Items that got hidden by KFileItemModel::setNameFilter()
bool m_requestRole[RolesCount];
QTimer* m_maximumUpdateIntervalTimer;
QTimer* m_resortAllItemsTimer;
- KFileItemList m_pendingItemsToInsert;
+ QList<ItemData*> m_pendingItemsToInsert;
// Cache for KFileItemModel::groups()
mutable QList<QPair<int, QVariant> > m_groups;
- // Stores the smallest expansion level of the root-URL. Is required to calculate
- // the "expandedParentsCount" role in an efficient way. A value < 0 indicates a
- // special meaning:
- enum ExpandedParentsCountRootTypes
- {
- // m_expandedParentsCountRoot is uninitialized and must be determined by checking
- // the root URL from the KDirLister.
- UninitializedExpandedParentsCountRoot = -1,
- // All items should be forced to get an expanded parents count of 0 even if they
- // represent child items. This is useful for slaves that provide no parent items
- // for child items like e.g. the search IO slaves.
- ForceExpandedParentsCountRoot = -2
- };
- mutable int m_expandedParentsCountRoot;
-
// Stores the URLs of the expanded directories.
QSet<KUrl> m_expandedDirs;
@@ -476,9 +473,10 @@ private:
// and done step after step in slotCompleted().
QSet<KUrl> m_urlsToExpand;
- friend class KFileItemModelSortAlgorithm; // Accesses lessThan() method
+ friend class KFileItemModelLessThan; // Accesses lessThan() method
friend class KFileItemModelRolesUpdater; // Accesses emitSortProgress() method
friend class KFileItemModelTest; // For unit testing
+ friend class KFileItemModelBenchmark; // For unit testing
friend class KFileItemListViewTest; // For unit testing
friend class DolphinPart; // Accesses m_dirLister
};
diff --git a/src/kitemviews/kfileitemmodelrolesupdater.cpp b/src/kitemviews/kfileitemmodelrolesupdater.cpp
index 7cade10f5..eaaab6bc0 100644
--- a/src/kitemviews/kfileitemmodelrolesupdater.cpp
+++ b/src/kitemviews/kfileitemmodelrolesupdater.cpp
@@ -38,9 +38,12 @@
#include <QElapsedTimer>
#include <QTimer>
+#include <algorithm>
+
#ifdef HAVE_NEPOMUK
#include "private/knepomukrolesprovider.h"
#include <Nepomuk2/ResourceWatcher>
+ #include <Nepomuk2/ResourceManager>
#endif
// Required includes for subItemsCount():
@@ -58,34 +61,41 @@ namespace {
// may perform a blocking operation
const int MaxBlockTimeout = 200;
- // Maximum number of items that will get resolved synchronously.
- // The value should roughly represent the number of maximum visible
- // items, as it does not make sense to resolve more items synchronously
- // and probably reach the MaxBlockTimeout because of invisible items.
- const int MaxResolveItemsCount = 100;
+ // If the number of items is smaller than ResolveAllItemsLimit,
+ // the roles of all items will be resolved.
+ const int ResolveAllItemsLimit = 500;
+
+ // Not only the visible area, but up to ReadAheadPages before and after
+ // this area will be resolved.
+ const int ReadAheadPages = 5;
}
KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QObject* parent) :
QObject(parent),
- m_paused(false),
+ m_state(Idle),
m_previewChangedDuringPausing(false),
m_iconSizeChangedDuringPausing(false),
m_rolesChangedDuringPausing(false),
m_previewShown(false),
m_enlargeSmallPreviews(true),
m_clearPreviews(false),
- m_sortingProgress(-1),
+ m_finishedItems(),
m_model(model),
m_iconSize(),
m_firstVisibleIndex(0),
m_lastVisibleIndex(-1),
- m_maximumVisibleItems(100),
+ m_maximumVisibleItems(50),
m_roles(),
+ m_resolvableRoles(),
m_enabledPlugins(),
- m_pendingVisibleItems(),
- m_pendingInvisibleItems(),
- m_previewJobs(),
- m_changedItemsTimer(0),
+ m_pendingSortRoleItems(),
+ m_hasUnknownIcons(false),
+ m_firstIndexWithoutIcon(0),
+ m_pendingIndexes(),
+ m_pendingPreviewItems(),
+ m_previewJob(),
+ m_recentlyChangedItemsTimer(0),
+ m_recentlyChangedItems(),
m_changedItems(),
m_dirWatcher(0),
m_watchedDirs()
@@ -108,15 +118,17 @@ KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QO
this, SLOT(slotItemsRemoved(KItemRangeList)));
connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
+ connect(m_model, SIGNAL(itemsMoved(KItemRange,QList<int>)),
+ this, SLOT(slotItemsMoved(KItemRange,QList<int>)));
connect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)),
this, SLOT(slotSortRoleChanged(QByteArray,QByteArray)));
// Use a timer to prevent that each call of slotItemsChanged() results in a synchronous
// resolving of the roles. Postpone the resolving until no update has been done for 1 second.
- m_changedItemsTimer = new QTimer(this);
- m_changedItemsTimer->setInterval(1000);
- m_changedItemsTimer->setSingleShot(true);
- connect(m_changedItemsTimer, SIGNAL(timeout()), this, SLOT(resolveChangedItems()));
+ m_recentlyChangedItemsTimer = new QTimer(this);
+ m_recentlyChangedItemsTimer->setInterval(1000);
+ m_recentlyChangedItemsTimer->setSingleShot(true);
+ connect(m_recentlyChangedItemsTimer, SIGNAL(timeout()), this, SLOT(resolveRecentlyChangedItems()));
m_resolvableRoles.insert("size");
m_resolvableRoles.insert("type");
@@ -133,21 +145,20 @@ KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QO
KFileItemModelRolesUpdater::~KFileItemModelRolesUpdater()
{
- resetPendingRoles();
+ killPreviewJob();
}
void KFileItemModelRolesUpdater::setIconSize(const QSize& size)
{
if (size != m_iconSize) {
m_iconSize = size;
- if (m_paused) {
+ if (m_state == Paused) {
m_iconSizeChangedDuringPausing = true;
} else if (m_previewShown) {
// An icon size change requires the regenerating of
// all previews
- sortAndResolveAllRoles();
- } else {
- sortAndResolvePendingRoles();
+ m_finishedItems.clear();
+ startUpdating();
}
}
}
@@ -174,9 +185,7 @@ void KFileItemModelRolesUpdater::setVisibleIndexRange(int index, int count)
m_firstVisibleIndex = index;
m_lastVisibleIndex = qMin(index + count - 1, m_model->count() - 1);
- if (hasPendingRoles() && !m_paused) {
- sortAndResolvePendingRoles();
- }
+ startUpdating();
}
void KFileItemModelRolesUpdater::setMaximumVisibleItems(int count)
@@ -230,31 +239,33 @@ void KFileItemModelRolesUpdater::setEnabledPlugins(const QStringList& list)
void KFileItemModelRolesUpdater::setPaused(bool paused)
{
- if (paused == m_paused) {
+ if (paused == (m_state == Paused)) {
return;
}
- m_paused = paused;
if (paused) {
- if (hasPendingRoles()) {
- foreach (KJob* job, m_previewJobs) {
- job->kill();
- }
- Q_ASSERT(m_previewJobs.isEmpty());
- }
+ m_state = Paused;
+ killPreviewJob();
} else {
- const bool resolveAll = (m_iconSizeChangedDuringPausing && m_previewShown) ||
- m_previewChangedDuringPausing ||
- m_rolesChangedDuringPausing;
+ const bool updatePreviews = (m_iconSizeChangedDuringPausing && m_previewShown) ||
+ m_previewChangedDuringPausing;
+ const bool resolveAll = updatePreviews || m_rolesChangedDuringPausing;
if (resolveAll) {
- sortAndResolveAllRoles();
- } else {
- sortAndResolvePendingRoles();
+ m_finishedItems.clear();
}
m_iconSizeChangedDuringPausing = false;
m_previewChangedDuringPausing = false;
m_rolesChangedDuringPausing = false;
+
+ if (!m_pendingSortRoleItems.isEmpty()) {
+ m_state = ResolvingSortRole;
+ resolveNextSortRole();
+ } else {
+ m_state = Idle;
+ }
+
+ startUpdating();
}
}
@@ -264,40 +275,40 @@ void KFileItemModelRolesUpdater::setRoles(const QSet<QByteArray>& roles)
m_roles = roles;
#ifdef HAVE_NEPOMUK
- // Check whether there is at least one role that must be resolved
- // with the help of Nepomuk. If this is the case, a (quite expensive)
- // resolving will be done in KFileItemModelRolesUpdater::rolesData() and
- // the role gets watched for changes.
- const KNepomukRolesProvider& rolesProvider = KNepomukRolesProvider::instance();
- bool hasNepomukRole = false;
- QSetIterator<QByteArray> it(roles);
- while (it.hasNext()) {
- const QByteArray& role = it.next();
- if (rolesProvider.roles().contains(role)) {
- hasNepomukRole = true;
- break;
+ if (Nepomuk2::ResourceManager::instance()->initialized()) {
+ // Check whether there is at least one role that must be resolved
+ // with the help of Nepomuk. If this is the case, a (quite expensive)
+ // resolving will be done in KFileItemModelRolesUpdater::rolesData() and
+ // the role gets watched for changes.
+ const KNepomukRolesProvider& rolesProvider = KNepomukRolesProvider::instance();
+ bool hasNepomukRole = false;
+ QSetIterator<QByteArray> it(roles);
+ while (it.hasNext()) {
+ const QByteArray& role = it.next();
+ if (rolesProvider.roles().contains(role)) {
+ hasNepomukRole = true;
+ break;
+ }
}
- }
- if (hasNepomukRole && !m_nepomukResourceWatcher) {
- Q_ASSERT(m_nepomukUriItems.isEmpty());
+ if (hasNepomukRole && !m_nepomukResourceWatcher) {
+ Q_ASSERT(m_nepomukUriItems.isEmpty());
- m_nepomukResourceWatcher = new Nepomuk2::ResourceWatcher(this);
- connect(m_nepomukResourceWatcher, SIGNAL(propertyChanged(Nepomuk2::Resource,Nepomuk2::Types::Property,QVariantList,QVariantList)),
- this, SLOT(applyChangedNepomukRoles(Nepomuk2::Resource)));
- } else if (!hasNepomukRole && m_nepomukResourceWatcher) {
- delete m_nepomukResourceWatcher;
- m_nepomukResourceWatcher = 0;
- m_nepomukUriItems.clear();
+ m_nepomukResourceWatcher = new Nepomuk2::ResourceWatcher(this);
+ connect(m_nepomukResourceWatcher, SIGNAL(propertyChanged(Nepomuk2::Resource,Nepomuk2::Types::Property,QVariantList,QVariantList)),
+ this, SLOT(applyChangedNepomukRoles(Nepomuk2::Resource)));
+ } else if (!hasNepomukRole && m_nepomukResourceWatcher) {
+ delete m_nepomukResourceWatcher;
+ m_nepomukResourceWatcher = 0;
+ m_nepomukUriItems.clear();
+ }
}
#endif
- updateSortProgress();
-
- if (m_paused) {
+ if (m_state == Paused) {
m_rolesChangedDuringPausing = true;
} else {
- sortAndResolveAllRoles();
+ startUpdating();
}
}
}
@@ -309,7 +320,7 @@ QSet<QByteArray> KFileItemModelRolesUpdater::roles() const
bool KFileItemModelRolesUpdater::isPaused() const
{
- return m_paused;
+ return m_state == Paused;
}
QStringList KFileItemModelRolesUpdater::enabledPlugins() const
@@ -319,7 +330,41 @@ QStringList KFileItemModelRolesUpdater::enabledPlugins() const
void KFileItemModelRolesUpdater::slotItemsInserted(const KItemRangeList& itemRanges)
{
- startUpdating(itemRanges);
+ QElapsedTimer timer;
+ timer.start();
+
+ const int firstInsertedIndex = itemRanges.first().index;
+ m_firstIndexWithoutIcon = qMin(m_firstIndexWithoutIcon, firstInsertedIndex);
+ m_hasUnknownIcons = true;
+
+ // Determine the sort role synchronously for as many items as possible.
+ if (m_resolvableRoles.contains(m_model->sortRole())) {
+ int insertedCount = 0;
+ foreach (const KItemRange& range, itemRanges) {
+ const int lastIndex = insertedCount + range.index + range.count - 1;
+ for (int i = insertedCount + range.index; i <= lastIndex; ++i) {
+ if (timer.elapsed() < MaxBlockTimeout) {
+ applySortRole(i);
+ } else {
+ m_pendingSortRoleItems.insert(m_model->fileItem(i));
+ }
+ }
+ insertedCount += range.count;
+ }
+
+ applySortProgressToModel();
+
+ // If there are still items whose sort role is unknown, check if the
+ // asynchronous determination of the sort role is already in progress,
+ // and start it if that is not the case.
+ if (!m_pendingSortRoleItems.isEmpty() && m_state != ResolvingSortRole) {
+ killPreviewJob();
+ m_state = ResolvingSortRole;
+ resolveNextSortRole();
+ }
+ }
+
+ startUpdating();
}
void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList& itemRanges)
@@ -328,6 +373,11 @@ void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList& itemRang
const bool allItemsRemoved = (m_model->count() == 0);
+ if (m_hasUnknownIcons) {
+ const int firstRemovedIndex = itemRanges.first().index;
+ m_firstIndexWithoutIcon = qMin(m_firstIndexWithoutIcon, firstRemovedIndex);
+ }
+
if (!m_watchedDirs.isEmpty()) {
// Don't let KDirWatch watch for removed items
if (allItemsRemoved) {
@@ -375,57 +425,75 @@ void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList& itemRang
}
#endif
- m_firstVisibleIndex = 0;
- m_lastVisibleIndex = -1;
- if (!hasPendingRoles()) {
- return;
- }
-
if (allItemsRemoved) {
- // Most probably a directory change is done. Clear all pending items
- // and also kill all ongoing preview-jobs.
- resetPendingRoles();
+ m_state = Idle;
+ m_finishedItems.clear();
+ m_pendingSortRoleItems.clear();
+ m_pendingIndexes.clear();
+ m_pendingPreviewItems.clear();
+ m_recentlyChangedItems.clear();
+ m_recentlyChangedItemsTimer->stop();
m_changedItems.clear();
- m_changedItemsTimer->stop();
+
+ killPreviewJob();
} else {
- // Remove all items from m_pendingVisibleItems and m_pendingInvisibleItems
- // that are not part of the model anymore. The items from m_changedItems
- // don't need to be handled here, removed items are just skipped in
- // resolveChangedItems().
- for (int i = 0; i <= 1; ++i) {
- QSet<KFileItem>& pendingItems = (i == 0) ? m_pendingVisibleItems : m_pendingInvisibleItems;
- QMutableSetIterator<KFileItem> it(pendingItems);
- while (it.hasNext()) {
- const KFileItem item = it.next();
- if (m_model->index(item) < 0) {
- pendingItems.remove(item);
- }
+ // Only remove the items from m_finishedItems. They will be removed
+ // from the other sets later on.
+ QSet<KFileItem>::iterator it = m_finishedItems.begin();
+ while (it != m_finishedItems.end()) {
+ if (m_model->index(*it) < 0) {
+ it = m_finishedItems.erase(it);
+ } else {
+ ++it;
}
}
+
+ // The visible items might have changed.
+ startUpdating();
}
}
+void KFileItemModelRolesUpdater::slotItemsMoved(const KItemRange& itemRange, QList<int> movedToIndexes)
+{
+ Q_UNUSED(itemRange);
+ Q_UNUSED(movedToIndexes);
+
+ if (m_hasUnknownIcons) {
+ const int firstMovedIndex = itemRange.index;
+ m_firstIndexWithoutIcon = qMin(m_firstIndexWithoutIcon, firstMovedIndex);
+ }
+
+ // The visible items might have changed.
+ startUpdating();
+}
+
void KFileItemModelRolesUpdater::slotItemsChanged(const KItemRangeList& itemRanges,
const QSet<QByteArray>& roles)
{
Q_UNUSED(roles);
- if (m_changedItemsTimer->isActive()) {
- // A call of slotItemsChanged() has been done recently. Postpone the resolving
- // of the roles until the timer has exceeded.
- foreach (const KItemRange& itemRange, itemRanges) {
- int index = itemRange.index;
- for (int count = itemRange.count; count > 0; --count) {
- m_changedItems.insert(m_model->fileItem(index));
- ++index;
- }
+ // Find out if slotItemsChanged() has been done recently. If that is the
+ // case, resolving the roles is postponed until a timer has exceeded
+ // to prevent expensive repeated updates if files are updated frequently.
+ const bool itemsChangedRecently = m_recentlyChangedItemsTimer->isActive();
+
+ QSet<KFileItem>& targetSet = itemsChangedRecently ? m_recentlyChangedItems : m_changedItems;
+
+ foreach (const KItemRange& itemRange, itemRanges) {
+ int index = itemRange.index;
+ for (int count = itemRange.count; count > 0; --count) {
+ const KFileItem item = m_model->fileItem(index);
+ targetSet.insert(item);
+ ++index;
}
- } else {
- // No call of slotItemsChanged() has been done recently, resolve the roles now.
- startUpdating(itemRanges);
}
- m_changedItemsTimer->start();
+
+ m_recentlyChangedItemsTimer->start();
+
+ if (!itemsChangedRecently) {
+ updateChangedItems();
+ }
}
void KFileItemModelRolesUpdater::slotSortRoleChanged(const QByteArray& current,
@@ -433,13 +501,46 @@ void KFileItemModelRolesUpdater::slotSortRoleChanged(const QByteArray& current,
{
Q_UNUSED(current);
Q_UNUSED(previous);
- updateSortProgress();
+
+ if (m_resolvableRoles.contains(current)) {
+ m_pendingSortRoleItems.clear();
+ m_finishedItems.clear();
+
+ const int count = m_model->count();
+ QElapsedTimer timer;
+ timer.start();
+
+ // Determine the sort role synchronously for as many items as possible.
+ for (int index = 0; index < count; ++index) {
+ if (timer.elapsed() < MaxBlockTimeout) {
+ applySortRole(index);
+ } else {
+ m_pendingSortRoleItems.insert(m_model->fileItem(index));
+ }
+ }
+
+ applySortProgressToModel();
+
+ if (!m_pendingSortRoleItems.isEmpty()) {
+ // Trigger the asynchronous determination of the sort role.
+ killPreviewJob();
+ m_state = ResolvingSortRole;
+ resolveNextSortRole();
+ }
+ } else {
+ m_state = Idle;
+ m_pendingSortRoleItems.clear();
+ applySortProgressToModel();
+ }
}
void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem& item, const QPixmap& pixmap)
{
- m_pendingVisibleItems.remove(item);
- m_pendingInvisibleItems.remove(item);
+ if (m_state != PreviewJobRunning) {
+ return;
+ }
+
+ m_changedItems.remove(item);
const int index = m_model->index(item);
if (index < 0) {
@@ -493,104 +594,156 @@ void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem& item, const QPi
connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
- applySortProgressToModel();
+ m_finishedItems.insert(item);
}
void KFileItemModelRolesUpdater::slotPreviewFailed(const KFileItem& item)
{
- m_pendingVisibleItems.remove(item);
- m_pendingInvisibleItems.remove(item);
+ if (m_state != PreviewJobRunning) {
+ return;
+ }
- const bool clearPreviews = m_clearPreviews;
- m_clearPreviews = true;
- applyResolvedRoles(item, ResolveAll);
- m_clearPreviews = clearPreviews;
+ m_changedItems.remove(item);
- applySortProgressToModel();
+ const int index = m_model->index(item);
+ if (index >= 0) {
+ QHash<QByteArray, QVariant> data;
+ data.insert("iconPixmap", QPixmap());
+
+ disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+ this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
+ m_model->setData(index, data);
+ connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+ this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
+
+ applyResolvedRoles(item, ResolveAll);
+ m_finishedItems.insert(item);
+ }
}
-void KFileItemModelRolesUpdater::slotPreviewJobFinished(KJob* job)
+void KFileItemModelRolesUpdater::slotPreviewJobFinished()
{
-#ifdef KFILEITEMMODELROLESUPDATER_DEBUG
- kDebug() << "Preview job finished. Pending visible:" << m_pendingVisibleItems.count() << "invisible:" << m_pendingInvisibleItems.count();
-#endif
+ m_previewJob = 0;
- m_previewJobs.removeOne(job);
- if (!m_previewJobs.isEmpty() || !hasPendingRoles()) {
+ if (m_state != PreviewJobRunning) {
return;
}
- const KFileItemList visibleItems = sortedItems(m_pendingVisibleItems);
- startPreviewJob(visibleItems + m_pendingInvisibleItems.toList());
+ m_state = Idle;
+
+ if (!m_pendingPreviewItems.isEmpty()) {
+ startPreviewJob();
+ } else {
+ if (!m_changedItems.isEmpty()) {
+ updateChangedItems();
+ }
+ }
}
-void KFileItemModelRolesUpdater::resolveNextPendingRoles()
+void KFileItemModelRolesUpdater::resolveNextSortRole()
{
- if (m_paused) {
+ if (m_state != ResolvingSortRole) {
return;
}
- if (m_previewShown) {
- // The preview has been turned on since the last run. Skip
- // resolving further pending roles as this is done as soon
- // as a preview has been received.
- return;
- }
+ QSet<KFileItem>::iterator it = m_pendingSortRoleItems.begin();
+ while (it != m_pendingSortRoleItems.end()) {
+ const KFileItem item = *it;
+ const int index = m_model->index(item);
- int resolvedCount = 0;
- bool changed = false;
- for (int i = 0; i <= 1; ++i) {
- QSet<KFileItem>& pendingItems = (i == 0) ? m_pendingVisibleItems : m_pendingInvisibleItems;
- QSet<KFileItem>::iterator it = pendingItems.begin();
- while (it != pendingItems.end() && !changed && resolvedCount < MaxResolveItemsCount) {
- changed = applyResolvedRoles(*it, ResolveAll);
- it = pendingItems.erase(it);
- ++resolvedCount;
+ // Continue if the sort role has already been determined for the
+ // item, and the item has not been changed recently.
+ if (!m_changedItems.contains(item) && m_model->data(index).contains(m_model->sortRole())) {
+ it = m_pendingSortRoleItems.erase(it);
+ continue;
}
- }
- if (hasPendingRoles()) {
- QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
- } else {
- m_clearPreviews = false;
+ applySortRole(index);
+ m_pendingSortRoleItems.erase(it);
+ break;
}
- applySortProgressToModel();
+ if (!m_pendingSortRoleItems.isEmpty()) {
+ applySortProgressToModel();
+ QTimer::singleShot(0, this, SLOT(resolveNextSortRole()));
+ } else {
+ m_state = Idle;
-#ifdef KFILEITEMMODELROLESUPDATER_DEBUG
- static int callCount = 0;
- ++callCount;
- if (callCount % 100 == 0) {
- kDebug() << "Remaining visible roles to resolve:" << m_pendingVisibleItems.count()
- << "invisible:" << m_pendingInvisibleItems.count();
+ // Prevent that we try to update the items twice.
+ disconnect(m_model, SIGNAL(itemsMoved(KItemRange,QList<int>)),
+ this, SLOT(slotItemsMoved(KItemRange,QList<int>)));
+ applySortProgressToModel();
+ connect(m_model, SIGNAL(itemsMoved(KItemRange,QList<int>)),
+ this, SLOT(slotItemsMoved(KItemRange,QList<int>)));
+ startUpdating();
}
-#endif
}
-void KFileItemModelRolesUpdater::resolveChangedItems()
+void KFileItemModelRolesUpdater::resolveNextPendingRoles()
{
- if (m_changedItems.isEmpty()) {
+ if (m_state != ResolvingAllRoles) {
return;
}
- KItemRangeList itemRanges;
+ while (!m_pendingIndexes.isEmpty()) {
+ const int index = m_pendingIndexes.takeFirst();
+ const KFileItem item = m_model->fileItem(index);
- QSetIterator<KFileItem> it(m_changedItems);
- while (it.hasNext()) {
- const KFileItem& item = it.next();
- const int index = m_model->index(item);
- if (index >= 0) {
- itemRanges.append(KItemRange(index, 1));
+ if (m_finishedItems.contains(item)) {
+ continue;
+ }
+
+ applyResolvedRoles(item, ResolveAll);
+ m_finishedItems.insert(item);
+ m_changedItems.remove(item);
+ break;
+ }
+
+ if (!m_pendingIndexes.isEmpty()) {
+ QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
+ } else {
+ m_state = Idle;
+
+ if (m_clearPreviews) {
+ // Only go through the list if there are items which might still have previews.
+ if (m_finishedItems.count() != m_model->count()) {
+ QHash<QByteArray, QVariant> data;
+ data.insert("iconPixmap", QPixmap());
+
+ disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+ this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
+ for (int index = 0; index <= m_model->count(); ++index) {
+ if (m_model->data(index).contains("iconPixmap")) {
+ m_model->setData(index, data);
+ }
+ }
+ connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+ this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
+
+ }
+ m_clearPreviews = false;
+ }
+
+ if (!m_changedItems.isEmpty()) {
+ updateChangedItems();
}
}
- m_changedItems.clear();
+}
- startUpdating(itemRanges);
+void KFileItemModelRolesUpdater::resolveRecentlyChangedItems()
+{
+ m_changedItems += m_recentlyChangedItems;
+ m_recentlyChangedItems.clear();
+ updateChangedItems();
}
void KFileItemModelRolesUpdater::applyChangedNepomukRoles(const Nepomuk2::Resource& resource)
{
#ifdef HAVE_NEPOMUK
+ if (!Nepomuk2::ResourceManager::instance()->initialized()) {
+ return;
+ }
+
const KUrl itemUrl = m_nepomukUriItems.value(resource.uri());
const KFileItem item = m_model->fileItem(itemUrl);
@@ -647,366 +800,310 @@ void KFileItemModelRolesUpdater::slotDirWatchDirty(const QString& path)
data.insert("isExpandable", count > 0);
}
+ // Note that we do not block the itemsChanged signal here.
+ // This ensures that a new preview will be generated.
m_model->setData(index, data);
}
}
}
-void KFileItemModelRolesUpdater::startUpdating(const KItemRangeList& itemRanges)
+void KFileItemModelRolesUpdater::startUpdating()
{
- // If no valid index range is given assume that all items are visible.
- // A cleanup will be done later as soon as the index range has been set.
- const bool hasValidIndexRange = (m_lastVisibleIndex >= 0);
+ if (m_state == Paused) {
+ return;
+ }
- if (hasValidIndexRange) {
- // Move all current pending visible items that are not visible anymore
- // to the pending invisible items.
- QSet<KFileItem>::iterator it = m_pendingVisibleItems.begin();
- while (it != m_pendingVisibleItems.end()) {
- const KFileItem item = *it;
- const int index = m_model->index(item);
- if (index < m_firstVisibleIndex || index > m_lastVisibleIndex) {
- it = m_pendingVisibleItems.erase(it);
- m_pendingInvisibleItems.insert(item);
- } else {
- ++it;
- }
- }
+ if (m_finishedItems.count() == m_model->count()) {
+ // All roles have been resolved already.
+ m_state = Idle;
+ return;
}
- int rangesCount = 0;
+ // Terminate all updates that are currently active.
+ killPreviewJob();
+ m_pendingIndexes.clear();
- foreach (const KItemRange& range, itemRanges) {
- rangesCount += range.count;
+ QElapsedTimer timer;
+ timer.start();
- // Add the inserted items to the pending visible and invisible items
- const int lastIndex = range.index + range.count - 1;
- for (int i = range.index; i <= lastIndex; ++i) {
- const KFileItem item = m_model->fileItem(i);
- bool visible;
- if (hasValidIndexRange) {
- visible = (i >= m_firstVisibleIndex && i <= m_lastVisibleIndex);
- } else {
- // If the view has not informed us about the visible range yet,
- // just assume that the first items are visible.
- visible = (i < m_maximumVisibleItems);
- }
+ // Determine the icons for the visible items synchronously.
+ updateVisibleIcons();
- if (visible) {
- m_pendingVisibleItems.insert(item);
- } else {
- m_pendingInvisibleItems.insert(item);
+ // Try to do at least a fast icon loading (without determining the
+ // mime type) for all items, to reduce the risk that the user sees
+ // "unknown" icons when scrolling.
+ if (m_hasUnknownIcons) {
+ updateAllIconsFast(MaxBlockTimeout - timer.elapsed());
+ }
+
+ // A detailed update of the items in and near the visible area
+ // only makes sense if sorting is finished.
+ if (m_state == ResolvingSortRole) {
+ return;
+ }
+
+ // Start the preview job or the asynchronous resolving of all roles.
+ QList<int> indexes = indexesToResolve();
+
+ if (m_previewShown) {
+ m_pendingPreviewItems.clear();
+ m_pendingPreviewItems.reserve(indexes.count());
+
+ foreach (int index, indexes) {
+ const KFileItem item = m_model->fileItem(index);
+ if (!m_finishedItems.contains(item)) {
+ m_pendingPreviewItems.append(item);
}
}
- }
- resolvePendingRoles();
+ startPreviewJob();
+ } else {
+ m_pendingIndexes = indexes;
+ // Trigger the asynchronous resolving of all roles.
+ m_state = ResolvingAllRoles;
+ QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
+ }
}
-void KFileItemModelRolesUpdater::startPreviewJob(const KFileItemList& items)
+void KFileItemModelRolesUpdater::updateVisibleIcons()
{
- if (items.isEmpty() || m_paused) {
- return;
+ int lastVisibleIndex = m_lastVisibleIndex;
+ if (lastVisibleIndex <= 0) {
+ // Guess a reasonable value for the last visible index if the view
+ // has not told us about the real value yet.
+ lastVisibleIndex = qMin(m_firstVisibleIndex + m_maximumVisibleItems, m_model->count() - 1);
+ if (lastVisibleIndex <= 0) {
+ lastVisibleIndex = qMin(200, m_model->count() - 1);
+ }
}
- // PreviewJob internally caches items always with the size of
- // 128 x 128 pixels or 256 x 256 pixels. A (slow) downscaling is done
- // by PreviewJob if a smaller size is requested. For images KFileItemModelRolesUpdater must
- // do a downscaling anyhow because of the frame, so in this case only the provided
- // cache sizes are requested.
- const QSize cacheSize = (m_iconSize.width() > 128) || (m_iconSize.height() > 128)
- ? QSize(256, 256) : QSize(128, 128);
-
- // KIO::filePreview() will request the MIME-type of all passed items, which (in the
- // worst case) might block the application for several seconds. To prevent such
- // a blocking the MIME-type of the items will determined until the MaxBlockTimeout
- // has been reached and only those items will get passed. As soon as the MIME-type
- // has been resolved once KIO::PreviewJob() can already access the resolved
- // MIME-type in a fast way.
QElapsedTimer timer;
timer.start();
- KFileItemList itemSubSet;
- const int count = items.count();
- itemSubSet.reserve(count);
- for (int i = 0; i < count; ++i) {
- KFileItem item = items.at(i);
- item.determineMimeType();
- itemSubSet.append(item);
- if (timer.elapsed() > MaxBlockTimeout) {
-#ifdef KFILEITEMMODELROLESUPDATER_DEBUG
- kDebug() << "Maximum time of" << MaxBlockTimeout << "ms exceeded, creating only previews for"
- << (i + 1) << "items," << (items.count() - (i + 1)) << "will be resolved later";
-#endif
- break;
- }
- }
- KIO::PreviewJob* job = new KIO::PreviewJob(itemSubSet, cacheSize, &m_enabledPlugins);
- job->setIgnoreMaximumSize(items.first().isLocalFile());
- if (job->ui()) {
- job->ui()->setWindow(qApp->activeWindow());
+ // Try to determine the final icons for all visible items.
+ int index;
+ for (index = m_firstVisibleIndex; index <= lastVisibleIndex && timer.elapsed() < MaxBlockTimeout; ++index) {
+ const KFileItem item = m_model->fileItem(index);
+ applyResolvedRoles(item, ResolveFast);
}
- connect(job, SIGNAL(gotPreview(KFileItem,QPixmap)),
- this, SLOT(slotGotPreview(KFileItem,QPixmap)));
- connect(job, SIGNAL(failed(KFileItem)),
- this, SLOT(slotPreviewFailed(KFileItem)));
- connect(job, SIGNAL(finished(KJob*)),
- this, SLOT(slotPreviewJobFinished(KJob*)));
+ if (index > lastVisibleIndex) {
+ return;
+ }
- m_previewJobs.append(job);
-}
+ // If this didn't work before MaxBlockTimeout was reached, at least
+ // prevent that the user sees 'unknown' icons.
+ disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+ this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
+ while (index <= lastVisibleIndex) {
+ if (!m_model->data(index).contains("iconName")) {
+ const KFileItem item = m_model->fileItem(index);
+ QHash<QByteArray, QVariant> data;
+ data.insert("iconName", item.iconName());
+ m_model->setData(index, data);
+ }
+ ++index;
+ }
-bool KFileItemModelRolesUpdater::hasPendingRoles() const
-{
- return !m_pendingVisibleItems.isEmpty() || !m_pendingInvisibleItems.isEmpty();
+ connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+ this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
}
-void KFileItemModelRolesUpdater::resolvePendingRoles()
+void KFileItemModelRolesUpdater::updateAllIconsFast(int timeout)
{
- int resolvedCount = 0;
-
- bool hasSlowRoles = m_previewShown;
- if (!hasSlowRoles) {
- QSetIterator<QByteArray> it(m_roles);
- while (it.hasNext()) {
- if (m_resolvableRoles.contains(it.next())) {
- hasSlowRoles = true;
- break;
- }
- }
+ if (timeout <= 0) {
+ return;
}
- const ResolveHint resolveHint = hasSlowRoles ? ResolveFast : ResolveAll;
-
- // Resolving the MIME type can be expensive. Assure that not more than MaxBlockTimeout ms are
- // spend for resolving them synchronously. Usually this is more than enough to determine
- // all visible items, but there are corner cases where this limit gets easily exceeded.
QElapsedTimer timer;
timer.start();
- // Resolve the MIME type of all visible items
- QSet<KFileItem>::iterator visibleIt = m_pendingVisibleItems.begin();
- while (visibleIt != m_pendingVisibleItems.end()) {
- const KFileItem item = *visibleIt;
- if (!hasSlowRoles) {
- Q_ASSERT(!m_pendingInvisibleItems.contains(item));
- // All roles will be resolved by applyResolvedRoles()
- visibleIt = m_pendingVisibleItems.erase(visibleIt);
- } else {
- ++visibleIt;
- }
- applyResolvedRoles(item, resolveHint);
- ++resolvedCount;
+ disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+ this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
- if (timer.elapsed() > MaxBlockTimeout) {
- break;
+ const int count = m_model->count();
+ while (m_firstIndexWithoutIcon < count && timer.elapsed() < timeout) {
+ if (!m_model->data(m_firstIndexWithoutIcon).contains("iconName")) {
+ const KFileItem item = m_model->fileItem(m_firstIndexWithoutIcon);
+ QHash<QByteArray, QVariant> data;
+ data.insert("iconName", item.iconName());
+ m_model->setData(m_firstIndexWithoutIcon, data);
}
+ ++m_firstIndexWithoutIcon;
}
- // Resolve the MIME type of the invisible items at least until the timeout
- // has been exceeded or the maximum number of items has been reached
- KFileItemList invisibleItems;
- if (m_lastVisibleIndex >= 0) {
- // The visible range is valid, don't care about the order how the MIME
- // type of invisible items get resolved
- invisibleItems = m_pendingInvisibleItems.toList();
- } else {
- // The visible range is temporary invalid (e.g. happens when loading
- // a directory) so take care to sort the currently invisible items where
- // a part will get visible later
- invisibleItems = sortedItems(m_pendingInvisibleItems);
+ if (m_firstIndexWithoutIcon == count) {
+ m_hasUnknownIcons = false;
}
- int index = 0;
- while (resolvedCount < MaxResolveItemsCount && index < invisibleItems.count() && timer.elapsed() <= MaxBlockTimeout) {
- const KFileItem item = invisibleItems.at(index);
- applyResolvedRoles(item, resolveHint);
+ connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+ this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
+}
- if (!hasSlowRoles) {
- // All roles have been resolved already by applyResolvedRoles()
- m_pendingInvisibleItems.remove(item);
- }
- ++index;
- ++resolvedCount;
+void KFileItemModelRolesUpdater::startPreviewJob()
+{
+ m_state = PreviewJobRunning;
+
+ if (m_pendingPreviewItems.isEmpty()) {
+ QTimer::singleShot(0, this, SLOT(slotPreviewJobFinished()));
+ return;
}
- if (m_previewShown) {
- KFileItemList items = sortedItems(m_pendingVisibleItems);
- items += invisibleItems;
- startPreviewJob(items);
+ // PreviewJob internally caches items always with the size of
+ // 128 x 128 pixels or 256 x 256 pixels. A (slow) downscaling is done
+ // by PreviewJob if a smaller size is requested. For images KFileItemModelRolesUpdater must
+ // do a downscaling anyhow because of the frame, so in this case only the provided
+ // cache sizes are requested.
+ const QSize cacheSize = (m_iconSize.width() > 128) || (m_iconSize.height() > 128)
+ ? QSize(256, 256) : QSize(128, 128);
+
+ // KIO::filePreview() will request the MIME-type of all passed items, which (in the
+ // worst case) might block the application for several seconds. To prevent such
+ // a blocking, we only pass items with known mime type to the preview job.
+ const int count = m_pendingPreviewItems.count();
+ KFileItemList itemSubSet;
+ itemSubSet.reserve(count);
+
+ if (m_pendingPreviewItems.first().isMimeTypeKnown()) {
+ // Some mime types are known already, probably because they were
+ // determined when loading the icons for the visible items. Start
+ // a preview job for all items at the beginning of the list which
+ // have a known mime type.
+ do {
+ itemSubSet.append(m_pendingPreviewItems.takeFirst());
+ } while (!m_pendingPreviewItems.isEmpty() && m_pendingPreviewItems.first().isMimeTypeKnown());
} else {
- QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
- }
+ // Determine mime types for MaxBlockTimeout ms, and start a preview
+ // job for the corresponding items.
+ QElapsedTimer timer;
+ timer.start();
-#ifdef KFILEITEMMODELROLESUPDATER_DEBUG
- if (timer.elapsed() > MaxBlockTimeout) {
- kDebug() << "Maximum time of" << MaxBlockTimeout
- << "ms exceeded, skipping items... Remaining visible:" << m_pendingVisibleItems.count()
- << "invisible:" << m_pendingInvisibleItems.count();
+ do {
+ const KFileItem item = m_pendingPreviewItems.takeFirst();
+ item.determineMimeType();
+ itemSubSet.append(item);
+ } while (!m_pendingPreviewItems.isEmpty() && timer.elapsed() < MaxBlockTimeout);
}
- kDebug() << "[TIME] Resolved pending roles:" << timer.elapsed();
-#endif
- applySortProgressToModel();
-}
-
-void KFileItemModelRolesUpdater::resetPendingRoles()
-{
- m_pendingVisibleItems.clear();
- m_pendingInvisibleItems.clear();
+ KIO::PreviewJob* job = new KIO::PreviewJob(itemSubSet, cacheSize, &m_enabledPlugins);
- foreach (KJob* job, m_previewJobs) {
- job->kill();
+ job->setIgnoreMaximumSize(itemSubSet.first().isLocalFile());
+ if (job->ui()) {
+ job->ui()->setWindow(qApp->activeWindow());
}
- Q_ASSERT(m_previewJobs.isEmpty());
+
+ connect(job, SIGNAL(gotPreview(KFileItem,QPixmap)),
+ this, SLOT(slotGotPreview(KFileItem,QPixmap)));
+ connect(job, SIGNAL(failed(KFileItem)),
+ this, SLOT(slotPreviewFailed(KFileItem)));
+ connect(job, SIGNAL(finished(KJob*)),
+ this, SLOT(slotPreviewJobFinished()));
+
+ m_previewJob = job;
}
-void KFileItemModelRolesUpdater::sortAndResolveAllRoles()
+void KFileItemModelRolesUpdater::updateChangedItems()
{
- if (m_paused) {
+ if (m_state == Paused) {
return;
}
- resetPendingRoles();
- Q_ASSERT(m_pendingVisibleItems.isEmpty());
- Q_ASSERT(m_pendingInvisibleItems.isEmpty());
-
- if (m_model->count() == 0) {
+ if (m_changedItems.isEmpty()) {
return;
}
- // Determine all visible items
- Q_ASSERT(m_firstVisibleIndex >= 0);
- for (int i = m_firstVisibleIndex; i <= m_lastVisibleIndex; ++i) {
- const KFileItem item = m_model->fileItem(i);
- if (!item.isNull()) {
- m_pendingVisibleItems.insert(item);
- }
- }
+ m_finishedItems -= m_changedItems;
- // Determine all invisible items
- for (int i = 0; i < m_firstVisibleIndex; ++i) {
- const KFileItem item = m_model->fileItem(i);
- if (!item.isNull()) {
- m_pendingInvisibleItems.insert(item);
- }
- }
- const int count = m_model->count();
- for (int i = m_lastVisibleIndex + 1; i < count; ++i) {
- const KFileItem item = m_model->fileItem(i);
- if (!item.isNull()) {
- m_pendingInvisibleItems.insert(item);
- }
- }
+ if (m_resolvableRoles.contains(m_model->sortRole())) {
+ m_pendingSortRoleItems += m_changedItems;
- resolvePendingRoles();
-}
+ if (m_state != ResolvingSortRole) {
+ // Stop the preview job if necessary, and trigger the
+ // asynchronous determination of the sort role.
+ killPreviewJob();
+ m_state = ResolvingSortRole;
+ QTimer::singleShot(0, this, SLOT(resolveNextSortRole()));
+ }
-void KFileItemModelRolesUpdater::sortAndResolvePendingRoles()
-{
- Q_ASSERT(!m_paused);
- if (m_model->count() == 0) {
return;
}
- // If no valid index range is given assume that all items are visible.
- // A cleanup will be done later as soon as the index range has been set.
- const bool hasValidIndexRange = (m_lastVisibleIndex >= 0);
+ QList<int> visibleChangedIndexes;
+ QList<int> invisibleChangedIndexes;
- // Trigger a preview generation of all pending items. Assure that the visible
- // pending items get generated first.
+ foreach (const KFileItem& item, m_changedItems) {
+ const int index = m_model->index(item);
- // Step 1: Check if any items in m_pendingVisibleItems are not visible any more
- // and move them to m_pendingInvisibleItems.
- QSet<KFileItem>::iterator itVisible = m_pendingVisibleItems.begin();
- while (itVisible != m_pendingVisibleItems.end()) {
- const KFileItem item = *itVisible;
- if (item.isNull()) {
- itVisible = m_pendingVisibleItems.erase(itVisible);
+ if (index < 0) {
+ m_changedItems.remove(item);
continue;
}
- const int index = m_model->index(item);
- if (!hasValidIndexRange || (index >= m_firstVisibleIndex && index <= m_lastVisibleIndex)) {
- ++itVisible;
+ if (index >= m_firstVisibleIndex && index <= m_lastVisibleIndex) {
+ visibleChangedIndexes.append(index);
} else {
- itVisible = m_pendingVisibleItems.erase(itVisible);
- m_pendingInvisibleItems.insert(item);
+ invisibleChangedIndexes.append(index);
}
}
- // Step 2: Check if any items in m_pendingInvisibleItems have become visible
- // and move them to m_pendingVisibleItems.
- QSet<KFileItem>::iterator itInvisible = m_pendingInvisibleItems.begin();
- while (itInvisible != m_pendingInvisibleItems.end()) {
- const KFileItem item = *itInvisible;
- if (item.isNull()) {
- itInvisible = m_pendingInvisibleItems.erase(itInvisible);
- continue;
- }
+ std::sort(visibleChangedIndexes.begin(), visibleChangedIndexes.end());
- const int index = m_model->index(item);
- if (!hasValidIndexRange || (index >= m_firstVisibleIndex && index <= m_lastVisibleIndex)) {
- itInvisible = m_pendingInvisibleItems.erase(itInvisible);
- m_pendingVisibleItems.insert(item);
- } else {
- ++itInvisible;
+ if (m_previewShown) {
+ foreach (int index, visibleChangedIndexes) {
+ m_pendingPreviewItems.append(m_model->fileItem(index));
}
- }
-
- resolvePendingRoles();
-}
-void KFileItemModelRolesUpdater::applySortProgressToModel()
-{
- if (m_sortingProgress < 0) {
- return;
- }
+ foreach (int index, invisibleChangedIndexes) {
+ m_pendingPreviewItems.append(m_model->fileItem(index));
+ }
- // Inform the model about the progress of the resolved items,
- // so that it can give an indication when the sorting has been finished.
- const int resolvedCount = m_model->count()
- - m_pendingVisibleItems.count()
- - m_pendingInvisibleItems.count();
- if (resolvedCount > 0) {
- m_model->emitSortProgress(resolvedCount);
- if (resolvedCount == m_model->count()) {
- m_sortingProgress = -1;
+ if (!m_previewJob) {
+ startPreviewJob();
+ }
+ } else {
+ const bool resolvingInProgress = !m_pendingIndexes.isEmpty();
+ m_pendingIndexes = visibleChangedIndexes + m_pendingIndexes + invisibleChangedIndexes;
+ if (!resolvingInProgress) {
+ // Trigger the asynchronous resolving of the changed roles.
+ m_state = ResolvingAllRoles;
+ QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
}
}
}
-void KFileItemModelRolesUpdater::updateSortProgress()
+void KFileItemModelRolesUpdater::applySortRole(int index)
{
- const QByteArray sortRole = m_model->sortRole();
+ QHash<QByteArray, QVariant> data;
+ const KFileItem item = m_model->fileItem(index);
- // Optimization if the sorting is done by type: In case if all MIME-types
- // are known, the types have been resolved already by KFileItemModel and
- // no sort-progress feedback is required.
- const bool showProgress = (sortRole == "type")
- ? hasUnknownMimeTypes()
- : m_resolvableRoles.contains(sortRole);
+ if (m_model->sortRole() == "type") {
+ if (!item.isMimeTypeKnown()) {
+ item.determineMimeType();
+ }
- if (m_sortingProgress >= 0) {
- // Mark the current sorting as finished
- m_model->emitSortProgress(m_model->count());
+ data.insert("type", item.mimeComment());
+ } else if (m_model->sortRole() == "size" && item.isLocalFile() && item.isDir()) {
+ const QString path = item.localPath();
+ data.insert("size", subItemsCount(path));
+ } else {
+ // Probably the sort role is a Nepomuk role - just determine all roles.
+ data = rolesData(item);
}
- m_sortingProgress = showProgress ? 0 : -1;
+
+ disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+ this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
+ m_model->setData(index, data);
+ connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
+ this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
}
-bool KFileItemModelRolesUpdater::hasUnknownMimeTypes() const
+void KFileItemModelRolesUpdater::applySortProgressToModel()
{
- const int count = m_model->count();
- for (int i = 0; i < count; ++i) {
- const KFileItem item = m_model->fileItem(i);
- if (!item.isMimeTypeKnown()) {
- return true;
- }
- }
-
- return false;
+ // Inform the model about the progress of the resolved items,
+ // so that it can give an indication when the sorting has been finished.
+ const int resolvedCount = m_model->count() - m_pendingSortRoleItems.count();
+ m_model->emitSortProgress(resolvedCount);
}
bool KFileItemModelRolesUpdater::applyResolvedRoles(const KFileItem& item, ResolveHint hint)
@@ -1017,13 +1114,18 @@ bool KFileItemModelRolesUpdater::applyResolvedRoles(const KFileItem& item, Resol
const bool resolveAll = (hint == ResolveAll);
- bool mimeTypeChanged = false;
+ bool iconChanged = false;
if (!item.isMimeTypeKnown() || !item.isFinalIconKnown()) {
item.determineMimeType();
- mimeTypeChanged = true;
+ iconChanged = true;
+ } else {
+ const int index = m_model->index(item);
+ if (!m_model->data(index).contains("iconName")) {
+ iconChanged = true;
+ }
}
- if (mimeTypeChanged || resolveAll || m_clearPreviews) {
+ if (iconChanged || resolveAll || m_clearPreviews) {
const int index = m_model->index(item);
if (index < 0) {
return false;
@@ -1116,42 +1218,6 @@ QHash<QByteArray, QVariant> KFileItemModelRolesUpdater::rolesData(const KFileIte
return data;
}
-KFileItemList KFileItemModelRolesUpdater::sortedItems(const QSet<KFileItem>& items) const
-{
- KFileItemList itemList;
- if (items.isEmpty()) {
- return itemList;
- }
-
-#ifdef KFILEITEMMODELROLESUPDATER_DEBUG
- QElapsedTimer timer;
- timer.start();
-#endif
-
- QList<int> indexes;
- indexes.reserve(items.count());
-
- QSetIterator<KFileItem> it(items);
- while (it.hasNext()) {
- const KFileItem item = it.next();
- const int index = m_model->index(item);
- if (index >= 0) {
- indexes.append(index);
- }
- }
- qSort(indexes);
-
- itemList.reserve(items.count());
- foreach (int index, indexes) {
- itemList.append(m_model->fileItem(index));
- }
-
-#ifdef KFILEITEMMODELROLESUPDATER_DEBUG
- kDebug() << "[TIME] Sorting of items:" << timer.elapsed();
-#endif
- return itemList;
-}
-
int KFileItemModelRolesUpdater::subItemsCount(const QString& path) const
{
const bool countHiddenFiles = m_model->showHiddenFiles();
@@ -1209,11 +1275,84 @@ int KFileItemModelRolesUpdater::subItemsCount(const QString& path) const
void KFileItemModelRolesUpdater::updateAllPreviews()
{
- if (m_paused) {
+ if (m_state == Paused) {
m_previewChangedDuringPausing = true;
} else {
- sortAndResolveAllRoles();
+ m_finishedItems.clear();
+ startUpdating();
+ }
+}
+
+void KFileItemModelRolesUpdater::killPreviewJob()
+{
+ if (m_previewJob) {
+ disconnect(m_previewJob, SIGNAL(gotPreview(KFileItem,QPixmap)),
+ this, SLOT(slotGotPreview(KFileItem,QPixmap)));
+ disconnect(m_previewJob, SIGNAL(failed(KFileItem)),
+ this, SLOT(slotPreviewFailed(KFileItem)));
+ disconnect(m_previewJob, SIGNAL(finished(KJob*)),
+ this, SLOT(slotPreviewJobFinished()));
+ m_previewJob->kill();
+ m_previewJob = 0;
+ m_pendingPreviewItems.clear();
+ }
+}
+
+QList<int> KFileItemModelRolesUpdater::indexesToResolve() const
+{
+ const int count = m_model->count();
+
+ QList<int> result;
+ result.reserve(ResolveAllItemsLimit);
+
+ // Add visible items.
+ for (int i = m_firstVisibleIndex; i <= m_lastVisibleIndex; ++i) {
+ result.append(i);
+ }
+
+ // We need a reasonable upper limit for number of items to resolve after
+ // and before the visible range. m_maximumVisibleItems can be quite large
+ // when using Compace View.
+ const int readAheadItems = qMin(ReadAheadPages * m_maximumVisibleItems, ResolveAllItemsLimit / 2);
+
+ // Add items after the visible range.
+ const int endExtendedVisibleRange = qMin(m_lastVisibleIndex + readAheadItems, count - 1);
+ for (int i = m_lastVisibleIndex + 1; i <= endExtendedVisibleRange; ++i) {
+ result.append(i);
+ }
+
+ // Add items before the visible range in reverse order.
+ const int beginExtendedVisibleRange = qMax(0, m_firstVisibleIndex - readAheadItems);
+ for (int i = m_firstVisibleIndex - 1; i >= beginExtendedVisibleRange; --i) {
+ result.append(i);
}
+
+ // Add items on the last page.
+ const int beginLastPage = qMax(qMin(endExtendedVisibleRange + 1, count - 1), count - m_maximumVisibleItems);
+ for (int i = beginLastPage; i < count; ++i) {
+ result.append(i);
+ }
+
+ // Add items on the first page.
+ const int endFirstPage = qMin(qMax(beginExtendedVisibleRange - 1, 0), m_maximumVisibleItems);
+ for (int i = 0; i <= endFirstPage; ++i) {
+ result.append(i);
+ }
+
+ // Continue adding items until ResolveAllItemsLimit is reached.
+ int remainingItems = ResolveAllItemsLimit - result.count();
+
+ for (int i = endExtendedVisibleRange + 1; i < beginLastPage && remainingItems > 0; ++i) {
+ result.append(i);
+ --remainingItems;
+ }
+
+ for (int i = beginExtendedVisibleRange - 1; i > endFirstPage && remainingItems > 0; --i) {
+ result.append(i);
+ --remainingItems;
+ }
+
+ return result;
}
#include "kfileitemmodelrolesupdater.moc"
diff --git a/src/kitemviews/kfileitemmodelrolesupdater.h b/src/kitemviews/kfileitemmodelrolesupdater.h
index b837e8c7f..20ce21cfa 100644
--- a/src/kitemviews/kfileitemmodelrolesupdater.h
+++ b/src/kitemviews/kfileitemmodelrolesupdater.h
@@ -59,6 +59,38 @@ class QTimer;
* KFileItemModel only resolves roles that are inexpensive like e.g. the file name or
* the permissions. Creating previews or determining the MIME-type can be quite expensive
* and KFileItemModelRolesUpdater takes care to update such roles asynchronously.
+ *
+ * To prevent a huge CPU and I/O load, these roles are not updated for all
+ * items, but only for the visible items, some items around the visible area,
+ * and the items on the first and last pages of the view. This is a compromise
+ * that aims to minimize the risk that the user sees items with unknown icons
+ * in the view when scrolling or pressing Home or End.
+ *
+ * Determining the roles is done in several phases:
+ *
+ * 1. If the sort role is "slow", it is determined for all items. If this
+ * cannot be finished synchronously in 200 ms, the remaining items are
+ * handled asynchronously by \a resolveNextSortRole().
+ *
+ * 2. The function startUpdating(), which is called if either the sort role
+ * has been successfully determined for all items, or items are inserted
+ * in the view, or the visible items might have changed because items
+ * were removed or moved, tries to determine the icons for all visible
+ * items synchronously for 200 ms. Then:
+ *
+ * (a) If previews are disabled, icons and all other roles are determined
+ * asynchronously for the interesting items. This is done by the
+ * function \a resolveNextPendingRoles().
+ *
+ * (b) If previews are enabled, a \a KIO::PreviewJob is started that loads
+ * the previews for the interesting items. At the same time, the icons
+ * for these items are determined asynchronously as fast as possible
+ * by \a resolveNextPendingRoles(). This minimizes the risk that the
+ * user sees "unknown" icons when scrolling before the previews have
+ * arrived.
+ *
+ * 3. Finally, the entire process is repeated for any items that might have
+ * changed in the mean time.
*/
class LIBDOLPHINPRIVATE_EXPORT KFileItemModelRolesUpdater : public QObject
{
@@ -129,6 +161,7 @@ public:
private slots:
void slotItemsInserted(const KItemRangeList& itemRanges);
void slotItemsRemoved(const KItemRangeList& itemRanges);
+ void slotItemsMoved(const KItemRange& itemRange, QList<int> movedToIndexes);
void slotItemsChanged(const KItemRangeList& itemRanges,
const QSet<QByteArray>& roles);
void slotSortRoleChanged(const QByteArray& current,
@@ -147,20 +180,33 @@ private slots:
void slotPreviewFailed(const KFileItem& item);
/**
- * Is invoked when the preview job has been finished and
- * removes the job from the m_previewJobs list.
+ * Is invoked when the preview job has been finished. Starts a new preview
+ * job if there are any interesting items without previews left, or updates
+ * the changed items otherwise. *
* @see startPreviewJob()
*/
- void slotPreviewJobFinished(KJob* job);
+ void slotPreviewJobFinished();
+
+ /**
+ * Resolves the sort role of the next item in m_pendingSortRole, applies it
+ * to the model, and invokes itself if there are any pending items left. If
+ * that is not the case, \a startUpdating() is called.
+ */
+ void resolveNextSortRole();
+ /**
+ * Resolves the icon name and (if previews are disabled) all other roles
+ * for the next interesting item. If there are no pending items left, any
+ * changed items are updated.
+ */
void resolveNextPendingRoles();
/**
* Resolves items that have not been resolved yet after the change has been
* notified by slotItemsChanged(). Is invoked if the m_changedItemsTimer
- * exceeds.
+ * expires.
*/
- void resolveChangedItems();
+ void resolveRecentlyChangedItems();
void applyChangedNepomukRoles(const Nepomuk2::Resource& resource);
@@ -173,41 +219,46 @@ private slots:
private:
/**
- * Updates the roles for the given item ranges. The roles for the currently
- * visible items will get updated first.
+ * Starts the updating of all roles. The visible items are handled first.
+ */
+ void startUpdating();
+
+ /**
+ * Loads the icons for the visible items. After 200 ms, the function
+ * stops determining mime types and only loads preliminary icons.
+ * This is a compromise that prevents that
+ * (a) the GUI is blocked for more than 200 ms, and
+ * (b) "unknown" icons could be shown in the view.
+ */
+ void updateVisibleIcons();
+
+ /**
+ * Tries to load at least preliminary icons (without determining the
+ * mime type) for all items for \a timeout milliseconds.
*/
- void startUpdating(const KItemRangeList& itemRanges);
+ void updateAllIconsFast(int timeout);
/**
- * Creates previews for the items starting from the first item of the
- * given list.
+ * Creates previews for the items starting from the first item in
+ * m_pendingPreviewItems.
* @see slotGotPreview()
* @see slotPreviewFailed()
* @see slotPreviewJobFinished()
*/
- void startPreviewJob(const KFileItemList& items);
-
- bool hasPendingRoles() const;
- void resolvePendingRoles();
- void resetPendingRoles();
- void sortAndResolveAllRoles();
- void sortAndResolvePendingRoles();
- void applySortProgressToModel();
+ void startPreviewJob();
/**
- * Updates m_sortProgress to be 0 if the sort-role
- * needs to get resolved asynchronously and hence a
- * progress is required. Otherwise m_sortProgress
- * will be set to -1 which means that no progress
- * will be provided.
+ * Ensures that icons, previews, and other roles are determined for any
+ * items that have been changed.
*/
- void updateSortProgress();
+ void updateChangedItems();
/**
- * @return True, if at least one item from the model
- * has an unknown MIME-type.
+ * Resolves the sort role of the item and applies it to the model.
*/
- bool hasUnknownMimeTypes() const;
+ void applySortRole(int index);
+
+ void applySortProgressToModel();
enum ResolveHint {
ResolveFast,
@@ -216,8 +267,6 @@ private:
bool applyResolvedRoles(const KFileItem& item, ResolveHint hint);
QHash<QByteArray, QVariant> rolesData(const KFileItem& item) const;
- KFileItemList sortedItems(const QSet<KFileItem>& items) const;
-
/**
* @return The number of items of the path \a path.
*/
@@ -229,9 +278,20 @@ private:
*/
void updateAllPreviews();
+ void killPreviewJob();
+
+ QList<int> indexesToResolve() const;
+
private:
- // Property for setPaused()/isPaused().
- bool m_paused;
+ enum State {
+ Idle,
+ Paused,
+ ResolvingSortRole,
+ ResolvingAllRoles,
+ PreviewJobRunning
+ };
+
+ State m_state;
// Property changes during pausing must be remembered to be able
// to react when unpausing again:
@@ -250,7 +310,9 @@ private:
// during the roles-updater has been paused by setPaused().
bool m_clearPreviews;
- int m_sortingProgress;
+ // Remembers which items have been handled already, to prevent that
+ // previews and other expensive roles are determined again.
+ QSet<KFileItem> m_finishedItems;
KFileItemModel* m_model;
QSize m_iconSize;
@@ -261,16 +323,33 @@ private:
QSet<QByteArray> m_resolvableRoles;
QStringList m_enabledPlugins;
- QSet<KFileItem> m_pendingVisibleItems;
- QSet<KFileItem> m_pendingInvisibleItems;
- QList<KJob*> m_previewJobs;
+ // Items for which the sort role still has to be determined.
+ QSet<KFileItem> m_pendingSortRoleItems;
+
+ // Determines if the next call of startUpdating() will try to do a fast
+ // icon loading (i.e., without determining the mime type) for all items.
+ bool m_hasUnknownIcons;
+ int m_firstIndexWithoutIcon;
+
+ // Indexes of items which still have to be handled by
+ // resolveNextPendingRoles().
+ QList<int> m_pendingIndexes;
+
+ // Items which have been left over from the last call of startPreviewJob().
+ // A new preview job will be started from them once the first one finishes.
+ KFileItemList m_pendingPreviewItems;
+
+ KJob* m_previewJob;
// When downloading or copying large files, the slot slotItemsChanged()
// will be called periodically within a quite short delay. To prevent
// a high CPU-load by generating e.g. previews for each notification, the update
// will be postponed until no file change has been done within a longer period
// of time.
- QTimer* m_changedItemsTimer;
+ QTimer* m_recentlyChangedItemsTimer;
+ QSet<KFileItem> m_recentlyChangedItems;
+
+ // Items which have not been changed repeatedly recently.
QSet<KFileItem> m_changedItems;
KDirWatch* m_dirWatcher;
diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp
index c6239df94..4629b29f1 100644
--- a/src/kitemviews/kitemlistcontroller.cpp
+++ b/src/kitemviews/kitemlistcontroller.cpp
@@ -838,27 +838,39 @@ bool KItemListController::dragMoveEvent(QGraphicsSceneDragDropEvent* event, cons
oldHoveredWidget->setHovered(false);
emit itemUnhovered(oldHoveredWidget->index());
}
+ }
- if (newHoveredWidget) {
- bool droppingBetweenItems = false;
- if (m_model->sortRole().isEmpty()) {
- // The model supports inserting items between other items.
- droppingBetweenItems = (m_view->showDropIndicator(pos) >= 0);
- }
+ if (newHoveredWidget) {
+ bool droppingBetweenItems = false;
+ if (m_model->sortRole().isEmpty()) {
+ // The model supports inserting items between other items.
+ droppingBetweenItems = (m_view->showDropIndicator(pos) >= 0);
+ }
- const int index = newHoveredWidget->index();
- if (!droppingBetweenItems && m_model->supportsDropping(index)) {
+ const int index = newHoveredWidget->index();
+ if (!droppingBetweenItems) {
+ if (m_model->supportsDropping(index)) {
// Something has been dragged on an item.
m_view->hideDropIndicator();
- newHoveredWidget->setHovered(true);
- emit itemHovered(index);
+ if (!newHoveredWidget->isHovered()) {
+ newHoveredWidget->setHovered(true);
+ emit itemHovered(index);
+ }
- if (m_autoActivationTimer->interval() >= 0) {
+ if (!m_autoActivationTimer->isActive() && m_autoActivationTimer->interval() >= 0) {
m_autoActivationTimer->setProperty("index", index);
m_autoActivationTimer->start();
}
}
+ } else {
+ m_autoActivationTimer->stop();
+ if (newHoveredWidget && newHoveredWidget->isHovered()) {
+ newHoveredWidget->setHovered(false);
+ emit itemUnhovered(index);
+ }
}
+ } else {
+ m_view->hideDropIndicator();
}
return false;
diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp
index a2629c565..b5e105843 100644
--- a/src/kitemviews/kitemlistview.cpp
+++ b/src/kitemviews/kitemlistview.cpp
@@ -678,6 +678,16 @@ void KItemListView::paint(QPainter* painter, const QStyleOptionGraphicsItem* opt
}
}
+QVariant KItemListView::itemChange(GraphicsItemChange change, const QVariant &value)
+{
+ if (change == QGraphicsItem::ItemSceneHasChanged && scene()) {
+ if (!scene()->views().isEmpty()) {
+ m_styleOption.palette = scene()->views().at(0)->palette();
+ }
+ }
+ return QGraphicsItem::itemChange(change, value);
+}
+
void KItemListView::setItemSize(const QSizeF& size)
{
const QSizeF previousSize = m_itemSize;
@@ -2365,7 +2375,8 @@ int KItemListView::showDropIndicator(const QPointF& pos)
const QRectF rect = itemRect(widget->index());
if (mappedPos.y() >= 0 && mappedPos.y() <= rect.height()) {
if (m_model->supportsDropping(widget->index())) {
- const int gap = qMax(4, m_styleOption.padding);
+ // Keep 30% of the rectangle as the gap instead of always having a fixed gap
+ const int gap = qMax(4.0, 0.3 * rect.height());
if (mappedPos.y() >= gap && mappedPos.y() <= rect.height() - gap) {
return -1;
}
diff --git a/src/kitemviews/kitemlistview.h b/src/kitemviews/kitemlistview.h
index 6d609a9df..6467b8c91 100644
--- a/src/kitemviews/kitemlistview.h
+++ b/src/kitemviews/kitemlistview.h
@@ -321,6 +321,7 @@ signals:
void roleEditingFinished(int index, const QByteArray& role, const QVariant& value);
protected:
+ virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value);
void setItemSize(const QSizeF& size);
void setStyleOption(const KItemListStyleOption& option);
diff --git a/src/kitemviews/private/kfileitemmodelsortalgorithm.cpp b/src/kitemviews/private/kfileitemmodelsortalgorithm.cpp
deleted file mode 100644
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/kitemviews/private/kitemlistkeyboardsearchmanager.cpp b/src/kitemviews/private/kitemlistkeyboardsearchmanager.cpp
index da8f72b7e..38154864b 100644
--- a/src/kitemviews/private/kitemlistkeyboardsearchmanager.cpp
+++ b/src/kitemviews/private/kitemlistkeyboardsearchmanager.cpp
@@ -40,7 +40,7 @@ void KItemListKeyboardSearchManager::addKeys(const QString& keys)
{
const bool keyboardTimeWasValid = m_keyboardInputTime.isValid();
const qint64 keyboardInputTimeElapsed = m_keyboardInputTime.restart();
- if (keyboardInputTimeElapsed > m_timeout || !keyboardTimeWasValid || keys.isEmpty()) {
+ if (keyboardInputTimeElapsed > m_timeout || !keyboardTimeWasValid) {
m_searchedString.clear();
}
diff --git a/src/kitemviews/private/knepomukdatamanagement_export.h b/src/kitemviews/private/knepomukdatamanagement_export.h
deleted file mode 100644
index 929a737c9..000000000
--- a/src/kitemviews/private/knepomukdatamanagement_export.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/* This file is part of the KDE project
- Copyright (C) 2007 David Faure <[email protected]>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later version.
-
- This library 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
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public License
- along with this library; see the file COPYING.LIB. If not, write to
- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- Boston, MA 02110-1301, USA.
-*/
-
-#ifndef NEPOMUKDATAMANAGEMENT_EXPORT_H
-#define NEPOMUKDATAMANAGEMENT_EXPORT_H
-
-/* needed for KDE_EXPORT and KDE_IMPORT macros */
-#include <kdemacros.h>
-
-#ifndef NEPOMUK_DATA_MANAGEMENT_EXPORT
-# if defined(MAKE_NEPOMUKDATAMANAGEMENT_LIB)
- /* We are building this library */
-# define NEPOMUK_DATA_MANAGEMENT_EXPORT KDE_EXPORT
-# else
- /* We are using this library */
-# define NEPOMUK_DATA_MANAGEMENT_EXPORT KDE_IMPORT
-# endif
-#endif
-
-# ifndef NEPOMUK_DATA_MANAGEMENT_EXPORT_DEPRECATED
-# define NEPOMUK_DATA_MANAGEMENT_EXPORT_DEPRECATED KDE_DEPRECATED NEPOMUK_DATA_MANAGEMENT_EXPORT
-# endif
-
-#endif
diff --git a/src/kitemviews/private/knepomukrolesprovider.cpp b/src/kitemviews/private/knepomukrolesprovider.cpp
index 3b1899278..661670cfa 100644
--- a/src/kitemviews/private/knepomukrolesprovider.cpp
+++ b/src/kitemviews/private/knepomukrolesprovider.cpp
@@ -103,6 +103,13 @@ QHash<QByteArray, QVariant> KNepomukRolesProvider::roleValues(const Nepomuk2::Re
} else if (value.isResource()) {
const Nepomuk2::Resource resource = value.toResource();
values.insert(role, resource.genericLabel());
+ } else if (value.isResourceList()) {
+ const QList<Nepomuk2::Resource> resList = value.toResourceList();
+ QStringList strList;
+ foreach (const Nepomuk2::Resource& res, resList) {
+ strList << res.genericLabel();
+ }
+ values.insert(role, strList.join(QLatin1String(", ")));
} else {
values.insert(role, value.toString());
}