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