diff options
| -rw-r--r-- | src/kitemviews/kfileitemmodel.cpp | 27 | ||||
| -rw-r--r-- | src/panels/places/placespanel.cpp | 37 | ||||
| -rw-r--r-- | src/tests/kfileitemmodeltest.cpp | 51 |
3 files changed, 102 insertions, 13 deletions
diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index 60fc27546..d16a6c1d3 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -795,6 +795,33 @@ void KFileItemModel::slotItemsDeleted(const KFileItemList& items) 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); + } + + 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); diff --git a/src/panels/places/placespanel.cpp b/src/panels/places/placespanel.cpp index e23732c97..0f5b83ad5 100644 --- a/src/panels/places/placespanel.cpp +++ b/src/panels/places/placespanel.cpp @@ -262,23 +262,34 @@ void PlacesPanel::slotItemContextMenuRequested(int index, const QPointF& pos) emptyTrash(); } else if (action == addAction) { addEntry(); - } else if (action == editAction) { - editEntry(index); - } else if (action == removeAction) { - m_model->removeItem(index); - } else if (action == hideAction) { - item->setHidden(hideAction->isChecked()); - } else if (action == openInNewTabAction) { - const KUrl url = m_model->item(index)->dataValue("url").value<KUrl>(); - emit placeMiddleClicked(url); } else if (action == showAllAction) { m_model->setHiddenItemsShown(showAllAction->isChecked()); - } else if (action == teardownAction) { - m_model->requestTeardown(index); - } else if (action == ejectAction) { - m_model->requestEject(index); } else if (iconSizeActionMap.contains(action)) { m_view->setIconSize(iconSizeActionMap.value(action)); + } else { + // The index might have changed if devices were added/removed while + // the context menu was open. + index = m_model->index(item); + if (index < 0) { + // The item is not in the model any more, probably because it was an + // external device that has been removed while the context menu was open. + return; + } + + if (action == editAction) { + editEntry(index); + } else if (action == removeAction) { + m_model->removeItem(index); + } else if (action == hideAction) { + item->setHidden(hideAction->isChecked()); + } else if (action == openInNewTabAction) { + const KUrl url = m_model->item(index)->dataValue("url").value<KUrl>(); + emit placeMiddleClicked(url); + } else if (action == teardownAction) { + m_model->requestTeardown(index); + } else if (action == ejectAction) { + m_model->requestEject(index); + } } } diff --git a/src/tests/kfileitemmodeltest.cpp b/src/tests/kfileitemmodeltest.cpp index c9f8a3468..483c5b245 100644 --- a/src/tests/kfileitemmodeltest.cpp +++ b/src/tests/kfileitemmodeltest.cpp @@ -79,6 +79,7 @@ private slots: void testNameFilter(); void testEmptyPath(); void testRemoveHiddenItems(); + void removeParentOfHiddenItems(); private: QStringList itemsInModel() const; @@ -903,6 +904,56 @@ void KFileItemModelTest::testRemoveHiddenItems() m_model->setShowHiddenFiles(false); } +/** + * Verify that filtered items are removed when their parent is deleted. + */ +void KFileItemModelTest::removeParentOfHiddenItems() +{ + QSet<QByteArray> modelRoles = m_model->roles(); + modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount"; + m_model->setRoles(modelRoles); + + QStringList files; + files << "a/1" << "a/b/1" << "a/b/c/1" << "a/b/c/d/1"; + m_testDir->createFiles(files); + + m_model->loadDirectory(m_testDir->url()); + QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QCOMPARE(m_model->count(), 1); // Only "a/" + + // Expand "a/". + m_model->setExpanded(0, true); + QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/b/", "a/1" + + // Expand "a/b/". + m_model->setExpanded(1, true); + QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QCOMPARE(m_model->count(), 5); // 5 items: "a/", "a/b/", "a/b/c", "a/b/1", "a/1" + + // Expand "a/b/c/". + m_model->setExpanded(2, true); + QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QCOMPARE(m_model->count(), 7); // 7 items: "a/", "a/b/", "a/b/c", "a/b/c/d/", "a/b/c/1", "a/b/1", "a/1" + + // Set a name filter that matches nothing -> only the expanded folders remain. + m_model->setNameFilter("xyz"); + QCOMPARE(m_model->count(), 3); + QCOMPARE(itemsInModel(), QStringList() << "a" << "b" << "c"); + + // Simulate the deletion of the directory "a/b/". + QSignalSpy spyItemsRemoved(m_model, SIGNAL(itemsRemoved(KItemRangeList))); + m_model->slotItemsDeleted(KFileItemList() << m_model->fileItem(1)); + QCOMPARE(spyItemsRemoved.count(), 1); + QCOMPARE(m_model->count(), 1); + QCOMPARE(itemsInModel(), QStringList() << "a"); + + // Remove the filter -> only the file "a/1" should appear. + m_model->setNameFilter(QString()); + QCOMPARE(m_model->count(), 2); + QCOMPARE(itemsInModel(), QStringList() << "a" << "1"); +} + QStringList KFileItemModelTest::itemsInModel() const { QStringList items; |
