mirror of
https://invent.kde.org/system/dolphin
synced 2024-11-05 18:47:12 +00:00
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:
parent
b8c3d933e6
commit
d9dbd3398a
11 changed files with 207 additions and 18 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()));
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue