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