diff --git a/src/dolphinviewcontainer.cpp b/src/dolphinviewcontainer.cpp index 28bf5250c4..1c15c88a99 100644 --- a/src/dolphinviewcontainer.cpp +++ b/src/dolphinviewcontainer.cpp @@ -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); } } diff --git a/src/dolphinviewcontainer.h b/src/dolphinviewcontainer.h index 59b3c79edd..734021aa99 100644 --- a/src/dolphinviewcontainer.h +++ b/src/dolphinviewcontainer.h @@ -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 diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index 7644ae5ef2..685af89729 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -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" diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h index 42cb154030..eaf35fecd9 100644 --- a/src/kitemviews/kfileitemmodel.h +++ b/src/kitemviews/kfileitemmodel.h @@ -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 m_dirLister; @@ -344,6 +363,7 @@ private: bool m_sortFoldersFirst; RoleType m_sortRole; + int m_sortProgressPercent; // Value of sortProgress() signal QSet m_roles; Qt::CaseSensitivity m_caseSensitivity; @@ -385,6 +405,7 @@ private: QSet m_urlsToExpand; friend class KFileItemModelSortAlgorithm; // Accesses lessThan() method + friend class KFileItemModelRolesUpdater; // Accesses emitSortProgress() method friend class KFileItemModelTest; // For unit testing }; diff --git a/src/kitemviews/kfileitemmodelrolesupdater.cpp b/src/kitemviews/kfileitemmodelrolesupdater.cpp index 34165843ff..632df676d4 100644 --- a/src/kitemviews/kfileitemmodelrolesupdater.cpp +++ b/src/kitemviews/kfileitemmodelrolesupdater.cpp @@ -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)), this, SLOT(slotItemsChanged(KItemRangeList,QSet))); + 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& roles) QSetIterator 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& 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)), this, SLOT(slotItemsChanged(KItemRangeList,QSet))); + + 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 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 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()) { diff --git a/src/kitemviews/kfileitemmodelrolesupdater.h b/src/kitemviews/kfileitemmodelrolesupdater.h index e5e105e040..ce8cf1c73e 100644 --- a/src/kitemviews/kfileitemmodelrolesupdater.h +++ b/src/kitemviews/kfileitemmodelrolesupdater.h @@ -128,6 +128,8 @@ private slots: void slotItemsRemoved(const KItemRangeList& itemRanges); void slotItemsChanged(const KItemRangeList& itemRanges, const QSet& 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 m_roles; + QSet m_resolvableRoles; QStringList m_enabledPlugins; QSet m_pendingVisibleItems; diff --git a/src/kitemviews/knepomukrolesprovider.cpp b/src/kitemviews/knepomukrolesprovider.cpp index ac371e1890..25fb161212 100644 --- a/src/kitemviews/knepomukrolesprovider.cpp +++ b/src/kitemviews/knepomukrolesprovider.cpp @@ -44,9 +44,9 @@ KNepomukRolesProvider::~KNepomukRolesProvider() { } -bool KNepomukRolesProvider::isNepomukRole(const QByteArray& role) const +QSet KNepomukRolesProvider::roles() const { - return m_roles.contains(role); + return m_roles; } QHash KNepomukRolesProvider::roleValues(const Nepomuk::Resource& resource, diff --git a/src/kitemviews/knepomukrolesprovider_p.h b/src/kitemviews/knepomukrolesprovider_p.h index c7781cd002..46a78d4ee7 100644 --- a/src/kitemviews/knepomukrolesprovider_p.h +++ b/src/kitemviews/knepomukrolesprovider_p.h @@ -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 roles() const; /** * @return Values for the roles \a roles that can be determined from the file diff --git a/src/statusbar/dolphinstatusbar.cpp b/src/statusbar/dolphinstatusbar.cpp index b01f6042cb..71e86dd60f 100644 --- a/src/statusbar/dolphinstatusbar.cpp +++ b/src/statusbar/dolphinstatusbar.cpp @@ -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())); diff --git a/src/views/dolphinview.cpp b/src/views/dolphinview.cpp index 872853642a..2ffc33e08f 100644 --- a/src/views/dolphinview.cpp +++ b/src/views/dolphinview.cpp @@ -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(); diff --git a/src/views/dolphinview.h b/src/views/dolphinview.h index 130657b166..b1d057e6f4 100644 --- a/src/views/dolphinview.h +++ b/src/views/dolphinview.h @@ -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;