X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/1041f340a2ddb634476c5d84585a56c29e5a70fd..e6ea3ab4c41dcc115143a237aafd3a1152849433:/src/kitemviews/kfileitemmodelrolesupdater.cpp diff --git a/src/kitemviews/kfileitemmodelrolesupdater.cpp b/src/kitemviews/kfileitemmodelrolesupdater.cpp index 7050d21c9..c28e240a5 100644 --- a/src/kitemviews/kfileitemmodelrolesupdater.cpp +++ b/src/kitemviews/kfileitemmodelrolesupdater.cpp @@ -20,27 +20,30 @@ #include "kfileitemmodelrolesupdater.h" #include "kfileitemmodel.h" -#include "kpixmapmodifier_p.h" +#include "private/kdirectorycontentscounter.h" +#include "private/kpixmapmodifier.h" #include #include -#include -#include -#include +#include #include +#include +#include +#include +#include +#include + +#ifdef HAVE_BALOO +#include "private/kbaloorolesprovider.h" +#include +#include +#endif + +#include #include -#include #include #include -// Required includes for subItemsCount(): -#ifdef Q_WS_WIN - #include -#else - #include - #include -#endif - // #define KFILEITEMMODELROLESUPDATER_DEBUG namespace { @@ -48,72 +51,108 @@ 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_changedItemsTimer(0), - m_changedItems() + m_pendingSortRoleItems(), + m_pendingIndexes(), + m_pendingPreviewItems(), + m_previewJob(), + m_recentlyChangedItemsTimer(nullptr), + m_recentlyChangedItems(), + m_changedItems(), + m_directoryContentsCounter(nullptr) + #ifdef HAVE_BALOO + , m_balooFileMonitor(nullptr) + #endif { Q_ASSERT(model); - const KConfigGroup globalConfig(KGlobal::config(), "PreviewSettings"); - m_enabledPlugins = globalConfig.readEntry("Plugins", QStringList() - << "directorythumbnail" - << "imagethumbnail" - << "jpegthumbnail"); + const KConfigGroup globalConfig(KSharedConfig::openConfig(), "PreviewSettings"); + m_enabledPlugins = globalConfig.readEntry("Plugins", KIO::PreviewJob::defaultPlugins()); - connect(m_model, SIGNAL(itemsInserted(KItemRangeList)), - this, SLOT(slotItemsInserted(KItemRangeList))); - connect(m_model, SIGNAL(itemsRemoved(KItemRangeList)), - this, SLOT(slotItemsRemoved(KItemRangeList))); - connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet)), - this, SLOT(slotItemsChanged(KItemRangeList,QSet))); + connect(m_model, &KFileItemModel::itemsInserted, + this, &KFileItemModelRolesUpdater::slotItemsInserted); + connect(m_model, &KFileItemModel::itemsRemoved, + this, &KFileItemModelRolesUpdater::slotItemsRemoved); + connect(m_model, &KFileItemModel::itemsChanged, + this, &KFileItemModelRolesUpdater::slotItemsChanged); + connect(m_model, &KFileItemModel::itemsMoved, + this, &KFileItemModelRolesUpdater::slotItemsMoved); + connect(m_model, &KFileItemModel::sortRoleChanged, + this, &KFileItemModelRolesUpdater::slotSortRoleChanged); // 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 100 ms. + m_recentlyChangedItemsTimer = new QTimer(this); + m_recentlyChangedItemsTimer->setInterval(100); + m_recentlyChangedItemsTimer->setSingleShot(true); + connect(m_recentlyChangedItemsTimer, &QTimer::timeout, this, &KFileItemModelRolesUpdater::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, &KDirectoryContentsCounter::result, + this, &KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived); + + auto plugins = KPluginLoader::instantiatePlugins(QStringLiteral("kf5/overlayicon"), nullptr, qApp); + foreach (QObject *it, plugins) { + auto plugin = qobject_cast(it); + if (plugin) { + m_overlayIconsPlugin.append(plugin); + connect(plugin, &KOverlayIconPlugin::overlaysChanged, this, &KFileItemModelRolesUpdater::slotOverlaysChanged); + } else { + // not our/valid plugin, so delete the created object + it->deleteLater(); + } + } } 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(); } } } @@ -140,12 +179,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::setMaximumVisibleItems(int count) +{ + m_maximumVisibleItems = count; } -void KFileItemModelRolesUpdater::setPreviewShown(bool show) +void KFileItemModelRolesUpdater::setPreviewsShown(bool show) { if (show == m_previewShown) { return; @@ -156,85 +198,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_balooConfig.fileIndexingEnabled() && !m_balooFileMonitor) { + m_balooFileMonitor = new Baloo::FileMonitor(this); + connect(m_balooFileMonitor, &Baloo::FileMonitor::fileMetaDataChanged, + this, &KFileItemModelRolesUpdater::applyChangedBalooRoles); + } else if (!hasBalooRole && m_balooFileMonitor) { + delete m_balooFileMonitor; + m_balooFileMonitor = nullptr; + } +#endif - if (m_paused) { - m_rolesChangedDuringPausing = true; - } else { - sortAndResolveAllRoles(); + if (m_state == Paused) { + m_rolesChangedDuringPausing = true; + } else { + startUpdating(); + } } } @@ -245,7 +309,7 @@ QSet KFileItemModelRolesUpdater::roles() const bool KFileItemModelRolesUpdater::isPaused() const { - return m_paused; + return m_state == Paused; } QStringList KFileItemModelRolesUpdater::enabledPlugins() const @@ -255,69 +319,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; + Q_UNUSED(itemRanges) + + 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& file, m_balooFileMonitor->files()) { + if (m_model->index(QUrl::fromLocalFile(file)) >= 0) { + newFileList.append(file); + } + } + 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, const 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); + Q_UNUSED(roles) + + // 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(); + } +} - 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; +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) { @@ -326,390 +494,542 @@ void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem& item, const QPi QPixmap scaledPixmap = pixmap; - const QString mimeType = item.mimetype(); - const int slashIndex = mimeType.indexOf(QLatin1Char('/')); - const QString mimeTypeGroup = mimeType.left(slashIndex); - if (mimeTypeGroup == QLatin1String("image")) { - KPixmapModifier::applyFrame(scaledPixmap, m_iconSize); + if (!pixmap.hasAlpha() + && m_iconSize.width() > KIconLoader::SizeSmallMedium + && m_iconSize.height() > KIconLoader::SizeSmallMedium) { + 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() / scaledPixmap.devicePixelRatio(); + 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() / scaledPixmap.devicePixelRatio()) / 2, + (largeFrame.height() - scaledPixmap.height() / scaledPixmap.devicePixelRatio()) / 2, + scaledPixmap); + scaledPixmap = largeFrame; + } else { + // The image must be shrunk as it is too large to fit into + // the available icon size + KPixmapModifier::applyFrame(scaledPixmap, m_iconSize); + } + } } else { - KPixmapModifier::scale(scaledPixmap, m_iconSize); + KPixmapModifier::scale(scaledPixmap, m_iconSize * qApp->devicePixelRatio()); + scaledPixmap.setDevicePixelRatio(qApp->devicePixelRatio()); } 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)), - this, SLOT(slotItemsChanged(KItemRangeList,QSet))); + disconnect(m_model, &KFileItemModel::itemsChanged, + this, &KFileItemModelRolesUpdater::slotItemsChanged); m_model->setData(index, data); - connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet)), - this, SLOT(slotItemsChanged(KItemRangeList,QSet))); + connect(m_model, &KFileItemModel::itemsChanged, + this, &KFileItemModelRolesUpdater::slotItemsChanged); + + m_finishedItems.insert(item); } void KFileItemModelRolesUpdater::slotPreviewFailed(const KFileItem& item) { - 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) { + QHash data; + data.insert("iconPixmap", QPixmap()); + + disconnect(m_model, &KFileItemModel::itemsChanged, + this, &KFileItemModelRolesUpdater::slotItemsChanged); + m_model->setData(index, data); + connect(m_model, &KFileItemModel::itemsChanged, + this, &KFileItemModelRolesUpdater::slotItemsChanged); - const bool clearPreviews = m_clearPreviews; - m_clearPreviews = true; - applyResolvedRoles(item, ResolveAll); - m_clearPreviews = clearPreviews; + applyResolvedRoles(index, ResolveAll); + m_finishedItems.insert(item); + } } -void KFileItemModelRolesUpdater::slotPreviewJobFinished(KJob* job) +void KFileItemModelRolesUpdater::slotPreviewJobFinished() { -#ifdef KFILEITEMMODELROLESUPDATER_DEBUG - kDebug() << "Preview job finished. Pending visible:" << m_pendingVisibleItems.count() << "invisible:" << m_pendingInvisibleItems.count(); -#endif + m_previewJob = nullptr; - m_previewJobs.removeOne(job); - if (!m_previewJobs.isEmpty() || !hasPendingRoles()) { + if (m_state != PreviewJobRunning) { return; } - const KFileItemList visibleItems = sortedItems(m_pendingVisibleItems); - startPreviewJob(visibleItems + m_pendingInvisibleItems.toList()); + m_state = Idle; + + if (!m_pendingPreviewItems.isEmpty()) { + startPreviewJob(); + } else { + if (!m_changedItems.isEmpty()) { + updateChangedItems(); + } + } } -void KFileItemModelRolesUpdater::resolveNextPendingRoles() +void KFileItemModelRolesUpdater::resolveNextSortRole() { - if (m_paused) { + if (m_state != ResolvingSortRole) { 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; - } + QSet::iterator it = m_pendingSortRoleItems.begin(); + while (it != m_pendingSortRoleItems.end()) { + const KFileItem item = *it; + const int index = m_model->index(item); - 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; + // 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; } - } - if (hasPendingRoles()) { - QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles())); - } else { - m_clearPreviews = false; + applySortRole(index); + m_pendingSortRoleItems.erase(it); + break; } -#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(); + if (!m_pendingSortRoleItems.isEmpty()) { + applySortProgressToModel(); + QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::resolveNextSortRole); + } else { + m_state = Idle; + + // Prevent that we try to update the items twice. + disconnect(m_model, &KFileItemModel::itemsMoved, + this, &KFileItemModelRolesUpdater::slotItemsMoved); + applySortProgressToModel(); + connect(m_model, &KFileItemModel::itemsMoved, + this, &KFileItemModelRolesUpdater::slotItemsMoved); + startUpdating(); } -#endif } -void KFileItemModelRolesUpdater::resolveChangedItems() +void KFileItemModelRolesUpdater::resolveNextPendingRoles() { - if (m_changedItems.isEmpty()) { + if (m_state != ResolvingAllRoles) { return; } - KItemRangeList itemRanges; + while (!m_pendingIndexes.isEmpty()) { + const int index = m_pendingIndexes.takeFirst(); + const KFileItem item = m_model->fileItem(index); - QSetIterator it(m_changedItems); - while (it.hasNext()) { - const KFileItem& item = it.next(); - const int index = m_model->index(item); - if (index >= 0) { - itemRanges.append(KItemRange(index, 1)); + if (m_finishedItems.contains(item)) { + continue; } + + applyResolvedRoles(index, ResolveAll); + m_finishedItems.insert(item); + m_changedItems.remove(item); + break; } - startUpdating(itemRanges); -} + if (!m_pendingIndexes.isEmpty()) { + QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::resolveNextPendingRoles); + } else { + m_state = Idle; -void KFileItemModelRolesUpdater::startUpdating(const KItemRangeList& itemRanges) -{ - // 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); + 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, &KFileItemModel::itemsChanged, + this, &KFileItemModelRolesUpdater::slotItemsChanged); + for (int index = 0; index <= m_model->count(); ++index) { + if (m_model->data(index).contains("iconPixmap")) { + m_model->setData(index, data); + } + } + connect(m_model, &KFileItemModel::itemsChanged, + this, &KFileItemModelRolesUpdater::slotItemsChanged); - 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); } + m_clearPreviews = false; } - } - - int rangesCount = 0; - - foreach (const KItemRange& range, itemRanges) { - rangesCount += range.count; - // 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); - } + if (!m_changedItems.isEmpty()) { + updateChangedItems(); } } +} - resolvePendingRoles(); +void KFileItemModelRolesUpdater::resolveRecentlyChangedItems() +{ + m_changedItems += m_recentlyChangedItems; + m_recentlyChangedItems.clear(); + updateChangedItems(); } -void KFileItemModelRolesUpdater::startPreviewJob(const KFileItemList& items) +void KFileItemModelRolesUpdater::applyChangedBalooRoles(const QString& file) { - if (items.isEmpty() || m_paused) { +#ifdef HAVE_BALOO + const KFileItem item = m_model->fileItem(QUrl::fromLocalFile(file)); + + if (item.isNull()) { + // itemUrl is not in the model anymore, probably because + // the corresponding file has been deleted in the meantime. return; } + applyChangedBalooRolesForItem(item); +#else + Q_UNUSED(file) +#endif +} - // PreviewJob internally caches items always with the size of - // 128 x 128 pixels or 256 x 256 pixels. A (slow) downscaling is done - // by PreviewJob if a smaller size is requested. For images KFileItemModelRolesUpdater must - // 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); +void KFileItemModelRolesUpdater::applyChangedBalooRolesForItem(const KFileItem &item) +{ +#ifdef HAVE_BALOO + Baloo::File file(item.localPath()); + file.load(); - // 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(); - 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; - } + 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()); } - 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*))); + QHashIterator it(rolesProvider.roleValues(file, m_roles)); + while (it.hasNext()) { + it.next(); + data.insert(it.key(), it.value()); + } - m_previewJobs.append(job); + disconnect(m_model, &KFileItemModel::itemsChanged, + this, &KFileItemModelRolesUpdater::slotItemsChanged); + const int index = m_model->index(item); + m_model->setData(index, data); + connect(m_model, &KFileItemModel::itemsChanged, + this, &KFileItemModelRolesUpdater::slotItemsChanged); +#else +#ifndef Q_CC_MSVC + Q_UNUSED(item) +#endif +#endif } - -bool KFileItemModelRolesUpdater::hasPendingRoles() const +void KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived(const QString& path, int count, long size) { - return !m_pendingVisibleItems.isEmpty() || !m_pendingInvisibleItems.isEmpty(); + const bool getSizeRole = m_roles.contains("size"); + const bool getIsExpandableRole = m_roles.contains("isExpandable"); + + if (getSizeRole || getIsExpandableRole) { + const int index = m_model->index(QUrl::fromLocalFile(path)); + if (index >= 0) { + QHash data; + + if (getSizeRole) { + data.insert("count", count); + if (size != -1) { + data.insert("size", QVariant::fromValue(size)); + } + } + if (getIsExpandableRole) { + data.insert("isExpandable", count > 0); + } + + m_model->setData(index, data); + } + } } -void KFileItemModelRolesUpdater::resolvePendingRoles() +void KFileItemModelRolesUpdater::startUpdating() { - int resolvedCount = 0; + if (m_state == Paused) { + return; + } - const bool hasSlowRoles = m_previewShown - || m_roles.contains("size") - || m_roles.contains("type") - || m_roles.contains("isExpandable"); - const ResolveHint resolveHint = hasSlowRoles ? ResolveFast : ResolveAll; + if (m_finishedItems.count() == m_model->count()) { + // All roles have been resolved already. + m_state = Idle; + return; + } + + // Terminate all updates that are currently active. + killPreviewJob(); + m_pendingIndexes.clear(); - // 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(); - // 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; + // Determine the icons for the visible items synchronously. + updateVisibleIcons(); - if (timer.elapsed() > MaxBlockTimeout) { - break; - } + // A detailed update of the items in and near the visible area + // only makes sense if sorting is finished. + if (m_state == ResolvingSortRole) { + return; } - // 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); - } + // Start the preview job or the asynchronous resolving of all roles. + QList indexes = indexesToResolve(); - int index = 0; - while (resolvedCount < MaxResolveItemsCount && index < invisibleItems.count() && timer.elapsed() <= MaxBlockTimeout) { - const KFileItem item = invisibleItems.at(index); - applyResolvedRoles(item, resolveHint); + if (m_previewShown) { + m_pendingPreviewItems.clear(); + m_pendingPreviewItems.reserve(indexes.count()); - if (!hasSlowRoles) { - // All roles have been resolved already by applyResolvedRoles() - m_pendingInvisibleItems.remove(item); + foreach (int index, indexes) { + const KFileItem item = m_model->fileItem(index); + if (!m_finishedItems.contains(item)) { + m_pendingPreviewItems.append(item); + } } - ++index; - ++resolvedCount; - } - if (m_previewShown) { - KFileItemList items = sortedItems(m_pendingVisibleItems); - items += invisibleItems; - startPreviewJob(items); + startPreviewJob(); } else { - QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles())); + m_pendingIndexes = indexes; + // Trigger the asynchronous resolving of all roles. + m_state = ResolvingAllRoles; + QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::resolveNextPendingRoles); } - -#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(); - } - kDebug() << "[TIME] Resolved pending roles:" << timer.elapsed(); -#endif } -void KFileItemModelRolesUpdater::resetPendingRoles() +void KFileItemModelRolesUpdater::updateVisibleIcons() { - m_pendingVisibleItems.clear(); - m_pendingInvisibleItems.clear(); + 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(); - foreach (KJob* job, m_previewJobs) { - job->kill(); + // 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); } - Q_ASSERT(m_previewJobs.isEmpty()); + + // KFileItemListView::initializeItemListWidget(KItemListWidget*) will load + // preliminary icons (i.e., without mime type determination) for the + // remaining items. } -void KFileItemModelRolesUpdater::sortAndResolveAllRoles() +void KFileItemModelRolesUpdater::startPreviewJob() { - if (m_paused) { + m_state = PreviewJobRunning; + + if (m_pendingPreviewItems.isEmpty()) { + QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::slotPreviewJobFinished); return; } - resetPendingRoles(); - Q_ASSERT(m_pendingVisibleItems.isEmpty()); - Q_ASSERT(m_pendingInvisibleItems.isEmpty()); + // PreviewJob internally caches items always with the size of + // 128 x 128 pixels or 256 x 256 pixels. A (slow) downscaling is done + // by PreviewJob if a smaller size is requested. For images KFileItemModelRolesUpdater must + // 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); - if (m_model->count() == 0) { - return; + // 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, we only pass items with known mime type to the preview job. + const int count = m_pendingPreviewItems.count(); + KFileItemList itemSubSet; + 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); } - // 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); - } - } + KIO::PreviewJob* job = new KIO::PreviewJob(itemSubSet, cacheSize, &m_enabledPlugins); - // 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); - } - } - 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); - } + job->setIgnoreMaximumSize(itemSubSet.first().isLocalFile()); + if (job->uiDelegate()) { + KJobWidgets::setWindow(job, qApp->activeWindow()); } - resolvePendingRoles(); + connect(job, &KIO::PreviewJob::gotPreview, + this, &KFileItemModelRolesUpdater::slotGotPreview); + connect(job, &KIO::PreviewJob::failed, + this, &KFileItemModelRolesUpdater::slotPreviewFailed); + connect(job, &KIO::PreviewJob::finished, + this, &KFileItemModelRolesUpdater::slotPreviewJobFinished); + + m_previewJob = job; } -void KFileItemModelRolesUpdater::sortAndResolvePendingRoles() +void KFileItemModelRolesUpdater::updateChangedItems() { - Q_ASSERT(!m_paused); - if (m_model->count() == 0) { + if (m_state == Paused) { return; } - // 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); + if (m_changedItems.isEmpty()) { + return; + } - // 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; + m_finishedItems -= m_changedItems; - resetPendingRoles(); - Q_ASSERT(m_pendingVisibleItems.isEmpty()); - Q_ASSERT(m_pendingInvisibleItems.isEmpty()); + if (m_resolvableRoles.contains(m_model->sortRole())) { + m_pendingSortRoleItems += m_changedItems; - QSetIterator it(pendingItems); - while (it.hasNext()) { - const KFileItem item = it.next(); - if (item.isNull()) { - continue; + 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, &KFileItemModelRolesUpdater::resolveNextSortRole); } + return; + } + + QList visibleChangedIndexes; + QList invisibleChangedIndexes; + + foreach (const KFileItem& item, m_changedItems) { const int index = m_model->index(item); - if (!hasValidIndexRange || (index >= m_firstVisibleIndex && index <= m_lastVisibleIndex)) { - m_pendingVisibleItems.insert(item); + + if (index < 0) { + m_changedItems.remove(item); + continue; + } + + if (index >= m_firstVisibleIndex && index <= m_lastVisibleIndex) { + visibleChangedIndexes.append(index); } else { - m_pendingInvisibleItems.insert(item); + invisibleChangedIndexes.append(index); } } - resolvePendingRoles(); + std::sort(visibleChangedIndexes.begin(), visibleChangedIndexes.end()); + + if (m_previewShown) { + foreach (int index, visibleChangedIndexes) { + m_pendingPreviewItems.append(m_model->fileItem(index)); + } + + foreach (int index, invisibleChangedIndexes) { + m_pendingPreviewItems.append(m_model->fileItem(index)); + } + + 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, &KFileItemModelRolesUpdater::resolveNextPendingRoles); + } + } } -bool KFileItemModelRolesUpdater::applyResolvedRoles(const KFileItem& item, ResolveHint hint) +void KFileItemModelRolesUpdater::applySortRole(int index) { - if (item.isNull()) { - return false; + QHash data; + const KFileItem item = m_model->fileItem(index); + + if (m_model->sortRole() == "type") { + if (!item.isMimeTypeKnown()) { + item.determineMimeType(); + } + + data.insert("type", item.mimeComment()); + } else if (m_model->sortRole() == "size" && item.isLocalFile() && item.isDir()) { + const QString path = item.localPath(); + m_directoryContentsCounter->scanDirectory(path); + } else { + // Probably the sort role is a baloo role - just determine all roles. + data = rolesData(item); } + disconnect(m_model, &KFileItemModel::itemsChanged, + this, &KFileItemModelRolesUpdater::slotItemsChanged); + m_model->setData(index, data); + connect(m_model, &KFileItemModel::itemsChanged, + this, &KFileItemModelRolesUpdater::slotItemsChanged); +} + +void KFileItemModelRolesUpdater::applySortProgressToModel() +{ + // 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; } @@ -722,21 +1042,21 @@ 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)), - this, SLOT(slotItemsChanged(KItemRangeList,QSet))); + disconnect(m_model, &KFileItemModel::itemsChanged, + this, &KFileItemModelRolesUpdater::slotItemsChanged); m_model->setData(index, data); - connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet)), - this, SLOT(slotItemsChanged(KItemRangeList,QSet))); + connect(m_model, &KFileItemModel::itemsChanged, + this, &KFileItemModelRolesUpdater::slotItemsChanged); return true; } return false; } -QHash KFileItemModelRolesUpdater::rolesData(const KFileItem& item) const +QHash KFileItemModelRolesUpdater::rolesData(const KFileItem& item) { QHash data; @@ -745,14 +1065,10 @@ QHash KFileItemModelRolesUpdater::rolesData(const KFileIte 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(); - const int count = subItemsCount(path); - if (getSizeRole) { - data.insert("size", count); - } - if (getIsExpandableRole) { - data.insert("isExpandable", count > 0); - } + m_directoryContentsCounter->scanDirectory(path); } else if (getSizeRole) { data.insert("size", -1); // -1 indicates an unknown number of items } @@ -762,95 +1078,116 @@ QHash KFileItemModelRolesUpdater::rolesData(const KFileIte data.insert("type", item.mimeComment()); } - data.insert("iconOverlays", item.overlays()); + QStringList overlays = item.overlays(); + foreach(KOverlayIconPlugin *it, m_overlayIconsPlugin) { + overlays.append(it->getOverlays(item.url())); + } + data.insert("iconOverlays", overlays); +#ifdef HAVE_BALOO + if (m_balooFileMonitor) { + m_balooFileMonitor->addFile(item.localPath()); + applyChangedBalooRolesForItem(item); + } +#endif return data; } -KFileItemList KFileItemModelRolesUpdater::sortedItems(const QSet& items) const +void KFileItemModelRolesUpdater::slotOverlaysChanged(const QUrl& url, const QStringList &) { - KFileItemList itemList; - if (items.isEmpty()) { - return itemList; + const KFileItem item = m_model->fileItem(url); + if (item.isNull()) { + return; } - -#ifdef KFILEITEMMODELROLESUPDATER_DEBUG - QElapsedTimer timer; - timer.start(); -#endif - - QList indexes; - indexes.reserve(items.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); - } + const int index = m_model->index(item); + QHash data = m_model->data(index); + QStringList overlays = item.overlays(); + foreach (KOverlayIconPlugin *it, m_overlayIconsPlugin) { + overlays.append(it->getOverlays(url)); } - qSort(indexes); + data.insert("iconOverlays", overlays); + m_model->setData(index, data); +} - itemList.reserve(items.count()); - foreach (int index, indexes) { - itemList.append(m_model->fileItem(index)); +void KFileItemModelRolesUpdater::updateAllPreviews() +{ + if (m_state == Paused) { + m_previewChangedDuringPausing = true; + } else { + m_finishedItems.clear(); + startUpdating(); } +} -#ifdef KFILEITEMMODELROLESUPDATER_DEBUG - kDebug() << "[TIME] Sorting of items:" << timer.elapsed(); -#endif - return itemList; +void KFileItemModelRolesUpdater::killPreviewJob() +{ + if (m_previewJob) { + disconnect(m_previewJob, &KIO::PreviewJob::gotPreview, + this, &KFileItemModelRolesUpdater::slotGotPreview); + disconnect(m_previewJob, &KIO::PreviewJob::failed, + this, &KFileItemModelRolesUpdater::slotPreviewFailed); + disconnect(m_previewJob, &KIO::PreviewJob::finished, + this, &KFileItemModelRolesUpdater::slotPreviewJobFinished); + m_previewJob->kill(); + m_previewJob = nullptr; + m_pendingPreviewItems.clear(); + } } -int KFileItemModelRolesUpdater::subItemsCount(const QString& path) const +QList KFileItemModelRolesUpdater::indexesToResolve() const { - const bool countHiddenFiles = m_model->showHiddenFiles(); - const bool showFoldersOnly = m_model->showFoldersOnly(); + const int count = m_model->count(); + + QList result; + result.reserve(ResolveAllItemsLimit); + + // Add visible items. + for (int i = m_firstVisibleIndex; i <= m_lastVisibleIndex; ++i) { + result.append(i); + } + + // 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 Compact View. + const int readAheadItems = qMin(ReadAheadPages * m_maximumVisibleItems, ResolveAllItemsLimit / 2); -#ifdef Q_WS_WIN - QDir dir(path); - QDir::Filters filters = QDir::NoDotAndDotDot | QDir::System; - if (countHiddenFiles) { - filters |= QDir::Hidden; + // 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); } - if (showFoldersOnly) { - filters |= QDir::Dirs; - } else { - filters |= QDir::AllEntries; + + // 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); } - 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; - } - } + // 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); + } - // If only directories are counted, consider an unknown file type also - // as directory instead of trying to do an expensive stat() (see bug 292642). - if (!showFoldersOnly || dirEntry->d_type == DT_DIR || dirEntry->d_type == DT_UNKNOWN) { - ++count; - } - } - ::closedir(dir); + // 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 count; -#endif + + // 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; + } + + for (int i = beginExtendedVisibleRange - 1; i > endFirstPage && remainingItems > 0; --i) { + result.append(i); + --remainingItems; + } + + return result; } -#include "kfileitemmodelrolesupdater.moc"