X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/e615bfaed4cd562d31ea0506529f730c414b6ec0..caf49dafa557caa9bd985702a240284348c82b40:/src/kitemviews/kfileitemmodelrolesupdater.cpp diff --git a/src/kitemviews/kfileitemmodelrolesupdater.cpp b/src/kitemviews/kfileitemmodelrolesupdater.cpp index 92a020003..49657a9b1 100644 --- a/src/kitemviews/kfileitemmodelrolesupdater.cpp +++ b/src/kitemviews/kfileitemmodelrolesupdater.cpp @@ -1,55 +1,39 @@ -/*************************************************************************** - * Copyright (C) 2011 by Peter Penz * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * - ***************************************************************************/ +/* + * SPDX-FileCopyrightText: 2011 Peter Penz + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "kfileitemmodelrolesupdater.h" +#include "dolphindebug.h" #include "kfileitemmodel.h" +#include "private/kdirectorycontentscounter.h" +#include "private/kpixmapmodifier.h" #include #include -#include -#include -#include -#include #include #include -#include +#include +#include #include +#include +#include -#include "private/kpixmapmodifier.h" -#include "private/kdirectorycontentscounter.h" +#ifdef HAVE_BALOO +#include "private/kbaloorolesprovider.h" +#include +#include +#endif #include +#include #include -#include +#include #include #include -#include - -#ifdef HAVE_BALOO - #include "private/kbaloorolesprovider.h" - #include - #include -#endif - - // #define KFILEITEMMODELROLESUPDATER_DEBUG namespace { @@ -84,25 +68,29 @@ KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QO m_roles(), m_resolvableRoles(), m_enabledPlugins(), + m_localFileSizePreviewLimit(0), + m_scanDirectories(true), m_pendingSortRoleItems(), m_pendingIndexes(), m_pendingPreviewItems(), m_previewJob(), - m_recentlyChangedItemsTimer(0), + m_hoverSequenceItem(), + m_hoverSequenceIndex(0), + m_hoverSequencePreviewJob(nullptr), + m_hoverSequenceNumSuccessiveFailures(0), + m_recentlyChangedItemsTimer(nullptr), m_recentlyChangedItems(), m_changedItems(), - m_directoryContentsCounter(0) + m_directoryContentsCounter(nullptr) #ifdef HAVE_BALOO - , m_balooFileMonitor(0) + , m_balooFileMonitor(nullptr) #endif { Q_ASSERT(model); const KConfigGroup globalConfig(KSharedConfig::openConfig(), "PreviewSettings"); - m_enabledPlugins = globalConfig.readEntry("Plugins", QStringList() - << QStringLiteral("directorythumbnail") - << QStringLiteral("imagethumbnail") - << QStringLiteral("jpegthumbnail")); + m_enabledPlugins = globalConfig.readEntry("Plugins", KIO::PreviewJob::defaultPlugins()); + m_localFileSizePreviewLimit = static_cast(globalConfig.readEntry("MaximumSize", 0)); connect(m_model, &KFileItemModel::itemsInserted, this, &KFileItemModelRolesUpdater::slotItemsInserted); @@ -116,9 +104,9 @@ KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QO 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. + // resolving of the roles. Postpone the resolving until no update has been done for 100 ms. m_recentlyChangedItemsTimer = new QTimer(this); - m_recentlyChangedItemsTimer->setInterval(1000); + m_recentlyChangedItemsTimer->setInterval(100); m_recentlyChangedItemsTimer->setSingleShot(true); connect(m_recentlyChangedItemsTimer, &QTimer::timeout, this, &KFileItemModelRolesUpdater::resolveRecentlyChangedItems); @@ -133,15 +121,16 @@ KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QO 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); + const auto plugins = KPluginMetaData::findPlugins(QStringLiteral("kf5/overlayicon")); + for (const KPluginMetaData &data : plugins) { + auto instance = QPluginLoader(data.fileName()).instance(); + auto plugin = qobject_cast(instance); 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(); + delete instance; } } } @@ -299,7 +288,7 @@ void KFileItemModelRolesUpdater::setRoles(const QSet& roles) this, &KFileItemModelRolesUpdater::applyChangedBalooRoles); } else if (!hasBalooRole && m_balooFileMonitor) { delete m_balooFileMonitor; - m_balooFileMonitor = 0; + m_balooFileMonitor = nullptr; } #endif @@ -326,6 +315,46 @@ QStringList KFileItemModelRolesUpdater::enabledPlugins() const return m_enabledPlugins; } +void KFileItemModelRolesUpdater::setLocalFileSizePreviewLimit(const qlonglong size) +{ + m_localFileSizePreviewLimit = size; +} + +qlonglong KFileItemModelRolesUpdater::localFileSizePreviewLimit() const +{ + return m_localFileSizePreviewLimit; +} + +void KFileItemModelRolesUpdater::setScanDirectories(bool enabled) +{ + m_scanDirectories = enabled; +} + +bool KFileItemModelRolesUpdater::scanDirectories() const +{ + return m_scanDirectories; +} + +void KFileItemModelRolesUpdater::setHoverSequenceState(const QUrl& itemUrl, int seqIdx) +{ + const KFileItem item = m_model->fileItem(itemUrl); + + if (item != m_hoverSequenceItem) { + killHoverSequencePreviewJob(); + } + + m_hoverSequenceItem = item; + m_hoverSequenceIndex = seqIdx; + + if (!m_previewShown) { + return; + } + + m_hoverSequenceNumSuccessiveFailures = 0; + + loadNextHoverSequencePreview(); +} + void KFileItemModelRolesUpdater::slotItemsInserted(const KItemRangeList& itemRanges) { QElapsedTimer timer; @@ -334,7 +363,7 @@ void KFileItemModelRolesUpdater::slotItemsInserted(const KItemRangeList& itemRan // 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) { + for (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) { @@ -363,7 +392,7 @@ void KFileItemModelRolesUpdater::slotItemsInserted(const KItemRangeList& itemRan void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList& itemRanges) { - Q_UNUSED(itemRanges); + Q_UNUSED(itemRanges) const bool allItemsRemoved = (m_model->count() == 0); @@ -374,7 +403,8 @@ void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList& itemRang m_balooFileMonitor->clear(); } else { QStringList newFileList; - foreach (const QString& file, m_balooFileMonitor->files()) { + const QStringList oldFileList = m_balooFileMonitor->files(); + for (const QString& file : oldFileList) { if (m_model->index(QUrl::fromLocalFile(file)) >= 0) { newFileList.append(file); } @@ -394,6 +424,7 @@ void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList& itemRang m_recentlyChangedItems.clear(); m_recentlyChangedItemsTimer->stop(); m_changedItems.clear(); + m_hoverSequenceLoadedItems.clear(); killPreviewJob(); } else { @@ -408,15 +439,25 @@ void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList& itemRang } } + // Removed items won't have hover previews loaded anymore. + for (const KItemRange& itemRange : itemRanges) { + int index = itemRange.index; + for (int count = itemRange.count; count > 0; --count) { + const KFileItem item = m_model->fileItem(index); + m_hoverSequenceLoadedItems.remove(item); + ++index; + } + } + // The visible items might have changed. startUpdating(); } } -void KFileItemModelRolesUpdater::slotItemsMoved(const KItemRange& itemRange, QList movedToIndexes) +void KFileItemModelRolesUpdater::slotItemsMoved(const KItemRange& itemRange, const QList &movedToIndexes) { - Q_UNUSED(itemRange); - Q_UNUSED(movedToIndexes); + Q_UNUSED(itemRange) + Q_UNUSED(movedToIndexes) // The visible items might have changed. startUpdating(); @@ -425,7 +466,7 @@ void KFileItemModelRolesUpdater::slotItemsMoved(const KItemRange& itemRange, QLi 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 @@ -434,7 +475,7 @@ void KFileItemModelRolesUpdater::slotItemsChanged(const KItemRangeList& itemRang QSet& targetSet = itemsChangedRecently ? m_recentlyChangedItems : m_changedItems; - foreach (const KItemRange& itemRange, itemRanges) { + for (const KItemRange& itemRange : itemRanges) { int index = itemRange.index; for (int count = itemRange.count; count > 0; --count) { const KFileItem item = m_model->fileItem(index); @@ -453,8 +494,8 @@ void KFileItemModelRolesUpdater::slotItemsChanged(const KItemRangeList& itemRang void KFileItemModelRolesUpdater::slotSortRoleChanged(const QByteArray& current, const QByteArray& previous) { - Q_UNUSED(current); - Q_UNUSED(previous); + Q_UNUSED(current) + Q_UNUSED(previous) if (m_resolvableRoles.contains(current)) { m_pendingSortRoleItems.clear(); @@ -501,43 +542,7 @@ void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem& item, const QPi return; } - 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 (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 shrinked as it is too large to fit into - // the available icon size - KPixmapModifier::applyFrame(scaledPixmap, m_iconSize); - } - } - } else { - KPixmapModifier::scale(scaledPixmap, m_iconSize); - } + QPixmap scaledPixmap = transformPreviewPixmap(pixmap); QHash data = rolesData(item); @@ -547,12 +552,14 @@ void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem& item, const QPi // 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; + if (!scaledPixmap.isNull()) { + for (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; + } } } @@ -593,7 +600,7 @@ void KFileItemModelRolesUpdater::slotPreviewFailed(const KFileItem& item) void KFileItemModelRolesUpdater::slotPreviewJobFinished() { - m_previewJob = 0; + m_previewJob = nullptr; if (m_state != PreviewJobRunning) { return; @@ -610,6 +617,112 @@ void KFileItemModelRolesUpdater::slotPreviewJobFinished() } } +void KFileItemModelRolesUpdater::slotHoverSequenceGotPreview(const KFileItem& item, const QPixmap& pixmap) +{ + const int index = m_model->index(item); + if (index < 0) { + return; + } + + QHash data = m_model->data(index); + QVector pixmaps = data["hoverSequencePixmaps"].value>(); + const int loadedIndex = pixmaps.size(); + + float wap = m_hoverSequencePreviewJob->sequenceIndexWraparoundPoint(); + if (!m_hoverSequencePreviewJob->handlesSequences()) { + wap = 1.0f; + } + if (wap >= 0.0f) { + data["hoverSequenceWraparoundPoint"] = wap; + m_model->setData(index, data); + } + + // For hover sequence previews we never load index 0, because that's just the regular preview + // in "iconPixmap". But that means we'll load index 1 even for thumbnailers that don't support + // sequences, in which case we can just throw away the preview because it's the same as for + // index 0. Unfortunately we can't find it out earlier :( + if (wap < 0.0f || loadedIndex < static_cast(wap)) { + // Add the preview to the model data + + const QPixmap scaledPixmap = transformPreviewPixmap(pixmap); + + pixmaps.append(scaledPixmap); + data["hoverSequencePixmaps"] = QVariant::fromValue(pixmaps); + + m_model->setData(index, data); + + const auto loadedIt = std::find(m_hoverSequenceLoadedItems.begin(), + m_hoverSequenceLoadedItems.end(), item); + if (loadedIt == m_hoverSequenceLoadedItems.end()) { + m_hoverSequenceLoadedItems.push_back(item); + trimHoverSequenceLoadedItems(); + } + } + + m_hoverSequenceNumSuccessiveFailures = 0; +} + +void KFileItemModelRolesUpdater::slotHoverSequencePreviewFailed(const KFileItem& item) +{ + const int index = m_model->index(item); + if (index < 0) { + return; + } + + static const int numRetries = 2; + + QHash data = m_model->data(index); + QVector pixmaps = data["hoverSequencePixmaps"].value>(); + + qCDebug(DolphinDebug).nospace() + << "Failed to generate hover sequence preview #" << pixmaps.size() + << " for file " << item.url().toString() + << " (attempt " << (m_hoverSequenceNumSuccessiveFailures+1) + << "/" << (numRetries+1) << ")"; + + if (m_hoverSequenceNumSuccessiveFailures >= numRetries) { + // Give up and simply duplicate the previous sequence image (if any) + + pixmaps.append(pixmaps.empty() ? QPixmap() : pixmaps.last()); + data["hoverSequencePixmaps"] = QVariant::fromValue(pixmaps); + + if (!data.contains("hoverSequenceWraparoundPoint")) { + // hoverSequenceWraparoundPoint is only available when PreviewJob succeeds, so unless + // it has previously succeeded, it's best to assume that it just doesn't handle + // sequences instead of trying to load the next image indefinitely. + data["hoverSequenceWraparoundPoint"] = 1.0f; + } + + m_model->setData(index, data); + + m_hoverSequenceNumSuccessiveFailures = 0; + } else { + // Retry + + m_hoverSequenceNumSuccessiveFailures++; + } + + // Next image in the sequence (or same one if the retry limit wasn't reached yet) will be + // loaded automatically, because slotHoverSequencePreviewJobFinished() will be triggered + // even when PreviewJob fails. +} + +void KFileItemModelRolesUpdater::slotHoverSequencePreviewJobFinished() +{ + const int index = m_model->index(m_hoverSequenceItem); + if (index < 0) { + m_hoverSequencePreviewJob = nullptr; + return; + } + + // Since a PreviewJob can only have one associated sequence index, we can only generate + // one sequence image per job, so we have to start another one for the next index. + + // Load the next image in the sequence + m_hoverSequencePreviewJob = nullptr; + loadNextHoverSequencePreview(); +} + void KFileItemModelRolesUpdater::resolveNextSortRole() { if (m_state != ResolvingSortRole) { @@ -679,11 +792,14 @@ void KFileItemModelRolesUpdater::resolveNextPendingRoles() if (m_finishedItems.count() != m_model->count()) { QHash data; data.insert("iconPixmap", QPixmap()); + data.insert("hoverSequencePixmaps", QVariant::fromValue(QVector())); disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); for (int index = 0; index <= m_model->count(); ++index) { - if (m_model->data(index).contains("iconPixmap")) { + if (m_model->data(index).contains("iconPixmap") || + m_model->data(index).contains("hoverSequencePixmaps")) + { m_model->setData(index, data); } } @@ -718,6 +834,8 @@ void KFileItemModelRolesUpdater::applyChangedBalooRoles(const QString& file) return; } applyChangedBalooRolesForItem(item); +#else + Q_UNUSED(file) #endif } @@ -730,7 +848,8 @@ void KFileItemModelRolesUpdater::applyChangedBalooRolesForItem(const KFileItem & const KBalooRolesProvider& rolesProvider = KBalooRolesProvider::instance(); QHash data; - foreach (const QByteArray& role, rolesProvider.roles()) { + const auto roles = rolesProvider.roles(); + for (const QByteArray& role : 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 @@ -751,12 +870,12 @@ void KFileItemModelRolesUpdater::applyChangedBalooRolesForItem(const KFileItem & this, &KFileItemModelRolesUpdater::slotItemsChanged); #else #ifndef Q_CC_MSVC - Q_UNUSED(item); + Q_UNUSED(item) #endif #endif } -void KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived(const QString& path, int count) +void KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived(const QString& path, int count, long size) { const bool getSizeRole = m_roles.contains("size"); const bool getIsExpandableRole = m_roles.contains("isExpandable"); @@ -767,7 +886,8 @@ void KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived(const QStrin QHash data; if (getSizeRole) { - data.insert("size", count); + data.insert("count", count); + data.insert("size", QVariant::fromValue(size)); } if (getIsExpandableRole) { data.insert("isExpandable", count > 0); @@ -777,7 +897,7 @@ void KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived(const QStrin this, &KFileItemModelRolesUpdater::slotItemsChanged); m_model->setData(index, data); connect(m_model, &KFileItemModel::itemsChanged, - this, &KFileItemModelRolesUpdater::slotItemsChanged); + this, &KFileItemModelRolesUpdater::slotItemsChanged); } } } @@ -817,7 +937,7 @@ void KFileItemModelRolesUpdater::startUpdating() m_pendingPreviewItems.clear(); m_pendingPreviewItems.reserve(indexes.count()); - foreach (int index, indexes) { + for (int index : qAsConst(indexes)) { const KFileItem item = m_model->fileItem(index); if (!m_finishedItems.contains(item)) { m_pendingPreviewItems.append(item); @@ -906,8 +1026,8 @@ void KFileItemModelRolesUpdater::startPreviewJob() KIO::PreviewJob* job = new KIO::PreviewJob(itemSubSet, cacheSize, &m_enabledPlugins); - job->setIgnoreMaximumSize(itemSubSet.first().isLocalFile()); - if (job->ui()) { + job->setIgnoreMaximumSize(itemSubSet.first().isLocalFile() && m_localFileSizePreviewLimit <= 0); + if (job->uiDelegate()) { KJobWidgets::setWindow(job, qApp->activeWindow()); } @@ -921,6 +1041,129 @@ void KFileItemModelRolesUpdater::startPreviewJob() m_previewJob = job; } +QPixmap KFileItemModelRolesUpdater::transformPreviewPixmap(const QPixmap& pixmap) +{ + QPixmap scaledPixmap = pixmap; + + if (!pixmap.hasAlpha() && !pixmap.isNull() + && 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 if (!pixmap.isNull()) { + KPixmapModifier::scale(scaledPixmap, m_iconSize * qApp->devicePixelRatio()); + scaledPixmap.setDevicePixelRatio(qApp->devicePixelRatio()); + } + + return scaledPixmap; +} + +void KFileItemModelRolesUpdater::loadNextHoverSequencePreview() +{ + if (m_hoverSequenceItem.isNull() || m_hoverSequencePreviewJob) { + return; + } + + const int index = m_model->index(m_hoverSequenceItem); + if (index < 0) { + return; + } + + // We generate the next few sequence indices in advance (buffering) + const int maxSeqIdx = m_hoverSequenceIndex+5; + + QHash data = m_model->data(index); + + if (!data.contains("hoverSequencePixmaps")) { + // The pixmap at index 0 isn't used ("iconPixmap" will be used instead) + data.insert("hoverSequencePixmaps", QVariant::fromValue(QVector() << QPixmap())); + m_model->setData(index, data); + } + + const QVector pixmaps = data["hoverSequencePixmaps"].value>(); + + const int loadSeqIdx = pixmaps.size(); + + float wap = -1.0f; + if (data.contains("hoverSequenceWraparoundPoint")) { + wap = data["hoverSequenceWraparoundPoint"].toFloat(); + } + if (wap >= 1.0f && loadSeqIdx >= static_cast(wap)) { + // Reached the wraparound point -> no more previews to load. + return; + } + + if (loadSeqIdx > maxSeqIdx) { + // Wait until setHoverSequenceState() is called with a higher sequence index. + 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); + + KIO::PreviewJob* job = new KIO::PreviewJob({m_hoverSequenceItem}, cacheSize, &m_enabledPlugins); + + job->setSequenceIndex(loadSeqIdx); + job->setIgnoreMaximumSize(m_hoverSequenceItem.isLocalFile() && m_localFileSizePreviewLimit <= 0); + if (job->uiDelegate()) { + KJobWidgets::setWindow(job, qApp->activeWindow()); + } + + connect(job, &KIO::PreviewJob::gotPreview, + this, &KFileItemModelRolesUpdater::slotHoverSequenceGotPreview); + connect(job, &KIO::PreviewJob::failed, + this, &KFileItemModelRolesUpdater::slotHoverSequencePreviewFailed); + connect(job, &KIO::PreviewJob::finished, + this, &KFileItemModelRolesUpdater::slotHoverSequencePreviewJobFinished); + + m_hoverSequencePreviewJob = job; +} + +void KFileItemModelRolesUpdater::killHoverSequencePreviewJob() +{ + if (m_hoverSequencePreviewJob) { + disconnect(m_hoverSequencePreviewJob, &KIO::PreviewJob::gotPreview, + this, &KFileItemModelRolesUpdater::slotHoverSequenceGotPreview); + disconnect(m_hoverSequencePreviewJob, &KIO::PreviewJob::failed, + this, &KFileItemModelRolesUpdater::slotHoverSequencePreviewFailed); + disconnect(m_hoverSequencePreviewJob, &KIO::PreviewJob::finished, + this, &KFileItemModelRolesUpdater::slotHoverSequencePreviewJobFinished); + m_hoverSequencePreviewJob->kill(); + m_hoverSequencePreviewJob = nullptr; + } +} + void KFileItemModelRolesUpdater::updateChangedItems() { if (m_state == Paused) { @@ -949,14 +1192,19 @@ void KFileItemModelRolesUpdater::updateChangedItems() QList visibleChangedIndexes; QList invisibleChangedIndexes; + visibleChangedIndexes.reserve(m_changedItems.size()); + invisibleChangedIndexes.reserve(m_changedItems.size()); - foreach (const KFileItem& item, m_changedItems) { + auto changedItemsIt = m_changedItems.begin(); + while (changedItemsIt != m_changedItems.end()) { + const auto& item = *changedItemsIt; const int index = m_model->index(item); if (index < 0) { - m_changedItems.remove(item); + changedItemsIt = m_changedItems.erase(changedItemsIt); continue; } + ++changedItemsIt; if (index >= m_firstVisibleIndex && index <= m_lastVisibleIndex) { visibleChangedIndexes.append(index); @@ -968,11 +1216,11 @@ void KFileItemModelRolesUpdater::updateChangedItems() std::sort(visibleChangedIndexes.begin(), visibleChangedIndexes.end()); if (m_previewShown) { - foreach (int index, visibleChangedIndexes) { + for (int index : qAsConst(visibleChangedIndexes)) { m_pendingPreviewItems.append(m_model->fileItem(index)); } - foreach (int index, invisibleChangedIndexes) { + for (int index : qAsConst(invisibleChangedIndexes)) { m_pendingPreviewItems.append(m_model->fileItem(index)); } @@ -1003,7 +1251,9 @@ void KFileItemModelRolesUpdater::applySortRole(int index) data.insert("type", item.mimeComment()); } else if (m_model->sortRole() == "size" && item.isLocalFile() && item.isDir()) { const QString path = item.localPath(); - data.insert("size", m_directoryContentsCounter->countDirectoryContentsSynchronously(path)); + if (m_scanDirectories) { + m_directoryContentsCounter->scanDirectory(path); + } } else { // Probably the sort role is a baloo role - just determine all roles. data = rolesData(item); @@ -1047,10 +1297,13 @@ bool KFileItemModelRolesUpdater::applyResolvedRoles(int index, ResolveHint hint) data = rolesData(item); } - data.insert("iconName", item.iconName()); + if (!item.iconName().isEmpty()) { + data.insert("iconName", item.iconName()); + } if (m_clearPreviews) { data.insert("iconPixmap", QPixmap()); + data.insert("hoverSequencePixmaps", QVariant::fromValue(QVector())); } disconnect(m_model, &KFileItemModel::itemsChanged, @@ -1075,8 +1328,10 @@ QHash KFileItemModelRolesUpdater::rolesData(const KFileIte if (item.isLocalFile()) { // Tell m_directoryContentsCounter that we want to count the items // inside the directory. The result will be received in slotDirectoryContentsCountReceived. - const QString path = item.localPath(); - m_directoryContentsCounter->addDirectory(path); + if (m_scanDirectories) { + const QString path = item.localPath(); + m_directoryContentsCounter->scanDirectory(path); + } } else if (getSizeRole) { data.insert("size", -1); // -1 indicates an unknown number of items } @@ -1087,7 +1342,7 @@ QHash KFileItemModelRolesUpdater::rolesData(const KFileIte } QStringList overlays = item.overlays(); - foreach(KOverlayIconPlugin *it, m_overlayIconsPlugin) { + for (KOverlayIconPlugin *it : qAsConst(m_overlayIconsPlugin)) { overlays.append(it->getOverlays(item.url())); } data.insert("iconOverlays", overlays); @@ -1110,7 +1365,7 @@ void KFileItemModelRolesUpdater::slotOverlaysChanged(const QUrl& url, const QStr const int index = m_model->index(item); QHash data = m_model->data(index); QStringList overlays = item.overlays(); - foreach (KOverlayIconPlugin *it, m_overlayIconsPlugin) { + for (KOverlayIconPlugin *it : qAsConst(m_overlayIconsPlugin)) { overlays.append(it->getOverlays(url)); } data.insert("iconOverlays", overlays); @@ -1137,7 +1392,7 @@ void KFileItemModelRolesUpdater::killPreviewJob() disconnect(m_previewJob, &KIO::PreviewJob::finished, this, &KFileItemModelRolesUpdater::slotPreviewJobFinished); m_previewJob->kill(); - m_previewJob = 0; + m_previewJob = nullptr; m_pendingPreviewItems.clear(); } } @@ -1147,16 +1402,27 @@ QList KFileItemModelRolesUpdater::indexesToResolve() const const int count = m_model->count(); QList result; - result.reserve(ResolveAllItemsLimit); + result.reserve(qMin(count, (m_lastVisibleIndex - m_firstVisibleIndex + 1) + + ResolveAllItemsLimit + + (2 * m_maximumVisibleItems))); // Add visible items. + // Resolve files first, their previews are quicker. + QList visibleDirs; for (int i = m_firstVisibleIndex; i <= m_lastVisibleIndex; ++i) { - result.append(i); + const KFileItem item = m_model->fileItem(i); + if (item.isDir()) { + visibleDirs.append(i); + } else { + result.append(i); + } } + result.append(visibleDirs); + // We need a reasonable upper limit for number of items to resolve after // and before the visible range. m_maximumVisibleItems can be quite large - // when using Compace View. + // when using Compact View. const int readAheadItems = qMin(ReadAheadPages * m_maximumVisibleItems, ResolveAllItemsLimit / 2); // Add items after the visible range. @@ -1172,14 +1438,14 @@ QList KFileItemModelRolesUpdater::indexesToResolve() const } // Add items on the last page. - const int beginLastPage = qMax(qMin(endExtendedVisibleRange + 1, count - 1), count - m_maximumVisibleItems); + const int beginLastPage = qMax(endExtendedVisibleRange + 1, count - m_maximumVisibleItems); for (int i = beginLastPage; i < count; ++i) { result.append(i); } // Add items on the first page. - const int endFirstPage = qMin(qMax(beginExtendedVisibleRange - 1, 0), m_maximumVisibleItems); - for (int i = 0; i <= endFirstPage; ++i) { + const int endFirstPage = qMin(beginExtendedVisibleRange, m_maximumVisibleItems); + for (int i = 0; i < endFirstPage; ++i) { result.append(i); } @@ -1191,7 +1457,7 @@ QList KFileItemModelRolesUpdater::indexesToResolve() const --remainingItems; } - for (int i = beginExtendedVisibleRange - 1; i > endFirstPage && remainingItems > 0; --i) { + for (int i = beginExtendedVisibleRange - 1; i >= endFirstPage && remainingItems > 0; --i) { result.append(i); --remainingItems; } @@ -1199,3 +1465,23 @@ QList KFileItemModelRolesUpdater::indexesToResolve() const return result; } +void KFileItemModelRolesUpdater::trimHoverSequenceLoadedItems() +{ + static const size_t maxLoadedItems = 20; + + size_t loadedItems = m_hoverSequenceLoadedItems.size(); + while (loadedItems > maxLoadedItems) { + const KFileItem item = m_hoverSequenceLoadedItems.front(); + + m_hoverSequenceLoadedItems.pop_front(); + loadedItems--; + + const int index = m_model->index(item); + if (index >= 0) { + QHash data = m_model->data(index); + data["hoverSequencePixmaps"] = QVariant::fromValue(QVector() << QPixmap()); + m_model->setData(index, data); + } + } +} +