+ if (m_requestRole[ExpandedParentsCountRole]) {
+ // If the expanding of items is enabled, the call
+ // dirLister->openUrl(url, KDirLister::Keep) in KFileItemModel::setExpanded()
+ // might result in emitting the same items twice due to the Keep-parameter.
+ // This case happens if an item gets expanded, collapsed and expanded again
+ // before the items could be loaded for the first expansion.
+ if (index(items.first().url()) >= 0) {
+ // The items are already part of the model.
+ 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.
+ const int parentIndex = index(parentUrl);
+ if (parentIndex >= 0 && !m_itemData[parentIndex]->values.value("isExpanded").toBool()) {
+ // The parent is not expanded.
+ return;
+ }
+ }
+
+ QList<ItemData*> itemDataList = createItemDataList(parentUrl, items);
+
+ if (!m_filter.hasSetFilters()) {
+ 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.
+ foreach (ItemData* itemData, itemDataList) {
+ if (m_filter.matches(itemData->item)) {
+ m_pendingItemsToInsert.append(itemData);
+ } else {
+ m_filteredItems.insert(itemData->item, itemData);
+ }
+ }
+ }
+
+ if (useMaximumUpdateInterval() && !m_maximumUpdateIntervalTimer->isActive()) {
+ // Assure that items get dispatched if no completed() or canceled() signal is
+ // emitted during the maximum update interval.
+ m_maximumUpdateIntervalTimer->start();
+ }
+}
+
+void KFileItemModel::slotItemsDeleted(const KFileItemList& items)
+{
+ dispatchPendingItemsToInsert();
+
+ QVector<int> indexesToRemove;
+ indexesToRemove.reserve(items.count());
+
+ foreach (const KFileItem& item, items) {
+ const int indexForItem = index(item);
+ if (indexForItem >= 0) {
+ indexesToRemove.append(indexForItem);
+ } else {
+ // Probably the item has been filtered.
+ QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.find(item);
+ if (it != m_filteredItems.end()) {
+ delete it.value();
+ m_filteredItems.erase(it);
+ }
+ }
+ }
+
+ std::sort(indexesToRemove.begin(), indexesToRemove.end());
+
+ if (m_requestRole[ExpandedParentsCountRole] && !m_expandedDirs.isEmpty()) {
+ // Assure that removing a parent item also results in removing all children
+ QVector<int> indexesToRemoveWithChildren;
+ indexesToRemoveWithChildren.reserve(m_itemData.count());
+
+ const int itemCount = m_itemData.count();
+ foreach (int index, indexesToRemove) {
+ indexesToRemoveWithChildren.append(index);
+
+ const int parentLevel = expandedParentsCount(index);
+ int childIndex = index + 1;
+ while (childIndex < itemCount && expandedParentsCount(childIndex) > parentLevel) {
+ indexesToRemoveWithChildren.append(childIndex);
+ ++childIndex;
+ }
+ }
+
+ indexesToRemove = indexesToRemoveWithChildren;
+ }
+
+ const KItemRangeList itemRanges = KItemRangeList::fromSortedContainer(indexesToRemove);
+ removeFilteredChildren(itemRanges);
+ removeItems(itemRanges, DeleteItemData);
+}
+
+void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >& items)
+{
+ Q_ASSERT(!items.isEmpty());
+#ifdef KFILEITEMMODEL_DEBUG
+ qCDebug(DolphinDebug) << "Refreshing" << items.count() << "items";
+#endif
+
+ // 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();
+ const KFileItem& oldItem = itemPair.first;
+ const KFileItem& newItem = itemPair.second;
+ const int indexForItem = index(oldItem);
+ if (indexForItem >= 0) {
+ m_itemData[indexForItem]->item = newItem;
+
+ // 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, m_itemData.at(indexForItem)->parent));
+ QHash<QByteArray, QVariant>& values = m_itemData[indexForItem]->values;
+ while (it.hasNext()) {
+ it.next();
+ const QByteArray& role = it.key();
+ if (values.value(role) != it.value()) {
+ values.insert(role, it.value());
+ changedRoles.insert(role);
+ }
+ }
+
+ m_items.remove(oldItem.url());
+ m_items.insert(newItem.url(), indexForItem);
+ indexes.append(indexForItem);
+ } else {
+ // Check if 'oldItem' is one of the filtered items.
+ QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.find(oldItem);
+ if (it != m_filteredItems.end()) {
+ ItemData* itemData = it.value();
+ itemData->item = newItem;
+
+ // The data stored in 'values' might have changed. Therefore, we clear
+ // 'values' and re-populate it the next time it is requested via data(int).
+ itemData->values.clear();
+
+ m_filteredItems.erase(it);
+ m_filteredItems.insert(newItem, itemData);
+ }
+ }
+ }
+
+ // If the changed items have been created recently, they might not be in m_items yet.
+ // In that case, the list 'indexes' might be empty.
+ if (indexes.isEmpty()) {
+ return;
+ }
+
+ // Extract the item-ranges out of the changed indexes
+ qSort(indexes);
+ const KItemRangeList itemRangeList = KItemRangeList::fromSortedContainer(indexes);
+ emitItemsChangedAndTriggerResorting(itemRangeList, changedRoles);
+}
+
+void KFileItemModel::slotClear()
+{
+#ifdef KFILEITEMMODEL_DEBUG
+ qCDebug(DolphinDebug) << "Clearing all items";
+#endif
+
+ qDeleteAll(m_filteredItems);
+ m_filteredItems.clear();
+ m_groups.clear();
+
+ m_maximumUpdateIntervalTimer->stop();
+ m_resortAllItemsTimer->stop();
+
+ qDeleteAll(m_pendingItemsToInsert);
+ m_pendingItemsToInsert.clear();
+
+ const int removedCount = m_itemData.count();
+ if (removedCount > 0) {
+ qDeleteAll(m_itemData);
+ m_itemData.clear();
+ m_items.clear();
+ emit itemsRemoved(KItemRangeList() << KItemRange(0, removedCount));
+ }
+
+ m_expandedDirs.clear();
+}
+
+void KFileItemModel::slotSortingChoiceChanged()
+{
+ loadSortingSettings();
+ resortAllItems();
+}
+
+void KFileItemModel::dispatchPendingItemsToInsert()
+{
+ if (!m_pendingItemsToInsert.isEmpty()) {
+ insertItems(m_pendingItemsToInsert);
+ m_pendingItemsToInsert.clear();
+ }
+}
+
+void KFileItemModel::insertItems(QList<ItemData*>& newItems)
+{
+ if (newItems.isEmpty()) {
+ return;
+ }
+
+#ifdef KFILEITEMMODEL_DEBUG
+ QElapsedTimer timer;
+ timer.start();
+ qCDebug(DolphinDebug) << "===========================================================";
+ qCDebug(DolphinDebug) << "Inserting" << newItems.count() << "items";
+#endif
+
+ m_groups.clear();
+ prepareItemsForSorting(newItems);
+
+ if (m_sortRole == NameRole && m_naturalSorting) {
+ // Natural sorting of items can be very slow. However, it becomes much
+ // faster if the input sequence is already mostly sorted. Therefore, we
+ // first sort 'newItems' according to the QStrings returned by
+ // KFileItem::text() using QString::operator<(), which is quite fast.
+ parallelMergeSort(newItems.begin(), newItems.end(), nameLessThan, QThread::idealThreadCount());
+ }
+
+ sort(newItems.begin(), newItems.end());
+
+#ifdef KFILEITEMMODEL_DEBUG
+ qCDebug(DolphinDebug) << "[TIME] Sorting:" << timer.elapsed();
+#endif
+
+ KItemRangeList itemRanges;
+ const int existingItemCount = m_itemData.count();
+ const int newItemCount = newItems.count();
+ const int totalItemCount = existingItemCount + newItemCount;
+
+ if (existingItemCount == 0) {
+ // Optimization for the common special case that there are no
+ // items in the model yet. Happens, e.g., when entering a folder.
+ m_itemData = newItems;
+ itemRanges << KItemRange(0, newItemCount);
+ } else {
+ m_itemData.reserve(totalItemCount);
+ for (int i = existingItemCount; i < totalItemCount; ++i) {
+ m_itemData.append(0);
+ }
+
+ // We build the new list m_itemData 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), m_collator)) {
+ // Move an existing item to its new position. If any new items
+ // are behind it, push the item range to itemRanges.
+ if (rangeCount > 0) {
+ itemRanges << KItemRange(sourceIndexExistingItems + 1, rangeCount);
+ rangeCount = 0;
+ }
+
+ m_itemData[targetIndex] = m_itemData.at(sourceIndexExistingItems);
+ --sourceIndexExistingItems;
+ } else {
+ // Insert a new item into the list.
+ ++rangeCount;
+ m_itemData[targetIndex] = newItem;
+ --sourceIndexNewItems;
+ }
+ --targetIndex;
+ }
+
+ // Push the final item range to itemRanges.
+ if (rangeCount > 0) {
+ itemRanges << KItemRange(sourceIndexExistingItems + 1, rangeCount);
+ }
+
+ // Note that itemRanges is still sorted in reverse order.
+ std::reverse(itemRanges.begin(), itemRanges.end());
+ }
+
+ // The indexes in m_items are not correct anymore. Therefore, we clear m_items.
+ // It will be re-populated with the updated indices if index(const QUrl&) is called.
+ m_items.clear();
+
+ emit itemsInserted(itemRanges);
+
+#ifdef KFILEITEMMODEL_DEBUG
+ qCDebug(DolphinDebug) << "[TIME] Inserting of" << newItems.count() << "items:" << timer.elapsed();
+#endif
+}
+
+void KFileItemModel::removeItems(const KItemRangeList& itemRanges, RemoveItemsBehavior behavior)
+{
+ if (itemRanges.isEmpty()) {
+ return;
+ }
+
+ m_groups.clear();
+
+ // Step 1: Remove the items from m_itemData, and free the ItemData.
+ int removedItemsCount = 0;
+ foreach (const KItemRange& range, itemRanges) {
+ removedItemsCount += range.count;
+
+ for (int index = range.index; index < range.index + range.count; ++index) {
+ if (behavior == DeleteItemData) {
+ delete m_itemData.at(index);
+ }
+
+ m_itemData[index] = 0;
+ }
+ }
+
+ // Step 2: Remove the ItemData pointers from the list m_itemData.
+ int target = itemRanges.at(0).index;
+ int source = itemRanges.at(0).index + itemRanges.at(0).count;
+ int nextRange = 1;
+
+ const int oldItemDataCount = m_itemData.count();
+ while (source < oldItemDataCount) {
+ m_itemData[target] = m_itemData[source];
+ ++target;
+ ++source;
+
+ if (nextRange < itemRanges.count() && source == itemRanges.at(nextRange).index) {
+ // Skip the items in the next removed range.
+ source += itemRanges.at(nextRange).count;
+ ++nextRange;
+ }
+ }
+
+ m_itemData.erase(m_itemData.end() - removedItemsCount, m_itemData.end());
+
+ // The indexes in m_items are not correct anymore. Therefore, we clear m_items.
+ // It will be re-populated with the updated indices if index(const QUrl&) is called.
+ m_items.clear();
+
+ emit itemsRemoved(itemRanges);
+}
+
+QList<KFileItemModel::ItemData*> KFileItemModel::createItemDataList(const QUrl& 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 = index(parentUrl);
+ 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->parent = parentItem;
+ itemDataList.append(itemData);
+ }
+
+ return itemDataList;
+}
+
+void KFileItemModel::prepareItemsForSorting(QList<ItemData*>& itemDataList)
+{
+ switch (m_sortRole) {
+ case PermissionsRole:
+ case OwnerRole:
+ case GroupRole:
+ case DestinationRole:
+ case PathRole:
+ // These roles can be determined with retrieveData, and they have to be stored
+ // in the QHash "values" for the sorting.
+ foreach (ItemData* itemData, itemDataList) {
+ if (itemData->values.isEmpty()) {
+ itemData->values = retrieveData(itemData->item, itemData->parent);
+ }
+ }
+ break;
+
+ case TypeRole:
+ // At least store the data including the file type for items with known MIME type.
+ foreach (ItemData* itemData, itemDataList) {
+ if (itemData->values.isEmpty()) {
+ const KFileItem item = itemData->item;
+ if (item.isDir() || item.isMimeTypeKnown()) {
+ itemData->values = retrieveData(itemData->item, itemData->parent);
+ }
+ }
+ }
+ break;
+
+ default:
+ // The other roles are either resolved by KFileItemModelRolesUpdater
+ // (this includes the SizeRole for directories), or they do not need
+ // to be stored in the QHash "values" for sorting because the data can
+ // be retrieved directly from the KFileItem (NameRole, SizeRole for files,
+ // DateRole).
+ break;
+ }
+}
+
+int KFileItemModel::expandedParentsCount(const ItemData* data)
+{
+ // The hash 'values' is only guaranteed to contain the key "expandedParentsCount"
+ // if the corresponding item is expanded, and it is not a top-level item.
+ const ItemData* parent = data->parent;
+ if (parent) {
+ if (parent->parent) {
+ Q_ASSERT(parent->values.contains("expandedParentsCount"));
+ return parent->values.value("expandedParentsCount").toInt() + 1;
+ } else {
+ return 1;
+ }
+ } else {
+ return 0;
+ }
+}
+
+void KFileItemModel::removeExpandedItems()
+{
+ QVector<int> indexesToRemove;
+
+ const int maxIndex = m_itemData.count() - 1;