diff options
Diffstat (limited to 'src/kitemviews')
| -rw-r--r-- | src/kitemviews/kfileitemmodel.cpp | 783 | ||||
| -rw-r--r-- | src/kitemviews/kfileitemmodel.h | 76 | ||||
| -rw-r--r-- | src/kitemviews/kfileitemmodelrolesupdater.cpp | 1083 | ||||
| -rw-r--r-- | src/kitemviews/kfileitemmodelrolesupdater.h | 151 | ||||
| -rw-r--r-- | src/kitemviews/kitemlistcontroller.cpp | 34 | ||||
| -rw-r--r-- | src/kitemviews/kitemlistview.cpp | 13 | ||||
| -rw-r--r-- | src/kitemviews/kitemlistview.h | 1 | ||||
| -rw-r--r-- | src/kitemviews/private/kfileitemmodelsortalgorithm.cpp | 190 | ||||
| -rw-r--r-- | src/kitemviews/private/kfileitemmodelsortalgorithm.h | 180 | ||||
| -rw-r--r-- | src/kitemviews/private/kitemlistkeyboardsearchmanager.cpp | 2 | ||||
| -rw-r--r-- | src/kitemviews/private/knepomukdatamanagement_export.h | 40 | ||||
| -rw-r--r-- | src/kitemviews/private/knepomukrolesprovider.cpp | 7 |
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()); } |
