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