X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/355b2c4fb65cc1390715187018e4d38561e584a0..2f045c60109e0a5811f68bcce617236e3478e402:/src/kitemviews/kfileitemmodelrolesupdater.cpp diff --git a/src/kitemviews/kfileitemmodelrolesupdater.cpp b/src/kitemviews/kfileitemmodelrolesupdater.cpp index 098c844ab..4eb23fca8 100644 --- a/src/kitemviews/kfileitemmodelrolesupdater.cpp +++ b/src/kitemviews/kfileitemmodelrolesupdater.cpp @@ -20,25 +20,33 @@ #include "kfileitemmodelrolesupdater.h" #include "kfileitemmodel.h" -#include "kpixmapmodifier_p.h" #include #include #include #include #include +#include +#include +#include #include + +#include "private/kpixmapmodifier.h" +#include "private/kdirectorycontentscounter.h" + +#include #include #include #include #include -// Required includes for subDirectoriesCount(): -#ifdef Q_WS_WIN - #include -#else - #include - #include +#include + +#ifdef HAVE_BALOO + #include "private/kbaloorolesprovider.h" + #include + #include + #include #endif // #define KFILEITEMMODELROLESUPDATER_DEBUG @@ -48,33 +56,44 @@ namespace { // may perform a blocking operation const int MaxBlockTimeout = 200; - // Maximum number of items that will get resolved synchronously. - // The value should roughly represent the number of maximum visible - // items, as it does not make sense to resolve more items synchronously - // and probably reach the MaxBlockTimeout because of invisible items. - const int MaxResolveItemsCount = 100; + // If the number of items is smaller than ResolveAllItemsLimit, + // the roles of all items will be resolved. + const int ResolveAllItemsLimit = 500; + + // Not only the visible area, but up to ReadAheadPages before and after + // this area will be resolved. + const int ReadAheadPages = 5; } KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QObject* parent) : QObject(parent), - m_paused(false), + m_state(Idle), m_previewChangedDuringPausing(false), m_iconSizeChangedDuringPausing(false), m_rolesChangedDuringPausing(false), m_previewShown(false), + m_enlargeSmallPreviews(true), m_clearPreviews(false), + m_finishedItems(), m_model(model), m_iconSize(), m_firstVisibleIndex(0), m_lastVisibleIndex(-1), + m_maximumVisibleItems(50), m_roles(), + m_resolvableRoles(), m_enabledPlugins(), - m_pendingVisibleItems(), - m_pendingInvisibleItems(), - m_previewJobs(), - m_resolvePendingRolesTimer(0), - m_changedItemsTimer(0), - m_changedItems() + m_pendingSortRoleItems(), + m_pendingIndexes(), + m_pendingPreviewItems(), + m_previewJob(), + m_recentlyChangedItemsTimer(0), + m_recentlyChangedItems(), + m_changedItems(), + m_directoryContentsCounter(0) + #ifdef HAVE_BALOO + , m_balooFileMonitor(0) + #endif { Q_ASSERT(model); @@ -90,38 +109,46 @@ KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QO this, SLOT(slotItemsRemoved(KItemRangeList))); connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet)), this, SLOT(slotItemsChanged(KItemRangeList,QSet))); - - // A timer with a minimal timeout is used to merge several triggerPendingRolesResolving() calls - // to only one call of resolvePendingRoles(). - m_resolvePendingRolesTimer = new QTimer(this); - m_resolvePendingRolesTimer->setInterval(1); - m_resolvePendingRolesTimer->setSingleShot(true); - connect(m_resolvePendingRolesTimer, SIGNAL(timeout()), this, SLOT(resolvePendingRoles())); + connect(m_model, SIGNAL(itemsMoved(KItemRange,QList)), + this, SLOT(slotItemsMoved(KItemRange,QList))); + 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 2 seconds. - m_changedItemsTimer = new QTimer(this); - m_changedItemsTimer->setInterval(2000); - m_changedItemsTimer->setSingleShot(true); - connect(m_changedItemsTimer, SIGNAL(timeout()), this, SLOT(resolveChangedItems())); + // resolving of the roles. Postpone the resolving until no update has been done for 1 second. + m_recentlyChangedItemsTimer = new QTimer(this); + m_recentlyChangedItemsTimer->setInterval(1000); + m_recentlyChangedItemsTimer->setSingleShot(true); + connect(m_recentlyChangedItemsTimer, SIGNAL(timeout()), this, SLOT(resolveRecentlyChangedItems())); + + m_resolvableRoles.insert("size"); + m_resolvableRoles.insert("type"); + m_resolvableRoles.insert("isExpandable"); +#ifdef HAVE_BALOO + m_resolvableRoles += KBalooRolesProvider::instance().roles(); +#endif + + m_directoryContentsCounter = new KDirectoryContentsCounter(m_model, this); + connect(m_directoryContentsCounter, SIGNAL(result(QString,int)), + this, SLOT(slotDirectoryContentsCountReceived(QString,int))); } KFileItemModelRolesUpdater::~KFileItemModelRolesUpdater() { + killPreviewJob(); } void KFileItemModelRolesUpdater::setIconSize(const QSize& size) { if (size != m_iconSize) { m_iconSize = size; - if (m_paused) { + if (m_state == Paused) { m_iconSizeChangedDuringPausing = true; } else if (m_previewShown) { // An icon size change requires the regenerating of // all previews - sortAndResolveAllRoles(); - } else { - sortAndResolvePendingRoles(); + m_finishedItems.clear(); + startUpdating(); } } } @@ -148,12 +175,15 @@ void KFileItemModelRolesUpdater::setVisibleIndexRange(int index, int count) m_firstVisibleIndex = index; m_lastVisibleIndex = qMin(index + count - 1, m_model->count() - 1); - if (hasPendingRoles() && !m_paused) { - sortAndResolvePendingRoles(); - } + startUpdating(); } -void KFileItemModelRolesUpdater::setPreviewShown(bool show) +void KFileItemModelRolesUpdater::setMaximumVisibleItems(int count) +{ + m_maximumVisibleItems = count; +} + +void KFileItemModelRolesUpdater::setPreviewsShown(bool show) { if (show == m_previewShown) { return; @@ -164,85 +194,107 @@ void KFileItemModelRolesUpdater::setPreviewShown(bool show) m_clearPreviews = true; } - if (m_paused) { - m_previewChangedDuringPausing = true; - } else { - sortAndResolveAllRoles(); - } + updateAllPreviews(); } -bool KFileItemModelRolesUpdater::isPreviewShown() const +bool KFileItemModelRolesUpdater::previewsShown() const { return m_previewShown; } -void KFileItemModelRolesUpdater::setEnabledPlugins(const QStringList& list) +void KFileItemModelRolesUpdater::setEnlargeSmallPreviews(bool enlarge) { - if (m_enabledPlugins == list) { - return; + if (enlarge != m_enlargeSmallPreviews) { + m_enlargeSmallPreviews = enlarge; + if (m_previewShown) { + updateAllPreviews(); + } } +} - m_enabledPlugins = list; - if (m_previewShown) { - if (m_paused) { - m_previewChangedDuringPausing = true; - } else { - sortAndResolveAllRoles(); +bool KFileItemModelRolesUpdater::enlargeSmallPreviews() const +{ + return m_enlargeSmallPreviews; +} + +void KFileItemModelRolesUpdater::setEnabledPlugins(const QStringList& list) +{ + if (m_enabledPlugins != list) { + m_enabledPlugins = list; + if (m_previewShown) { + updateAllPreviews(); } } } void KFileItemModelRolesUpdater::setPaused(bool paused) { - if (paused == m_paused) { + if (paused == (m_state == Paused)) { return; } - m_paused = paused; if (paused) { - if (hasPendingRoles()) { - foreach (KJob* job, m_previewJobs) { - job->kill(); - } - Q_ASSERT(m_previewJobs.isEmpty()); - } + m_state = Paused; + killPreviewJob(); } else { - const bool resolveAll = (m_iconSizeChangedDuringPausing && m_previewShown) || - m_previewChangedDuringPausing || - m_rolesChangedDuringPausing; + const bool updatePreviews = (m_iconSizeChangedDuringPausing && m_previewShown) || + m_previewChangedDuringPausing; + const bool resolveAll = updatePreviews || m_rolesChangedDuringPausing; if (resolveAll) { - sortAndResolveAllRoles(); - } else { - sortAndResolvePendingRoles(); + m_finishedItems.clear(); } m_iconSizeChangedDuringPausing = false; m_previewChangedDuringPausing = false; m_rolesChangedDuringPausing = false; + + if (!m_pendingSortRoleItems.isEmpty()) { + m_state = ResolvingSortRole; + resolveNextSortRole(); + } else { + m_state = Idle; + } + + startUpdating(); } } void KFileItemModelRolesUpdater::setRoles(const QSet& roles) { - if (roles.count() == m_roles.count()) { - bool isEqual = true; - foreach (const QByteArray& role, roles) { - if (!m_roles.contains(role)) { - isEqual = false; + if (m_roles != roles) { + m_roles = roles; + +#ifdef HAVE_BALOO + // Check whether there is at least one role that must be resolved + // with the help of Baloo. If this is the case, a (quite expensive) + // resolving will be done in KFileItemModelRolesUpdater::rolesData() and + // the role gets watched for changes. + const KBalooRolesProvider& rolesProvider = KBalooRolesProvider::instance(); + bool hasBalooRole = false; + QSetIterator it(roles); + while (it.hasNext()) { + const QByteArray& role = it.next(); + if (rolesProvider.roles().contains(role)) { + hasBalooRole = true; break; } } - if (isEqual) { - return; - } - } - m_roles = roles; + if (hasBalooRole && !m_balooFileMonitor) { + m_balooFileMonitor = new Baloo::FileMonitor(this); + connect(m_balooFileMonitor, SIGNAL(fileMetaDataChanged(QString)), + this, SLOT(applyChangedBalooRoles(QString))); + } else if (!hasBalooRole && m_balooFileMonitor) { + delete m_balooFileMonitor; + m_balooFileMonitor = 0; + } +#endif - if (m_paused) { - m_rolesChangedDuringPausing = true; - } else { - sortAndResolveAllRoles(); + if (m_state == Paused) { + m_rolesChangedDuringPausing = true; + } else { + startUpdating(); + } } } @@ -253,7 +305,7 @@ QSet KFileItemModelRolesUpdater::roles() const bool KFileItemModelRolesUpdater::isPaused() const { - return m_paused; + return m_state == Paused; } QStringList KFileItemModelRolesUpdater::enabledPlugins() const @@ -263,69 +315,173 @@ QStringList KFileItemModelRolesUpdater::enabledPlugins() const void KFileItemModelRolesUpdater::slotItemsInserted(const KItemRangeList& itemRanges) { - startUpdating(itemRanges); + QElapsedTimer timer; + timer.start(); + + // Determine the sort role synchronously for as many items as possible. + if (m_resolvableRoles.contains(m_model->sortRole())) { + int insertedCount = 0; + foreach (const KItemRange& range, itemRanges) { + const int lastIndex = insertedCount + range.index + range.count - 1; + for (int i = insertedCount + range.index; i <= lastIndex; ++i) { + if (timer.elapsed() < MaxBlockTimeout) { + applySortRole(i); + } else { + m_pendingSortRoleItems.insert(m_model->fileItem(i)); + } + } + insertedCount += range.count; + } + + applySortProgressToModel(); + + // If there are still items whose sort role is unknown, check if the + // asynchronous determination of the sort role is already in progress, + // and start it if that is not the case. + if (!m_pendingSortRoleItems.isEmpty() && m_state != ResolvingSortRole) { + killPreviewJob(); + m_state = ResolvingSortRole; + resolveNextSortRole(); + } + } + + startUpdating(); } void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList& itemRanges) { Q_UNUSED(itemRanges); - m_firstVisibleIndex = 0; - m_lastVisibleIndex = -1; - if (!hasPendingRoles()) { - return; + + const bool allItemsRemoved = (m_model->count() == 0); + +#ifdef HAVE_BALOO + if (m_balooFileMonitor) { + // Don't let the FileWatcher watch for removed items + if (allItemsRemoved) { + m_balooFileMonitor->clear(); + } else { + QStringList newFileList; + foreach (const QString& itemUrl, m_balooFileMonitor->files()) { + if (m_model->index(itemUrl) >= 0) { + newFileList.append(itemUrl); + } + } + m_balooFileMonitor->setFiles(newFileList); + } } +#endif - if (m_model->count() == 0) { - // Most probably a directory change is done. Clear all pending items - // and also kill all ongoing preview-jobs. - resetPendingRoles(); + if (allItemsRemoved) { + m_state = Idle; + m_finishedItems.clear(); + m_pendingSortRoleItems.clear(); + m_pendingIndexes.clear(); + m_pendingPreviewItems.clear(); + m_recentlyChangedItems.clear(); + m_recentlyChangedItemsTimer->stop(); m_changedItems.clear(); - m_changedItemsTimer->stop(); + + killPreviewJob(); } else { - // Remove all items from m_pendingVisibleItems and m_pendingInvisibleItems - // that are not part of the model anymore. The items from m_changedItems - // don't need to be handled here, removed items are just skipped in - // resolveChangedItems(). - for (int i = 0; i <= 1; ++i) { - QSet& pendingItems = (i == 0) ? m_pendingVisibleItems : m_pendingInvisibleItems; - QMutableSetIterator it(pendingItems); - while (it.hasNext()) { - const KFileItem item = it.next(); - if (m_model->index(item) < 0) { - pendingItems.remove(item); - } + // Only remove the items from m_finishedItems. They will be removed + // from the other sets later on. + QSet::iterator it = m_finishedItems.begin(); + while (it != m_finishedItems.end()) { + if (m_model->index(*it) < 0) { + it = m_finishedItems.erase(it); + } else { + ++it; } } + + // The visible items might have changed. + startUpdating(); } } +void KFileItemModelRolesUpdater::slotItemsMoved(const KItemRange& itemRange, QList movedToIndexes) +{ + Q_UNUSED(itemRange); + Q_UNUSED(movedToIndexes); + + // The visible items might have changed. + startUpdating(); +} + void KFileItemModelRolesUpdater::slotItemsChanged(const KItemRangeList& itemRanges, const QSet& roles) { Q_UNUSED(roles); - if (m_changedItemsTimer->isActive()) { - // A call of slotItemsChanged() has been done recently. Postpone the resolving - // of the roles until the timer has exceeded. - foreach (const KItemRange& itemRange, itemRanges) { - int index = itemRange.index; - for (int count = itemRange.count; count > 0; --count) { - m_changedItems.insert(m_model->fileItem(index)); - ++index; + // Find out if slotItemsChanged() has been done recently. If that is the + // case, resolving the roles is postponed until a timer has exceeded + // to prevent expensive repeated updates if files are updated frequently. + const bool itemsChangedRecently = m_recentlyChangedItemsTimer->isActive(); + + QSet& targetSet = itemsChangedRecently ? m_recentlyChangedItems : m_changedItems; + + foreach (const KItemRange& itemRange, itemRanges) { + int index = itemRange.index; + for (int count = itemRange.count; count > 0; --count) { + const KFileItem item = m_model->fileItem(index); + targetSet.insert(item); + ++index; + } + } + + m_recentlyChangedItemsTimer->start(); + + if (!itemsChangedRecently) { + updateChangedItems(); + } +} + +void KFileItemModelRolesUpdater::slotSortRoleChanged(const QByteArray& current, + const QByteArray& previous) +{ + Q_UNUSED(current); + Q_UNUSED(previous); + + if (m_resolvableRoles.contains(current)) { + m_pendingSortRoleItems.clear(); + m_finishedItems.clear(); + + const int count = m_model->count(); + QElapsedTimer timer; + timer.start(); + + // Determine the sort role synchronously for as many items as possible. + for (int index = 0; index < count; ++index) { + if (timer.elapsed() < MaxBlockTimeout) { + applySortRole(index); + } else { + m_pendingSortRoleItems.insert(m_model->fileItem(index)); } } + + applySortProgressToModel(); + + if (!m_pendingSortRoleItems.isEmpty()) { + // Trigger the asynchronous determination of the sort role. + killPreviewJob(); + m_state = ResolvingSortRole; + resolveNextSortRole(); + } } else { - // No call of slotItemsChanged() has been done recently, resolve the roles now. - startUpdating(itemRanges); + m_state = Idle; + m_pendingSortRoleItems.clear(); + applySortProgressToModel(); } - m_changedItemsTimer->start(); } void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem& item, const QPixmap& pixmap) { - m_pendingVisibleItems.remove(item); - m_pendingInvisibleItems.remove(item); + if (m_state != PreviewJobRunning) { + return; + } + + m_changedItems.remove(item); const int index = m_model->index(item); if (index < 0) { @@ -338,12 +494,55 @@ void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem& item, const QPi const int slashIndex = mimeType.indexOf(QLatin1Char('/')); const QString mimeTypeGroup = mimeType.left(slashIndex); if (mimeTypeGroup == QLatin1String("image")) { - KPixmapModifier::applyFrame(scaledPixmap, m_iconSize); + if (m_enlargeSmallPreviews) { + KPixmapModifier::applyFrame(scaledPixmap, m_iconSize); + } else { + // Assure that small previews don't get enlarged. Instead they + // should be shown centered within the frame. + const QSize contentSize = KPixmapModifier::sizeInsideFrame(m_iconSize); + const bool enlargingRequired = scaledPixmap.width() < contentSize.width() && + scaledPixmap.height() < contentSize.height(); + if (enlargingRequired) { + QSize frameSize = scaledPixmap.size(); + frameSize.scale(m_iconSize, Qt::KeepAspectRatio); + + QPixmap largeFrame(frameSize); + largeFrame.fill(Qt::transparent); + + KPixmapModifier::applyFrame(largeFrame, frameSize); + + QPainter painter(&largeFrame); + painter.drawPixmap((largeFrame.width() - scaledPixmap.width()) / 2, + (largeFrame.height() - scaledPixmap.height()) / 2, + scaledPixmap); + scaledPixmap = largeFrame; + } else { + // The image must be shrinked as it is too large to fit into + // the available icon size + KPixmapModifier::applyFrame(scaledPixmap, m_iconSize); + } + } } else { KPixmapModifier::scale(scaledPixmap, m_iconSize); } QHash data = rolesData(item); + + const QStringList overlays = data["iconOverlays"].toStringList(); + // Strangely KFileItem::overlays() returns empty string-values, so + // we need to check first whether an overlay must be drawn at all. + // It is more efficient to do it here, as KIconLoader::drawOverlays() + // assumes that an overlay will be drawn and has some additional + // setup time. + foreach (const QString& overlay, overlays) { + if (!overlay.isEmpty()) { + // There is at least one overlay, draw all overlays above m_pixmap + // and cancel the check + KIconLoader::global()->drawOverlays(overlays, scaledPixmap, KIconLoader::Desktop); + break; + } + } + data.insert("iconPixmap", scaledPixmap); disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet)), @@ -351,217 +550,312 @@ 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))); + + m_finishedItems.insert(item); } void KFileItemModelRolesUpdater::slotPreviewFailed(const KFileItem& item) { - m_pendingVisibleItems.remove(item); - m_pendingInvisibleItems.remove(item); + if (m_state != PreviewJobRunning) { + return; + } - const bool clearPreviews = m_clearPreviews; - m_clearPreviews = true; - applyResolvedRoles(item, ResolveAll); - m_clearPreviews = clearPreviews; -} + m_changedItems.remove(item); -void KFileItemModelRolesUpdater::slotPreviewJobFinished(KJob* job) -{ -#ifdef KFILEITEMMODELROLESUPDATER_DEBUG - kDebug() << "Preview job finished. Pending visible:" << m_pendingVisibleItems.count() << "invisible:" << m_pendingInvisibleItems.count(); -#endif + const int index = m_model->index(item); + if (index >= 0) { + QHash data; + data.insert("iconPixmap", QPixmap()); - m_previewJobs.removeOne(job); - if (!m_previewJobs.isEmpty() || !hasPendingRoles()) { - return; - } + disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet)), + this, SLOT(slotItemsChanged(KItemRangeList,QSet))); + m_model->setData(index, data); + connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet)), + this, SLOT(slotItemsChanged(KItemRangeList,QSet))); - const KFileItemList visibleItems = sortedItems(m_pendingVisibleItems); - startPreviewJob(visibleItems + m_pendingInvisibleItems.toList()); + applyResolvedRoles(index, ResolveAll); + m_finishedItems.insert(item); + } } -void KFileItemModelRolesUpdater::resolvePendingRoles() +void KFileItemModelRolesUpdater::slotPreviewJobFinished() { - int resolvedCount = 0; - - const bool hasSlowRoles = m_previewShown - || m_roles.contains("size") - || m_roles.contains("type") - || m_roles.contains("isExpandable"); - const ResolveHint resolveHint = hasSlowRoles ? ResolveFast : ResolveAll; + m_previewJob = 0; - // Resolving the MIME type can be expensive. Assure that not more than MaxBlockTimeout ms are - // spend for resolving them synchronously. Usually this is more than enough to determine - // all visible items, but there are corner cases where this limit gets easily exceeded. - QElapsedTimer timer; - timer.start(); + if (m_state != PreviewJobRunning) { + return; + } - // Resolve the MIME type of all visible items - 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() - m_pendingVisibleItems.remove(item); - } - ++resolvedCount; + m_state = Idle; - if (timer.elapsed() > MaxBlockTimeout) { - break; + if (!m_pendingPreviewItems.isEmpty()) { + startPreviewJob(); + } else { + if (!m_changedItems.isEmpty()) { + updateChangedItems(); } } +} - // Resolve the MIME type of the invisible items at least until the timeout - // has been exceeded or the maximum number of items has been reached - KFileItemList invisibleItems; - if (m_lastVisibleIndex >= 0) { - // The visible range is valid, don't care about the order how the MIME - // type of invisible items get resolved - invisibleItems = m_pendingInvisibleItems.toList(); - } else { - // The visible range is temporary invalid (e.g. happens when loading - // a directory) so take care to sort the currently invisible items where - // a part will get visible later - invisibleItems = sortedItems(m_pendingInvisibleItems); +void KFileItemModelRolesUpdater::resolveNextSortRole() +{ + if (m_state != ResolvingSortRole) { + return; } - int index = 0; - while (resolvedCount < MaxResolveItemsCount && index < invisibleItems.count() && timer.elapsed() <= MaxBlockTimeout) { - const KFileItem item = invisibleItems.at(index); - applyResolvedRoles(item, resolveHint); + QSet::iterator it = m_pendingSortRoleItems.begin(); + while (it != m_pendingSortRoleItems.end()) { + const KFileItem item = *it; + const int index = m_model->index(item); - if (!hasSlowRoles) { - // All roles have been resolved already by applyResolvedRoles() - m_pendingInvisibleItems.remove(item); + // Continue if the sort role has already been determined for the + // item, and the item has not been changed recently. + if (!m_changedItems.contains(item) && m_model->data(index).contains(m_model->sortRole())) { + it = m_pendingSortRoleItems.erase(it); + continue; } - ++index; - ++resolvedCount; - } - if (m_previewShown) { - KFileItemList items = sortedItems(m_pendingVisibleItems); - items += invisibleItems; - startPreviewJob(items); - } else { - QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles())); + applySortRole(index); + m_pendingSortRoleItems.erase(it); + break; } -#ifdef KFILEITEMMODELROLESUPDATER_DEBUG - if (timer.elapsed() > MaxBlockTimeout) { - kDebug() << "Maximum time of" << MaxBlockTimeout - << "ms exceeded, skipping items... Remaining visible:" << m_pendingVisibleItems.count() - << "invisible:" << m_pendingInvisibleItems.count(); + if (!m_pendingSortRoleItems.isEmpty()) { + applySortProgressToModel(); + QTimer::singleShot(0, this, SLOT(resolveNextSortRole())); + } else { + m_state = Idle; + + // Prevent that we try to update the items twice. + disconnect(m_model, SIGNAL(itemsMoved(KItemRange,QList)), + this, SLOT(slotItemsMoved(KItemRange,QList))); + applySortProgressToModel(); + connect(m_model, SIGNAL(itemsMoved(KItemRange,QList)), + this, SLOT(slotItemsMoved(KItemRange,QList))); + startUpdating(); } - kDebug() << "[TIME] Resolved pending roles:" << timer.elapsed(); -#endif } void KFileItemModelRolesUpdater::resolveNextPendingRoles() { - if (m_paused) { + if (m_state != ResolvingAllRoles) { return; } - if (m_previewShown) { - // The preview has been turned on since the last run. Skip - // resolving further pending roles as this is done as soon - // as a preview has been received. - return; - } + while (!m_pendingIndexes.isEmpty()) { + const int index = m_pendingIndexes.takeFirst(); + const KFileItem item = m_model->fileItem(index); - int resolvedCount = 0; - bool changed = false; - for (int i = 0; i <= 1; ++i) { - QSet& pendingItems = (i == 0) ? m_pendingVisibleItems : m_pendingInvisibleItems; - QSetIterator it(pendingItems); - while (it.hasNext() && !changed && resolvedCount < MaxResolveItemsCount) { - const KFileItem item = it.next(); - pendingItems.remove(item); - changed = applyResolvedRoles(item, ResolveAll); - ++resolvedCount; + if (m_finishedItems.contains(item)) { + continue; } + + applyResolvedRoles(index, ResolveAll); + m_finishedItems.insert(item); + m_changedItems.remove(item); + break; } - if (hasPendingRoles()) { + if (!m_pendingIndexes.isEmpty()) { QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles())); } else { - m_clearPreviews = false; - } + m_state = Idle; + + if (m_clearPreviews) { + // Only go through the list if there are items which might still have previews. + if (m_finishedItems.count() != m_model->count()) { + QHash data; + data.insert("iconPixmap", QPixmap()); + + disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet)), + this, SLOT(slotItemsChanged(KItemRangeList,QSet))); + for (int index = 0; index <= m_model->count(); ++index) { + if (m_model->data(index).contains("iconPixmap")) { + m_model->setData(index, data); + } + } + connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet)), + this, SLOT(slotItemsChanged(KItemRangeList,QSet))); -#ifdef KFILEITEMMODELROLESUPDATER_DEBUG - static int callCount = 0; - ++callCount; - if (callCount % 100 == 0) { - kDebug() << "Remaining visible roles to resolve:" << m_pendingVisibleItems.count() - << "invisible:" << m_pendingInvisibleItems.count(); + } + m_clearPreviews = false; + } + + if (!m_changedItems.isEmpty()) { + updateChangedItems(); + } } -#endif } -void KFileItemModelRolesUpdater::resolveChangedItems() +void KFileItemModelRolesUpdater::resolveRecentlyChangedItems() { - if (m_changedItems.isEmpty()) { + m_changedItems += m_recentlyChangedItems; + m_recentlyChangedItems.clear(); + updateChangedItems(); +} + +void KFileItemModelRolesUpdater::applyChangedBalooRoles(const QString& itemUrl) +{ +#ifdef HAVE_BALOO + const KFileItem item = m_model->fileItem(itemUrl); + + if (item.isNull()) { + // itemUrl is not in the model anymore, probably because + // the corresponding file has been deleted in the meantime. return; } - KItemRangeList itemRanges; + Baloo::FileFetchJob* job = new Baloo::FileFetchJob(item.localPath()); + connect(job, SIGNAL(finished(KJob*)), this, SLOT(applyChangedBalooRolesJobFinished(KJob*))); + job->setProperty("item", QVariant::fromValue(item)); + job->start(); +#else +#ifndef Q_CC_MSVC + Q_UNUSED(itemUrl); +#endif +#endif +} + +void KFileItemModelRolesUpdater::applyChangedBalooRolesJobFinished(KJob* kjob) +{ +#ifdef HAVE_BALOO + const KFileItem item = kjob->property("item").value(); + + const KBalooRolesProvider& rolesProvider = KBalooRolesProvider::instance(); + QHash data; + + foreach (const QByteArray& role, rolesProvider.roles()) { + // Overwrite all the role values with an empty QVariant, because the roles + // provider doesn't overwrite it when the property value list is empty. + // See bug 322348 + data.insert(role, QVariant()); + } - QSetIterator it(m_changedItems); + Baloo::FileFetchJob* job = static_cast(kjob); + QHashIterator it(rolesProvider.roleValues(job->file(), m_roles)); while (it.hasNext()) { - const KFileItem& item = it.next(); - const int index = m_model->index(item); - if (index >= 0) { - itemRanges.append(KItemRange(index, 1)); - } + it.next(); + data.insert(it.key(), it.value()); } - startUpdating(itemRanges); + disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet)), + this, SLOT(slotItemsChanged(KItemRangeList,QSet))); + const int index = m_model->index(item); + m_model->setData(index, data); + connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet)), + this, SLOT(slotItemsChanged(KItemRangeList,QSet))); +#endif } -void KFileItemModelRolesUpdater::startUpdating(const KItemRangeList& itemRanges) +void KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived(const QString& path, int count) { - // If no valid index range is given assume that all items are visible. - // A cleanup will be done later as soon as the index range has been set. - const bool hasValidIndexRange = (m_lastVisibleIndex >= 0); + const bool getSizeRole = m_roles.contains("size"); + const bool getIsExpandableRole = m_roles.contains("isExpandable"); - if (hasValidIndexRange) { - // Move all current pending visible items that are not visible anymore - // to the pending invisible items. - QSetIterator it(m_pendingVisibleItems); - while (it.hasNext()) { - const KFileItem item = it.next(); - const int index = m_model->index(item); - if (index < m_firstVisibleIndex || index > m_lastVisibleIndex) { - m_pendingVisibleItems.remove(item); - m_pendingInvisibleItems.insert(item); + if (getSizeRole || getIsExpandableRole) { + const int index = m_model->index(KUrl(path)); + if (index >= 0) { + QHash data; + + if (getSizeRole) { + data.insert("size", count); } + if (getIsExpandableRole) { + data.insert("isExpandable", count > 0); + } + + disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet)), + this, SLOT(slotItemsChanged(KItemRangeList,QSet))); + m_model->setData(index, data); + connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet)), + this, SLOT(slotItemsChanged(KItemRangeList,QSet))); } } +} + +void KFileItemModelRolesUpdater::startUpdating() +{ + if (m_state == Paused) { + return; + } - int rangesCount = 0; + if (m_finishedItems.count() == m_model->count()) { + // All roles have been resolved already. + m_state = Idle; + return; + } - foreach (const KItemRange& range, itemRanges) { - rangesCount += range.count; + // Terminate all updates that are currently active. + killPreviewJob(); + m_pendingIndexes.clear(); - // Add the inserted items to the pending visible and invisible items - const int lastIndex = range.index + range.count - 1; - for (int i = range.index; i <= lastIndex; ++i) { - const KFileItem item = m_model->fileItem(i); - if (!hasValidIndexRange || (i >= m_firstVisibleIndex && i <= m_lastVisibleIndex)) { - m_pendingVisibleItems.insert(item); - } else { - m_pendingInvisibleItems.insert(item); + QElapsedTimer timer; + timer.start(); + + // Determine the icons for the visible items synchronously. + updateVisibleIcons(); + + // A detailed update of the items in and near the visible area + // only makes sense if sorting is finished. + if (m_state == ResolvingSortRole) { + return; + } + + // Start the preview job or the asynchronous resolving of all roles. + QList indexes = indexesToResolve(); + + if (m_previewShown) { + m_pendingPreviewItems.clear(); + m_pendingPreviewItems.reserve(indexes.count()); + + foreach (int index, indexes) { + const KFileItem item = m_model->fileItem(index); + if (!m_finishedItems.contains(item)) { + m_pendingPreviewItems.append(item); } } + + startPreviewJob(); + } else { + m_pendingIndexes = indexes; + // Trigger the asynchronous resolving of all roles. + m_state = ResolvingAllRoles; + QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles())); + } +} + +void KFileItemModelRolesUpdater::updateVisibleIcons() +{ + int lastVisibleIndex = m_lastVisibleIndex; + if (lastVisibleIndex <= 0) { + // Guess a reasonable value for the last visible index if the view + // has not told us about the real value yet. + lastVisibleIndex = qMin(m_firstVisibleIndex + m_maximumVisibleItems, m_model->count() - 1); + if (lastVisibleIndex <= 0) { + lastVisibleIndex = qMin(200, m_model->count() - 1); + } + } + + QElapsedTimer timer; + timer.start(); + + // Try to determine the final icons for all visible items. + int index; + for (index = m_firstVisibleIndex; index <= lastVisibleIndex && timer.elapsed() < MaxBlockTimeout; ++index) { + applyResolvedRoles(index, ResolveFast); } - triggerPendingRolesResolving(rangesCount); + // KFileItemListView::initializeItemListWidget(KItemListWidget*) will load + // preliminary icons (i.e., without mime type determination) for the + // remaining items. } -void KFileItemModelRolesUpdater::startPreviewJob(const KFileItemList& items) +void KFileItemModelRolesUpdater::startPreviewJob() { - if (items.isEmpty() || m_paused) { + m_state = PreviewJobRunning; + + if (m_pendingPreviewItems.isEmpty()) { + QTimer::singleShot(0, this, SLOT(slotPreviewJobFinished())); return; } @@ -571,170 +865,170 @@ void KFileItemModelRolesUpdater::startPreviewJob(const KFileItemList& items) // do a downscaling anyhow because of the frame, so in this case only the provided // cache sizes are requested. const QSize cacheSize = (m_iconSize.width() > 128) || (m_iconSize.height() > 128) - ? QSize(256, 256) : QSize(128, 128); + ? QSize(256, 256) : QSize(128, 128); // KIO::filePreview() will request the MIME-type of all passed items, which (in the // worst case) might block the application for several seconds. To prevent such - // a blocking the MIME-type of the items will determined until the MaxBlockTimeout - // has been reached and only those items will get passed. As soon as the MIME-type - // has been resolved once KIO::filePreview() can already access the resolved - // MIME-type in a fast way. - QElapsedTimer timer; - timer.start(); + // a blocking, we only pass items with known mime type to the preview job. + const int count = m_pendingPreviewItems.count(); KFileItemList itemSubSet; - for (int i = 0; i < items.count(); ++i) { - KFileItem item = items.at(i); - item.determineMimeType(); - itemSubSet.append(items.at(i)); - if (timer.elapsed() > MaxBlockTimeout) { -#ifdef KFILEITEMMODELROLESUPDATER_DEBUG - kDebug() << "Maximum time of" << MaxBlockTimeout << "ms exceeded, creating only previews for" - << (i + 1) << "items," << (items.count() - (i + 1)) << "will be resolved later"; -#endif - break; - } + itemSubSet.reserve(count); + + if (m_pendingPreviewItems.first().isMimeTypeKnown()) { + // Some mime types are known already, probably because they were + // determined when loading the icons for the visible items. Start + // a preview job for all items at the beginning of the list which + // have a known mime type. + do { + itemSubSet.append(m_pendingPreviewItems.takeFirst()); + } while (!m_pendingPreviewItems.isEmpty() && m_pendingPreviewItems.first().isMimeTypeKnown()); + } else { + // Determine mime types for MaxBlockTimeout ms, and start a preview + // job for the corresponding items. + QElapsedTimer timer; + timer.start(); + + do { + const KFileItem item = m_pendingPreviewItems.takeFirst(); + item.determineMimeType(); + itemSubSet.append(item); + } while (!m_pendingPreviewItems.isEmpty() && timer.elapsed() < MaxBlockTimeout); + } + + KIO::PreviewJob* job = new KIO::PreviewJob(itemSubSet, cacheSize, &m_enabledPlugins); + + job->setIgnoreMaximumSize(itemSubSet.first().isLocalFile()); + if (job->ui()) { + KJobWidgets::setWindow(job, qApp->activeWindow()); } - KJob* job = KIO::filePreview(itemSubSet, cacheSize, &m_enabledPlugins); connect(job, SIGNAL(gotPreview(KFileItem,QPixmap)), this, SLOT(slotGotPreview(KFileItem,QPixmap))); connect(job, SIGNAL(failed(KFileItem)), this, SLOT(slotPreviewFailed(KFileItem))); connect(job, SIGNAL(finished(KJob*)), - this, SLOT(slotPreviewJobFinished(KJob*))); - - m_previewJobs.append(job); -} - - -bool KFileItemModelRolesUpdater::hasPendingRoles() const -{ - return !m_pendingVisibleItems.isEmpty() || !m_pendingInvisibleItems.isEmpty(); -} + this, SLOT(slotPreviewJobFinished())); -void KFileItemModelRolesUpdater::resetPendingRoles() -{ - m_pendingVisibleItems.clear(); - m_pendingInvisibleItems.clear(); - - foreach (KJob* job, m_previewJobs) { - job->kill(); - } - Q_ASSERT(m_previewJobs.isEmpty()); + m_previewJob = job; } -void KFileItemModelRolesUpdater::triggerPendingRolesResolving(int count) +void KFileItemModelRolesUpdater::updateChangedItems() { - if (count == m_model->count()) { - // When initially loading a directory a synchronous resolving prevents a minor - // flickering when opening directories. This is also fine from a performance point - // of view as it is assured in resolvePendingRoles() to never block the event-loop - // for more than 200 ms. - resolvePendingRoles(); - } else { - // Items have been added. This can be done in several small steps within one loop - // because of the sorting and hence may not trigger any expensive operation. - m_resolvePendingRolesTimer->start(); + if (m_state == Paused) { + return; } -} -void KFileItemModelRolesUpdater::sortAndResolveAllRoles() -{ - if (m_paused) { + if (m_changedItems.isEmpty()) { return; } - resetPendingRoles(); - Q_ASSERT(m_pendingVisibleItems.isEmpty()); - Q_ASSERT(m_pendingInvisibleItems.isEmpty()); + m_finishedItems -= m_changedItems; - if (m_model->count() == 0) { - return; - } + if (m_resolvableRoles.contains(m_model->sortRole())) { + m_pendingSortRoleItems += m_changedItems; - // Determine all visible items - Q_ASSERT(m_firstVisibleIndex >= 0); - for (int i = m_firstVisibleIndex; i <= m_lastVisibleIndex; ++i) { - const KFileItem item = m_model->fileItem(i); - if (!item.isNull()) { - m_pendingVisibleItems.insert(item); + if (m_state != ResolvingSortRole) { + // Stop the preview job if necessary, and trigger the + // asynchronous determination of the sort role. + killPreviewJob(); + m_state = ResolvingSortRole; + QTimer::singleShot(0, this, SLOT(resolveNextSortRole())); } + + return; } - // Determine all invisible items - for (int i = 0; i < m_firstVisibleIndex; ++i) { - const KFileItem item = m_model->fileItem(i); - if (!item.isNull()) { - m_pendingInvisibleItems.insert(item); + QList visibleChangedIndexes; + QList invisibleChangedIndexes; + + foreach (const KFileItem& item, m_changedItems) { + const int index = m_model->index(item); + + if (index < 0) { + m_changedItems.remove(item); + continue; } - } - for (int i = m_lastVisibleIndex + 1; i < m_model->count(); ++i) { - const KFileItem item = m_model->fileItem(i); - if (!item.isNull()) { - m_pendingInvisibleItems.insert(item); + + if (index >= m_firstVisibleIndex && index <= m_lastVisibleIndex) { + visibleChangedIndexes.append(index); + } else { + invisibleChangedIndexes.append(index); } } - triggerPendingRolesResolving(m_pendingVisibleItems.count() + - m_pendingInvisibleItems.count()); -} + std::sort(visibleChangedIndexes.begin(), visibleChangedIndexes.end()); -void KFileItemModelRolesUpdater::sortAndResolvePendingRoles() -{ - Q_ASSERT(!m_paused); - if (m_model->count() == 0) { - return; - } + if (m_previewShown) { + foreach (int index, visibleChangedIndexes) { + m_pendingPreviewItems.append(m_model->fileItem(index)); + } - // If no valid index range is given assume that all items are visible. - // A cleanup will be done later as soon as the index range has been set. - const bool hasValidIndexRange = (m_lastVisibleIndex >= 0); + foreach (int index, invisibleChangedIndexes) { + m_pendingPreviewItems.append(m_model->fileItem(index)); + } - // Trigger a preview generation of all pending items. Assure that the visible - // pending items get generated first. - QSet pendingItems; - pendingItems += m_pendingVisibleItems; - pendingItems += m_pendingInvisibleItems; + if (!m_previewJob) { + startPreviewJob(); + } + } else { + const bool resolvingInProgress = !m_pendingIndexes.isEmpty(); + m_pendingIndexes = visibleChangedIndexes + m_pendingIndexes + invisibleChangedIndexes; + if (!resolvingInProgress) { + // Trigger the asynchronous resolving of the changed roles. + m_state = ResolvingAllRoles; + QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles())); + } + } +} - resetPendingRoles(); - Q_ASSERT(m_pendingVisibleItems.isEmpty()); - Q_ASSERT(m_pendingInvisibleItems.isEmpty()); +void KFileItemModelRolesUpdater::applySortRole(int index) +{ + QHash data; + const KFileItem item = m_model->fileItem(index); - QSetIterator it(pendingItems); - while (it.hasNext()) { - const KFileItem item = it.next(); - if (item.isNull()) { - continue; + if (m_model->sortRole() == "type") { + if (!item.isMimeTypeKnown()) { + item.determineMimeType(); } - const int index = m_model->index(item); - if (!hasValidIndexRange || (index >= m_firstVisibleIndex && index <= m_lastVisibleIndex)) { - m_pendingVisibleItems.insert(item); - } else { - m_pendingInvisibleItems.insert(item); - } + data.insert("type", item.mimeComment()); + } else if (m_model->sortRole() == "size" && item.isLocalFile() && item.isDir()) { + const QString path = item.localPath(); + data.insert("size", m_directoryContentsCounter->countDirectoryContentsSynchronously(path)); + } else { + // Probably the sort role is a baloo role - just determine all roles. + data = rolesData(item); } - triggerPendingRolesResolving(m_pendingVisibleItems.count() + - m_pendingInvisibleItems.count()); + disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet)), + this, SLOT(slotItemsChanged(KItemRangeList,QSet))); + m_model->setData(index, data); + connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet)), + this, SLOT(slotItemsChanged(KItemRangeList,QSet))); } -bool KFileItemModelRolesUpdater::applyResolvedRoles(const KFileItem& item, ResolveHint hint) +void KFileItemModelRolesUpdater::applySortProgressToModel() { - if (item.isNull()) { - return false; - } + // 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_pendingSortRoleItems.count(); + m_model->emitSortProgress(resolvedCount); +} +bool KFileItemModelRolesUpdater::applyResolvedRoles(int index, ResolveHint hint) +{ + const KFileItem item = m_model->fileItem(index); const bool resolveAll = (hint == ResolveAll); - bool mimeTypeChanged = false; - if (!item.isMimeTypeKnown()) { + bool iconChanged = false; + if (!item.isMimeTypeKnown() || !item.isFinalIconKnown()) { item.determineMimeType(); - mimeTypeChanged = true; + iconChanged = true; + } else if (!m_model->data(index).contains("iconName")) { + iconChanged = true; } - if (mimeTypeChanged || resolveAll || m_clearPreviews) { - const int index = m_model->index(item); + if (iconChanged || resolveAll || m_clearPreviews) { if (index < 0) { return false; } @@ -747,7 +1041,7 @@ bool KFileItemModelRolesUpdater::applyResolvedRoles(const KFileItem& item, Resol data.insert("iconName", item.iconName()); if (m_clearPreviews) { - data.insert("iconPixmap", QString()); + data.insert("iconPixmap", QPixmap()); } disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet)), @@ -761,23 +1055,21 @@ bool KFileItemModelRolesUpdater::applyResolvedRoles(const KFileItem& item, Resol return false; } -QHash KFileItemModelRolesUpdater::rolesData(const KFileItem& item) const +QHash KFileItemModelRolesUpdater::rolesData(const KFileItem& item) { QHash data; const bool getSizeRole = m_roles.contains("size"); const bool getIsExpandableRole = m_roles.contains("isExpandable"); - if ((getSizeRole || getIsExpandableRole) && item.isDir() && item.isLocalFile()) { - const QString path = item.localPath(); - const int count = subDirectoriesCount(path); - if (count >= 0) { - if (getSizeRole) { - data.insert("size", KIO::filesize_t(count)); - } - if (getIsExpandableRole) { - data.insert("isExpandable", count > 0); - } + if ((getSizeRole || getIsExpandableRole) && item.isDir()) { + if (item.isLocalFile()) { + // Tell m_directoryContentsCounter that we want to count the items + // inside the directory. The result will be received in slotDirectoryContentsCountReceived. + const QString path = item.localPath(); + m_directoryContentsCounter->addDirectory(path); + } else if (getSizeRole) { + data.insert("size", -1); // -1 indicates an unknown number of items } } @@ -787,82 +1079,95 @@ QHash KFileItemModelRolesUpdater::rolesData(const KFileIte data.insert("iconOverlays", item.overlays()); +#ifdef HAVE_BALOO + if (m_balooFileMonitor) { + m_balooFileMonitor->addFile(item.localPath()); + applyChangedBalooRoles(item.localPath()); + } +#endif return data; } -KFileItemList KFileItemModelRolesUpdater::sortedItems(const QSet& items) const +void KFileItemModelRolesUpdater::updateAllPreviews() { - KFileItemList itemList; - if (items.isEmpty()) { - return itemList; + if (m_state == Paused) { + m_previewChangedDuringPausing = true; + } else { + m_finishedItems.clear(); + startUpdating(); } +} -#ifdef KFILEITEMMODELROLESUPDATER_DEBUG - QElapsedTimer timer; - timer.start(); -#endif +void KFileItemModelRolesUpdater::killPreviewJob() +{ + if (m_previewJob) { + disconnect(m_previewJob, SIGNAL(gotPreview(KFileItem,QPixmap)), + this, SLOT(slotGotPreview(KFileItem,QPixmap))); + disconnect(m_previewJob, SIGNAL(failed(KFileItem)), + this, SLOT(slotPreviewFailed(KFileItem))); + disconnect(m_previewJob, SIGNAL(finished(KJob*)), + this, SLOT(slotPreviewJobFinished())); + m_previewJob->kill(); + m_previewJob = 0; + m_pendingPreviewItems.clear(); + } +} - QList indexes; - indexes.reserve(items.count()); +QList KFileItemModelRolesUpdater::indexesToResolve() const +{ + const int count = m_model->count(); - QSetIterator it(items); - while (it.hasNext()) { - const KFileItem item = it.next(); - const int index = m_model->index(item); - if (index >= 0) { - indexes.append(index); - } + QList result; + result.reserve(ResolveAllItemsLimit); + + // Add visible items. + for (int i = m_firstVisibleIndex; i <= m_lastVisibleIndex; ++i) { + result.append(i); } - qSort(indexes); - itemList.reserve(items.count()); - foreach (int index, indexes) { - itemList.append(m_model->fileItem(index)); + // We need a reasonable upper limit for number of items to resolve after + // and before the visible range. m_maximumVisibleItems can be quite large + // when using Compace View. + const int readAheadItems = qMin(ReadAheadPages * m_maximumVisibleItems, ResolveAllItemsLimit / 2); + + // Add items after the visible range. + const int endExtendedVisibleRange = qMin(m_lastVisibleIndex + readAheadItems, count - 1); + for (int i = m_lastVisibleIndex + 1; i <= endExtendedVisibleRange; ++i) { + result.append(i); } -#ifdef KFILEITEMMODELROLESUPDATER_DEBUG - kDebug() << "[TIME] Sorting of items:" << timer.elapsed(); -#endif - return itemList; -} + // Add items before the visible range in reverse order. + const int beginExtendedVisibleRange = qMax(0, m_firstVisibleIndex - readAheadItems); + for (int i = m_firstVisibleIndex - 1; i >= beginExtendedVisibleRange; --i) { + result.append(i); + } -int KFileItemModelRolesUpdater::subDirectoriesCount(const QString& path) const -{ - const bool countHiddenFiles = m_model->showHiddenFiles(); + // Add items on the last page. + const int beginLastPage = qMax(qMin(endExtendedVisibleRange + 1, count - 1), count - m_maximumVisibleItems); + for (int i = beginLastPage; i < count; ++i) { + result.append(i); + } -#ifdef Q_WS_WIN - QDir dir(path); - QDir::Filters filters = QDir::AllEntries | QDir::NoDotAndDotDot | QDir::System; - if (countHiddenFiles) { - filters |= QDir::Hidden; + // Add items on the first page. + const int endFirstPage = qMin(qMax(beginExtendedVisibleRange - 1, 0), m_maximumVisibleItems); + for (int i = 0; i <= endFirstPage; ++i) { + result.append(i); } - return dir.entryList(filters).count(); -#else - // Taken from kdelibs/kio/kio/kdirmodel.cpp - // Copyright (C) 2006 David Faure - int count = -1; - DIR* dir = ::opendir(QFile::encodeName(path)); - if (dir) { - count = 0; - struct dirent *dirEntry = 0; - while ((dirEntry = ::readdir(dir))) { // krazy:exclude=syscalls - if (dirEntry->d_name[0] == '.') { - if (dirEntry->d_name[1] == '\0' || !countHiddenFiles) { - // Skip "." or hidden files - continue; - } - if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') { - // Skip ".." - continue; - } - } - ++count; - } - ::closedir(dir); + // Continue adding items until ResolveAllItemsLimit is reached. + int remainingItems = ResolveAllItemsLimit - result.count(); + + for (int i = endExtendedVisibleRange + 1; i < beginLastPage && remainingItems > 0; ++i) { + result.append(i); + --remainingItems; } - return count; -#endif + + for (int i = beginExtendedVisibleRange - 1; i > endFirstPage && remainingItems > 0; --i) { + result.append(i); + --remainingItems; + } + + return result; } #include "kfileitemmodelrolesupdater.moc"