Implement restoring expanded folders in Details View

This commit is contained in:
Frank Reininghaus 2011-09-17 14:35:25 +02:00
parent 5070666ad2
commit 9424f5a789
5 changed files with 114 additions and 23 deletions

View file

@ -45,7 +45,10 @@ KFileItemModel::KFileItemModel(KDirLister* dirLister, QObject* parent) :
m_minimumUpdateIntervalTimer(0),
m_maximumUpdateIntervalTimer(0),
m_pendingItemsToInsert(),
m_rootExpansionLevel(-1)
m_pendingEmitLoadingCompleted(false),
m_rootExpansionLevel(-1),
m_expandedUrls(),
m_restoredExpandedUrls()
{
resetRoles();
m_requestRole[NameRole] = true;
@ -306,14 +309,18 @@ bool KFileItemModel::setExpanded(int index, bool expanded)
return false;
}
if (expanded) {
const KUrl url = m_sortedItems.at(index).url();
if (expanded) {
m_expandedUrls.insert(url);
KDirLister* dirLister = m_dirLister.data();
if (dirLister) {
dirLister->openUrl(url, KDirLister::Keep);
return true;
}
} else {
m_expandedUrls.remove(url);
KFileItemList itemsToRemove;
const int expansionLevel = data(index)["expansionLevel"].toInt();
++index;
@ -344,6 +351,16 @@ bool KFileItemModel::isExpandable(int index) const
return false;
}
QSet<KUrl> KFileItemModel::expandedUrls() const
{
return m_expandedUrls;
}
void KFileItemModel::restoreExpandedUrls(const QSet<KUrl>& urls)
{
m_restoredExpandedUrls = urls;
}
void KFileItemModel::onGroupRoleChanged(const QByteArray& current, const QByteArray& previous)
{
Q_UNUSED(previous);
@ -381,13 +398,38 @@ void KFileItemModel::onSortRoleChanged(const QByteArray& current, const QByteArr
void KFileItemModel::slotCompleted()
{
if (m_minimumUpdateIntervalTimer->isActive()) {
if (m_restoredExpandedUrls.isEmpty() && m_minimumUpdateIntervalTimer->isActive()) {
// dispatchPendingItems() will be called when the timer
// has been expired.
m_pendingEmitLoadingCompleted = true;
return;
}
m_pendingEmitLoadingCompleted = false;
dispatchPendingItemsToInsert();
if (!m_restoredExpandedUrls.isEmpty()) {
// Try to find a URL that can be expanded.
// Note that the parent folder must be expanded before any of its subfolders become visible.
// Therefore, some URLs in m_restoredExpandedUrls might not be visible yet
// -> we expand the first visible URL we find in m_restoredExpandedUrls.
foreach(const KUrl& url, m_restoredExpandedUrls) {
const int index = m_items.value(url, -1);
if (index >= 0) {
// We have found an expandable URL. Expand it and return - when
// the dir lister has finished, this slot will be called again.
m_restoredExpandedUrls.remove(url);
setExpanded(index, true);
return;
}
}
// None of the URLs in m_restoredExpandedUrls could be found in the model. This can happen
// if these URLs have been deleted in the meantime.
m_restoredExpandedUrls.clear();
}
emit loadingCompleted();
m_minimumUpdateIntervalTimer->start();
}
@ -492,6 +534,8 @@ void KFileItemModel::slotClear()
m_data.clear();
emit itemsRemoved(KItemRangeList() << KItemRange(0, removedCount));
}
m_expandedUrls.clear();
}
void KFileItemModel::slotClear(const KUrl& url)
@ -505,6 +549,10 @@ void KFileItemModel::dispatchPendingItemsToInsert()
insertItems(m_pendingItemsToInsert);
m_pendingItemsToInsert.clear();
}
if (m_pendingEmitLoadingCompleted) {
emit loadingCompleted();
}
}
void KFileItemModel::insertItems(const KFileItemList& items)
@ -669,6 +717,7 @@ void KFileItemModel::removeExpandedItems()
removeItems(expandedItems);
m_rootExpansionLevel = -1;
m_expandedUrls.clear();
}
void KFileItemModel::resetRoles()

View file

@ -113,6 +113,11 @@ public:
bool setExpanded(int index, bool expanded);
bool isExpanded(int index) const;
bool isExpandable(int index) const;
QSet<KUrl> expandedUrls() const;
void restoreExpandedUrls(const QSet<KUrl>& urls);
signals:
void loadingCompleted();
protected:
virtual void onGroupRoleChanged(const QByteArray& current, const QByteArray& previous);
@ -199,12 +204,19 @@ private:
QTimer* m_minimumUpdateIntervalTimer;
QTimer* m_maximumUpdateIntervalTimer;
KFileItemList m_pendingItemsToInsert;
bool m_pendingEmitLoadingCompleted;
// Stores the smallest expansion level of the root-URL. Is required to calculate
// the "expansionLevel" role in an efficient way. A value < 0 indicates that
// it has not been initialized yet.
mutable int m_rootExpansionLevel;
// Stores the URLs of the expanded folders.
QSet<KUrl> m_expandedUrls;
// Stores the URLs which have to be expanded in order to restore a previous state of the model.
QSet<KUrl> m_restoredExpandedUrls;
friend class KFileItemModelTest; // For unit testing
};

View file

@ -243,6 +243,10 @@ void KFileItemModelTest::testExpandItems()
files << "a/a/1" << "a/a-1/1"; // missing folders are created automatically
m_testDir->createFiles(files);
// Store the URLs of all folders in a set.
QSet<KUrl> allFolders;
allFolders << KUrl(m_testDir->name() + "a") << KUrl(m_testDir->name() + "a/a") << KUrl(m_testDir->name() + "a/a-1");
m_dirLister->openUrl(m_testDir->url());
QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
@ -250,6 +254,7 @@ void KFileItemModelTest::testExpandItems()
QCOMPARE(m_model->count(), 1);
QVERIFY(m_model->isExpandable(0));
QVERIFY(!m_model->isExpanded(0));
QVERIFY(m_model->expandedUrls().empty());
QSignalSpy spyInserted(m_model, SIGNAL(itemsInserted(KItemRangeList)));
@ -258,6 +263,7 @@ void KFileItemModelTest::testExpandItems()
QVERIFY(m_model->isExpanded(0));
QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/a/", "a/a-1/"
QCOMPARE(m_model->expandedUrls(), QSet<KUrl>() << KUrl(m_testDir->name() + "a"));
QCOMPARE(spyInserted.count(), 1);
KItemRangeList itemRangeList = spyInserted.takeFirst().at(0).value<KItemRangeList>();
@ -273,6 +279,7 @@ void KFileItemModelTest::testExpandItems()
QVERIFY(m_model->isExpanded(1));
QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
QCOMPARE(m_model->count(), 4); // 4 items: "a/", "a/a/", "a/a/1", "a/a-1/"
QCOMPARE(m_model->expandedUrls(), QSet<KUrl>() << KUrl(m_testDir->name() + "a") << KUrl(m_testDir->name() + "a/a"));
QCOMPARE(spyInserted.count(), 1);
itemRangeList = spyInserted.takeFirst().at(0).value<KItemRangeList>();
@ -285,7 +292,8 @@ void KFileItemModelTest::testExpandItems()
m_model->setExpanded(3, true);
QVERIFY(m_model->isExpanded(3));
QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
QCOMPARE(m_model->count(), 5); // 4 items: "a/", "a/a/", "a/a/1", "a/a-1/", "a/a-1/1"
QCOMPARE(m_model->count(), 5); // 5 items: "a/", "a/a/", "a/a/1", "a/a-1/", "a/a-1/1"
QCOMPARE(m_model->expandedUrls(), allFolders);
QCOMPARE(spyInserted.count(), 1);
itemRangeList = spyInserted.takeFirst().at(0).value<KItemRangeList>();
@ -299,9 +307,28 @@ void KFileItemModelTest::testExpandItems()
// Collapse the top-level folder -> all other items should disappear
m_model->setExpanded(0, false);
QVERIFY(!m_model->isExpanded(0));
QCOMPARE(m_model->count(), 1);
QVERIFY(!m_model->expandedUrls().contains(KUrl(m_testDir->name() + "a"))); // TODO: Make sure that child URLs are also removed
QCOMPARE(spyRemoved.count(), 1);
itemRangeList = spyRemoved.takeFirst().at(0).value<KItemRangeList>();
QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 4)); // 4 items removed
// Clear the model, reload the folder and try to restore the expanded folders.
m_model->clear();
QCOMPARE(m_model->count(), 0);
QVERIFY(m_model->expandedUrls().empty());
m_dirLister->openUrl(m_testDir->url());
m_model->restoreExpandedUrls(allFolders);
QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(loadingCompleted()), DefaultTimeout));
QCOMPARE(m_model->count(), 5); // 5 items: "a/", "a/a/", "a/a/1", "a/a-1/", "a/a-1/1"
QVERIFY(m_model->isExpanded(0));
QVERIFY(m_model->isExpanded(1));
QVERIFY(!m_model->isExpanded(2));
QVERIFY(m_model->isExpanded(3));
QVERIFY(!m_model->isExpanded(4));
QCOMPARE(m_model->expandedUrls(), allFolders);
}
void KFileItemModelTest::testExpansionLevelsCompare_data()

View file

@ -151,7 +151,6 @@ DolphinView::DolphinView(const KUrl& url, QWidget* parent) :
connect(m_dirLister, SIGNAL(redirection(KUrl,KUrl)), this, SLOT(slotRedirection(KUrl,KUrl)));
connect(m_dirLister, SIGNAL(started(KUrl)), this, SLOT(slotDirListerStarted(KUrl)));
connect(m_dirLister, SIGNAL(completed()), this, SLOT(slotDirListerCompleted()));
connect(m_dirLister, SIGNAL(refreshItems(QList<QPair<KFileItem,KFileItem> >)),
this, SLOT(slotRefreshItems()));
@ -180,6 +179,11 @@ DolphinView::DolphinView(const KUrl& url, QWidget* parent) :
connect(controller, SIGNAL(itemDropEvent(int,QGraphicsSceneDragDropEvent*)), this, SLOT(slotItemDropEvent(int,QGraphicsSceneDragDropEvent*)));
connect(controller, SIGNAL(modelChanged(KItemModelBase*,KItemModelBase*)), this, SLOT(slotModelChanged(KItemModelBase*,KItemModelBase*)));
KFileItemModel* model = fileItemModel();
if (model) {
connect(model, SIGNAL(loadingCompleted()), this, SLOT(slotLoadingCompleted()));
}
KItemListSelectionManager* selectionManager = controller->selectionManager();
connect(selectionManager, SIGNAL(selectionChanged(QSet<int>,QSet<int>)),
this, SLOT(slotSelectionChanged(QSet<int>,QSet<int>)));
@ -187,7 +191,7 @@ DolphinView::DolphinView(const KUrl& url, QWidget* parent) :
m_toolTipManager = new ToolTipManager(this);
m_versionControlObserver = new VersionControlObserver(this);
m_versionControlObserver->setModel(fileItemModel());
m_versionControlObserver->setModel(model);
connect(m_versionControlObserver, SIGNAL(infoMessage(QString)), this, SIGNAL(infoMessage(QString)));
connect(m_versionControlObserver, SIGNAL(errorMessage(QString)), this, SIGNAL(errorMessage(QString)));
connect(m_versionControlObserver, SIGNAL(operationCompletedMessage(QString)), this, SIGNAL(operationCompletedMessage(QString)));
@ -812,8 +816,12 @@ void DolphinView::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* even
void DolphinView::slotModelChanged(KItemModelBase* current, KItemModelBase* previous)
{
Q_UNUSED(previous);
if (previous != 0) {
disconnect(previous, SIGNAL(loadingCompleted()), this, SLOT(slotLoadingCompleted()));
}
Q_ASSERT(qobject_cast<KFileItemModel*>(current));
connect(current, SIGNAL(loadingCompleted()), this, SLOT(slotLoadingCompleted()));
KFileItemModel* fileItemModel = static_cast<KFileItemModel*>(current);
m_versionControlObserver->setModel(fileItemModel);
@ -921,16 +929,9 @@ void DolphinView::restoreState(QDataStream& stream)
stream >> m_restoredContentsPosition;
// Restore expanded folders (only relevant for the details view - will be ignored by the view in other view modes)
QSet<KUrl> urlsToExpand;
stream >> urlsToExpand;
/*const DolphinDetailsViewExpander* expander = m_viewAccessor.setExpandedUrls(urlsToExpand);
if (expander) {
m_expanderActive = true;
connect (expander, SIGNAL(completed()), this, SLOT(slotLoadingCompleted()));
}
else {
m_expanderActive = false;
}*/
QSet<KUrl> urls;
stream >> urls;
fileItemModel()->restoreExpandedUrls(urls);
}
void DolphinView::saveState(QDataStream& stream)
@ -944,7 +945,7 @@ void DolphinView::saveState(QDataStream& stream)
stream << QPoint(x, y);
// Save expanded folders (only relevant for the details view - the set will be empty in other view modes)
//stream << m_viewAccessor.expandedUrls();
stream << fileItemModel()->expandedUrls();
}
bool DolphinView::hasSelection() const
@ -1044,7 +1045,7 @@ void DolphinView::slotDeleteFileFinished(KJob* job)
void DolphinView::slotDirListerStarted(const KUrl& url)
{
// Disable the writestate temporary until it can be determined in a fast way
// in DolphinView::slotDirListerCompleted()
// in DolphinView::slotLoadingCompleted()
if (m_isFolderWritable) {
m_isFolderWritable = false;
emit writeStateChanged(m_isFolderWritable);
@ -1053,7 +1054,7 @@ void DolphinView::slotDirListerStarted(const KUrl& url)
emit startedPathLoading(url);
}
void DolphinView::slotDirListerCompleted()
void DolphinView::slotLoadingCompleted()
{
// Update the view-state. This has to be done using a Qt::QueuedConnection
// because the view might not be in its final state yet (the view also

View file

@ -640,10 +640,12 @@ private slots:
void slotDirListerStarted(const KUrl& url);
/**
* Invoked when the directory lister has completed the loading of
* items. Assures that pasted items and renamed items get seleced.
* Invoked when the file item model indicates that the directory lister has completed the loading
* of items, and that expanded folders have been restored (if the view mode is 'Details', and the
* view state is restored after navigating back or forward in history). Assures that pasted items
* and renamed items get seleced.
*/
void slotDirListerCompleted();
void slotLoadingCompleted();
/**
* Is invoked when the KDirLister indicates refreshed items.