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