X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/bc662543042a7677538ecb95a16f31b460b2b661..e6ea3ab4c41dcc115143a237aafd3a1152849433:/src/kitemviews/kfileitemmodelrolesupdater.cpp diff --git a/src/kitemviews/kfileitemmodelrolesupdater.cpp b/src/kitemviews/kfileitemmodelrolesupdater.cpp index 5d6bdda9c..c28e240a5 100644 --- a/src/kitemviews/kfileitemmodelrolesupdater.cpp +++ b/src/kitemviews/kfileitemmodelrolesupdater.cpp @@ -20,37 +20,30 @@ #include "kfileitemmodelrolesupdater.h" #include "kfileitemmodel.h" +#include "private/kdirectorycontentscounter.h" +#include "private/kpixmapmodifier.h" #include #include -#include -#include -#include -#include #include #include - -#include "private/kpixmapmodifier.h" +#include +#include +#include +#include +#include + +#ifdef HAVE_BALOO +#include "private/kbaloorolesprovider.h" +#include +#include +#endif #include #include -#include #include #include -#ifdef HAVE_NEPOMUK - #include "private/knepomukrolesprovider.h" - #include "private/nepomuk/resourcewatcher.h" -#endif - -// Required includes for subItemsCount(): -#ifdef Q_WS_WIN - #include -#else - #include - #include -#endif - // #define KFILEITEMMODELROLESUPDATER_DEBUG namespace { @@ -58,95 +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_sortingProgress(-1), + 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_pendingSortRoleItems(), + m_pendingIndexes(), + m_pendingPreviewItems(), + m_previewJob(), + m_recentlyChangedItemsTimer(nullptr), + m_recentlyChangedItems(), m_changedItems(), - m_dirWatcher(0), - m_watchedDirs() - #ifdef HAVE_NEPOMUK - , m_nepomukResourceWatcher(0), - m_nepomukUriItems() + 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"); - - 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, SIGNAL(sortRoleChanged(QByteArray,QByteArray)), - this, SLOT(slotSortRoleChanged(QByteArray,QByteArray))); + const KConfigGroup globalConfig(KSharedConfig::openConfig(), "PreviewSettings"); + m_enabledPlugins = globalConfig.readEntry("Plugins", KIO::PreviewJob::defaultPlugins()); + + 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 1 second. - m_changedItemsTimer = new QTimer(this); - m_changedItemsTimer->setInterval(1000); - 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_NEPOMUK - m_resolvableRoles += KNepomukRolesProvider::instance().roles(); +#ifdef HAVE_BALOO + m_resolvableRoles += KBalooRolesProvider::instance().roles(); #endif - // When folders are expandable or the item-count is shown for folders, it is necessary - // to watch the number of items of the sub-folder to be able to react on changes. - m_dirWatcher = new KDirWatch(this); - connect(m_dirWatcher, SIGNAL(dirty(QString)), this, SLOT(slotDirWatchDirty(QString))); + 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() { - resetPendingRoles(); + 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(); } } } @@ -173,9 +179,12 @@ 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::setPreviewsShown(bool show) @@ -224,31 +233,33 @@ void KFileItemModelRolesUpdater::setEnabledPlugins(const QStringList& list) 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(); } } @@ -257,47 +268,36 @@ void KFileItemModelRolesUpdater::setRoles(const QSet& roles) if (m_roles != roles) { m_roles = roles; -#ifdef HAVE_NEPOMUK +#ifdef HAVE_BALOO // Check whether there is at least one role that must be resolved - // with the help of Nepomuk. If this is the case, a (quite expensive) + // 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 KNepomukRolesProvider& rolesProvider = KNepomukRolesProvider::instance(); - bool hasNepomukRole = false; + const KBalooRolesProvider& rolesProvider = KBalooRolesProvider::instance(); + bool hasBalooRole = false; QSetIterator it(roles); while (it.hasNext()) { const QByteArray& role = it.next(); if (rolesProvider.roles().contains(role)) { - hasNepomukRole = true; + hasBalooRole = true; break; } } - if (hasNepomukRole && !m_nepomukResourceWatcher) { - Q_ASSERT(m_nepomukUriItems.isEmpty()); - - m_nepomukResourceWatcher = new Nepomuk::ResourceWatcher(this); - connect(m_nepomukResourceWatcher, SIGNAL(propertyChanged(Nepomuk::Resource,Nepomuk::Types::Property,QVariantList,QVariantList)), - this, SLOT(applyChangedNepomukRoles(Nepomuk::Resource))); - connect(m_nepomukResourceWatcher, SIGNAL(propertyRemoved(Nepomuk::Resource,Nepomuk::Types::Property,QVariant)), - this, SLOT(applyChangedNepomukRoles(Nepomuk::Resource))); - connect(m_nepomukResourceWatcher, SIGNAL(propertyAdded(Nepomuk::Resource,Nepomuk::Types::Property,QVariant)), - this, SLOT(applyChangedNepomukRoles(Nepomuk::Resource))); - connect(m_nepomukResourceWatcher, SIGNAL(resourceCreated(Nepomuk::Resource,QList)), - this, SLOT(applyChangedNepomukRoles(Nepomuk::Resource))); - } else if (!hasNepomukRole && m_nepomukResourceWatcher) { - delete m_nepomukResourceWatcher; - m_nepomukResourceWatcher = 0; - m_nepomukUriItems.clear(); + 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 - updateSortProgress(); - - if (m_paused) { + if (m_state == Paused) { m_rolesChangedDuringPausing = true; } else { - sortAndResolveAllRoles(); + startUpdating(); } } } @@ -309,7 +309,7 @@ QSet KFileItemModelRolesUpdater::roles() const bool KFileItemModelRolesUpdater::isPaused() const { - return m_paused; + return m_state == Paused; } QStringList KFileItemModelRolesUpdater::enabledPlugins() const @@ -319,127 +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); + Q_UNUSED(itemRanges) const bool allItemsRemoved = (m_model->count() == 0); - if (!m_watchedDirs.isEmpty()) { - // Don't let KDirWatch watch for removed items +#ifdef HAVE_BALOO + if (m_balooFileMonitor) { + // Don't let the FileWatcher watch for removed items if (allItemsRemoved) { - foreach (const QString& path, m_watchedDirs) { - m_dirWatcher->removeDir(path); - } - m_watchedDirs.clear(); + m_balooFileMonitor->clear(); } else { - QMutableSetIterator it(m_watchedDirs); - while (it.hasNext()) { - const QString& path = it.next(); - if (m_model->index(KUrl(path)) < 0) { - m_dirWatcher->removeDir(path); - it.remove(); + QStringList newFileList; + foreach (const QString& file, m_balooFileMonitor->files()) { + if (m_model->index(QUrl::fromLocalFile(file)) >= 0) { + newFileList.append(file); } } - } - } - -#ifdef HAVE_NEPOMUK - if (m_nepomukResourceWatcher) { - // Don't let the ResourceWatcher watch for removed items - if (allItemsRemoved) { - m_nepomukResourceWatcher->setResources(QList()); - m_nepomukResourceWatcher->stop(); - m_nepomukUriItems.clear(); - } else { - QList newResources; - const QList oldResources = m_nepomukResourceWatcher->resources(); - foreach (const Nepomuk::Resource& resource, oldResources) { - const QUrl uri = resource.resourceUri(); - const KUrl itemUrl = m_nepomukUriItems.value(uri); - if (m_model->index(itemUrl) >= 0) { - newResources.append(resource); - } else { - m_nepomukUriItems.remove(uri); - } - } - m_nepomukResourceWatcher->setResources(newResources); - if (newResources.isEmpty()) { - Q_ASSERT(m_nepomukUriItems.isEmpty()); - m_nepomukResourceWatcher->stop(); - } + m_balooFileMonitor->setFiles(newFileList); } } #endif - m_firstVisibleIndex = 0; - m_lastVisibleIndex = -1; - if (!hasPendingRoles()) { - return; - } - if (allItemsRemoved) { - // Most probably a directory change is done. Clear all pending items - // and also kill all ongoing preview-jobs. - resetPendingRoles(); - + 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); - - 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; - } + 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; } - } else { - // No call of slotItemsChanged() has been done recently, resolve the roles now. - startUpdating(itemRanges); } - m_changedItemsTimer->start(); + + m_recentlyChangedItemsTimer->start(); + + if (!itemsChangedRecently) { + updateChangedItems(); + } } void KFileItemModelRolesUpdater::slotSortRoleChanged(const QByteArray& current, const QByteArray& previous) { - Q_UNUSED(current); - Q_UNUSED(previous); - updateSortProgress(); + 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 { + m_state = Idle; + m_pendingSortRoleItems.clear(); + applySortProgressToModel(); + } } 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) { @@ -448,10 +494,9 @@ 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")) { + if (!pixmap.hasAlpha() + && m_iconSize.width() > KIconLoader::SizeSmallMedium + && m_iconSize.height() > KIconLoader::SizeSmallMedium) { if (m_enlargeSmallPreviews) { KPixmapModifier::applyFrame(scaledPixmap, m_iconSize); } else { @@ -461,7 +506,7 @@ void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem& item, const QPi const bool enlargingRequired = scaledPixmap.width() < contentSize.width() && scaledPixmap.height() < contentSize.height(); if (enlargingRequired) { - QSize frameSize = scaledPixmap.size(); + QSize frameSize = scaledPixmap.size() / scaledPixmap.devicePixelRatio(); frameSize.scale(m_iconSize, Qt::KeepAspectRatio); QPixmap largeFrame(frameSize); @@ -470,171 +515,255 @@ void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem& item, const QPi KPixmapModifier::applyFrame(largeFrame, frameSize); QPainter painter(&largeFrame); - painter.drawPixmap((largeFrame.width() - scaledPixmap.width()) / 2, - (largeFrame.height() - scaledPixmap.height()) / 2, + painter.drawPixmap((largeFrame.width() - scaledPixmap.width() / scaledPixmap.devicePixelRatio()) / 2, + (largeFrame.height() - scaledPixmap.height() / scaledPixmap.devicePixelRatio()) / 2, scaledPixmap); scaledPixmap = largeFrame; } else { - // The image must be shrinked as it is too large to fit into + // 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); - applySortProgressToModel(); + 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()); - const bool clearPreviews = m_clearPreviews; - m_clearPreviews = true; - applyResolvedRoles(item, ResolveAll); - m_clearPreviews = clearPreviews; + disconnect(m_model, &KFileItemModel::itemsChanged, + this, &KFileItemModelRolesUpdater::slotItemsChanged); + m_model->setData(index, data); + connect(m_model, &KFileItemModel::itemsChanged, + this, &KFileItemModelRolesUpdater::slotItemsChanged); - applySortProgressToModel(); + 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; - QSet::iterator it = pendingItems.begin(); - while (it != pendingItems.end() && !changed && resolvedCount < MaxResolveItemsCount) { - changed = applyResolvedRoles(*it, ResolveAll); - it = pendingItems.erase(it); - ++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; } - applySortProgressToModel(); + if (!m_pendingSortRoleItems.isEmpty()) { + applySortProgressToModel(); + QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::resolveNextSortRole); + } else { + m_state = Idle; -#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(); + // 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; } - m_changedItems.clear(); - startUpdating(itemRanges); + if (!m_pendingIndexes.isEmpty()) { + QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::resolveNextPendingRoles); + } else { + 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, &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); + + } + m_clearPreviews = false; + } + + if (!m_changedItems.isEmpty()) { + updateChangedItems(); + } + } +} + +void KFileItemModelRolesUpdater::resolveRecentlyChangedItems() +{ + m_changedItems += m_recentlyChangedItems; + m_recentlyChangedItems.clear(); + updateChangedItems(); } -void KFileItemModelRolesUpdater::applyChangedNepomukRoles(const Nepomuk::Resource& resource) +void KFileItemModelRolesUpdater::applyChangedBalooRoles(const QString& file) { -#ifdef HAVE_NEPOMUK - const KUrl itemUrl = m_nepomukUriItems.value(resource.resourceUri()); - const KFileItem item = m_model->fileItem(itemUrl); +#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 +} - QHash data = rolesData(item); +void KFileItemModelRolesUpdater::applyChangedBalooRolesForItem(const KFileItem &item) +{ +#ifdef HAVE_BALOO + Baloo::File file(item.localPath()); + file.load(); - const KNepomukRolesProvider& rolesProvider = KNepomukRolesProvider::instance(); - QHashIterator it(rolesProvider.roleValues(resource, m_roles)); + 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()); + } + + QHashIterator it(rolesProvider.roleValues(file, m_roles)); while (it.hasNext()) { it.next(); data.insert(it.key(), it.value()); } - disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet)), - this, SLOT(slotItemsChanged(KItemRangeList,QSet))); + disconnect(m_model, &KFileItemModel::itemsChanged, + this, &KFileItemModelRolesUpdater::slotItemsChanged); const int index = m_model->index(item); 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); #else #ifndef Q_CC_MSVC - Q_UNUSED(resource); + Q_UNUSED(item) #endif #endif } -void KFileItemModelRolesUpdater::slotDirWatchDirty(const QString& path) +void KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived(const QString& path, int count, long size) { const bool getSizeRole = m_roles.contains("size"); const bool getIsExpandableRole = m_roles.contains("isExpandable"); if (getSizeRole || getIsExpandableRole) { - const int index = m_model->index(KUrl(path)); + const int index = m_model->index(QUrl::fromLocalFile(path)); if (index >= 0) { QHash data; - const int count = subItemsCount(path); if (getSizeRole) { - data.insert("size", count); + data.insert("count", count); + if (size != -1) { + data.insert("size", QVariant::fromValue(size)); + } } if (getIsExpandableRole) { data.insert("isExpandable", count > 0); @@ -645,370 +774,262 @@ void KFileItemModelRolesUpdater::slotDirWatchDirty(const QString& path) } } -void KFileItemModelRolesUpdater::startUpdating(const KItemRangeList& itemRanges) +void KFileItemModelRolesUpdater::startUpdating() { - // 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 (hasValidIndexRange) { - // Move all current pending visible items that are not visible anymore - // to the pending invisible items. - QSet::iterator it = m_pendingVisibleItems.begin(); - while (it != m_pendingVisibleItems.end()) { - const KFileItem item = *it; - const int index = m_model->index(item); - if (index < m_firstVisibleIndex || index > m_lastVisibleIndex) { - it = m_pendingVisibleItems.erase(it); - m_pendingInvisibleItems.insert(item); - } else { - ++it; - } - } - } - - 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_state == Paused) { + return; } - resolvePendingRoles(); -} - -void KFileItemModelRolesUpdater::startPreviewJob(const KFileItemList& items) -{ - if (items.isEmpty() || m_paused) { + if (m_finishedItems.count() == m_model->count()) { + // All roles have been resolved already. + m_state = Idle; return; } - // 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); + // Terminate all updates that are currently active. + killPreviewJob(); + m_pendingIndexes.clear(); - // 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::PreviewJob() can already access the resolved - // MIME-type in a fast way. QElapsedTimer timer; timer.start(); - KFileItemList itemSubSet; - const int count = items.count(); - itemSubSet.reserve(count); - for (int i = 0; i < count; ++i) { - KFileItem item = items.at(i); - item.determineMimeType(); - itemSubSet.append(item); - 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; - } - } - KIO::PreviewJob* job = new KIO::PreviewJob(itemSubSet, cacheSize, &m_enabledPlugins); - job->setIgnoreMaximumSize(items.first().isLocalFile()); - if (job->ui()) { - job->ui()->setWindow(qApp->activeWindow()); + // 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; } - 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*))); + // Start the preview job or the asynchronous resolving of all roles. + QList indexes = indexesToResolve(); - m_previewJobs.append(job); -} + 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); + } + } -bool KFileItemModelRolesUpdater::hasPendingRoles() const -{ - return !m_pendingVisibleItems.isEmpty() || !m_pendingInvisibleItems.isEmpty(); + startPreviewJob(); + } else { + m_pendingIndexes = indexes; + // Trigger the asynchronous resolving of all roles. + m_state = ResolvingAllRoles; + QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::resolveNextPendingRoles); + } } -void KFileItemModelRolesUpdater::resolvePendingRoles() +void KFileItemModelRolesUpdater::updateVisibleIcons() { - int resolvedCount = 0; - - bool hasSlowRoles = m_previewShown; - if (!hasSlowRoles) { - QSetIterator it(m_roles); - while (it.hasNext()) { - if (m_resolvableRoles.contains(it.next())) { - hasSlowRoles = true; - break; - } + 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); } } - const ResolveHint resolveHint = hasSlowRoles ? ResolveFast : ResolveAll; - - // 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 - QSet::iterator visibleIt = m_pendingVisibleItems.begin(); - while (visibleIt != m_pendingVisibleItems.end()) { - const KFileItem item = *visibleIt; - if (!hasSlowRoles) { - Q_ASSERT(!m_pendingInvisibleItems.contains(item)); - // All roles will be resolved by applyResolvedRoles() - visibleIt = m_pendingVisibleItems.erase(visibleIt); - } else { - ++visibleIt; - } - applyResolvedRoles(item, resolveHint); - ++resolvedCount; - - if (timer.elapsed() > MaxBlockTimeout) { - break; - } + // 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); } - // 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); - } + // KFileItemListView::initializeItemListWidget(KItemListWidget*) will load + // preliminary icons (i.e., without mime type determination) for the + // remaining items. +} - int index = 0; - while (resolvedCount < MaxResolveItemsCount && index < invisibleItems.count() && timer.elapsed() <= MaxBlockTimeout) { - const KFileItem item = invisibleItems.at(index); - applyResolvedRoles(item, resolveHint); +void KFileItemModelRolesUpdater::startPreviewJob() +{ + m_state = PreviewJobRunning; - if (!hasSlowRoles) { - // All roles have been resolved already by applyResolvedRoles() - m_pendingInvisibleItems.remove(item); - } - ++index; - ++resolvedCount; + if (m_pendingPreviewItems.isEmpty()) { + QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::slotPreviewJobFinished); + return; } - if (m_previewShown) { - KFileItemList items = sortedItems(m_pendingVisibleItems); - items += invisibleItems; - startPreviewJob(items); + // 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); + + // 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 { - QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles())); - } + // Determine mime types for MaxBlockTimeout ms, and start a preview + // job for the corresponding items. + QElapsedTimer timer; + timer.start(); -#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(); + do { + const KFileItem item = m_pendingPreviewItems.takeFirst(); + item.determineMimeType(); + itemSubSet.append(item); + } while (!m_pendingPreviewItems.isEmpty() && timer.elapsed() < MaxBlockTimeout); } - kDebug() << "[TIME] Resolved pending roles:" << timer.elapsed(); -#endif - - applySortProgressToModel(); -} -void KFileItemModelRolesUpdater::resetPendingRoles() -{ - m_pendingVisibleItems.clear(); - m_pendingInvisibleItems.clear(); + KIO::PreviewJob* job = new KIO::PreviewJob(itemSubSet, cacheSize, &m_enabledPlugins); - foreach (KJob* job, m_previewJobs) { - job->kill(); + job->setIgnoreMaximumSize(itemSubSet.first().isLocalFile()); + if (job->uiDelegate()) { + KJobWidgets::setWindow(job, qApp->activeWindow()); } - Q_ASSERT(m_previewJobs.isEmpty()); + + 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::sortAndResolveAllRoles() +void KFileItemModelRolesUpdater::updateChangedItems() { - if (m_paused) { + if (m_state == Paused) { return; } - resetPendingRoles(); - Q_ASSERT(m_pendingVisibleItems.isEmpty()); - Q_ASSERT(m_pendingInvisibleItems.isEmpty()); - - if (m_model->count() == 0) { + if (m_changedItems.isEmpty()) { return; } - // 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); - } - } + m_finishedItems -= m_changedItems; - // 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); - } - } - const int count = m_model->count(); - for (int i = m_lastVisibleIndex + 1; i < count; ++i) { - const KFileItem item = m_model->fileItem(i); - if (!item.isNull()) { - m_pendingInvisibleItems.insert(item); - } - } + if (m_resolvableRoles.contains(m_model->sortRole())) { + m_pendingSortRoleItems += m_changedItems; - resolvePendingRoles(); -} + 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); + } -void KFileItemModelRolesUpdater::sortAndResolvePendingRoles() -{ - Q_ASSERT(!m_paused); - if (m_model->count() == 0) { 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); + QList visibleChangedIndexes; + QList invisibleChangedIndexes; - // Trigger a preview generation of all pending items. Assure that the visible - // pending items get generated first. + foreach (const KFileItem& item, m_changedItems) { + const int index = m_model->index(item); - // Step 1: Check if any items in m_pendingVisibleItems are not visible any more - // and move them to m_pendingInvisibleItems. - QSet::iterator itVisible = m_pendingVisibleItems.begin(); - while (itVisible != m_pendingVisibleItems.end()) { - const KFileItem item = *itVisible; - if (item.isNull()) { - itVisible = m_pendingVisibleItems.erase(itVisible); + if (index < 0) { + m_changedItems.remove(item); continue; } - const int index = m_model->index(item); - if (!hasValidIndexRange || (index >= m_firstVisibleIndex && index <= m_lastVisibleIndex)) { - ++itVisible; + if (index >= m_firstVisibleIndex && index <= m_lastVisibleIndex) { + visibleChangedIndexes.append(index); } else { - itVisible = m_pendingVisibleItems.erase(itVisible); - m_pendingInvisibleItems.insert(item); + invisibleChangedIndexes.append(index); } } - // Step 2: Check if any items in m_pendingInvisibleItems have become visible - // and move them to m_pendingVisibleItems. - QSet::iterator itInvisible = m_pendingInvisibleItems.begin(); - while (itInvisible != m_pendingInvisibleItems.end()) { - const KFileItem item = *itInvisible; - if (item.isNull()) { - itInvisible = m_pendingInvisibleItems.erase(itInvisible); - continue; - } + std::sort(visibleChangedIndexes.begin(), visibleChangedIndexes.end()); - const int index = m_model->index(item); - if (!hasValidIndexRange || (index >= m_firstVisibleIndex && index <= m_lastVisibleIndex)) { - itInvisible = m_pendingInvisibleItems.erase(itInvisible); - m_pendingVisibleItems.insert(item); - } else { - ++itInvisible; + if (m_previewShown) { + foreach (int index, visibleChangedIndexes) { + m_pendingPreviewItems.append(m_model->fileItem(index)); } - } - resolvePendingRoles(); -} - -void KFileItemModelRolesUpdater::applySortProgressToModel() -{ - if (m_sortingProgress < 0) { - return; - } + foreach (int index, invisibleChangedIndexes) { + m_pendingPreviewItems.append(m_model->fileItem(index)); + } - // Inform the model about the progress of the resolved items, - // so that it can give an indication when the sorting has been finished. - const int resolvedCount = m_model->count() - - m_pendingVisibleItems.count() - - m_pendingInvisibleItems.count(); - if (resolvedCount > 0) { - m_model->emitSortProgress(resolvedCount); - if (resolvedCount == m_model->count()) { - m_sortingProgress = -1; + 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); } } } -void KFileItemModelRolesUpdater::updateSortProgress() +void KFileItemModelRolesUpdater::applySortRole(int index) { - const QByteArray sortRole = m_model->sortRole(); - - // Optimization if the sorting is done by type: In case if all MIME-types - // are known, the types have been resolved already by KFileItemModel and - // no sort-progress feedback is required. - const bool showProgress = (sortRole == "type") - ? hasUnknownMimeTypes() - : m_resolvableRoles.contains(sortRole); - - if (m_sortingProgress >= 0) { - // Mark the current sorting as finished - m_model->emitSortProgress(m_model->count()); - } - m_sortingProgress = showProgress ? 0 : -1; -} + QHash data; + const KFileItem item = m_model->fileItem(index); -bool KFileItemModelRolesUpdater::hasUnknownMimeTypes() const -{ - const int count = m_model->count(); - for (int i = 0; i < count; ++i) { - const KFileItem item = m_model->fileItem(i); + if (m_model->sortRole() == "type") { if (!item.isMimeTypeKnown()) { - return true; + 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); } - return false; + disconnect(m_model, &KFileItemModel::itemsChanged, + this, &KFileItemModelRolesUpdater::slotItemsChanged); + m_model->setData(index, data); + connect(m_model, &KFileItemModel::itemsChanged, + this, &KFileItemModelRolesUpdater::slotItemsChanged); } -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; } @@ -1024,18 +1045,18 @@ bool KFileItemModelRolesUpdater::applyResolvedRoles(const KFileItem& item, Resol 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; @@ -1044,19 +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); - } - - if (!m_dirWatcher->contains(path)) { - m_dirWatcher->addDir(path); - m_watchedDirs.insert(path); - } + m_directoryContentsCounter->scanDirectory(path); } else if (getSizeRole) { data.insert("size", -1); // -1 indicates an unknown number of items } @@ -1066,138 +1078,116 @@ QHash KFileItemModelRolesUpdater::rolesData(const KFileIte data.insert("type", item.mimeComment()); } - data.insert("iconOverlays", item.overlays()); - -#ifdef HAVE_NEPOMUK - if (m_nepomukResourceWatcher) { - const KNepomukRolesProvider& rolesProvider = KNepomukRolesProvider::instance(); - Nepomuk::Resource resource(item.nepomukUri()); - QHashIterator it(rolesProvider.roleValues(resource, m_roles)); - while (it.hasNext()) { - it.next(); - data.insert(it.key(), it.value()); - } - - QUrl uri = resource.resourceUri(); - if (uri.isEmpty()) { - // TODO: Is there another way to explicitly create a resource? - // We need a resource to be able to track it for changes. - resource.setRating(0); - uri = resource.resourceUri(); - } - if (!uri.isEmpty() && !m_nepomukUriItems.contains(uri)) { - m_nepomukResourceWatcher->addResource(resource); - - if (m_nepomukUriItems.isEmpty()) { - m_nepomukResourceWatcher->start(); - } + QStringList overlays = item.overlays(); + foreach(KOverlayIconPlugin *it, m_overlayIconsPlugin) { + overlays.append(it->getOverlays(item.url())); + } + data.insert("iconOverlays", overlays); - m_nepomukUriItems.insert(uri, item.url()); - } +#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->showDirectoriesOnly(); + const int count = m_model->count(); + + QList result; + result.reserve(ResolveAllItemsLimit); -#ifdef Q_WS_WIN - QDir dir(path); - QDir::Filters filters = QDir::NoDotAndDotDot | QDir::System; - if (countHiddenFiles) { - filters |= QDir::Hidden; + // Add visible items. + for (int i = m_firstVisibleIndex; i <= m_lastVisibleIndex; ++i) { + result.append(i); } - if (showFoldersOnly) { - filters |= QDir::Dirs; - } else { - filters |= QDir::AllEntries; + + // 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); + + // 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); } - 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) { // krazy:exclude=syscalls - count = 0; - struct dirent *dirEntry = 0; - while ((dirEntry = ::readdir(dir))) { - 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 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); + } - // If only directories are counted, consider an unknown file type and links also - // as directory instead of trying to do an expensive stat() - // (see bugs 292642 and 299997). - const bool countEntry = !showFoldersOnly || - dirEntry->d_type == DT_DIR || - dirEntry->d_type == DT_LNK || - dirEntry->d_type == DT_UNKNOWN; - if (countEntry) { - ++count; - } - } - ::closedir(dir); + // 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); } - return count; -#endif -} -void KFileItemModelRolesUpdater::updateAllPreviews() -{ - if (m_paused) { - m_previewChangedDuringPausing = true; - } else { - sortAndResolveAllRoles(); + // 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); + } + + // 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"