From 9424f5a789b98b148e3c6b39738da164c73e0431 Mon Sep 17 00:00:00 2001 From: Frank Reininghaus Date: Sat, 17 Sep 2011 14:35:25 +0200 Subject: [PATCH] Implement restoring expanded folders in Details View --- src/kitemviews/kfileitemmodel.cpp | 55 +++++++++++++++++++++++++++++-- src/kitemviews/kfileitemmodel.h | 12 +++++++ src/tests/kfileitemmodeltest.cpp | 29 +++++++++++++++- src/views/dolphinview.cpp | 33 ++++++++++--------- src/views/dolphinview.h | 8 +++-- 5 files changed, 114 insertions(+), 23 deletions(-) diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index ca1583752..f5d8d41a2 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -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; } + const KUrl url = m_sortedItems.at(index).url(); if (expanded) { - const KUrl url = m_sortedItems.at(index).url(); + 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 KFileItemModel::expandedUrls() const +{ + return m_expandedUrls; +} + +void KFileItemModel::restoreExpandedUrls(const QSet& 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() diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h index 160817043..4156b2be8 100644 --- a/src/kitemviews/kfileitemmodel.h +++ b/src/kitemviews/kfileitemmodel.h @@ -113,6 +113,11 @@ public: bool setExpanded(int index, bool expanded); bool isExpanded(int index) const; bool isExpandable(int index) const; + QSet expandedUrls() const; + void restoreExpandedUrls(const QSet& 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 m_expandedUrls; + + // Stores the URLs which have to be expanded in order to restore a previous state of the model. + QSet m_restoredExpandedUrls; + friend class KFileItemModelTest; // For unit testing }; diff --git a/src/tests/kfileitemmodeltest.cpp b/src/tests/kfileitemmodeltest.cpp index f8398c578..d3404782c 100644 --- a/src/tests/kfileitemmodeltest.cpp +++ b/src/tests/kfileitemmodeltest.cpp @@ -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 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(m_testDir->name() + "a")); QCOMPARE(spyInserted.count(), 1); KItemRangeList itemRangeList = spyInserted.takeFirst().at(0).value(); @@ -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(m_testDir->name() + "a") << KUrl(m_testDir->name() + "a/a")); QCOMPARE(spyInserted.count(), 1); itemRangeList = spyInserted.takeFirst().at(0).value(); @@ -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(); @@ -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(); 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() diff --git a/src/views/dolphinview.cpp b/src/views/dolphinview.cpp index 7cb594d8e..cdcb9f7cb 100644 --- a/src/views/dolphinview.cpp +++ b/src/views/dolphinview.cpp @@ -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 >)), 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,QSet)), this, SLOT(slotSelectionChanged(QSet,QSet))); @@ -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(current)); + connect(current, SIGNAL(loadingCompleted()), this, SLOT(slotLoadingCompleted())); KFileItemModel* fileItemModel = static_cast(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 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 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 diff --git a/src/views/dolphinview.h b/src/views/dolphinview.h index 65bd77529..8d1094273 100644 --- a/src/views/dolphinview.h +++ b/src/views/dolphinview.h @@ -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. -- 2.47.3