Improve handling of filtered items when folders are deleted/collapsed

If an expanded folder with filtered children is collapsed or removed,
and the parent-child relationship cannot be determined by parsing the
URLs, this patch makes sure that the filtered children do not reappear
when the filter bar is cleared.

REVIEW: 109455
This commit is contained in:
Frank Reininghaus 2013-03-15 00:23:44 +01:00
parent 986aaa766f
commit 162d4500d6
3 changed files with 149 additions and 80 deletions

View file

@ -1,22 +1,23 @@
/***************************************************************************
* Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
* Copyright (C) 2013 by Frank Reininghaus <frank78ac@googlemail.com> *
* *
* 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 <peter.penz19@gmail.com> *
* Copyright (C) 2013 by Frank Reininghaus <frank78ac@googlemail.com> *
* Copyright (C) 2013 by Emmanuel Pescosta <emmanuelpescosta099@gmail.com> *
* *
* 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"
@ -421,7 +422,8 @@ bool KFileItemModel::setExpanded(int index, bool expanded)
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);
@ -429,38 +431,10 @@ 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);
}
QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.begin();
while (it != m_filteredItems.end()) {
const KUrl url = it.key().url();
KUrl parentUrl = url.upUrl();
parentUrl.adjustPath(KUrl::RemoveTrailingSlash);
if (urlsToRemove.contains(parentUrl)) {
delete it.value();
it = m_filteredItems.erase(it);
} else {
++it;
}
}
const KFileItemList itemsToRemove = childItems(item);
removeFilteredChildren(itemsToRemove);
removeItems(itemsToRemove, DeleteItemData);
}
@ -598,6 +572,30 @@ void KFileItemModel::applyFilters()
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;
@ -826,35 +824,7 @@ void KFileItemModel::slotItemsDeleted(const KFileItemList& items)
}
if (m_requestRole[ExpandedParentsCountRole]) {
// 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.
//
// TODO: This does currently *not* work if the parent-child
// relationships can not be determined just by using KUrl::upUrl().
// This is the case, e.g., when browsing smb:/.
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.begin();
while (it != m_filteredItems.end()) {
const KUrl url = it.key().url();
KUrl parentUrl = url.upUrl();
parentUrl.adjustPath(KUrl::RemoveTrailingSlash);
if (urlsToRemove.contains(parentUrl)) {
delete it.value();
it = m_filteredItems.erase(it);
} else {
++it;
}
}
removeFilteredChildren(itemsToRemove);
}
}

View file

@ -396,6 +396,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.

View file

@ -81,6 +81,7 @@ private slots:
void testRemoveHiddenItems();
void collapseParentOfHiddenItems();
void removeParentOfHiddenItems();
void testGeneralParentChildRelationships();
private:
QStringList itemsInModel() const;
@ -1004,6 +1005,98 @@ void KFileItemModelTest::removeParentOfHiddenItems()
QCOMPARE(itemsInModel(), QStringList() << "a" << "1");
}
/**
* Create a tree structure where parent-child relationships can not be
* determined by parsing the URLs, and verify that KFileItemModel
* handles them correctly.
*/
void KFileItemModelTest::testGeneralParentChildRelationships()
{
QSet<QByteArray> modelRoles = m_model->roles();
modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount";
m_model->setRoles(modelRoles);
QStringList files;
files << "parent1/realChild1/realGrandChild1" << "parent2/realChild2/realGrandChild2";
m_testDir->createFiles(files);
m_model->loadDirectory(m_testDir->url());
QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
QCOMPARE(itemsInModel(), QStringList() << "parent1" << "parent2");
// Expand all folders.
m_model->setExpanded(0, true);
QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "parent2");
m_model->setExpanded(1, true);
QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "realGrandChild1" << "parent2");
m_model->setExpanded(3, true);
QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "realGrandChild1" << "parent2" << "realChild2");
m_model->setExpanded(4, true);
QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "realGrandChild1" << "parent2" << "realChild2" << "realGrandChild2");
// Add some more children and grand-children.
const KUrl parent1 = m_model->fileItem(0).url();
const KUrl parent2 = m_model->fileItem(3).url();
const KUrl realChild1 = m_model->fileItem(1).url();
const KUrl realChild2 = m_model->fileItem(4).url();
m_model->slotItemsAdded(parent1, KFileItemList() << KFileItem(KUrl("child1"), QString(), KFileItem::Unknown));
m_model->slotCompleted();
QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "realGrandChild1" << "child1" << "parent2" << "realChild2" << "realGrandChild2");
m_model->slotItemsAdded(parent2, KFileItemList() << KFileItem(KUrl("child2"), QString(), KFileItem::Unknown));
m_model->slotCompleted();
QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "realGrandChild1" << "child1" << "parent2" << "realChild2" << "realGrandChild2" << "child2");
m_model->slotItemsAdded(realChild1, KFileItemList() << KFileItem(KUrl("grandChild1"), QString(), KFileItem::Unknown));
m_model->slotCompleted();
QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "grandChild1" << "realGrandChild1" << "child1" << "parent2" << "realChild2" << "realGrandChild2" << "child2");
m_model->slotItemsAdded(realChild1, KFileItemList() << KFileItem(KUrl("grandChild1"), QString(), KFileItem::Unknown));
m_model->slotCompleted();
QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "grandChild1" << "realGrandChild1" << "child1" << "parent2" << "realChild2" << "realGrandChild2" << "child2");
m_model->slotItemsAdded(realChild2, KFileItemList() << KFileItem(KUrl("grandChild2"), QString(), KFileItem::Unknown));
m_model->slotCompleted();
QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "grandChild1" << "realGrandChild1" << "child1" << "parent2" << "realChild2" << "grandChild2" << "realGrandChild2" << "child2");
// Set a name filter that matches nothing -> only expanded folders remain.
QSignalSpy itemsRemovedSpy(m_model, SIGNAL(itemsRemoved(KItemRangeList)));
m_model->setNameFilter("xyz");
QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "parent2" << "realChild2");
QCOMPARE(itemsRemovedSpy.count(), 1);
QList<QVariant> arguments = itemsRemovedSpy.takeFirst();
KItemRangeList itemRangeList = arguments.at(0).value<KItemRangeList>();
QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(2, 3) << KItemRange(7, 3));
// Collapse "parent1".
m_model->setExpanded(0, false);
QCOMPARE(itemsInModel(), QStringList() << "parent1" << "parent2" << "realChild2");
QCOMPARE(itemsRemovedSpy.count(), 1);
arguments = itemsRemovedSpy.takeFirst();
itemRangeList = arguments.at(0).value<KItemRangeList>();
QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 1));
// Remove "parent2".
m_model->slotItemsDeleted(KFileItemList() << m_model->fileItem(1));
QCOMPARE(itemsInModel(), QStringList() << "parent1");
QCOMPARE(itemsRemovedSpy.count(), 1);
arguments = itemsRemovedSpy.takeFirst();
itemRangeList = arguments.at(0).value<KItemRangeList>();
QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 2));
// Clear filter, verify that no items reappear.
m_model->setNameFilter(QString());
QCOMPARE(itemsInModel(), QStringList() << "parent1");
}
QStringList KFileItemModelTest::itemsInModel() const
{
QStringList items;