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