+ 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;
+ KFileItemList changedFiles;
+
+ // Contains the indexes of the currently visible items
+ // that should get hidden and hence moved to m_filteredItems.
+ QVector<int> newFilteredIndexes;
+
+ // Contains currently hidden items that should
+ // get visible and hence removed from m_filteredItems
+ QList<ItemData *> newVisibleItems;
+
+ 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);
+ const bool newItemMatchesFilter = m_filter.matches(newItem);
+ 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.
+ ItemData *const itemData = m_itemData.at(indexForItem);
+ QHashIterator<QByteArray, QVariant> it(retrieveData(newItem, itemData->parent));
+ while (it.hasNext()) {
+ it.next();
+ const QByteArray &role = it.key();
+ if (itemData->values.value(role) != it.value()) {
+ itemData->values.insert(role, it.value());
+ changedRoles.insert(role);
+ }
+ }
+
+ m_items.remove(oldItem.url());
+ // We must maintain m_items consistent with m_itemData for now, this very loop is using it.
+ // We leave it to be cleared by removeItems() later, when m_itemData actually gets updated.
+ m_items.insert(newItem.url(), indexForItem);
+ if (newItemMatchesFilter
+ || (itemData->values.value("isExpanded").toBool()
+ && (indexForItem + 1 < m_itemData.count() && m_itemData.at(indexForItem + 1)->parent == itemData))) {
+ // We are lenient with expanded folders that originally had visible children.
+ // If they become childless now they will be caught by filterChildlessParents()
+ changedFiles.append(newItem);
+ indexes.append(indexForItem);
+ } else {
+ newFilteredIndexes.append(indexForItem);
+ m_filteredItems.insert(newItem, itemData);
+ }
+ } 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 *const 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).
+ // Before clearing, we must remember if it was expanded and the expanded parents count,
+ // otherwise these states would be lost. The data() method will deal with this special case.
+ const bool isExpanded = itemData->values.value("isExpanded").toBool();
+ bool hasExpandedParentsCount = false;
+ const int expandedParentsCount = itemData->values.value("expandedParentsCount").toInt(&hasExpandedParentsCount);
+ itemData->values.clear();
+ if (isExpanded) {
+ itemData->values.insert("isExpanded", true);
+ if (hasExpandedParentsCount) {
+ itemData->values.insert("expandedParentsCount", expandedParentsCount);
+ }
+ }
+
+ m_filteredItems.erase(it);
+ if (newItemMatchesFilter) {
+ newVisibleItems.append(itemData);
+ } else {
+ m_filteredItems.insert(newItem, itemData);
+ }
+ }
+ }
+ }
+
+ std::sort(newFilteredIndexes.begin(), newFilteredIndexes.end());
+
+ // We must keep track of parents of new visible items since they must be shown no matter what
+ // They will be considered "immune" to filterChildlessParents()
+ QSet<ItemData *> parentsToEnsureVisible;
+
+ for (ItemData *item : newVisibleItems) {
+ for (ItemData *parent = item->parent; parent && !parentsToEnsureVisible.contains(parent); parent = parent->parent) {
+ parentsToEnsureVisible.insert(parent);
+ }
+ }
+ for (ItemData *parent : parentsToEnsureVisible) {
+ // We make sure they are all unfiltered.
+ if (m_filteredItems.remove(parent->item)) {
+ // If it is being unfiltered now, we mark it to be inserted by appending it to newVisibleItems
+ newVisibleItems.append(parent);
+ // It could be in newFilteredIndexes, we must remove it if it's there:
+ const int parentIndex = index(parent->item);
+ if (parentIndex >= 0) {
+ QVector<int>::iterator it = std::lower_bound(newFilteredIndexes.begin(), newFilteredIndexes.end(), parentIndex);
+ if (it != newFilteredIndexes.end() && *it == parentIndex) {
+ newFilteredIndexes.erase(it);
+ }
+ }
+ }
+ }
+
+ KItemRangeList removedRanges = KItemRangeList::fromSortedContainer(newFilteredIndexes);
+
+ // This call will update itemRanges to include the childless parents that have been filtered.
+ filterChildlessParents(removedRanges, parentsToEnsureVisible);
+
+ removeItems(removedRanges, KeepItemData);
+
+ // Show previously hidden items that should get visible
+ insertItems(newVisibleItems);
+
+ // Final step: we will emit 'itemsChanged' and 'fileItemsChanged' signals and trigger the asynchronous re-sorting logic.
+
+ // 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;
+ }
+
+ if (newVisibleItems.count() > 0 || removedRanges.count() > 0) {
+ // The original indexes have changed and are now worthless since items were removed and/or inserted.
+ indexes.clear();
+ // m_items is not yet rebuilt at this point, so we use our own means to resolve the new indexes.
+ const QSet<const KFileItem> changedFilesSet(changedFiles.cbegin(), changedFiles.cend());
+ for (int i = 0; i < m_itemData.count(); i++) {
+ if (changedFilesSet.contains(m_itemData.at(i)->item)) {
+ indexes.append(i);
+ }
+ }
+ } else {
+ std::sort(indexes.begin(), indexes.end());
+ }
+
+ // Extract the item-ranges out of the changed indexes
+ const KItemRangeList itemRangeList = KItemRangeList::fromSortedContainer(indexes);
+ emitItemsChangedAndTriggerResorting(itemRangeList, changedRoles);
+
+ Q_EMIT fileItemsChanged(changedFiles);
+}
+
+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();
+ Q_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);
+
+ // 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 using QString::operator<(), which is quite fast.
+ if (m_naturalSorting) {
+ if (m_sortRole == NameRole) {
+ parallelMergeSort(newItems.begin(), newItems.end(), nameLessThan, QThread::idealThreadCount());
+ } else if (isRoleValueNatural(m_sortRole)) {
+ auto lambdaLessThan = [&](const KFileItemModel::ItemData *a, const KFileItemModel::ItemData *b) {
+ const QByteArray role = roleForType(m_sortRole);
+ return a->values.value(role).toString() < b->values.value(role).toString();
+ };
+ parallelMergeSort(newItems.begin(), newItems.end(), lambdaLessThan, 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(nullptr);
+ }
+
+ // 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();
+
+ Q_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()) {