File indexing completed on 2024-04-28 05:45:05
0001 /* 0002 * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "kfileitemmodelrolesupdater.h" 0008 0009 #include "dolphindebug.h" 0010 #include "kfileitemmodel.h" 0011 #include "private/kdirectorycontentscounter.h" 0012 #include "private/kpixmapmodifier.h" 0013 0014 #include <KConfig> 0015 #include <KConfigGroup> 0016 #include <KIO/ListJob> 0017 #include <KIO/PreviewJob> 0018 #include <KIconLoader> 0019 #include <KJobWidgets> 0020 #include <KOverlayIconPlugin> 0021 #include <KPluginMetaData> 0022 #include <KSharedConfig> 0023 0024 #include "dolphin_contentdisplaysettings.h" 0025 0026 #if HAVE_BALOO 0027 #include "private/kbaloorolesprovider.h" 0028 #include <Baloo/File> 0029 #include <Baloo/FileMonitor> 0030 #endif 0031 0032 #include <QApplication> 0033 #include <QElapsedTimer> 0034 #include <QFileInfo> 0035 #include <QPainter> 0036 #include <QPluginLoader> 0037 #include <QTimer> 0038 #include <chrono> 0039 0040 using namespace std::chrono_literals; 0041 0042 // #define KFILEITEMMODELROLESUPDATER_DEBUG 0043 0044 namespace 0045 { 0046 // Maximum time in ms that the KFileItemModelRolesUpdater 0047 // may perform a blocking operation 0048 const int MaxBlockTimeout = 200; 0049 0050 // If the number of items is smaller than ResolveAllItemsLimit, 0051 // the roles of all items will be resolved. 0052 const int ResolveAllItemsLimit = 500; 0053 0054 // Not only the visible area, but up to ReadAheadPages before and after 0055 // this area will be resolved. 0056 const int ReadAheadPages = 5; 0057 } 0058 0059 KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel *model, QObject *parent) 0060 : QObject(parent) 0061 , m_state(Idle) 0062 , m_previewChangedDuringPausing(false) 0063 , m_iconSizeChangedDuringPausing(false) 0064 , m_rolesChangedDuringPausing(false) 0065 , m_previewShown(false) 0066 , m_enlargeSmallPreviews(true) 0067 , m_clearPreviews(false) 0068 , m_finishedItems() 0069 , m_model(model) 0070 , m_iconSize() 0071 , m_devicePixelRatio(1.0) 0072 , m_firstVisibleIndex(0) 0073 , m_lastVisibleIndex(-1) 0074 , m_maximumVisibleItems(50) 0075 , m_roles() 0076 , m_resolvableRoles() 0077 , m_enabledPlugins() 0078 , m_localFileSizePreviewLimit(0) 0079 , m_pendingSortRoleItems() 0080 , m_pendingIndexes() 0081 , m_pendingPreviewItems() 0082 , m_previewJob() 0083 , m_hoverSequenceItem() 0084 , m_hoverSequenceIndex(0) 0085 , m_hoverSequencePreviewJob(nullptr) 0086 , m_hoverSequenceNumSuccessiveFailures(0) 0087 , m_recentlyChangedItemsTimer(nullptr) 0088 , m_recentlyChangedItems() 0089 , m_changedItems() 0090 , m_directoryContentsCounter(nullptr) 0091 #if HAVE_BALOO 0092 , m_balooFileMonitor(nullptr) 0093 #endif 0094 { 0095 Q_ASSERT(model); 0096 0097 const KConfigGroup globalConfig(KSharedConfig::openConfig(), QStringLiteral("PreviewSettings")); 0098 m_enabledPlugins = globalConfig.readEntry("Plugins", KIO::PreviewJob::defaultPlugins()); 0099 m_localFileSizePreviewLimit = static_cast<qulonglong>(globalConfig.readEntry("MaximumSize", 0)); 0100 0101 connect(m_model, &KFileItemModel::itemsInserted, this, &KFileItemModelRolesUpdater::slotItemsInserted); 0102 connect(m_model, &KFileItemModel::itemsRemoved, this, &KFileItemModelRolesUpdater::slotItemsRemoved); 0103 connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); 0104 connect(m_model, &KFileItemModel::itemsMoved, this, &KFileItemModelRolesUpdater::slotItemsMoved); 0105 connect(m_model, &KFileItemModel::sortRoleChanged, this, &KFileItemModelRolesUpdater::slotSortRoleChanged); 0106 0107 // Use a timer to prevent that each call of slotItemsChanged() results in a synchronous 0108 // resolving of the roles. Postpone the resolving until no update has been done for 100 ms. 0109 m_recentlyChangedItemsTimer = new QTimer(this); 0110 m_recentlyChangedItemsTimer->setInterval(100ms); 0111 m_recentlyChangedItemsTimer->setSingleShot(true); 0112 connect(m_recentlyChangedItemsTimer, &QTimer::timeout, this, &KFileItemModelRolesUpdater::resolveRecentlyChangedItems); 0113 0114 m_resolvableRoles.insert("size"); 0115 m_resolvableRoles.insert("type"); 0116 m_resolvableRoles.insert("isExpandable"); 0117 #if HAVE_BALOO 0118 m_resolvableRoles += KBalooRolesProvider::instance().roles(); 0119 #endif 0120 0121 m_directoryContentsCounter = new KDirectoryContentsCounter(m_model, this); 0122 connect(m_directoryContentsCounter, &KDirectoryContentsCounter::result, this, &KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived); 0123 0124 const auto plugins = KPluginMetaData::findPlugins(QStringLiteral("kf6/overlayicon"), {}, KPluginMetaData::AllowEmptyMetaData); 0125 for (const KPluginMetaData &data : plugins) { 0126 auto instance = QPluginLoader(data.fileName()).instance(); 0127 auto plugin = qobject_cast<KOverlayIconPlugin *>(instance); 0128 if (plugin) { 0129 m_overlayIconsPlugin.append(plugin); 0130 connect(plugin, &KOverlayIconPlugin::overlaysChanged, this, &KFileItemModelRolesUpdater::slotOverlaysChanged); 0131 } else { 0132 // not our/valid plugin, so delete the created object 0133 delete instance; 0134 } 0135 } 0136 } 0137 0138 KFileItemModelRolesUpdater::~KFileItemModelRolesUpdater() 0139 { 0140 killPreviewJob(); 0141 } 0142 0143 void KFileItemModelRolesUpdater::setIconSize(const QSize &size) 0144 { 0145 if (size != m_iconSize) { 0146 m_iconSize = size; 0147 if (m_state == Paused) { 0148 m_iconSizeChangedDuringPausing = true; 0149 } else if (m_previewShown) { 0150 // An icon size change requires the regenerating of 0151 // all previews 0152 m_finishedItems.clear(); 0153 startUpdating(); 0154 } 0155 } 0156 } 0157 0158 QSize KFileItemModelRolesUpdater::iconSize() const 0159 { 0160 return m_iconSize; 0161 } 0162 0163 void KFileItemModelRolesUpdater::setDevicePixelRatio(qreal devicePixelRatio) 0164 { 0165 if (m_devicePixelRatio != devicePixelRatio) { 0166 m_devicePixelRatio = devicePixelRatio; 0167 if (m_state == Paused) { 0168 m_iconSizeChangedDuringPausing = true; 0169 } else if (m_previewShown) { 0170 // A dpr change requires the regenerating of all previews. 0171 m_finishedItems.clear(); 0172 startUpdating(); 0173 } 0174 } 0175 } 0176 0177 qreal KFileItemModelRolesUpdater::devicePixelRatio() const 0178 { 0179 return m_devicePixelRatio; 0180 } 0181 0182 void KFileItemModelRolesUpdater::setVisibleIndexRange(int index, int count) 0183 { 0184 if (index < 0) { 0185 index = 0; 0186 } 0187 if (count < 0) { 0188 count = 0; 0189 } 0190 0191 if (index == m_firstVisibleIndex && count == m_lastVisibleIndex - m_firstVisibleIndex + 1) { 0192 // The range has not been changed 0193 return; 0194 } 0195 0196 m_firstVisibleIndex = index; 0197 m_lastVisibleIndex = qMin(index + count - 1, m_model->count() - 1); 0198 0199 startUpdating(); 0200 } 0201 0202 void KFileItemModelRolesUpdater::setMaximumVisibleItems(int count) 0203 { 0204 m_maximumVisibleItems = count; 0205 } 0206 0207 void KFileItemModelRolesUpdater::setPreviewsShown(bool show) 0208 { 0209 if (show == m_previewShown) { 0210 return; 0211 } 0212 0213 m_previewShown = show; 0214 if (!show) { 0215 m_clearPreviews = true; 0216 } 0217 0218 updateAllPreviews(); 0219 } 0220 0221 bool KFileItemModelRolesUpdater::previewsShown() const 0222 { 0223 return m_previewShown; 0224 } 0225 0226 void KFileItemModelRolesUpdater::setEnlargeSmallPreviews(bool enlarge) 0227 { 0228 if (enlarge != m_enlargeSmallPreviews) { 0229 m_enlargeSmallPreviews = enlarge; 0230 if (m_previewShown) { 0231 updateAllPreviews(); 0232 } 0233 } 0234 } 0235 0236 bool KFileItemModelRolesUpdater::enlargeSmallPreviews() const 0237 { 0238 return m_enlargeSmallPreviews; 0239 } 0240 0241 void KFileItemModelRolesUpdater::setEnabledPlugins(const QStringList &list) 0242 { 0243 if (m_enabledPlugins != list) { 0244 m_enabledPlugins = list; 0245 if (m_previewShown) { 0246 updateAllPreviews(); 0247 } 0248 } 0249 } 0250 0251 void KFileItemModelRolesUpdater::setPaused(bool paused) 0252 { 0253 if (paused == (m_state == Paused)) { 0254 return; 0255 } 0256 0257 if (paused) { 0258 m_state = Paused; 0259 killPreviewJob(); 0260 } else { 0261 const bool updatePreviews = (m_iconSizeChangedDuringPausing && m_previewShown) || m_previewChangedDuringPausing; 0262 const bool resolveAll = updatePreviews || m_rolesChangedDuringPausing; 0263 if (resolveAll) { 0264 m_finishedItems.clear(); 0265 } 0266 0267 m_iconSizeChangedDuringPausing = false; 0268 m_previewChangedDuringPausing = false; 0269 m_rolesChangedDuringPausing = false; 0270 0271 if (!m_pendingSortRoleItems.isEmpty()) { 0272 m_state = ResolvingSortRole; 0273 resolveNextSortRole(); 0274 } else { 0275 m_state = Idle; 0276 } 0277 0278 startUpdating(); 0279 } 0280 } 0281 0282 void KFileItemModelRolesUpdater::setRoles(const QSet<QByteArray> &roles) 0283 { 0284 if (m_roles != roles) { 0285 m_roles = roles; 0286 0287 #if HAVE_BALOO 0288 // Check whether there is at least one role that must be resolved 0289 // with the help of Baloo. If this is the case, a (quite expensive) 0290 // resolving will be done in KFileItemModelRolesUpdater::rolesData() and 0291 // the role gets watched for changes. 0292 const KBalooRolesProvider &rolesProvider = KBalooRolesProvider::instance(); 0293 bool hasBalooRole = false; 0294 QSetIterator<QByteArray> it(roles); 0295 while (it.hasNext()) { 0296 const QByteArray &role = it.next(); 0297 if (rolesProvider.roles().contains(role)) { 0298 hasBalooRole = true; 0299 break; 0300 } 0301 } 0302 0303 if (hasBalooRole && m_balooConfig.fileIndexingEnabled() && !m_balooFileMonitor) { 0304 m_balooFileMonitor = new Baloo::FileMonitor(this); 0305 connect(m_balooFileMonitor, &Baloo::FileMonitor::fileMetaDataChanged, this, &KFileItemModelRolesUpdater::applyChangedBalooRoles); 0306 } else if (!hasBalooRole && m_balooFileMonitor) { 0307 delete m_balooFileMonitor; 0308 m_balooFileMonitor = nullptr; 0309 } 0310 #endif 0311 0312 if (m_state == Paused) { 0313 m_rolesChangedDuringPausing = true; 0314 } else { 0315 startUpdating(); 0316 } 0317 } 0318 } 0319 0320 QSet<QByteArray> KFileItemModelRolesUpdater::roles() const 0321 { 0322 return m_roles; 0323 } 0324 0325 bool KFileItemModelRolesUpdater::isPaused() const 0326 { 0327 return m_state == Paused; 0328 } 0329 0330 QStringList KFileItemModelRolesUpdater::enabledPlugins() const 0331 { 0332 return m_enabledPlugins; 0333 } 0334 0335 void KFileItemModelRolesUpdater::setLocalFileSizePreviewLimit(const qlonglong size) 0336 { 0337 m_localFileSizePreviewLimit = size; 0338 } 0339 0340 qlonglong KFileItemModelRolesUpdater::localFileSizePreviewLimit() const 0341 { 0342 return m_localFileSizePreviewLimit; 0343 } 0344 0345 void KFileItemModelRolesUpdater::setHoverSequenceState(const QUrl &itemUrl, int seqIdx) 0346 { 0347 const KFileItem item = m_model->fileItem(itemUrl); 0348 0349 if (item != m_hoverSequenceItem) { 0350 killHoverSequencePreviewJob(); 0351 } 0352 0353 m_hoverSequenceItem = item; 0354 m_hoverSequenceIndex = seqIdx; 0355 0356 if (!m_previewShown) { 0357 return; 0358 } 0359 0360 m_hoverSequenceNumSuccessiveFailures = 0; 0361 0362 loadNextHoverSequencePreview(); 0363 } 0364 0365 void KFileItemModelRolesUpdater::slotItemsInserted(const KItemRangeList &itemRanges) 0366 { 0367 QElapsedTimer timer; 0368 timer.start(); 0369 0370 // Determine the sort role synchronously for as many items as possible. 0371 if (m_resolvableRoles.contains(m_model->sortRole())) { 0372 int insertedCount = 0; 0373 for (const KItemRange &range : itemRanges) { 0374 const int lastIndex = insertedCount + range.index + range.count - 1; 0375 for (int i = insertedCount + range.index; i <= lastIndex; ++i) { 0376 if (timer.elapsed() < MaxBlockTimeout) { 0377 applySortRole(i); 0378 } else { 0379 m_pendingSortRoleItems.insert(m_model->fileItem(i)); 0380 } 0381 } 0382 insertedCount += range.count; 0383 } 0384 0385 applySortProgressToModel(); 0386 0387 // If there are still items whose sort role is unknown, check if the 0388 // asynchronous determination of the sort role is already in progress, 0389 // and start it if that is not the case. 0390 if (!m_pendingSortRoleItems.isEmpty() && m_state != ResolvingSortRole) { 0391 killPreviewJob(); 0392 m_state = ResolvingSortRole; 0393 resolveNextSortRole(); 0394 } 0395 } 0396 0397 startUpdating(); 0398 } 0399 0400 void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList &itemRanges) 0401 { 0402 Q_UNUSED(itemRanges) 0403 0404 const bool allItemsRemoved = (m_model->count() == 0); 0405 0406 #if HAVE_BALOO 0407 if (m_balooFileMonitor) { 0408 // Don't let the FileWatcher watch for removed items 0409 if (allItemsRemoved) { 0410 m_balooFileMonitor->clear(); 0411 } else { 0412 QStringList newFileList; 0413 const QStringList oldFileList = m_balooFileMonitor->files(); 0414 for (const QString &file : oldFileList) { 0415 if (m_model->index(QUrl::fromLocalFile(file)) >= 0) { 0416 newFileList.append(file); 0417 } 0418 } 0419 m_balooFileMonitor->setFiles(newFileList); 0420 } 0421 } 0422 #endif 0423 0424 if (allItemsRemoved) { 0425 m_state = Idle; 0426 0427 m_finishedItems.clear(); 0428 m_pendingSortRoleItems.clear(); 0429 m_pendingIndexes.clear(); 0430 m_pendingPreviewItems.clear(); 0431 m_recentlyChangedItems.clear(); 0432 m_recentlyChangedItemsTimer->stop(); 0433 m_changedItems.clear(); 0434 m_hoverSequenceLoadedItems.clear(); 0435 0436 killPreviewJob(); 0437 if (!m_model->showDirectoriesOnly()) { 0438 m_directoryContentsCounter->stopWorker(); 0439 } 0440 } else { 0441 // Only remove the items from m_finishedItems. They will be removed 0442 // from the other sets later on. 0443 QSet<KFileItem>::iterator it = m_finishedItems.begin(); 0444 while (it != m_finishedItems.end()) { 0445 if (m_model->index(*it) < 0) { 0446 it = m_finishedItems.erase(it); 0447 } else { 0448 ++it; 0449 } 0450 } 0451 0452 // Removed items won't have hover previews loaded anymore. 0453 for (const KItemRange &itemRange : itemRanges) { 0454 int index = itemRange.index; 0455 for (int count = itemRange.count; count > 0; --count) { 0456 const KFileItem item = m_model->fileItem(index); 0457 m_hoverSequenceLoadedItems.remove(item); 0458 ++index; 0459 } 0460 } 0461 0462 // The visible items might have changed. 0463 startUpdating(); 0464 } 0465 } 0466 0467 void KFileItemModelRolesUpdater::slotItemsMoved(KItemRange itemRange, const QList<int> &movedToIndexes) 0468 { 0469 Q_UNUSED(itemRange) 0470 Q_UNUSED(movedToIndexes) 0471 0472 // The visible items might have changed. 0473 startUpdating(); 0474 } 0475 0476 void KFileItemModelRolesUpdater::slotItemsChanged(const KItemRangeList &itemRanges, const QSet<QByteArray> &roles) 0477 { 0478 Q_UNUSED(roles) 0479 0480 // Find out if slotItemsChanged() has been done recently. If that is the 0481 // case, resolving the roles is postponed until a timer has exceeded 0482 // to prevent expensive repeated updates if files are updated frequently. 0483 const bool itemsChangedRecently = m_recentlyChangedItemsTimer->isActive(); 0484 0485 QSet<KFileItem> &targetSet = itemsChangedRecently ? m_recentlyChangedItems : m_changedItems; 0486 0487 for (const KItemRange &itemRange : itemRanges) { 0488 int index = itemRange.index; 0489 for (int count = itemRange.count; count > 0; --count) { 0490 const KFileItem item = m_model->fileItem(index); 0491 targetSet.insert(item); 0492 ++index; 0493 } 0494 } 0495 0496 m_recentlyChangedItemsTimer->start(); 0497 0498 if (!itemsChangedRecently) { 0499 updateChangedItems(); 0500 } 0501 } 0502 0503 void KFileItemModelRolesUpdater::slotSortRoleChanged(const QByteArray ¤t, const QByteArray &previous) 0504 { 0505 Q_UNUSED(current) 0506 Q_UNUSED(previous) 0507 0508 if (m_resolvableRoles.contains(current)) { 0509 m_pendingSortRoleItems.clear(); 0510 m_finishedItems.clear(); 0511 0512 const int count = m_model->count(); 0513 QElapsedTimer timer; 0514 timer.start(); 0515 0516 // Determine the sort role synchronously for as many items as possible. 0517 for (int index = 0; index < count; ++index) { 0518 if (timer.elapsed() < MaxBlockTimeout) { 0519 applySortRole(index); 0520 } else { 0521 m_pendingSortRoleItems.insert(m_model->fileItem(index)); 0522 } 0523 } 0524 0525 applySortProgressToModel(); 0526 0527 if (!m_pendingSortRoleItems.isEmpty()) { 0528 // Trigger the asynchronous determination of the sort role. 0529 killPreviewJob(); 0530 m_state = ResolvingSortRole; 0531 resolveNextSortRole(); 0532 } 0533 } else { 0534 m_state = Idle; 0535 m_pendingSortRoleItems.clear(); 0536 applySortProgressToModel(); 0537 } 0538 } 0539 0540 void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem &item, const QPixmap &pixmap) 0541 { 0542 if (m_state != PreviewJobRunning) { 0543 return; 0544 } 0545 0546 m_changedItems.remove(item); 0547 0548 const int index = m_model->index(item); 0549 if (index < 0) { 0550 return; 0551 } 0552 0553 QPixmap scaledPixmap = transformPreviewPixmap(pixmap); 0554 0555 QHash<QByteArray, QVariant> data = rolesData(item, index); 0556 0557 const QStringList overlays = data["iconOverlays"].toStringList(); 0558 // Strangely KFileItem::overlays() returns empty string-values, so 0559 // we need to check first whether an overlay must be drawn at all. 0560 // It is more efficient to do it here, as KIconLoader::drawOverlays() 0561 // assumes that an overlay will be drawn and has some additional 0562 // setup time. 0563 if (!scaledPixmap.isNull()) { 0564 for (const QString &overlay : overlays) { 0565 if (!overlay.isEmpty()) { 0566 // There is at least one overlay, draw all overlays above m_pixmap 0567 // and cancel the check 0568 KIconLoader::global()->drawOverlays(overlays, scaledPixmap, KIconLoader::Desktop); 0569 break; 0570 } 0571 } 0572 } 0573 0574 data.insert("iconPixmap", scaledPixmap); 0575 0576 disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); 0577 m_model->setData(index, data); 0578 connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); 0579 0580 m_finishedItems.insert(item); 0581 } 0582 0583 void KFileItemModelRolesUpdater::slotPreviewFailed(const KFileItem &item) 0584 { 0585 if (m_state != PreviewJobRunning) { 0586 return; 0587 } 0588 0589 m_changedItems.remove(item); 0590 0591 const int index = m_model->index(item); 0592 if (index >= 0) { 0593 QHash<QByteArray, QVariant> data; 0594 data.insert("iconPixmap", QPixmap()); 0595 0596 disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); 0597 m_model->setData(index, data); 0598 connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); 0599 0600 applyResolvedRoles(index, ResolveAll); 0601 m_finishedItems.insert(item); 0602 } 0603 } 0604 0605 void KFileItemModelRolesUpdater::slotPreviewJobFinished() 0606 { 0607 m_previewJob = nullptr; 0608 0609 if (m_state != PreviewJobRunning) { 0610 return; 0611 } 0612 0613 m_state = Idle; 0614 0615 if (!m_pendingPreviewItems.isEmpty()) { 0616 startPreviewJob(); 0617 } else { 0618 if (!m_changedItems.isEmpty()) { 0619 updateChangedItems(); 0620 } 0621 } 0622 } 0623 0624 void KFileItemModelRolesUpdater::slotHoverSequenceGotPreview(const KFileItem &item, const QPixmap &pixmap) 0625 { 0626 const int index = m_model->index(item); 0627 if (index < 0) { 0628 return; 0629 } 0630 0631 QHash<QByteArray, QVariant> data = m_model->data(index); 0632 QVector<QPixmap> pixmaps = data["hoverSequencePixmaps"].value<QVector<QPixmap>>(); 0633 const int loadedIndex = pixmaps.size(); 0634 0635 float wap = m_hoverSequencePreviewJob->sequenceIndexWraparoundPoint(); 0636 if (!m_hoverSequencePreviewJob->handlesSequences()) { 0637 wap = 1.0f; 0638 } 0639 if (wap >= 0.0f) { 0640 data["hoverSequenceWraparoundPoint"] = wap; 0641 m_model->setData(index, data); 0642 } 0643 0644 // For hover sequence previews we never load index 0, because that's just the regular preview 0645 // in "iconPixmap". But that means we'll load index 1 even for thumbnailers that don't support 0646 // sequences, in which case we can just throw away the preview because it's the same as for 0647 // index 0. Unfortunately we can't find it out earlier :( 0648 if (wap < 0.0f || loadedIndex < static_cast<int>(wap)) { 0649 // Add the preview to the model data 0650 0651 const QPixmap scaledPixmap = transformPreviewPixmap(pixmap); 0652 0653 pixmaps.append(scaledPixmap); 0654 data["hoverSequencePixmaps"] = QVariant::fromValue(pixmaps); 0655 0656 m_model->setData(index, data); 0657 0658 const auto loadedIt = std::find(m_hoverSequenceLoadedItems.begin(), m_hoverSequenceLoadedItems.end(), item); 0659 if (loadedIt == m_hoverSequenceLoadedItems.end()) { 0660 m_hoverSequenceLoadedItems.push_back(item); 0661 trimHoverSequenceLoadedItems(); 0662 } 0663 } 0664 0665 m_hoverSequenceNumSuccessiveFailures = 0; 0666 } 0667 0668 void KFileItemModelRolesUpdater::slotHoverSequencePreviewFailed(const KFileItem &item) 0669 { 0670 const int index = m_model->index(item); 0671 if (index < 0) { 0672 return; 0673 } 0674 0675 static const int numRetries = 2; 0676 0677 QHash<QByteArray, QVariant> data = m_model->data(index); 0678 QVector<QPixmap> pixmaps = data["hoverSequencePixmaps"].value<QVector<QPixmap>>(); 0679 0680 qCDebug(DolphinDebug).nospace() << "Failed to generate hover sequence preview #" << pixmaps.size() << " for file " << item.url().toString() << " (attempt " 0681 << (m_hoverSequenceNumSuccessiveFailures + 1) << "/" << (numRetries + 1) << ")"; 0682 0683 if (m_hoverSequenceNumSuccessiveFailures >= numRetries) { 0684 // Give up and simply duplicate the previous sequence image (if any) 0685 0686 pixmaps.append(pixmaps.empty() ? QPixmap() : pixmaps.last()); 0687 data["hoverSequencePixmaps"] = QVariant::fromValue(pixmaps); 0688 0689 if (!data.contains("hoverSequenceWraparoundPoint")) { 0690 // hoverSequenceWraparoundPoint is only available when PreviewJob succeeds, so unless 0691 // it has previously succeeded, it's best to assume that it just doesn't handle 0692 // sequences instead of trying to load the next image indefinitely. 0693 data["hoverSequenceWraparoundPoint"] = 1.0f; 0694 } 0695 0696 m_model->setData(index, data); 0697 0698 m_hoverSequenceNumSuccessiveFailures = 0; 0699 } else { 0700 // Retry 0701 0702 m_hoverSequenceNumSuccessiveFailures++; 0703 } 0704 0705 // Next image in the sequence (or same one if the retry limit wasn't reached yet) will be 0706 // loaded automatically, because slotHoverSequencePreviewJobFinished() will be triggered 0707 // even when PreviewJob fails. 0708 } 0709 0710 void KFileItemModelRolesUpdater::slotHoverSequencePreviewJobFinished() 0711 { 0712 const int index = m_model->index(m_hoverSequenceItem); 0713 if (index < 0) { 0714 m_hoverSequencePreviewJob = nullptr; 0715 return; 0716 } 0717 0718 // Since a PreviewJob can only have one associated sequence index, we can only generate 0719 // one sequence image per job, so we have to start another one for the next index. 0720 0721 // Load the next image in the sequence 0722 m_hoverSequencePreviewJob = nullptr; 0723 loadNextHoverSequencePreview(); 0724 } 0725 0726 void KFileItemModelRolesUpdater::resolveNextSortRole() 0727 { 0728 if (m_state != ResolvingSortRole) { 0729 return; 0730 } 0731 0732 QSet<KFileItem>::iterator it = m_pendingSortRoleItems.begin(); 0733 while (it != m_pendingSortRoleItems.end()) { 0734 const KFileItem item = *it; 0735 const int index = m_model->index(item); 0736 0737 // Continue if the sort role has already been determined for the 0738 // item, and the item has not been changed recently. 0739 if (!m_changedItems.contains(item) && m_model->data(index).contains(m_model->sortRole())) { 0740 it = m_pendingSortRoleItems.erase(it); 0741 continue; 0742 } 0743 0744 applySortRole(index); 0745 m_pendingSortRoleItems.erase(it); 0746 break; 0747 } 0748 0749 if (!m_pendingSortRoleItems.isEmpty()) { 0750 applySortProgressToModel(); 0751 QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::resolveNextSortRole); 0752 } else { 0753 m_state = Idle; 0754 0755 // Prevent that we try to update the items twice. 0756 disconnect(m_model, &KFileItemModel::itemsMoved, this, &KFileItemModelRolesUpdater::slotItemsMoved); 0757 applySortProgressToModel(); 0758 connect(m_model, &KFileItemModel::itemsMoved, this, &KFileItemModelRolesUpdater::slotItemsMoved); 0759 startUpdating(); 0760 } 0761 } 0762 0763 void KFileItemModelRolesUpdater::resolveNextPendingRoles() 0764 { 0765 if (m_state != ResolvingAllRoles) { 0766 return; 0767 } 0768 0769 while (!m_pendingIndexes.isEmpty()) { 0770 const int index = m_pendingIndexes.takeFirst(); 0771 const KFileItem item = m_model->fileItem(index); 0772 0773 if (m_finishedItems.contains(item)) { 0774 continue; 0775 } 0776 0777 applyResolvedRoles(index, ResolveAll); 0778 m_finishedItems.insert(item); 0779 m_changedItems.remove(item); 0780 break; 0781 } 0782 0783 if (!m_pendingIndexes.isEmpty()) { 0784 QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::resolveNextPendingRoles); 0785 } else { 0786 m_state = Idle; 0787 0788 if (m_clearPreviews) { 0789 // Only go through the list if there are items which might still have previews. 0790 if (m_finishedItems.count() != m_model->count()) { 0791 QHash<QByteArray, QVariant> data; 0792 data.insert("iconPixmap", QPixmap()); 0793 data.insert("hoverSequencePixmaps", QVariant::fromValue(QVector<QPixmap>())); 0794 0795 disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); 0796 for (int index = 0; index <= m_model->count(); ++index) { 0797 if (m_model->data(index).contains("iconPixmap") || m_model->data(index).contains("hoverSequencePixmaps")) { 0798 m_model->setData(index, data); 0799 } 0800 } 0801 connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); 0802 } 0803 m_clearPreviews = false; 0804 } 0805 0806 if (!m_changedItems.isEmpty()) { 0807 updateChangedItems(); 0808 } 0809 } 0810 } 0811 0812 void KFileItemModelRolesUpdater::resolveRecentlyChangedItems() 0813 { 0814 m_changedItems += m_recentlyChangedItems; 0815 m_recentlyChangedItems.clear(); 0816 updateChangedItems(); 0817 } 0818 0819 void KFileItemModelRolesUpdater::applyChangedBalooRoles(const QString &file) 0820 { 0821 #if HAVE_BALOO 0822 const KFileItem item = m_model->fileItem(QUrl::fromLocalFile(file)); 0823 0824 if (item.isNull()) { 0825 // itemUrl is not in the model anymore, probably because 0826 // the corresponding file has been deleted in the meantime. 0827 return; 0828 } 0829 applyChangedBalooRolesForItem(item); 0830 #else 0831 Q_UNUSED(file) 0832 #endif 0833 } 0834 0835 void KFileItemModelRolesUpdater::applyChangedBalooRolesForItem(const KFileItem &item) 0836 { 0837 #if HAVE_BALOO 0838 Baloo::File file(item.localPath()); 0839 file.load(); 0840 0841 const KBalooRolesProvider &rolesProvider = KBalooRolesProvider::instance(); 0842 QHash<QByteArray, QVariant> data; 0843 0844 const auto roles = rolesProvider.roles(); 0845 for (const QByteArray &role : roles) { 0846 // Overwrite all the role values with an empty QVariant, because the roles 0847 // provider doesn't overwrite it when the property value list is empty. 0848 // See bug 322348 0849 data.insert(role, QVariant()); 0850 } 0851 0852 QHashIterator<QByteArray, QVariant> it(rolesProvider.roleValues(file, m_roles)); 0853 while (it.hasNext()) { 0854 it.next(); 0855 data.insert(it.key(), it.value()); 0856 } 0857 0858 disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); 0859 const int index = m_model->index(item); 0860 m_model->setData(index, data); 0861 connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); 0862 #else 0863 #ifndef Q_CC_MSVC 0864 Q_UNUSED(item) 0865 #endif 0866 #endif 0867 } 0868 0869 void KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived(const QString &path, int count, long long size) 0870 { 0871 const bool getIsExpandableRole = m_roles.contains("isExpandable"); 0872 const bool getSizeRole = m_roles.contains("size"); 0873 0874 if (getSizeRole || getIsExpandableRole) { 0875 const int index = m_model->index(QUrl::fromLocalFile(path)); 0876 if (index >= 0) { 0877 QHash<QByteArray, QVariant> data; 0878 0879 if (getSizeRole) { 0880 data.insert("count", count); 0881 data.insert("size", QVariant::fromValue(size)); 0882 } 0883 if (getIsExpandableRole) { 0884 data.insert("isExpandable", count > 0); 0885 } 0886 0887 disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); 0888 m_model->setData(index, data); 0889 connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); 0890 } 0891 } 0892 } 0893 0894 void KFileItemModelRolesUpdater::startUpdating() 0895 { 0896 if (m_state == Paused) { 0897 return; 0898 } 0899 0900 if (m_finishedItems.count() == m_model->count()) { 0901 // All roles have been resolved already. 0902 m_state = Idle; 0903 return; 0904 } 0905 0906 // Terminate all updates that are currently active. 0907 killPreviewJob(); 0908 m_pendingIndexes.clear(); 0909 0910 QElapsedTimer timer; 0911 timer.start(); 0912 0913 // Determine the icons for the visible items synchronously. 0914 updateVisibleIcons(); 0915 0916 // A detailed update of the items in and near the visible area 0917 // only makes sense if sorting is finished. 0918 if (m_state == ResolvingSortRole) { 0919 return; 0920 } 0921 0922 // Start the preview job or the asynchronous resolving of all roles. 0923 QList<int> indexes = indexesToResolve(); 0924 0925 if (m_previewShown) { 0926 m_pendingPreviewItems.clear(); 0927 m_pendingPreviewItems.reserve(indexes.count()); 0928 0929 for (int index : std::as_const(indexes)) { 0930 const KFileItem item = m_model->fileItem(index); 0931 if (!m_finishedItems.contains(item)) { 0932 m_pendingPreviewItems.append(item); 0933 } 0934 } 0935 0936 startPreviewJob(); 0937 } else { 0938 m_pendingIndexes = indexes; 0939 // Trigger the asynchronous resolving of all roles. 0940 m_state = ResolvingAllRoles; 0941 QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::resolveNextPendingRoles); 0942 } 0943 } 0944 0945 void KFileItemModelRolesUpdater::updateVisibleIcons() 0946 { 0947 int lastVisibleIndex = m_lastVisibleIndex; 0948 if (lastVisibleIndex <= 0) { 0949 // Guess a reasonable value for the last visible index if the view 0950 // has not told us about the real value yet. 0951 lastVisibleIndex = qMin(m_firstVisibleIndex + m_maximumVisibleItems, m_model->count() - 1); 0952 if (lastVisibleIndex <= 0) { 0953 lastVisibleIndex = qMin(200, m_model->count() - 1); 0954 } 0955 } 0956 0957 QElapsedTimer timer; 0958 timer.start(); 0959 0960 // Try to determine the final icons for all visible items. 0961 int index; 0962 for (index = m_firstVisibleIndex; index <= lastVisibleIndex && timer.elapsed() < MaxBlockTimeout; ++index) { 0963 applyResolvedRoles(index, ResolveFast); 0964 } 0965 0966 // KFileItemListView::initializeItemListWidget(KItemListWidget*) will load 0967 // preliminary icons (i.e., without mime type determination) for the 0968 // remaining items. 0969 } 0970 0971 void KFileItemModelRolesUpdater::startPreviewJob() 0972 { 0973 m_state = PreviewJobRunning; 0974 0975 if (m_pendingPreviewItems.isEmpty()) { 0976 QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::slotPreviewJobFinished); 0977 return; 0978 } 0979 0980 // PreviewJob internally caches items always with the size of 0981 // 128 x 128 pixels or 256 x 256 pixels. A (slow) downscaling is done 0982 // by PreviewJob if a smaller size is requested. For images KFileItemModelRolesUpdater must 0983 // do a downscaling anyhow because of the frame, so in this case only the provided 0984 // cache sizes are requested. 0985 const QSize cacheSize = (m_iconSize.width() > 128) || (m_iconSize.height() > 128) ? QSize(256, 256) : QSize(128, 128); 0986 0987 // KIO::filePreview() will request the MIME-type of all passed items, which (in the 0988 // worst case) might block the application for several seconds. To prevent such 0989 // a blocking, we only pass items with known mime type to the preview job. 0990 const int count = m_pendingPreviewItems.count(); 0991 KFileItemList itemSubSet; 0992 itemSubSet.reserve(count); 0993 0994 if (m_pendingPreviewItems.first().isMimeTypeKnown()) { 0995 // Some mime types are known already, probably because they were 0996 // determined when loading the icons for the visible items. Start 0997 // a preview job for all items at the beginning of the list which 0998 // have a known mime type. 0999 do { 1000 itemSubSet.append(m_pendingPreviewItems.takeFirst()); 1001 } while (!m_pendingPreviewItems.isEmpty() && m_pendingPreviewItems.first().isMimeTypeKnown()); 1002 } else { 1003 // Determine mime types for MaxBlockTimeout ms, and start a preview 1004 // job for the corresponding items. 1005 QElapsedTimer timer; 1006 timer.start(); 1007 1008 do { 1009 const KFileItem item = m_pendingPreviewItems.takeFirst(); 1010 item.determineMimeType(); 1011 itemSubSet.append(item); 1012 } while (!m_pendingPreviewItems.isEmpty() && timer.elapsed() < MaxBlockTimeout); 1013 } 1014 1015 KIO::PreviewJob *job = new KIO::PreviewJob(itemSubSet, cacheSize, &m_enabledPlugins); 1016 job->setDevicePixelRatio(m_devicePixelRatio); 1017 job->setIgnoreMaximumSize(itemSubSet.first().isLocalFile() && !itemSubSet.first().isSlow() && m_localFileSizePreviewLimit <= 0); 1018 if (job->uiDelegate()) { 1019 KJobWidgets::setWindow(job, qApp->activeWindow()); 1020 } 1021 1022 connect(job, &KIO::PreviewJob::gotPreview, this, &KFileItemModelRolesUpdater::slotGotPreview); 1023 connect(job, &KIO::PreviewJob::failed, this, &KFileItemModelRolesUpdater::slotPreviewFailed); 1024 connect(job, &KIO::PreviewJob::finished, this, &KFileItemModelRolesUpdater::slotPreviewJobFinished); 1025 1026 m_previewJob = job; 1027 } 1028 1029 QPixmap KFileItemModelRolesUpdater::transformPreviewPixmap(const QPixmap &pixmap) 1030 { 1031 QPixmap scaledPixmap = pixmap; 1032 1033 if (!pixmap.hasAlpha() && !pixmap.isNull() && m_iconSize.width() > KIconLoader::SizeSmallMedium && m_iconSize.height() > KIconLoader::SizeSmallMedium) { 1034 if (m_enlargeSmallPreviews) { 1035 KPixmapModifier::applyFrame(scaledPixmap, m_iconSize); 1036 } else { 1037 // Assure that small previews don't get enlarged. Instead they 1038 // should be shown centered within the frame. 1039 const QSize contentSize = KPixmapModifier::sizeInsideFrame(m_iconSize); 1040 const bool enlargingRequired = scaledPixmap.width() < contentSize.width() && scaledPixmap.height() < contentSize.height(); 1041 if (enlargingRequired) { 1042 QSize frameSize = scaledPixmap.size() / scaledPixmap.devicePixelRatio(); 1043 frameSize.scale(m_iconSize, Qt::KeepAspectRatio); 1044 1045 QPixmap largeFrame(frameSize); 1046 largeFrame.fill(Qt::transparent); 1047 1048 KPixmapModifier::applyFrame(largeFrame, frameSize); 1049 1050 QPainter painter(&largeFrame); 1051 painter.drawPixmap((largeFrame.width() - scaledPixmap.width() / scaledPixmap.devicePixelRatio()) / 2, 1052 (largeFrame.height() - scaledPixmap.height() / scaledPixmap.devicePixelRatio()) / 2, 1053 scaledPixmap); 1054 scaledPixmap = largeFrame; 1055 } else { 1056 // The image must be shrunk as it is too large to fit into 1057 // the available icon size 1058 KPixmapModifier::applyFrame(scaledPixmap, m_iconSize); 1059 } 1060 } 1061 } else if (!pixmap.isNull()) { 1062 KPixmapModifier::scale(scaledPixmap, m_iconSize * m_devicePixelRatio); 1063 scaledPixmap.setDevicePixelRatio(m_devicePixelRatio); 1064 } 1065 1066 return scaledPixmap; 1067 } 1068 1069 void KFileItemModelRolesUpdater::loadNextHoverSequencePreview() 1070 { 1071 if (m_hoverSequenceItem.isNull() || m_hoverSequencePreviewJob) { 1072 return; 1073 } 1074 1075 const int index = m_model->index(m_hoverSequenceItem); 1076 if (index < 0) { 1077 return; 1078 } 1079 1080 // We generate the next few sequence indices in advance (buffering) 1081 const int maxSeqIdx = m_hoverSequenceIndex + 5; 1082 1083 QHash<QByteArray, QVariant> data = m_model->data(index); 1084 1085 if (!data.contains("hoverSequencePixmaps")) { 1086 // The pixmap at index 0 isn't used ("iconPixmap" will be used instead) 1087 data.insert("hoverSequencePixmaps", QVariant::fromValue(QVector<QPixmap>() << QPixmap())); 1088 m_model->setData(index, data); 1089 } 1090 1091 const QVector<QPixmap> pixmaps = data["hoverSequencePixmaps"].value<QVector<QPixmap>>(); 1092 1093 const int loadSeqIdx = pixmaps.size(); 1094 1095 float wap = -1.0f; 1096 if (data.contains("hoverSequenceWraparoundPoint")) { 1097 wap = data["hoverSequenceWraparoundPoint"].toFloat(); 1098 } 1099 if (wap >= 1.0f && loadSeqIdx >= static_cast<int>(wap)) { 1100 // Reached the wraparound point -> no more previews to load. 1101 return; 1102 } 1103 1104 if (loadSeqIdx > maxSeqIdx) { 1105 // Wait until setHoverSequenceState() is called with a higher sequence index. 1106 return; 1107 } 1108 1109 // PreviewJob internally caches items always with the size of 1110 // 128 x 128 pixels or 256 x 256 pixels. A (slow) downscaling is done 1111 // by PreviewJob if a smaller size is requested. For images KFileItemModelRolesUpdater must 1112 // do a downscaling anyhow because of the frame, so in this case only the provided 1113 // cache sizes are requested. 1114 const QSize cacheSize = (m_iconSize.width() > 128) || (m_iconSize.height() > 128) ? QSize(256, 256) : QSize(128, 128); 1115 1116 KIO::PreviewJob *job = new KIO::PreviewJob({m_hoverSequenceItem}, cacheSize, &m_enabledPlugins); 1117 1118 job->setSequenceIndex(loadSeqIdx); 1119 job->setIgnoreMaximumSize(m_hoverSequenceItem.isLocalFile() && !m_hoverSequenceItem.isSlow() && m_localFileSizePreviewLimit <= 0); 1120 if (job->uiDelegate()) { 1121 KJobWidgets::setWindow(job, qApp->activeWindow()); 1122 } 1123 1124 connect(job, &KIO::PreviewJob::gotPreview, this, &KFileItemModelRolesUpdater::slotHoverSequenceGotPreview); 1125 connect(job, &KIO::PreviewJob::failed, this, &KFileItemModelRolesUpdater::slotHoverSequencePreviewFailed); 1126 connect(job, &KIO::PreviewJob::finished, this, &KFileItemModelRolesUpdater::slotHoverSequencePreviewJobFinished); 1127 1128 m_hoverSequencePreviewJob = job; 1129 } 1130 1131 void KFileItemModelRolesUpdater::killHoverSequencePreviewJob() 1132 { 1133 if (m_hoverSequencePreviewJob) { 1134 disconnect(m_hoverSequencePreviewJob, &KIO::PreviewJob::gotPreview, this, &KFileItemModelRolesUpdater::slotHoverSequenceGotPreview); 1135 disconnect(m_hoverSequencePreviewJob, &KIO::PreviewJob::failed, this, &KFileItemModelRolesUpdater::slotHoverSequencePreviewFailed); 1136 disconnect(m_hoverSequencePreviewJob, &KIO::PreviewJob::finished, this, &KFileItemModelRolesUpdater::slotHoverSequencePreviewJobFinished); 1137 m_hoverSequencePreviewJob->kill(); 1138 m_hoverSequencePreviewJob = nullptr; 1139 } 1140 } 1141 1142 void KFileItemModelRolesUpdater::updateChangedItems() 1143 { 1144 if (m_state == Paused) { 1145 return; 1146 } 1147 1148 if (m_changedItems.isEmpty()) { 1149 return; 1150 } 1151 1152 m_finishedItems -= m_changedItems; 1153 1154 if (m_resolvableRoles.contains(m_model->sortRole())) { 1155 m_pendingSortRoleItems += m_changedItems; 1156 1157 if (m_state != ResolvingSortRole) { 1158 // Stop the preview job if necessary, and trigger the 1159 // asynchronous determination of the sort role. 1160 killPreviewJob(); 1161 m_state = ResolvingSortRole; 1162 QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::resolveNextSortRole); 1163 } 1164 1165 return; 1166 } 1167 1168 QList<int> visibleChangedIndexes; 1169 QList<int> invisibleChangedIndexes; 1170 visibleChangedIndexes.reserve(m_changedItems.size()); 1171 invisibleChangedIndexes.reserve(m_changedItems.size()); 1172 1173 auto changedItemsIt = m_changedItems.begin(); 1174 while (changedItemsIt != m_changedItems.end()) { 1175 const auto &item = *changedItemsIt; 1176 const int index = m_model->index(item); 1177 1178 if (index < 0) { 1179 changedItemsIt = m_changedItems.erase(changedItemsIt); 1180 continue; 1181 } 1182 ++changedItemsIt; 1183 1184 if (index >= m_firstVisibleIndex && index <= m_lastVisibleIndex) { 1185 visibleChangedIndexes.append(index); 1186 } else { 1187 invisibleChangedIndexes.append(index); 1188 } 1189 } 1190 1191 std::sort(visibleChangedIndexes.begin(), visibleChangedIndexes.end()); 1192 1193 if (m_previewShown) { 1194 for (int index : std::as_const(visibleChangedIndexes)) { 1195 m_pendingPreviewItems.append(m_model->fileItem(index)); 1196 } 1197 1198 for (int index : std::as_const(invisibleChangedIndexes)) { 1199 m_pendingPreviewItems.append(m_model->fileItem(index)); 1200 } 1201 1202 if (!m_previewJob) { 1203 startPreviewJob(); 1204 } 1205 } else { 1206 const bool resolvingInProgress = !m_pendingIndexes.isEmpty(); 1207 m_pendingIndexes = visibleChangedIndexes + m_pendingIndexes + invisibleChangedIndexes; 1208 if (!resolvingInProgress) { 1209 // Trigger the asynchronous resolving of the changed roles. 1210 m_state = ResolvingAllRoles; 1211 QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::resolveNextPendingRoles); 1212 } 1213 } 1214 } 1215 1216 void KFileItemModelRolesUpdater::applySortRole(int index) 1217 { 1218 QHash<QByteArray, QVariant> data; 1219 const KFileItem item = m_model->fileItem(index); 1220 1221 if (m_model->sortRole() == "type") { 1222 if (!item.isMimeTypeKnown()) { 1223 item.determineMimeType(); 1224 } 1225 1226 data.insert("type", item.mimeComment()); 1227 } else if (m_model->sortRole() == "size" && item.isLocalFile() && item.isDir()) { 1228 startDirectorySizeCounting(item, index); 1229 return; 1230 } else { 1231 // Probably the sort role is a baloo role - just determine all roles. 1232 data = rolesData(item, index); 1233 } 1234 1235 disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); 1236 m_model->setData(index, data); 1237 connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); 1238 } 1239 1240 void KFileItemModelRolesUpdater::applySortProgressToModel() 1241 { 1242 // Inform the model about the progress of the resolved items, 1243 // so that it can give an indication when the sorting has been finished. 1244 const int resolvedCount = m_model->count() - m_pendingSortRoleItems.count(); 1245 m_model->emitSortProgress(resolvedCount); 1246 } 1247 1248 bool KFileItemModelRolesUpdater::applyResolvedRoles(int index, ResolveHint hint) 1249 { 1250 const KFileItem item = m_model->fileItem(index); 1251 const bool resolveAll = (hint == ResolveAll); 1252 1253 bool iconChanged = false; 1254 if (!item.isMimeTypeKnown() || !item.isFinalIconKnown()) { 1255 item.determineMimeType(); 1256 iconChanged = true; 1257 } else if (!m_model->data(index).contains("iconName")) { 1258 iconChanged = true; 1259 } 1260 1261 if (iconChanged || resolveAll || m_clearPreviews) { 1262 if (index < 0) { 1263 return false; 1264 } 1265 1266 QHash<QByteArray, QVariant> data; 1267 if (resolveAll) { 1268 data = rolesData(item, index); 1269 } 1270 1271 if (!item.iconName().isEmpty()) { 1272 data.insert("iconName", item.iconName()); 1273 } 1274 1275 if (m_clearPreviews) { 1276 data.insert("iconPixmap", QPixmap()); 1277 data.insert("hoverSequencePixmaps", QVariant::fromValue(QVector<QPixmap>())); 1278 } 1279 1280 disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); 1281 m_model->setData(index, data); 1282 connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); 1283 return true; 1284 } 1285 1286 return false; 1287 } 1288 1289 void KFileItemModelRolesUpdater::startDirectorySizeCounting(const KFileItem &item, int index) 1290 { 1291 if (!item.isLocalFile()) { 1292 return; 1293 } 1294 1295 if (ContentDisplaySettings::directorySizeCount() || item.isSlow()) { 1296 // fastpath no recursion necessary 1297 1298 auto data = m_model->data(index); 1299 if (data.value("size") == -2) { 1300 // means job already started 1301 return; 1302 } 1303 1304 auto url = item.url(); 1305 if (!item.localPath().isEmpty()) { 1306 // optimization for desktop:/, trash:/ 1307 url = QUrl::fromLocalFile(item.localPath()); 1308 } 1309 1310 data.insert("size", -2); // invalid size, -1 means size unknown 1311 1312 disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); 1313 m_model->setData(index, data); 1314 connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); 1315 1316 auto listJob = KIO::listDir(url, KIO::HideProgressInfo); 1317 QObject::connect(listJob, &KIO::ListJob::entries, this, [this, item](const KJob * /*job*/, const KIO::UDSEntryList &list) { 1318 int index = m_model->index(item); 1319 if (index < 0) { 1320 return; 1321 } 1322 auto data = m_model->data(index); 1323 int origCount = data.value("count").toInt(); 1324 int entryCount = origCount; 1325 1326 for (const KIO::UDSEntry &entry : list) { 1327 const auto name = entry.stringValue(KIO::UDSEntry::UDS_NAME); 1328 1329 if (name == QStringLiteral("..") || name == QStringLiteral(".")) { 1330 continue; 1331 } 1332 if (!m_model->showHiddenFiles() && name.startsWith(QLatin1Char('.'))) { 1333 continue; 1334 } 1335 if (m_model->showDirectoriesOnly() && !entry.isDir()) { 1336 continue; 1337 } 1338 ++entryCount; 1339 } 1340 1341 QHash<QByteArray, QVariant> newData; 1342 QVariant expandable = data.value("isExpandable"); 1343 if (expandable.isNull() || expandable.toBool() != (entryCount > 0)) { 1344 // if expandable has changed 1345 newData.insert("isExpandable", entryCount > 0); 1346 } 1347 1348 if (origCount != entryCount) { 1349 // count has changed 1350 newData.insert("count", entryCount); 1351 } 1352 1353 if (!newData.isEmpty()) { 1354 disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); 1355 m_model->setData(index, newData); 1356 connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); 1357 } 1358 }); 1359 return; 1360 } 1361 1362 // Tell m_directoryContentsCounter that we want to count the items 1363 // inside the directory. The result will be received in slotDirectoryContentsCountReceived. 1364 const QString path = item.localPath(); 1365 const auto priority = index >= m_firstVisibleIndex && index <= m_lastVisibleIndex ? KDirectoryContentsCounter::PathCountPriority::High 1366 : KDirectoryContentsCounter::PathCountPriority::Normal; 1367 1368 m_directoryContentsCounter->scanDirectory(path, priority); 1369 } 1370 1371 QHash<QByteArray, QVariant> KFileItemModelRolesUpdater::rolesData(const KFileItem &item, int index) 1372 { 1373 QHash<QByteArray, QVariant> data; 1374 1375 const bool getSizeRole = m_roles.contains("size"); 1376 const bool getIsExpandableRole = m_roles.contains("isExpandable"); 1377 1378 if ((getSizeRole || getIsExpandableRole) && item.isDir()) { 1379 startDirectorySizeCounting(item, index); 1380 } 1381 1382 if (m_roles.contains("extension")) { 1383 // TODO KF6 use KFileItem::suffix 464722 1384 data.insert("extension", QFileInfo(item.name()).suffix()); 1385 } 1386 1387 if (m_roles.contains("type")) { 1388 data.insert("type", item.mimeComment()); 1389 } 1390 1391 QStringList overlays = item.overlays(); 1392 for (KOverlayIconPlugin *it : std::as_const(m_overlayIconsPlugin)) { 1393 overlays.append(it->getOverlays(item.url())); 1394 } 1395 if (!overlays.isEmpty()) { 1396 data.insert("iconOverlays", overlays); 1397 } 1398 1399 #if HAVE_BALOO 1400 if (m_balooFileMonitor) { 1401 m_balooFileMonitor->addFile(item.localPath()); 1402 applyChangedBalooRolesForItem(item); 1403 } 1404 #endif 1405 return data; 1406 } 1407 1408 void KFileItemModelRolesUpdater::slotOverlaysChanged(const QUrl &url, const QStringList &) 1409 { 1410 const KFileItem item = m_model->fileItem(url); 1411 if (item.isNull()) { 1412 return; 1413 } 1414 const int index = m_model->index(item); 1415 QHash<QByteArray, QVariant> data = m_model->data(index); 1416 QStringList overlays = item.overlays(); 1417 for (KOverlayIconPlugin *it : std::as_const(m_overlayIconsPlugin)) { 1418 overlays.append(it->getOverlays(url)); 1419 } 1420 data.insert("iconOverlays", overlays); 1421 m_model->setData(index, data); 1422 } 1423 1424 void KFileItemModelRolesUpdater::updateAllPreviews() 1425 { 1426 if (m_state == Paused) { 1427 m_previewChangedDuringPausing = true; 1428 } else { 1429 m_finishedItems.clear(); 1430 startUpdating(); 1431 } 1432 } 1433 1434 void KFileItemModelRolesUpdater::killPreviewJob() 1435 { 1436 if (m_previewJob) { 1437 disconnect(m_previewJob, &KIO::PreviewJob::gotPreview, this, &KFileItemModelRolesUpdater::slotGotPreview); 1438 disconnect(m_previewJob, &KIO::PreviewJob::failed, this, &KFileItemModelRolesUpdater::slotPreviewFailed); 1439 disconnect(m_previewJob, &KIO::PreviewJob::finished, this, &KFileItemModelRolesUpdater::slotPreviewJobFinished); 1440 m_previewJob->kill(); 1441 m_previewJob = nullptr; 1442 m_pendingPreviewItems.clear(); 1443 } 1444 } 1445 1446 QList<int> KFileItemModelRolesUpdater::indexesToResolve() const 1447 { 1448 const int count = m_model->count(); 1449 1450 QList<int> result; 1451 result.reserve(qMin(count, (m_lastVisibleIndex - m_firstVisibleIndex + 1) + ResolveAllItemsLimit + (2 * m_maximumVisibleItems))); 1452 1453 // Add visible items. 1454 // Resolve files first, their previews are quicker. 1455 QList<int> visibleDirs; 1456 for (int i = m_firstVisibleIndex; i <= m_lastVisibleIndex; ++i) { 1457 const KFileItem item = m_model->fileItem(i); 1458 if (item.isDir()) { 1459 visibleDirs.append(i); 1460 } else { 1461 result.append(i); 1462 } 1463 } 1464 1465 result.append(visibleDirs); 1466 1467 // We need a reasonable upper limit for number of items to resolve after 1468 // and before the visible range. m_maximumVisibleItems can be quite large 1469 // when using Compact View. 1470 const int readAheadItems = qMin(ReadAheadPages * m_maximumVisibleItems, ResolveAllItemsLimit / 2); 1471 1472 // Add items after the visible range. 1473 const int endExtendedVisibleRange = qMin(m_lastVisibleIndex + readAheadItems, count - 1); 1474 for (int i = m_lastVisibleIndex + 1; i <= endExtendedVisibleRange; ++i) { 1475 result.append(i); 1476 } 1477 1478 // Add items before the visible range in reverse order. 1479 const int beginExtendedVisibleRange = qMax(0, m_firstVisibleIndex - readAheadItems); 1480 for (int i = m_firstVisibleIndex - 1; i >= beginExtendedVisibleRange; --i) { 1481 result.append(i); 1482 } 1483 1484 // Add items on the last page. 1485 const int beginLastPage = qMax(endExtendedVisibleRange + 1, count - m_maximumVisibleItems); 1486 for (int i = beginLastPage; i < count; ++i) { 1487 result.append(i); 1488 } 1489 1490 // Add items on the first page. 1491 const int endFirstPage = qMin(beginExtendedVisibleRange, m_maximumVisibleItems); 1492 for (int i = 0; i < endFirstPage; ++i) { 1493 result.append(i); 1494 } 1495 1496 // Continue adding items until ResolveAllItemsLimit is reached. 1497 int remainingItems = ResolveAllItemsLimit - result.count(); 1498 1499 for (int i = endExtendedVisibleRange + 1; i < beginLastPage && remainingItems > 0; ++i) { 1500 result.append(i); 1501 --remainingItems; 1502 } 1503 1504 for (int i = beginExtendedVisibleRange - 1; i >= endFirstPage && remainingItems > 0; --i) { 1505 result.append(i); 1506 --remainingItems; 1507 } 1508 1509 return result; 1510 } 1511 1512 void KFileItemModelRolesUpdater::trimHoverSequenceLoadedItems() 1513 { 1514 static const size_t maxLoadedItems = 20; 1515 1516 size_t loadedItems = m_hoverSequenceLoadedItems.size(); 1517 while (loadedItems > maxLoadedItems) { 1518 const KFileItem item = m_hoverSequenceLoadedItems.front(); 1519 1520 m_hoverSequenceLoadedItems.pop_front(); 1521 loadedItems--; 1522 1523 const int index = m_model->index(item); 1524 if (index >= 0) { 1525 QHash<QByteArray, QVariant> data = m_model->data(index); 1526 data["hoverSequencePixmaps"] = QVariant::fromValue(QVector<QPixmap>() << QPixmap()); 1527 m_model->setData(index, data); 1528 } 1529 } 1530 } 1531 1532 #include "moc_kfileitemmodelrolesupdater.cpp"