]> cloud.milkyroute.net Git - dolphin.git/commitdiff
Improvements for slow sorting roles
authorPeter Penz <peter.penz19@gmail.com>
Tue, 10 Apr 2012 14:30:50 +0000 (16:30 +0200)
committerPeter Penz <peter.penz19@gmail.com>
Tue, 10 Apr 2012 14:36:26 +0000 (16:36 +0200)
If the sorting is done for data which is resolved asynchronously
(e.g. rating), it is important to give a visual feedback about
the state of the sorting. This is done now by a progress
indication in the statusbar.

Also optimizations for "Sort by type" have been done: Although
resolving a type can be expensive in the most often case it is a
very cheap operation. So it the sorting is done by type, try
to resolve the type synchronously for at least 200 ms to prevent
a asynchronous resorting. This is usually sufficient to have
resolved types even for directories with several thousands of
items.

BUG: 292733
FIXED-IN: 4.9.0

src/dolphinviewcontainer.cpp
src/dolphinviewcontainer.h
src/kitemviews/kfileitemmodel.cpp
src/kitemviews/kfileitemmodel.h
src/kitemviews/kfileitemmodelrolesupdater.cpp
src/kitemviews/kfileitemmodelrolesupdater.h
src/kitemviews/knepomukrolesprovider.cpp
src/kitemviews/knepomukrolesprovider_p.h
src/statusbar/dolphinstatusbar.cpp
src/views/dolphinview.cpp
src/views/dolphinview.h

index 28bf5250c459510520d32b2f2e11ab53f727f880..1c15c88a99c6d8699d5d053a1c42b86b974ca7bf 100644 (file)
@@ -102,7 +102,8 @@ DolphinViewContainer::DolphinViewContainer(const KUrl& url, QWidget* parent) :
     connect(m_view, SIGNAL(startedPathLoading(KUrl)),     this, SLOT(slotStartedPathLoading()));
     connect(m_view, SIGNAL(finishedPathLoading(KUrl)),    this, SLOT(slotFinishedPathLoading()));
     connect(m_view, SIGNAL(itemCountChanged()),           this, SLOT(delayedStatusBarUpdate()));
-    connect(m_view, SIGNAL(pathLoadingProgress(int)),     this, SLOT(updateProgress(int)));
+    connect(m_view, SIGNAL(pathLoadingProgress(int)),     this, SLOT(updateLoadingProgress(int)));
+    connect(m_view, SIGNAL(sortProgress(int)),            this, SLOT(updateSortProgress(int)));
     connect(m_view, SIGNAL(infoMessage(QString)),         this, SLOT(showInfoMessage(QString)));
     connect(m_view, SIGNAL(errorMessage(QString)),        this, SLOT(showErrorMessage(QString)));
     connect(m_view, SIGNAL(urlIsFileError(KUrl)),         this, SLOT(openFile(KUrl)));
@@ -332,7 +333,7 @@ void DolphinViewContainer::updateStatusBar()
     }
 }
 
-void DolphinViewContainer::updateProgress(int percent)
+void DolphinViewContainer::updateLoadingProgress(int percent)
 {
     if (m_statusBar->progressText().isEmpty()) {
         m_statusBar->setProgressText(i18nc("@info:progress", "Loading folder..."));
@@ -340,6 +341,14 @@ void DolphinViewContainer::updateProgress(int percent)
     m_statusBar->setProgress(percent);
 }
 
+void DolphinViewContainer::updateSortProgress(int percent)
+{
+    if (m_statusBar->progressText().isEmpty()) {
+        m_statusBar->setProgressText(i18nc("@info:progress", "Sorting..."));
+    }
+    m_statusBar->setProgress(percent);
+}
+
 void DolphinViewContainer::slotStartedPathLoading()
 {
     if (isSearchUrl(url())) {
@@ -352,7 +361,7 @@ void DolphinViewContainer::slotStartedPathLoading()
         // Trigger an undetermined progress indication. The progress
         // information in percent will be triggered by the percent() signal
         // of the directory lister later.
-        updateProgress(-1);
+        updateLoadingProgress(-1);
     }
 }
 
index 59b3c79eddbde1da152977a8590012065e2eeb1c..734021aa9927731ae2ec889f4c779a5e8eacda9a 100644 (file)
@@ -154,7 +154,9 @@ private slots:
      */
     void updateStatusBar();
 
-    void updateProgress(int percent);
+    void updateLoadingProgress(int percent);
+
+    void updateSortProgress(int percent);
 
     /**
      * Updates the statusbar to show an undetermined progress with the correct
index 7644ae5ef216e6bb719e1fd4e8f38fccd730cd53..685af8972901421934178b5d59877a476c187e06 100644 (file)
@@ -38,6 +38,7 @@ KFileItemModel::KFileItemModel(KDirLister* dirLister, QObject* parent) :
     m_naturalSorting(KGlobalSettings::naturalSorting()),
     m_sortFoldersFirst(true),
     m_sortRole(NameRole),
+    m_sortProgressPercent(-1),
     m_roles(),
     m_caseSensitivity(Qt::CaseInsensitive),
     m_itemData(),
@@ -878,6 +879,13 @@ void KFileItemModel::insertItems(const KFileItemList& items)
         return;
     }
 
+    if (m_sortRole == TypeRole) {
+        // Try to resolve the MIME-types synchronously to prevent a reordering of
+        // the items when sorting by type (per default MIME-types are resolved
+        // asynchronously by KFileItemModelRolesUpdater).
+        determineMimeTypes(items, 200);
+    }
+
 #ifdef KFILEITEMMODEL_DEBUG
     QElapsedTimer timer;
     timer.start();
@@ -1829,6 +1837,34 @@ KFileItemList KFileItemModel::childItems(const KFileItem& item) const
     return items;
 }
 
+void KFileItemModel::emitSortProgress(int resolvedCount)
+{
+    // Be tolerant against a resolvedCount with a wrong range.
+    // Although there should not be a case where KFileItemModelRolesUpdater
+    // (= caller) provides a wrong range, it is important to emit
+    // a useful progress information even if there is an unexpected
+    // implementation issue.
+
+    const int itemCount = count();
+    if (resolvedCount >= itemCount) {
+        m_sortProgressPercent = -1;
+        if (m_resortAllItemsTimer->isActive()) {
+            m_resortAllItemsTimer->stop();
+            resortAllItems();
+        }
+
+        emit sortProgress(100);
+    } else if (itemCount > 0) {
+        resolvedCount = qBound(0, resolvedCount, itemCount);
+
+        const int progress = resolvedCount * 100 / itemCount;
+        if (m_sortProgressPercent != progress) {
+            m_sortProgressPercent = progress;
+            emit sortProgress(progress);
+        }
+    }
+}
+
 const KFileItemModel::RoleInfoMap* KFileItemModel::rolesInfoMap(int& count)
 {
     static const RoleInfoMap rolesInfoMap[] = {
@@ -1861,4 +1897,18 @@ const KFileItemModel::RoleInfoMap* KFileItemModel::rolesInfoMap(int& count)
     return rolesInfoMap;
 }
 
+void KFileItemModel::determineMimeTypes(const KFileItemList& items, int timeout)
+{
+    QElapsedTimer timer;
+    timer.start();
+    foreach (KFileItem item, items) {
+        item.determineMimeType();
+        if (timer.elapsed() > timeout) {
+            // Don't block the user interface, let the remaining items
+            // be resolved asynchronously.
+            return;
+        }
+    }
+}
+
 #include "kfileitemmodel.moc"
index 42cb15403079463165e9c8b116caa712dcba40ec..eaf35fecd92b641d60767c7bc79a7f0854d3e511 100644 (file)
@@ -184,6 +184,13 @@ signals:
      */
     void loadingCompleted();
 
+    /**
+     * Is emitted if the sort-role gets resolved asynchronously and provides
+     * the progress-information of the sorting in percent. It is assured
+     * that the last sortProgress-signal contains 100 as value.
+     */
+    void sortProgress(int percent);
+
 protected:
     virtual void onGroupedSortingChanged(bool current);
     virtual void onSortRoleChanged(const QByteArray& current, const QByteArray& previous);
@@ -316,6 +323,12 @@ private:
      */
     KFileItemList childItems(const KFileItem& item) const;
 
+    /**
+     * Is invoked by KFileItemModelRolesUpdater and results in emitting the
+     * sortProgress signal with a percent-value of the progress.
+     */
+    void emitSortProgress(int resolvedCount);
+
     /**
      * Maps the QByteArray-roles to RoleTypes and provides translation- and
      * group-contexts.
@@ -337,6 +350,12 @@ private:
      */
     static const RoleInfoMap* rolesInfoMap(int& count);
 
+    /**
+     * Determines the MIME-types of all items that can be done within
+     * the given timeout.
+     */
+    static void determineMimeTypes(const KFileItemList& items, int timeout);
+
 private:
     QWeakPointer<KDirLister> m_dirLister;
 
@@ -344,6 +363,7 @@ private:
     bool m_sortFoldersFirst;
 
     RoleType m_sortRole;
+    int m_sortProgressPercent; // Value of sortProgress() signal
     QSet<QByteArray> m_roles;
     Qt::CaseSensitivity m_caseSensitivity;
 
@@ -385,6 +405,7 @@ private:
     QSet<KUrl> m_urlsToExpand;
 
     friend class KFileItemModelSortAlgorithm;  // Accesses lessThan() method
+    friend class KFileItemModelRolesUpdater;   // Accesses emitSortProgress() method
     friend class KFileItemModelTest;           // For unit testing
 };
 
index 34165843ff2915301cd12a86563cb41936c4b0df..632df676d427ee1fd7a34e643e0bfe2121421aea 100644 (file)
@@ -69,6 +69,7 @@ KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QO
     m_previewShown(false),
     m_enlargeSmallPreviews(true),
     m_clearPreviews(false),
+    m_sortProgress(-1),
     m_model(model),
     m_iconSize(),
     m_firstVisibleIndex(0),
@@ -100,6 +101,8 @@ KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QO
             this,    SLOT(slotItemsRemoved(KItemRangeList)));
     connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
             this,    SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
+    connect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)),
+            this,    SLOT(slotSortRoleChanged(QByteArray,QByteArray)));
 
     // Use a timer to prevent that each call of slotItemsChanged() results in a synchronous
     // resolving of the roles. Postpone the resolving until no update has been done for 1 second.
@@ -107,6 +110,13 @@ KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QO
     m_changedItemsTimer->setInterval(1000);
     m_changedItemsTimer->setSingleShot(true);
     connect(m_changedItemsTimer, SIGNAL(timeout()), this, SLOT(resolveChangedItems()));
+
+    m_resolvableRoles.insert("size");
+    m_resolvableRoles.insert("type");
+    m_resolvableRoles.insert("isExpandable");
+#ifdef HAVE_NEPOMUK
+    m_resolvableRoles += KNepomukRolesProvider::instance().roles();
+#endif
 }
 
 KFileItemModelRolesUpdater::~KFileItemModelRolesUpdater()
@@ -245,7 +255,7 @@ void KFileItemModelRolesUpdater::setRoles(const QSet<QByteArray>& roles)
         QSetIterator<QByteArray> it(roles);
         while (it.hasNext()) {
             const QByteArray& role = it.next();
-            if (rolesProvider.isNepomukRole(role)) {
+            if (rolesProvider.roles().contains(role)) {
                 hasNepomukRole = true;
                 break;
             }
@@ -270,6 +280,8 @@ void KFileItemModelRolesUpdater::setRoles(const QSet<QByteArray>& roles)
         }
 #endif
 
+        updateSortProgress();
+
         if (m_paused) {
             m_rolesChangedDuringPausing = true;
         } else {
@@ -378,6 +390,14 @@ void KFileItemModelRolesUpdater::slotItemsChanged(const KItemRangeList& itemRang
     m_changedItemsTimer->start();
 }
 
+void KFileItemModelRolesUpdater::slotSortRoleChanged(const QByteArray& current,
+                                                     const QByteArray& previous)
+{
+    Q_UNUSED(current);
+    Q_UNUSED(previous);
+    updateSortProgress();
+}
+
 void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem& item, const QPixmap& pixmap)
 {
     m_pendingVisibleItems.remove(item);
@@ -434,6 +454,8 @@ void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem& item, const QPi
     m_model->setData(index, data);
     connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
             this,    SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
+
+    applySortProgressToModel();
 }
 
 void KFileItemModelRolesUpdater::slotPreviewFailed(const KFileItem& item)
@@ -445,6 +467,8 @@ void KFileItemModelRolesUpdater::slotPreviewFailed(const KFileItem& item)
     m_clearPreviews = true;
     applyResolvedRoles(item, ResolveAll);
     m_clearPreviews = clearPreviews;
+
+    applySortProgressToModel();
 }
 
 void KFileItemModelRolesUpdater::slotPreviewJobFinished(KJob* job)
@@ -494,6 +518,8 @@ void KFileItemModelRolesUpdater::resolveNextPendingRoles()
         m_clearPreviews = false;
     }
 
+    applySortProgressToModel();
+
 #ifdef KFILEITEMMODELROLESUPDATER_DEBUG
     static int callCount = 0;
     ++callCount;
@@ -648,10 +674,17 @@ void KFileItemModelRolesUpdater::resolvePendingRoles()
 {
     int resolvedCount = 0;
 
-    const bool hasSlowRoles = m_previewShown
-                              || m_roles.contains("size")
-                              || m_roles.contains("type")
-                              || m_roles.contains("isExpandable");
+    bool hasSlowRoles = m_previewShown;
+    if (!hasSlowRoles) {
+        QSetIterator<QByteArray> it(m_roles);
+        while (it.hasNext()) {
+            if (m_resolvableRoles.contains(it.next())) {
+                hasSlowRoles = true;
+                break;
+            }
+        }
+    }
+
     const ResolveHint resolveHint = hasSlowRoles ? ResolveFast : ResolveAll;
 
     // Resolving the MIME type can be expensive. Assure that not more than MaxBlockTimeout ms are
@@ -664,12 +697,12 @@ void KFileItemModelRolesUpdater::resolvePendingRoles()
     QSetIterator<KFileItem> visibleIt(m_pendingVisibleItems);
     while (visibleIt.hasNext()) {
         const KFileItem item = visibleIt.next();
-        applyResolvedRoles(item, resolveHint);
         if (!hasSlowRoles) {
             Q_ASSERT(!m_pendingInvisibleItems.contains(item));
-            // All roles have been resolved already by applyResolvedRoles()
+            // All roles will be resolved by applyResolvedRoles()
             m_pendingVisibleItems.remove(item);
         }
+        applyResolvedRoles(item, resolveHint);
         ++resolvedCount;
 
         if (timer.elapsed() > MaxBlockTimeout) {
@@ -720,6 +753,8 @@ void KFileItemModelRolesUpdater::resolvePendingRoles()
     }
     kDebug() << "[TIME] Resolved pending roles:" << timer.elapsed();
 #endif
+
+    applySortProgressToModel();
 }
 
 void KFileItemModelRolesUpdater::resetPendingRoles()
@@ -812,6 +847,56 @@ void KFileItemModelRolesUpdater::sortAndResolvePendingRoles()
     resolvePendingRoles();
 }
 
+void KFileItemModelRolesUpdater::applySortProgressToModel()
+{
+    if (m_sortProgress < 0) {
+        return;
+    }
+
+    // Inform the model about the progress of the resolved items,
+    // so that it can give an indication when the sorting has been finished.
+    const int resolvedCount = m_model->count()
+                              - m_pendingVisibleItems.count()
+                              - m_pendingInvisibleItems.count();
+    if (resolvedCount > 0) {
+        m_model->emitSortProgress(resolvedCount);
+        if (resolvedCount == m_model->count()) {
+            m_sortProgress = -1;
+        }
+    }
+}
+
+void KFileItemModelRolesUpdater::updateSortProgress()
+{
+    const QByteArray sortRole = m_model->sortRole();
+
+    // Optimization if the sorting is done by type: In case if all MIME-types
+    // are known, the types have been resolved already by KFileItemModel and
+    // no sort-progress feedback is required.
+    const bool showProgress = (sortRole == "type")
+                              ? hasUnknownMimeTypes()
+                              : m_resolvableRoles.contains(sortRole);
+
+    if (m_sortProgress >= 0) {
+        // Mark the current sorting as finished
+        m_model->emitSortProgress(m_model->count());
+    }
+    m_sortProgress = showProgress ? 0 : -1;
+}
+
+bool KFileItemModelRolesUpdater::hasUnknownMimeTypes() const
+{
+    const int count = m_model->count();
+    for (int i = 0; i < count; ++i) {
+        const KFileItem item = m_model->fileItem(i);
+        if (!item.isMimeTypeKnown()) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
 bool KFileItemModelRolesUpdater::applyResolvedRoles(const KFileItem& item, ResolveHint hint)
 {
     if (item.isNull()) {
index e5e105e040fcb0b65a6a91049cccd009dce97bea..ce8cf1c73e2ea309b5b3017ccb1ba0f5b968538d 100644 (file)
@@ -128,6 +128,8 @@ private slots:
     void slotItemsRemoved(const KItemRangeList& itemRanges);
     void slotItemsChanged(const KItemRangeList& itemRanges,
                           const QSet<QByteArray>& roles);
+    void slotSortRoleChanged(const QByteArray& current,
+                             const QByteArray& previous);
 
     /**
      * Is invoked after a preview has been received successfully.
@@ -180,6 +182,22 @@ private:
     void resetPendingRoles();
     void sortAndResolveAllRoles();
     void sortAndResolvePendingRoles();
+    void applySortProgressToModel();
+
+    /**
+     * Updates m_sortProgress to be 0 if the sort-role
+     * needs to get resolved asynchronously and hence a
+     * progress is required. Otherwise m_sortProgress
+     * will be set to -1 which means that no progress
+     * will be provided.
+     */
+    void updateSortProgress();
+
+    /**
+     * @return True, if at least one item from the model
+     *         has an unknown MIME-type.
+     */
+    bool hasUnknownMimeTypes() const;
 
     enum ResolveHint {
         ResolveFast,
@@ -222,11 +240,14 @@ private:
     // during the roles-updater has been paused by setPaused().
     bool m_clearPreviews;
 
+    int m_sortProgress;
+
     KFileItemModel* m_model;
     QSize m_iconSize;
     int m_firstVisibleIndex;
     int m_lastVisibleIndex;
     QSet<QByteArray> m_roles;
+    QSet<QByteArray> m_resolvableRoles;
     QStringList m_enabledPlugins;
 
     QSet<KFileItem> m_pendingVisibleItems;
index ac371e1890b89c5a9f413713f6f49350618ccb54..25fb161212a3e0537f9f91f1fa7632ea7cb27789 100644 (file)
@@ -44,9 +44,9 @@ KNepomukRolesProvider::~KNepomukRolesProvider()
 {
 }
 
-bool KNepomukRolesProvider::isNepomukRole(const QByteArray& role) const
+QSet<QByteArray> KNepomukRolesProvider::roles() const
 {
-    return m_roles.contains(role);
+    return m_roles;
 }
 
 QHash<QByteArray, QVariant> KNepomukRolesProvider::roleValues(const Nepomuk::Resource& resource,
index c7781cd002bd52bc167ddb1cbf720ca8d8fc6602..46a78d4ee78f5d02ba1883acce2b0a3cdf579767 100644 (file)
@@ -44,9 +44,9 @@ public:
     virtual ~KNepomukRolesProvider();
 
     /**
-     * @return True if the values of the role can be determined by Nepomuk.
+     * @return Roles that can be provided by KNepomukRolesProvider.
      */
-    bool isNepomukRole(const QByteArray& role) const;
+    QSet<QByteArray> roles() const;
 
     /**
      * @return Values for the roles \a roles that can be determined from the file
index b01f6042cb815358dda5a8f29979d675c1278730..71e86dd60f9e508223cb250ad0c30175f3d2d918 100644 (file)
@@ -96,7 +96,7 @@ DolphinStatusBar::DolphinStatusBar(QWidget* parent, DolphinView* view) :
     m_progressBar->hide();
 
     m_showProgressBarTimer = new QTimer(this);
-    m_showProgressBarTimer->setInterval(1000);
+    m_showProgressBarTimer->setInterval(200);
     m_showProgressBarTimer->setSingleShot(true);
     connect(m_showProgressBarTimer, SIGNAL(timeout()), this, SLOT(updateProgressInfo()));
 
index 872853642a01c157f886afab194097b1f76aa16f..2ffc33e08f0f82d80bae09d255c757769b46916f 100644 (file)
@@ -158,6 +158,7 @@ DolphinView::DolphinView(const KUrl& url, QWidget* parent) :
     KFileItemModel* model = fileItemModel();
     if (model) {
         connect(model, SIGNAL(loadingCompleted()), this, SLOT(slotLoadingCompleted()));
+        connect(model, SIGNAL(sortProgress(int)), this, SIGNAL(sortProgress(int)));
     }
 
     KItemListView* view = controller->view();
index 130657b166ef22baac6bc876e50390fc1ae9fe17..b1d057e6f4de9976249fbeb00a219eee18a7a025 100644 (file)
@@ -492,6 +492,8 @@ signals:
      */
     void pathLoadingProgress(int percent);
 
+    void sortProgress(int percent);
+
     /**
      * Is emitted if the DolphinView::setUrl() is invoked but the URL is not
      * a directory.
@@ -670,8 +672,6 @@ private slots:
 
     void hideToolTip();
 
-    //void slotUrlChangeRequested(const KUrl& url);
-
 private:
     KFileItemModel* fileItemModel() const;