]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemmodelrolesupdater.cpp
e0e61cd49e188dee64837845be47fbb947b4067f
[dolphin.git] / src / kitemviews / kfileitemmodelrolesupdater.cpp
1 /*
2 * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "kfileitemmodelrolesupdater.h"
8
9 #include "dolphindebug.h"
10 #include "kfileitemmodel.h"
11 #include "private/kdirectorycontentscounter.h"
12 #include "private/kpixmapmodifier.h"
13
14 #include <KConfig>
15 #include <KConfigGroup>
16 #include <KIO/PreviewJob>
17 #include <KIconLoader>
18 #include <KJobWidgets>
19 #include <KOverlayIconPlugin>
20 #include <KPluginMetaData>
21 #include <KSharedConfig>
22
23 #if HAVE_BALOO
24 #include "private/kbaloorolesprovider.h"
25 #include <Baloo/File>
26 #include <Baloo/FileMonitor>
27 #endif
28
29 #include <QApplication>
30 #include <QElapsedTimer>
31 #include <QFileInfo>
32 #include <QPainter>
33 #include <QPluginLoader>
34 #include <QTimer>
35 #include <chrono>
36
37 using namespace std::chrono_literals;
38
39 // #define KFILEITEMMODELROLESUPDATER_DEBUG
40
41 namespace
42 {
43 // Maximum time in ms that the KFileItemModelRolesUpdater
44 // may perform a blocking operation
45 const int MaxBlockTimeout = 200;
46
47 // If the number of items is smaller than ResolveAllItemsLimit,
48 // the roles of all items will be resolved.
49 const int ResolveAllItemsLimit = 500;
50
51 // Not only the visible area, but up to ReadAheadPages before and after
52 // this area will be resolved.
53 const int ReadAheadPages = 5;
54 }
55
56 KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel *model, QObject *parent)
57 : QObject(parent)
58 , m_state(Idle)
59 , m_previewChangedDuringPausing(false)
60 , m_iconSizeChangedDuringPausing(false)
61 , m_rolesChangedDuringPausing(false)
62 , m_previewShown(false)
63 , m_enlargeSmallPreviews(true)
64 , m_clearPreviews(false)
65 , m_finishedItems()
66 , m_model(model)
67 , m_iconSize()
68 , m_firstVisibleIndex(0)
69 , m_lastVisibleIndex(-1)
70 , m_maximumVisibleItems(50)
71 , m_roles()
72 , m_resolvableRoles()
73 , m_enabledPlugins()
74 , m_localFileSizePreviewLimit(0)
75 , m_scanDirectories(true)
76 , m_pendingSortRoleItems()
77 , m_pendingIndexes()
78 , m_pendingPreviewItems()
79 , m_previewJob()
80 , m_hoverSequenceItem()
81 , m_hoverSequenceIndex(0)
82 , m_hoverSequencePreviewJob(nullptr)
83 , m_hoverSequenceNumSuccessiveFailures(0)
84 , m_recentlyChangedItemsTimer(nullptr)
85 , m_recentlyChangedItems()
86 , m_changedItems()
87 , m_directoryContentsCounter(nullptr)
88 #if HAVE_BALOO
89 , m_balooFileMonitor(nullptr)
90 #endif
91 {
92 Q_ASSERT(model);
93
94 const KConfigGroup globalConfig(KSharedConfig::openConfig(), "PreviewSettings");
95 m_enabledPlugins = globalConfig.readEntry("Plugins", KIO::PreviewJob::defaultPlugins());
96 m_localFileSizePreviewLimit = static_cast<qulonglong>(globalConfig.readEntry("MaximumSize", 0));
97
98 connect(m_model, &KFileItemModel::itemsInserted, this, &KFileItemModelRolesUpdater::slotItemsInserted);
99 connect(m_model, &KFileItemModel::itemsRemoved, this, &KFileItemModelRolesUpdater::slotItemsRemoved);
100 connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged);
101 connect(m_model, &KFileItemModel::itemsMoved, this, &KFileItemModelRolesUpdater::slotItemsMoved);
102 connect(m_model, &KFileItemModel::sortRoleChanged, this, &KFileItemModelRolesUpdater::slotSortRoleChanged);
103
104 // Use a timer to prevent that each call of slotItemsChanged() results in a synchronous
105 // resolving of the roles. Postpone the resolving until no update has been done for 100 ms.
106 m_recentlyChangedItemsTimer = new QTimer(this);
107 m_recentlyChangedItemsTimer->setInterval(100ms);
108 m_recentlyChangedItemsTimer->setSingleShot(true);
109 connect(m_recentlyChangedItemsTimer, &QTimer::timeout, this, &KFileItemModelRolesUpdater::resolveRecentlyChangedItems);
110
111 m_resolvableRoles.insert("size");
112 m_resolvableRoles.insert("type");
113 m_resolvableRoles.insert("isExpandable");
114 #if HAVE_BALOO
115 m_resolvableRoles += KBalooRolesProvider::instance().roles();
116 #endif
117
118 m_directoryContentsCounter = new KDirectoryContentsCounter(m_model, this);
119 connect(m_directoryContentsCounter, &KDirectoryContentsCounter::result, this, &KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived);
120
121 const QString pluginNamespace = QStringLiteral("kf" QT_STRINGIFY(QT_VERSION_MAJOR)) + QStringLiteral("/overlayicon");
122 const auto plugins = KPluginMetaData::findPlugins(pluginNamespace, {}, KPluginMetaData::AllowEmptyMetaData);
123 for (const KPluginMetaData &data : plugins) {
124 auto instance = QPluginLoader(data.fileName()).instance();
125 auto plugin = qobject_cast<KOverlayIconPlugin *>(instance);
126 if (plugin) {
127 m_overlayIconsPlugin.append(plugin);
128 connect(plugin, &KOverlayIconPlugin::overlaysChanged, this, &KFileItemModelRolesUpdater::slotOverlaysChanged);
129 } else {
130 // not our/valid plugin, so delete the created object
131 delete instance;
132 }
133 }
134 }
135
136 KFileItemModelRolesUpdater::~KFileItemModelRolesUpdater()
137 {
138 killPreviewJob();
139 }
140
141 void KFileItemModelRolesUpdater::setIconSize(const QSize &size)
142 {
143 if (size != m_iconSize) {
144 m_iconSize = size;
145 if (m_state == Paused) {
146 m_iconSizeChangedDuringPausing = true;
147 } else if (m_previewShown) {
148 // An icon size change requires the regenerating of
149 // all previews
150 m_finishedItems.clear();
151 startUpdating();
152 }
153 }
154 }
155
156 QSize KFileItemModelRolesUpdater::iconSize() const
157 {
158 return m_iconSize;
159 }
160
161 void KFileItemModelRolesUpdater::setVisibleIndexRange(int index, int count)
162 {
163 if (index < 0) {
164 index = 0;
165 }
166 if (count < 0) {
167 count = 0;
168 }
169
170 if (index == m_firstVisibleIndex && count == m_lastVisibleIndex - m_firstVisibleIndex + 1) {
171 // The range has not been changed
172 return;
173 }
174
175 m_firstVisibleIndex = index;
176 m_lastVisibleIndex = qMin(index + count - 1, m_model->count() - 1);
177
178 startUpdating();
179 }
180
181 void KFileItemModelRolesUpdater::setMaximumVisibleItems(int count)
182 {
183 m_maximumVisibleItems = count;
184 }
185
186 void KFileItemModelRolesUpdater::setPreviewsShown(bool show)
187 {
188 if (show == m_previewShown) {
189 return;
190 }
191
192 m_previewShown = show;
193 if (!show) {
194 m_clearPreviews = true;
195 }
196
197 updateAllPreviews();
198 }
199
200 bool KFileItemModelRolesUpdater::previewsShown() const
201 {
202 return m_previewShown;
203 }
204
205 void KFileItemModelRolesUpdater::setEnlargeSmallPreviews(bool enlarge)
206 {
207 if (enlarge != m_enlargeSmallPreviews) {
208 m_enlargeSmallPreviews = enlarge;
209 if (m_previewShown) {
210 updateAllPreviews();
211 }
212 }
213 }
214
215 bool KFileItemModelRolesUpdater::enlargeSmallPreviews() const
216 {
217 return m_enlargeSmallPreviews;
218 }
219
220 void KFileItemModelRolesUpdater::setEnabledPlugins(const QStringList &list)
221 {
222 if (m_enabledPlugins != list) {
223 m_enabledPlugins = list;
224 if (m_previewShown) {
225 updateAllPreviews();
226 }
227 }
228 }
229
230 void KFileItemModelRolesUpdater::setPaused(bool paused)
231 {
232 if (paused == (m_state == Paused)) {
233 return;
234 }
235
236 if (paused) {
237 m_state = Paused;
238 killPreviewJob();
239 } else {
240 const bool updatePreviews = (m_iconSizeChangedDuringPausing && m_previewShown) || m_previewChangedDuringPausing;
241 const bool resolveAll = updatePreviews || m_rolesChangedDuringPausing;
242 if (resolveAll) {
243 m_finishedItems.clear();
244 }
245
246 m_iconSizeChangedDuringPausing = false;
247 m_previewChangedDuringPausing = false;
248 m_rolesChangedDuringPausing = false;
249
250 if (!m_pendingSortRoleItems.isEmpty()) {
251 m_state = ResolvingSortRole;
252 resolveNextSortRole();
253 } else {
254 m_state = Idle;
255 }
256
257 startUpdating();
258 }
259 }
260
261 void KFileItemModelRolesUpdater::setRoles(const QSet<QByteArray> &roles)
262 {
263 if (m_roles != roles) {
264 m_roles = roles;
265
266 #if HAVE_BALOO
267 // Check whether there is at least one role that must be resolved
268 // with the help of Baloo. If this is the case, a (quite expensive)
269 // resolving will be done in KFileItemModelRolesUpdater::rolesData() and
270 // the role gets watched for changes.
271 const KBalooRolesProvider &rolesProvider = KBalooRolesProvider::instance();
272 bool hasBalooRole = false;
273 QSetIterator<QByteArray> it(roles);
274 while (it.hasNext()) {
275 const QByteArray &role = it.next();
276 if (rolesProvider.roles().contains(role)) {
277 hasBalooRole = true;
278 break;
279 }
280 }
281
282 if (hasBalooRole && m_balooConfig.fileIndexingEnabled() && !m_balooFileMonitor) {
283 m_balooFileMonitor = new Baloo::FileMonitor(this);
284 connect(m_balooFileMonitor, &Baloo::FileMonitor::fileMetaDataChanged, this, &KFileItemModelRolesUpdater::applyChangedBalooRoles);
285 } else if (!hasBalooRole && m_balooFileMonitor) {
286 delete m_balooFileMonitor;
287 m_balooFileMonitor = nullptr;
288 }
289 #endif
290
291 if (m_state == Paused) {
292 m_rolesChangedDuringPausing = true;
293 } else {
294 startUpdating();
295 }
296 }
297 }
298
299 QSet<QByteArray> KFileItemModelRolesUpdater::roles() const
300 {
301 return m_roles;
302 }
303
304 bool KFileItemModelRolesUpdater::isPaused() const
305 {
306 return m_state == Paused;
307 }
308
309 QStringList KFileItemModelRolesUpdater::enabledPlugins() const
310 {
311 return m_enabledPlugins;
312 }
313
314 void KFileItemModelRolesUpdater::setLocalFileSizePreviewLimit(const qlonglong size)
315 {
316 m_localFileSizePreviewLimit = size;
317 }
318
319 qlonglong KFileItemModelRolesUpdater::localFileSizePreviewLimit() const
320 {
321 return m_localFileSizePreviewLimit;
322 }
323
324 void KFileItemModelRolesUpdater::setScanDirectories(bool enabled)
325 {
326 m_scanDirectories = enabled;
327 }
328
329 bool KFileItemModelRolesUpdater::scanDirectories() const
330 {
331 return m_scanDirectories;
332 }
333
334 void KFileItemModelRolesUpdater::setHoverSequenceState(const QUrl &itemUrl, int seqIdx)
335 {
336 const KFileItem item = m_model->fileItem(itemUrl);
337
338 if (item != m_hoverSequenceItem) {
339 killHoverSequencePreviewJob();
340 }
341
342 m_hoverSequenceItem = item;
343 m_hoverSequenceIndex = seqIdx;
344
345 if (!m_previewShown) {
346 return;
347 }
348
349 m_hoverSequenceNumSuccessiveFailures = 0;
350
351 loadNextHoverSequencePreview();
352 }
353
354 void KFileItemModelRolesUpdater::slotItemsInserted(const KItemRangeList &itemRanges)
355 {
356 QElapsedTimer timer;
357 timer.start();
358
359 // Determine the sort role synchronously for as many items as possible.
360 if (m_resolvableRoles.contains(m_model->sortRole())) {
361 int insertedCount = 0;
362 for (const KItemRange &range : itemRanges) {
363 const int lastIndex = insertedCount + range.index + range.count - 1;
364 for (int i = insertedCount + range.index; i <= lastIndex; ++i) {
365 if (timer.elapsed() < MaxBlockTimeout) {
366 applySortRole(i);
367 } else {
368 m_pendingSortRoleItems.insert(m_model->fileItem(i));
369 }
370 }
371 insertedCount += range.count;
372 }
373
374 applySortProgressToModel();
375
376 // If there are still items whose sort role is unknown, check if the
377 // asynchronous determination of the sort role is already in progress,
378 // and start it if that is not the case.
379 if (!m_pendingSortRoleItems.isEmpty() && m_state != ResolvingSortRole) {
380 killPreviewJob();
381 m_state = ResolvingSortRole;
382 resolveNextSortRole();
383 }
384 }
385
386 startUpdating();
387 }
388
389 void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList &itemRanges)
390 {
391 Q_UNUSED(itemRanges)
392
393 const bool allItemsRemoved = (m_model->count() == 0);
394
395 #if HAVE_BALOO
396 if (m_balooFileMonitor) {
397 // Don't let the FileWatcher watch for removed items
398 if (allItemsRemoved) {
399 m_balooFileMonitor->clear();
400 } else {
401 QStringList newFileList;
402 const QStringList oldFileList = m_balooFileMonitor->files();
403 for (const QString &file : oldFileList) {
404 if (m_model->index(QUrl::fromLocalFile(file)) >= 0) {
405 newFileList.append(file);
406 }
407 }
408 m_balooFileMonitor->setFiles(newFileList);
409 }
410 }
411 #endif
412
413 if (allItemsRemoved) {
414 m_state = Idle;
415
416 m_finishedItems.clear();
417 m_pendingSortRoleItems.clear();
418 m_pendingIndexes.clear();
419 m_pendingPreviewItems.clear();
420 m_recentlyChangedItems.clear();
421 m_recentlyChangedItemsTimer->stop();
422 m_changedItems.clear();
423 m_hoverSequenceLoadedItems.clear();
424
425 killPreviewJob();
426
427 if (m_scanDirectories) {
428 m_directoryContentsCounter->stopWorker();
429 }
430 } else {
431 // Only remove the items from m_finishedItems. They will be removed
432 // from the other sets later on.
433 QSet<KFileItem>::iterator it = m_finishedItems.begin();
434 while (it != m_finishedItems.end()) {
435 if (m_model->index(*it) < 0) {
436 it = m_finishedItems.erase(it);
437 } else {
438 ++it;
439 }
440 }
441
442 // Removed items won't have hover previews loaded anymore.
443 for (const KItemRange &itemRange : itemRanges) {
444 int index = itemRange.index;
445 for (int count = itemRange.count; count > 0; --count) {
446 const KFileItem item = m_model->fileItem(index);
447 m_hoverSequenceLoadedItems.remove(item);
448 ++index;
449 }
450 }
451
452 // The visible items might have changed.
453 startUpdating();
454 }
455 }
456
457 void KFileItemModelRolesUpdater::slotItemsMoved(KItemRange itemRange, const QList<int> &movedToIndexes)
458 {
459 Q_UNUSED(itemRange)
460 Q_UNUSED(movedToIndexes)
461
462 // The visible items might have changed.
463 startUpdating();
464 }
465
466 void KFileItemModelRolesUpdater::slotItemsChanged(const KItemRangeList &itemRanges, const QSet<QByteArray> &roles)
467 {
468 Q_UNUSED(roles)
469
470 // Find out if slotItemsChanged() has been done recently. If that is the
471 // case, resolving the roles is postponed until a timer has exceeded
472 // to prevent expensive repeated updates if files are updated frequently.
473 const bool itemsChangedRecently = m_recentlyChangedItemsTimer->isActive();
474
475 QSet<KFileItem> &targetSet = itemsChangedRecently ? m_recentlyChangedItems : m_changedItems;
476
477 for (const KItemRange &itemRange : itemRanges) {
478 int index = itemRange.index;
479 for (int count = itemRange.count; count > 0; --count) {
480 const KFileItem item = m_model->fileItem(index);
481 targetSet.insert(item);
482 ++index;
483 }
484 }
485
486 m_recentlyChangedItemsTimer->start();
487
488 if (!itemsChangedRecently) {
489 updateChangedItems();
490 }
491 }
492
493 void KFileItemModelRolesUpdater::slotSortRoleChanged(const QByteArray &current, const QByteArray &previous)
494 {
495 Q_UNUSED(current)
496 Q_UNUSED(previous)
497
498 if (m_resolvableRoles.contains(current)) {
499 m_pendingSortRoleItems.clear();
500 m_finishedItems.clear();
501
502 const int count = m_model->count();
503 QElapsedTimer timer;
504 timer.start();
505
506 // Determine the sort role synchronously for as many items as possible.
507 for (int index = 0; index < count; ++index) {
508 if (timer.elapsed() < MaxBlockTimeout) {
509 applySortRole(index);
510 } else {
511 m_pendingSortRoleItems.insert(m_model->fileItem(index));
512 }
513 }
514
515 applySortProgressToModel();
516
517 if (!m_pendingSortRoleItems.isEmpty()) {
518 // Trigger the asynchronous determination of the sort role.
519 killPreviewJob();
520 m_state = ResolvingSortRole;
521 resolveNextSortRole();
522 }
523 } else {
524 m_state = Idle;
525 m_pendingSortRoleItems.clear();
526 applySortProgressToModel();
527 }
528 }
529
530 void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem &item, const QPixmap &pixmap)
531 {
532 if (m_state != PreviewJobRunning) {
533 return;
534 }
535
536 m_changedItems.remove(item);
537
538 const int index = m_model->index(item);
539 if (index < 0) {
540 return;
541 }
542
543 QPixmap scaledPixmap = transformPreviewPixmap(pixmap);
544
545 QHash<QByteArray, QVariant> data = rolesData(item, index);
546
547 const QStringList overlays = data["iconOverlays"].toStringList();
548 // Strangely KFileItem::overlays() returns empty string-values, so
549 // we need to check first whether an overlay must be drawn at all.
550 // It is more efficient to do it here, as KIconLoader::drawOverlays()
551 // assumes that an overlay will be drawn and has some additional
552 // setup time.
553 if (!scaledPixmap.isNull()) {
554 for (const QString &overlay : overlays) {
555 if (!overlay.isEmpty()) {
556 // There is at least one overlay, draw all overlays above m_pixmap
557 // and cancel the check
558 KIconLoader::global()->drawOverlays(overlays, scaledPixmap, KIconLoader::Desktop);
559 break;
560 }
561 }
562 }
563
564 data.insert("iconPixmap", scaledPixmap);
565
566 disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged);
567 m_model->setData(index, data);
568 connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged);
569
570 m_finishedItems.insert(item);
571 }
572
573 void KFileItemModelRolesUpdater::slotPreviewFailed(const KFileItem &item)
574 {
575 if (m_state != PreviewJobRunning) {
576 return;
577 }
578
579 m_changedItems.remove(item);
580
581 const int index = m_model->index(item);
582 if (index >= 0) {
583 QHash<QByteArray, QVariant> data;
584 data.insert("iconPixmap", QPixmap());
585
586 disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged);
587 m_model->setData(index, data);
588 connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged);
589
590 applyResolvedRoles(index, ResolveAll);
591 m_finishedItems.insert(item);
592 }
593 }
594
595 void KFileItemModelRolesUpdater::slotPreviewJobFinished()
596 {
597 m_previewJob = nullptr;
598
599 if (m_state != PreviewJobRunning) {
600 return;
601 }
602
603 m_state = Idle;
604
605 if (!m_pendingPreviewItems.isEmpty()) {
606 startPreviewJob();
607 } else {
608 if (!m_changedItems.isEmpty()) {
609 updateChangedItems();
610 }
611 }
612 }
613
614 void KFileItemModelRolesUpdater::slotHoverSequenceGotPreview(const KFileItem &item, const QPixmap &pixmap)
615 {
616 const int index = m_model->index(item);
617 if (index < 0) {
618 return;
619 }
620
621 QHash<QByteArray, QVariant> data = m_model->data(index);
622 QVector<QPixmap> pixmaps = data["hoverSequencePixmaps"].value<QVector<QPixmap>>();
623 const int loadedIndex = pixmaps.size();
624
625 float wap = m_hoverSequencePreviewJob->sequenceIndexWraparoundPoint();
626 if (!m_hoverSequencePreviewJob->handlesSequences()) {
627 wap = 1.0f;
628 }
629 if (wap >= 0.0f) {
630 data["hoverSequenceWraparoundPoint"] = wap;
631 m_model->setData(index, data);
632 }
633
634 // For hover sequence previews we never load index 0, because that's just the regular preview
635 // in "iconPixmap". But that means we'll load index 1 even for thumbnailers that don't support
636 // sequences, in which case we can just throw away the preview because it's the same as for
637 // index 0. Unfortunately we can't find it out earlier :(
638 if (wap < 0.0f || loadedIndex < static_cast<int>(wap)) {
639 // Add the preview to the model data
640
641 const QPixmap scaledPixmap = transformPreviewPixmap(pixmap);
642
643 pixmaps.append(scaledPixmap);
644 data["hoverSequencePixmaps"] = QVariant::fromValue(pixmaps);
645
646 m_model->setData(index, data);
647
648 const auto loadedIt = std::find(m_hoverSequenceLoadedItems.begin(), m_hoverSequenceLoadedItems.end(), item);
649 if (loadedIt == m_hoverSequenceLoadedItems.end()) {
650 m_hoverSequenceLoadedItems.push_back(item);
651 trimHoverSequenceLoadedItems();
652 }
653 }
654
655 m_hoverSequenceNumSuccessiveFailures = 0;
656 }
657
658 void KFileItemModelRolesUpdater::slotHoverSequencePreviewFailed(const KFileItem &item)
659 {
660 const int index = m_model->index(item);
661 if (index < 0) {
662 return;
663 }
664
665 static const int numRetries = 2;
666
667 QHash<QByteArray, QVariant> data = m_model->data(index);
668 QVector<QPixmap> pixmaps = data["hoverSequencePixmaps"].value<QVector<QPixmap>>();
669
670 qCDebug(DolphinDebug).nospace() << "Failed to generate hover sequence preview #" << pixmaps.size() << " for file " << item.url().toString() << " (attempt "
671 << (m_hoverSequenceNumSuccessiveFailures + 1) << "/" << (numRetries + 1) << ")";
672
673 if (m_hoverSequenceNumSuccessiveFailures >= numRetries) {
674 // Give up and simply duplicate the previous sequence image (if any)
675
676 pixmaps.append(pixmaps.empty() ? QPixmap() : pixmaps.last());
677 data["hoverSequencePixmaps"] = QVariant::fromValue(pixmaps);
678
679 if (!data.contains("hoverSequenceWraparoundPoint")) {
680 // hoverSequenceWraparoundPoint is only available when PreviewJob succeeds, so unless
681 // it has previously succeeded, it's best to assume that it just doesn't handle
682 // sequences instead of trying to load the next image indefinitely.
683 data["hoverSequenceWraparoundPoint"] = 1.0f;
684 }
685
686 m_model->setData(index, data);
687
688 m_hoverSequenceNumSuccessiveFailures = 0;
689 } else {
690 // Retry
691
692 m_hoverSequenceNumSuccessiveFailures++;
693 }
694
695 // Next image in the sequence (or same one if the retry limit wasn't reached yet) will be
696 // loaded automatically, because slotHoverSequencePreviewJobFinished() will be triggered
697 // even when PreviewJob fails.
698 }
699
700 void KFileItemModelRolesUpdater::slotHoverSequencePreviewJobFinished()
701 {
702 const int index = m_model->index(m_hoverSequenceItem);
703 if (index < 0) {
704 m_hoverSequencePreviewJob = nullptr;
705 return;
706 }
707
708 // Since a PreviewJob can only have one associated sequence index, we can only generate
709 // one sequence image per job, so we have to start another one for the next index.
710
711 // Load the next image in the sequence
712 m_hoverSequencePreviewJob = nullptr;
713 loadNextHoverSequencePreview();
714 }
715
716 void KFileItemModelRolesUpdater::resolveNextSortRole()
717 {
718 if (m_state != ResolvingSortRole) {
719 return;
720 }
721
722 QSet<KFileItem>::iterator it = m_pendingSortRoleItems.begin();
723 while (it != m_pendingSortRoleItems.end()) {
724 const KFileItem item = *it;
725 const int index = m_model->index(item);
726
727 // Continue if the sort role has already been determined for the
728 // item, and the item has not been changed recently.
729 if (!m_changedItems.contains(item) && m_model->data(index).contains(m_model->sortRole())) {
730 it = m_pendingSortRoleItems.erase(it);
731 continue;
732 }
733
734 applySortRole(index);
735 m_pendingSortRoleItems.erase(it);
736 break;
737 }
738
739 if (!m_pendingSortRoleItems.isEmpty()) {
740 applySortProgressToModel();
741 QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::resolveNextSortRole);
742 } else {
743 m_state = Idle;
744
745 // Prevent that we try to update the items twice.
746 disconnect(m_model, &KFileItemModel::itemsMoved, this, &KFileItemModelRolesUpdater::slotItemsMoved);
747 applySortProgressToModel();
748 connect(m_model, &KFileItemModel::itemsMoved, this, &KFileItemModelRolesUpdater::slotItemsMoved);
749 startUpdating();
750 }
751 }
752
753 void KFileItemModelRolesUpdater::resolveNextPendingRoles()
754 {
755 if (m_state != ResolvingAllRoles) {
756 return;
757 }
758
759 while (!m_pendingIndexes.isEmpty()) {
760 const int index = m_pendingIndexes.takeFirst();
761 const KFileItem item = m_model->fileItem(index);
762
763 if (m_finishedItems.contains(item)) {
764 continue;
765 }
766
767 applyResolvedRoles(index, ResolveAll);
768 m_finishedItems.insert(item);
769 m_changedItems.remove(item);
770 break;
771 }
772
773 if (!m_pendingIndexes.isEmpty()) {
774 QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::resolveNextPendingRoles);
775 } else {
776 m_state = Idle;
777
778 if (m_clearPreviews) {
779 // Only go through the list if there are items which might still have previews.
780 if (m_finishedItems.count() != m_model->count()) {
781 QHash<QByteArray, QVariant> data;
782 data.insert("iconPixmap", QPixmap());
783 data.insert("hoverSequencePixmaps", QVariant::fromValue(QVector<QPixmap>()));
784
785 disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged);
786 for (int index = 0; index <= m_model->count(); ++index) {
787 if (m_model->data(index).contains("iconPixmap") || m_model->data(index).contains("hoverSequencePixmaps")) {
788 m_model->setData(index, data);
789 }
790 }
791 connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged);
792 }
793 m_clearPreviews = false;
794 }
795
796 if (!m_changedItems.isEmpty()) {
797 updateChangedItems();
798 }
799 }
800 }
801
802 void KFileItemModelRolesUpdater::resolveRecentlyChangedItems()
803 {
804 m_changedItems += m_recentlyChangedItems;
805 m_recentlyChangedItems.clear();
806 updateChangedItems();
807 }
808
809 void KFileItemModelRolesUpdater::applyChangedBalooRoles(const QString &file)
810 {
811 #if HAVE_BALOO
812 const KFileItem item = m_model->fileItem(QUrl::fromLocalFile(file));
813
814 if (item.isNull()) {
815 // itemUrl is not in the model anymore, probably because
816 // the corresponding file has been deleted in the meantime.
817 return;
818 }
819 applyChangedBalooRolesForItem(item);
820 #else
821 Q_UNUSED(file)
822 #endif
823 }
824
825 void KFileItemModelRolesUpdater::applyChangedBalooRolesForItem(const KFileItem &item)
826 {
827 #if HAVE_BALOO
828 Baloo::File file(item.localPath());
829 file.load();
830
831 const KBalooRolesProvider &rolesProvider = KBalooRolesProvider::instance();
832 QHash<QByteArray, QVariant> data;
833
834 const auto roles = rolesProvider.roles();
835 for (const QByteArray &role : roles) {
836 // Overwrite all the role values with an empty QVariant, because the roles
837 // provider doesn't overwrite it when the property value list is empty.
838 // See bug 322348
839 data.insert(role, QVariant());
840 }
841
842 QHashIterator<QByteArray, QVariant> it(rolesProvider.roleValues(file, m_roles));
843 while (it.hasNext()) {
844 it.next();
845 data.insert(it.key(), it.value());
846 }
847
848 disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged);
849 const int index = m_model->index(item);
850 m_model->setData(index, data);
851 connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged);
852 #else
853 #ifndef Q_CC_MSVC
854 Q_UNUSED(item)
855 #endif
856 #endif
857 }
858
859 void KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived(const QString &path, int count, long size)
860 {
861 const bool getSizeRole = m_roles.contains("size");
862 const bool getIsExpandableRole = m_roles.contains("isExpandable");
863
864 if (getSizeRole || getIsExpandableRole) {
865 const int index = m_model->index(QUrl::fromLocalFile(path));
866 if (index >= 0) {
867 QHash<QByteArray, QVariant> data;
868
869 if (getSizeRole) {
870 data.insert("count", count);
871 data.insert("size", QVariant::fromValue(size));
872 }
873 if (getIsExpandableRole) {
874 data.insert("isExpandable", count > 0);
875 }
876
877 disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged);
878 m_model->setData(index, data);
879 connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged);
880 }
881 }
882 }
883
884 void KFileItemModelRolesUpdater::startUpdating()
885 {
886 if (m_state == Paused) {
887 return;
888 }
889
890 if (m_finishedItems.count() == m_model->count()) {
891 // All roles have been resolved already.
892 m_state = Idle;
893 return;
894 }
895
896 // Terminate all updates that are currently active.
897 killPreviewJob();
898 m_pendingIndexes.clear();
899
900 QElapsedTimer timer;
901 timer.start();
902
903 // Determine the icons for the visible items synchronously.
904 updateVisibleIcons();
905
906 // A detailed update of the items in and near the visible area
907 // only makes sense if sorting is finished.
908 if (m_state == ResolvingSortRole) {
909 return;
910 }
911
912 // Start the preview job or the asynchronous resolving of all roles.
913 QList<int> indexes = indexesToResolve();
914
915 if (m_previewShown) {
916 m_pendingPreviewItems.clear();
917 m_pendingPreviewItems.reserve(indexes.count());
918
919 for (int index : qAsConst(indexes)) {
920 const KFileItem item = m_model->fileItem(index);
921 if (!m_finishedItems.contains(item)) {
922 m_pendingPreviewItems.append(item);
923 }
924 }
925
926 startPreviewJob();
927 } else {
928 m_pendingIndexes = indexes;
929 // Trigger the asynchronous resolving of all roles.
930 m_state = ResolvingAllRoles;
931 QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::resolveNextPendingRoles);
932 }
933 }
934
935 void KFileItemModelRolesUpdater::updateVisibleIcons()
936 {
937 int lastVisibleIndex = m_lastVisibleIndex;
938 if (lastVisibleIndex <= 0) {
939 // Guess a reasonable value for the last visible index if the view
940 // has not told us about the real value yet.
941 lastVisibleIndex = qMin(m_firstVisibleIndex + m_maximumVisibleItems, m_model->count() - 1);
942 if (lastVisibleIndex <= 0) {
943 lastVisibleIndex = qMin(200, m_model->count() - 1);
944 }
945 }
946
947 QElapsedTimer timer;
948 timer.start();
949
950 // Try to determine the final icons for all visible items.
951 int index;
952 for (index = m_firstVisibleIndex; index <= lastVisibleIndex && timer.elapsed() < MaxBlockTimeout; ++index) {
953 applyResolvedRoles(index, ResolveFast);
954 }
955
956 // KFileItemListView::initializeItemListWidget(KItemListWidget*) will load
957 // preliminary icons (i.e., without mime type determination) for the
958 // remaining items.
959 }
960
961 void KFileItemModelRolesUpdater::startPreviewJob()
962 {
963 m_state = PreviewJobRunning;
964
965 if (m_pendingPreviewItems.isEmpty()) {
966 QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::slotPreviewJobFinished);
967 return;
968 }
969
970 // PreviewJob internally caches items always with the size of
971 // 128 x 128 pixels or 256 x 256 pixels. A (slow) downscaling is done
972 // by PreviewJob if a smaller size is requested. For images KFileItemModelRolesUpdater must
973 // do a downscaling anyhow because of the frame, so in this case only the provided
974 // cache sizes are requested.
975 const QSize cacheSize = (m_iconSize.width() > 128) || (m_iconSize.height() > 128) ? QSize(256, 256) : QSize(128, 128);
976
977 // KIO::filePreview() will request the MIME-type of all passed items, which (in the
978 // worst case) might block the application for several seconds. To prevent such
979 // a blocking, we only pass items with known mime type to the preview job.
980 const int count = m_pendingPreviewItems.count();
981 KFileItemList itemSubSet;
982 itemSubSet.reserve(count);
983
984 if (m_pendingPreviewItems.first().isMimeTypeKnown()) {
985 // Some mime types are known already, probably because they were
986 // determined when loading the icons for the visible items. Start
987 // a preview job for all items at the beginning of the list which
988 // have a known mime type.
989 do {
990 itemSubSet.append(m_pendingPreviewItems.takeFirst());
991 } while (!m_pendingPreviewItems.isEmpty() && m_pendingPreviewItems.first().isMimeTypeKnown());
992 } else {
993 // Determine mime types for MaxBlockTimeout ms, and start a preview
994 // job for the corresponding items.
995 QElapsedTimer timer;
996 timer.start();
997
998 do {
999 const KFileItem item = m_pendingPreviewItems.takeFirst();
1000 item.determineMimeType();
1001 itemSubSet.append(item);
1002 } while (!m_pendingPreviewItems.isEmpty() && timer.elapsed() < MaxBlockTimeout);
1003 }
1004
1005 KIO::PreviewJob *job = new KIO::PreviewJob(itemSubSet, cacheSize, &m_enabledPlugins);
1006
1007 job->setIgnoreMaximumSize(itemSubSet.first().isLocalFile() && !itemSubSet.first().isSlow() && m_localFileSizePreviewLimit <= 0);
1008 if (job->uiDelegate()) {
1009 KJobWidgets::setWindow(job, qApp->activeWindow());
1010 }
1011
1012 connect(job, &KIO::PreviewJob::gotPreview, this, &KFileItemModelRolesUpdater::slotGotPreview);
1013 connect(job, &KIO::PreviewJob::failed, this, &KFileItemModelRolesUpdater::slotPreviewFailed);
1014 connect(job, &KIO::PreviewJob::finished, this, &KFileItemModelRolesUpdater::slotPreviewJobFinished);
1015
1016 m_previewJob = job;
1017 }
1018
1019 QPixmap KFileItemModelRolesUpdater::transformPreviewPixmap(const QPixmap &pixmap)
1020 {
1021 QPixmap scaledPixmap = pixmap;
1022
1023 if (!pixmap.hasAlpha() && !pixmap.isNull() && m_iconSize.width() > KIconLoader::SizeSmallMedium && m_iconSize.height() > KIconLoader::SizeSmallMedium) {
1024 if (m_enlargeSmallPreviews) {
1025 KPixmapModifier::applyFrame(scaledPixmap, m_iconSize);
1026 } else {
1027 // Assure that small previews don't get enlarged. Instead they
1028 // should be shown centered within the frame.
1029 const QSize contentSize = KPixmapModifier::sizeInsideFrame(m_iconSize);
1030 const bool enlargingRequired = scaledPixmap.width() < contentSize.width() && scaledPixmap.height() < contentSize.height();
1031 if (enlargingRequired) {
1032 QSize frameSize = scaledPixmap.size() / scaledPixmap.devicePixelRatio();
1033 frameSize.scale(m_iconSize, Qt::KeepAspectRatio);
1034
1035 QPixmap largeFrame(frameSize);
1036 largeFrame.fill(Qt::transparent);
1037
1038 KPixmapModifier::applyFrame(largeFrame, frameSize);
1039
1040 QPainter painter(&largeFrame);
1041 painter.drawPixmap((largeFrame.width() - scaledPixmap.width() / scaledPixmap.devicePixelRatio()) / 2,
1042 (largeFrame.height() - scaledPixmap.height() / scaledPixmap.devicePixelRatio()) / 2,
1043 scaledPixmap);
1044 scaledPixmap = largeFrame;
1045 } else {
1046 // The image must be shrunk as it is too large to fit into
1047 // the available icon size
1048 KPixmapModifier::applyFrame(scaledPixmap, m_iconSize);
1049 }
1050 }
1051 } else if (!pixmap.isNull()) {
1052 KPixmapModifier::scale(scaledPixmap, m_iconSize * qApp->devicePixelRatio());
1053 scaledPixmap.setDevicePixelRatio(qApp->devicePixelRatio());
1054 }
1055
1056 return scaledPixmap;
1057 }
1058
1059 void KFileItemModelRolesUpdater::loadNextHoverSequencePreview()
1060 {
1061 if (m_hoverSequenceItem.isNull() || m_hoverSequencePreviewJob) {
1062 return;
1063 }
1064
1065 const int index = m_model->index(m_hoverSequenceItem);
1066 if (index < 0) {
1067 return;
1068 }
1069
1070 // We generate the next few sequence indices in advance (buffering)
1071 const int maxSeqIdx = m_hoverSequenceIndex + 5;
1072
1073 QHash<QByteArray, QVariant> data = m_model->data(index);
1074
1075 if (!data.contains("hoverSequencePixmaps")) {
1076 // The pixmap at index 0 isn't used ("iconPixmap" will be used instead)
1077 data.insert("hoverSequencePixmaps", QVariant::fromValue(QVector<QPixmap>() << QPixmap()));
1078 m_model->setData(index, data);
1079 }
1080
1081 const QVector<QPixmap> pixmaps = data["hoverSequencePixmaps"].value<QVector<QPixmap>>();
1082
1083 const int loadSeqIdx = pixmaps.size();
1084
1085 float wap = -1.0f;
1086 if (data.contains("hoverSequenceWraparoundPoint")) {
1087 wap = data["hoverSequenceWraparoundPoint"].toFloat();
1088 }
1089 if (wap >= 1.0f && loadSeqIdx >= static_cast<int>(wap)) {
1090 // Reached the wraparound point -> no more previews to load.
1091 return;
1092 }
1093
1094 if (loadSeqIdx > maxSeqIdx) {
1095 // Wait until setHoverSequenceState() is called with a higher sequence index.
1096 return;
1097 }
1098
1099 // PreviewJob internally caches items always with the size of
1100 // 128 x 128 pixels or 256 x 256 pixels. A (slow) downscaling is done
1101 // by PreviewJob if a smaller size is requested. For images KFileItemModelRolesUpdater must
1102 // do a downscaling anyhow because of the frame, so in this case only the provided
1103 // cache sizes are requested.
1104 const QSize cacheSize = (m_iconSize.width() > 128) || (m_iconSize.height() > 128) ? QSize(256, 256) : QSize(128, 128);
1105
1106 KIO::PreviewJob *job = new KIO::PreviewJob({m_hoverSequenceItem}, cacheSize, &m_enabledPlugins);
1107
1108 job->setSequenceIndex(loadSeqIdx);
1109 job->setIgnoreMaximumSize(m_hoverSequenceItem.isLocalFile() && !m_hoverSequenceItem.isSlow() && m_localFileSizePreviewLimit <= 0);
1110 if (job->uiDelegate()) {
1111 KJobWidgets::setWindow(job, qApp->activeWindow());
1112 }
1113
1114 connect(job, &KIO::PreviewJob::gotPreview, this, &KFileItemModelRolesUpdater::slotHoverSequenceGotPreview);
1115 connect(job, &KIO::PreviewJob::failed, this, &KFileItemModelRolesUpdater::slotHoverSequencePreviewFailed);
1116 connect(job, &KIO::PreviewJob::finished, this, &KFileItemModelRolesUpdater::slotHoverSequencePreviewJobFinished);
1117
1118 m_hoverSequencePreviewJob = job;
1119 }
1120
1121 void KFileItemModelRolesUpdater::killHoverSequencePreviewJob()
1122 {
1123 if (m_hoverSequencePreviewJob) {
1124 disconnect(m_hoverSequencePreviewJob, &KIO::PreviewJob::gotPreview, this, &KFileItemModelRolesUpdater::slotHoverSequenceGotPreview);
1125 disconnect(m_hoverSequencePreviewJob, &KIO::PreviewJob::failed, this, &KFileItemModelRolesUpdater::slotHoverSequencePreviewFailed);
1126 disconnect(m_hoverSequencePreviewJob, &KIO::PreviewJob::finished, this, &KFileItemModelRolesUpdater::slotHoverSequencePreviewJobFinished);
1127 m_hoverSequencePreviewJob->kill();
1128 m_hoverSequencePreviewJob = nullptr;
1129 }
1130 }
1131
1132 void KFileItemModelRolesUpdater::updateChangedItems()
1133 {
1134 if (m_state == Paused) {
1135 return;
1136 }
1137
1138 if (m_changedItems.isEmpty()) {
1139 return;
1140 }
1141
1142 m_finishedItems -= m_changedItems;
1143
1144 if (m_resolvableRoles.contains(m_model->sortRole())) {
1145 m_pendingSortRoleItems += m_changedItems;
1146
1147 if (m_state != ResolvingSortRole) {
1148 // Stop the preview job if necessary, and trigger the
1149 // asynchronous determination of the sort role.
1150 killPreviewJob();
1151 m_state = ResolvingSortRole;
1152 QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::resolveNextSortRole);
1153 }
1154
1155 return;
1156 }
1157
1158 QList<int> visibleChangedIndexes;
1159 QList<int> invisibleChangedIndexes;
1160 visibleChangedIndexes.reserve(m_changedItems.size());
1161 invisibleChangedIndexes.reserve(m_changedItems.size());
1162
1163 auto changedItemsIt = m_changedItems.begin();
1164 while (changedItemsIt != m_changedItems.end()) {
1165 const auto &item = *changedItemsIt;
1166 const int index = m_model->index(item);
1167
1168 if (index < 0) {
1169 changedItemsIt = m_changedItems.erase(changedItemsIt);
1170 continue;
1171 }
1172 ++changedItemsIt;
1173
1174 if (index >= m_firstVisibleIndex && index <= m_lastVisibleIndex) {
1175 visibleChangedIndexes.append(index);
1176 } else {
1177 invisibleChangedIndexes.append(index);
1178 }
1179 }
1180
1181 std::sort(visibleChangedIndexes.begin(), visibleChangedIndexes.end());
1182
1183 if (m_previewShown) {
1184 for (int index : qAsConst(visibleChangedIndexes)) {
1185 m_pendingPreviewItems.append(m_model->fileItem(index));
1186 }
1187
1188 for (int index : qAsConst(invisibleChangedIndexes)) {
1189 m_pendingPreviewItems.append(m_model->fileItem(index));
1190 }
1191
1192 if (!m_previewJob) {
1193 startPreviewJob();
1194 }
1195 } else {
1196 const bool resolvingInProgress = !m_pendingIndexes.isEmpty();
1197 m_pendingIndexes = visibleChangedIndexes + m_pendingIndexes + invisibleChangedIndexes;
1198 if (!resolvingInProgress) {
1199 // Trigger the asynchronous resolving of the changed roles.
1200 m_state = ResolvingAllRoles;
1201 QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::resolveNextPendingRoles);
1202 }
1203 }
1204 }
1205
1206 void KFileItemModelRolesUpdater::applySortRole(int index)
1207 {
1208 QHash<QByteArray, QVariant> data;
1209 const KFileItem item = m_model->fileItem(index);
1210
1211 if (m_model->sortRole() == "type") {
1212 if (!item.isMimeTypeKnown()) {
1213 item.determineMimeType();
1214 }
1215
1216 data.insert("type", item.mimeComment());
1217 } else if (m_model->sortRole() == "size" && item.isLocalFile() && item.isDir()) {
1218 startDirectorySizeCounting(item, index);
1219 return;
1220 } else {
1221 // Probably the sort role is a baloo role - just determine all roles.
1222 data = rolesData(item, index);
1223 }
1224
1225 disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged);
1226 m_model->setData(index, data);
1227 connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged);
1228 }
1229
1230 void KFileItemModelRolesUpdater::applySortProgressToModel()
1231 {
1232 // Inform the model about the progress of the resolved items,
1233 // so that it can give an indication when the sorting has been finished.
1234 const int resolvedCount = m_model->count() - m_pendingSortRoleItems.count();
1235 m_model->emitSortProgress(resolvedCount);
1236 }
1237
1238 bool KFileItemModelRolesUpdater::applyResolvedRoles(int index, ResolveHint hint)
1239 {
1240 const KFileItem item = m_model->fileItem(index);
1241 const bool resolveAll = (hint == ResolveAll);
1242
1243 bool iconChanged = false;
1244 if (!item.isMimeTypeKnown() || !item.isFinalIconKnown()) {
1245 item.determineMimeType();
1246 iconChanged = true;
1247 } else if (!m_model->data(index).contains("iconName")) {
1248 iconChanged = true;
1249 }
1250
1251 if (iconChanged || resolveAll || m_clearPreviews) {
1252 if (index < 0) {
1253 return false;
1254 }
1255
1256 QHash<QByteArray, QVariant> data;
1257 if (resolveAll) {
1258 data = rolesData(item, index);
1259 }
1260
1261 if (!item.iconName().isEmpty()) {
1262 data.insert("iconName", item.iconName());
1263 }
1264
1265 if (m_clearPreviews) {
1266 data.insert("iconPixmap", QPixmap());
1267 data.insert("hoverSequencePixmaps", QVariant::fromValue(QVector<QPixmap>()));
1268 }
1269
1270 disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged);
1271 m_model->setData(index, data);
1272 connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged);
1273 return true;
1274 }
1275
1276 return false;
1277 }
1278
1279 void KFileItemModelRolesUpdater::startDirectorySizeCounting(const KFileItem &item, int index)
1280 {
1281 if (item.isSlow()) {
1282 return;
1283 }
1284
1285 // Tell m_directoryContentsCounter that we want to count the items
1286 // inside the directory. The result will be received in slotDirectoryContentsCountReceived.
1287 if (m_scanDirectories && item.isLocalFile()) {
1288 const QString path = item.localPath();
1289 const auto priority = index >= m_firstVisibleIndex && index <= m_lastVisibleIndex ? KDirectoryContentsCounter::PathCountPriority::High
1290 : KDirectoryContentsCounter::PathCountPriority::Normal;
1291 m_directoryContentsCounter->scanDirectory(path, priority);
1292 }
1293 }
1294
1295 QHash<QByteArray, QVariant> KFileItemModelRolesUpdater::rolesData(const KFileItem &item, int index)
1296 {
1297 QHash<QByteArray, QVariant> data;
1298
1299 const bool getSizeRole = m_roles.contains("size");
1300 const bool getIsExpandableRole = m_roles.contains("isExpandable");
1301
1302 if ((getSizeRole || getIsExpandableRole) && item.isDir()) {
1303 startDirectorySizeCounting(item, index);
1304 }
1305
1306 if (m_roles.contains("extension")) {
1307 data.insert("extension", QFileInfo(item.name()).suffix());
1308 }
1309
1310 if (m_roles.contains("type")) {
1311 data.insert("type", item.mimeComment());
1312 }
1313
1314 QStringList overlays = item.overlays();
1315 for (KOverlayIconPlugin *it : qAsConst(m_overlayIconsPlugin)) {
1316 overlays.append(it->getOverlays(item.url()));
1317 }
1318 if (!overlays.isEmpty()) {
1319 data.insert("iconOverlays", overlays);
1320 }
1321
1322 #if HAVE_BALOO
1323 if (m_balooFileMonitor) {
1324 m_balooFileMonitor->addFile(item.localPath());
1325 applyChangedBalooRolesForItem(item);
1326 }
1327 #endif
1328 return data;
1329 }
1330
1331 void KFileItemModelRolesUpdater::slotOverlaysChanged(const QUrl &url, const QStringList &)
1332 {
1333 const KFileItem item = m_model->fileItem(url);
1334 if (item.isNull()) {
1335 return;
1336 }
1337 const int index = m_model->index(item);
1338 QHash<QByteArray, QVariant> data = m_model->data(index);
1339 QStringList overlays = item.overlays();
1340 for (KOverlayIconPlugin *it : qAsConst(m_overlayIconsPlugin)) {
1341 overlays.append(it->getOverlays(url));
1342 }
1343 data.insert("iconOverlays", overlays);
1344 m_model->setData(index, data);
1345 }
1346
1347 void KFileItemModelRolesUpdater::updateAllPreviews()
1348 {
1349 if (m_state == Paused) {
1350 m_previewChangedDuringPausing = true;
1351 } else {
1352 m_finishedItems.clear();
1353 startUpdating();
1354 }
1355 }
1356
1357 void KFileItemModelRolesUpdater::killPreviewJob()
1358 {
1359 if (m_previewJob) {
1360 disconnect(m_previewJob, &KIO::PreviewJob::gotPreview, this, &KFileItemModelRolesUpdater::slotGotPreview);
1361 disconnect(m_previewJob, &KIO::PreviewJob::failed, this, &KFileItemModelRolesUpdater::slotPreviewFailed);
1362 disconnect(m_previewJob, &KIO::PreviewJob::finished, this, &KFileItemModelRolesUpdater::slotPreviewJobFinished);
1363 m_previewJob->kill();
1364 m_previewJob = nullptr;
1365 m_pendingPreviewItems.clear();
1366 }
1367 }
1368
1369 QList<int> KFileItemModelRolesUpdater::indexesToResolve() const
1370 {
1371 const int count = m_model->count();
1372
1373 QList<int> result;
1374 result.reserve(qMin(count, (m_lastVisibleIndex - m_firstVisibleIndex + 1) + ResolveAllItemsLimit + (2 * m_maximumVisibleItems)));
1375
1376 // Add visible items.
1377 // Resolve files first, their previews are quicker.
1378 QList<int> visibleDirs;
1379 for (int i = m_firstVisibleIndex; i <= m_lastVisibleIndex; ++i) {
1380 const KFileItem item = m_model->fileItem(i);
1381 if (item.isDir()) {
1382 visibleDirs.append(i);
1383 } else {
1384 result.append(i);
1385 }
1386 }
1387
1388 result.append(visibleDirs);
1389
1390 // We need a reasonable upper limit for number of items to resolve after
1391 // and before the visible range. m_maximumVisibleItems can be quite large
1392 // when using Compact View.
1393 const int readAheadItems = qMin(ReadAheadPages * m_maximumVisibleItems, ResolveAllItemsLimit / 2);
1394
1395 // Add items after the visible range.
1396 const int endExtendedVisibleRange = qMin(m_lastVisibleIndex + readAheadItems, count - 1);
1397 for (int i = m_lastVisibleIndex + 1; i <= endExtendedVisibleRange; ++i) {
1398 result.append(i);
1399 }
1400
1401 // Add items before the visible range in reverse order.
1402 const int beginExtendedVisibleRange = qMax(0, m_firstVisibleIndex - readAheadItems);
1403 for (int i = m_firstVisibleIndex - 1; i >= beginExtendedVisibleRange; --i) {
1404 result.append(i);
1405 }
1406
1407 // Add items on the last page.
1408 const int beginLastPage = qMax(endExtendedVisibleRange + 1, count - m_maximumVisibleItems);
1409 for (int i = beginLastPage; i < count; ++i) {
1410 result.append(i);
1411 }
1412
1413 // Add items on the first page.
1414 const int endFirstPage = qMin(beginExtendedVisibleRange, m_maximumVisibleItems);
1415 for (int i = 0; i < endFirstPage; ++i) {
1416 result.append(i);
1417 }
1418
1419 // Continue adding items until ResolveAllItemsLimit is reached.
1420 int remainingItems = ResolveAllItemsLimit - result.count();
1421
1422 for (int i = endExtendedVisibleRange + 1; i < beginLastPage && remainingItems > 0; ++i) {
1423 result.append(i);
1424 --remainingItems;
1425 }
1426
1427 for (int i = beginExtendedVisibleRange - 1; i >= endFirstPage && remainingItems > 0; --i) {
1428 result.append(i);
1429 --remainingItems;
1430 }
1431
1432 return result;
1433 }
1434
1435 void KFileItemModelRolesUpdater::trimHoverSequenceLoadedItems()
1436 {
1437 static const size_t maxLoadedItems = 20;
1438
1439 size_t loadedItems = m_hoverSequenceLoadedItems.size();
1440 while (loadedItems > maxLoadedItems) {
1441 const KFileItem item = m_hoverSequenceLoadedItems.front();
1442
1443 m_hoverSequenceLoadedItems.pop_front();
1444 loadedItems--;
1445
1446 const int index = m_model->index(item);
1447 if (index >= 0) {
1448 QHash<QByteArray, QVariant> data = m_model->data(index);
1449 data["hoverSequencePixmaps"] = QVariant::fromValue(QVector<QPixmap>() << QPixmap());
1450 m_model->setData(index, data);
1451 }
1452 }
1453 }