Improvements for slow sorting roles

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
This commit is contained in:
Peter Penz 2012-04-10 16:30:50 +02:00
parent b8c3d933e6
commit d9dbd3398a
11 changed files with 207 additions and 18 deletions

View 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);
}
}

View 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

View 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"

View 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
};

View 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()) {

View 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;

View 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,

View 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

View 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()));

View 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();

View 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;