]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemmodelrolesupdater.cpp
Fix includes
[dolphin.git] / src / kitemviews / kfileitemmodelrolesupdater.cpp
1 /***************************************************************************
2 * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18 ***************************************************************************/
19
20 #include "kfileitemmodelrolesupdater.h"
21
22 #include "kfileitemmodel.h"
23
24 #include <KConfig>
25 #include <KConfigGroup>
26 #include <KFileItem>
27 #include <KIconLoader>
28 #include <KJobWidgets>
29 #include <KIO/JobUiDelegate>
30 #include <KIO/PreviewJob>
31
32 #include "private/kpixmapmodifier.h"
33 #include "private/kdirectorycontentscounter.h"
34
35 #include <QApplication>
36 #include <QPainter>
37 #include <QPixmap>
38 #include <QElapsedTimer>
39 #include <QTimer>
40
41 #include <algorithm>
42
43 #ifdef HAVE_BALOO
44 #include "private/kbaloorolesprovider.h"
45 #include <Baloo/File>
46 #include <Baloo/FileMonitor>
47 #endif
48
49 // #define KFILEITEMMODELROLESUPDATER_DEBUG
50
51 namespace {
52 // Maximum time in ms that the KFileItemModelRolesUpdater
53 // may perform a blocking operation
54 const int MaxBlockTimeout = 200;
55
56 // If the number of items is smaller than ResolveAllItemsLimit,
57 // the roles of all items will be resolved.
58 const int ResolveAllItemsLimit = 500;
59
60 // Not only the visible area, but up to ReadAheadPages before and after
61 // this area will be resolved.
62 const int ReadAheadPages = 5;
63 }
64
65 KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QObject* parent) :
66 QObject(parent),
67 m_state(Idle),
68 m_previewChangedDuringPausing(false),
69 m_iconSizeChangedDuringPausing(false),
70 m_rolesChangedDuringPausing(false),
71 m_previewShown(false),
72 m_enlargeSmallPreviews(true),
73 m_clearPreviews(false),
74 m_finishedItems(),
75 m_model(model),
76 m_iconSize(),
77 m_firstVisibleIndex(0),
78 m_lastVisibleIndex(-1),
79 m_maximumVisibleItems(50),
80 m_roles(),
81 m_resolvableRoles(),
82 m_enabledPlugins(),
83 m_pendingSortRoleItems(),
84 m_pendingIndexes(),
85 m_pendingPreviewItems(),
86 m_previewJob(),
87 m_recentlyChangedItemsTimer(0),
88 m_recentlyChangedItems(),
89 m_changedItems(),
90 m_directoryContentsCounter(0)
91 #ifdef HAVE_BALOO
92 , m_balooFileMonitor(0)
93 #endif
94 {
95 Q_ASSERT(model);
96
97 const KConfigGroup globalConfig(KSharedConfig::openConfig(), "PreviewSettings");
98 m_enabledPlugins = globalConfig.readEntry("Plugins", QStringList()
99 << "directorythumbnail"
100 << "imagethumbnail"
101 << "jpegthumbnail");
102
103 connect(m_model, &KFileItemModel::itemsInserted,
104 this, &KFileItemModelRolesUpdater::slotItemsInserted);
105 connect(m_model, &KFileItemModel::itemsRemoved,
106 this, &KFileItemModelRolesUpdater::slotItemsRemoved);
107 connect(m_model, &KFileItemModel::itemsChanged,
108 this, &KFileItemModelRolesUpdater::slotItemsChanged);
109 connect(m_model, &KFileItemModel::itemsMoved,
110 this, &KFileItemModelRolesUpdater::slotItemsMoved);
111 connect(m_model, &KFileItemModel::sortRoleChanged,
112 this, &KFileItemModelRolesUpdater::slotSortRoleChanged);
113
114 // Use a timer to prevent that each call of slotItemsChanged() results in a synchronous
115 // resolving of the roles. Postpone the resolving until no update has been done for 1 second.
116 m_recentlyChangedItemsTimer = new QTimer(this);
117 m_recentlyChangedItemsTimer->setInterval(1000);
118 m_recentlyChangedItemsTimer->setSingleShot(true);
119 connect(m_recentlyChangedItemsTimer, &QTimer::timeout, this, &KFileItemModelRolesUpdater::resolveRecentlyChangedItems);
120
121 m_resolvableRoles.insert("size");
122 m_resolvableRoles.insert("type");
123 m_resolvableRoles.insert("isExpandable");
124 #ifdef HAVE_BALOO
125 m_resolvableRoles += KBalooRolesProvider::instance().roles();
126 #endif
127
128 m_directoryContentsCounter = new KDirectoryContentsCounter(m_model, this);
129 connect(m_directoryContentsCounter, &KDirectoryContentsCounter::result,
130 this, &KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived);
131 }
132
133 KFileItemModelRolesUpdater::~KFileItemModelRolesUpdater()
134 {
135 killPreviewJob();
136 }
137
138 void KFileItemModelRolesUpdater::setIconSize(const QSize& size)
139 {
140 if (size != m_iconSize) {
141 m_iconSize = size;
142 if (m_state == Paused) {
143 m_iconSizeChangedDuringPausing = true;
144 } else if (m_previewShown) {
145 // An icon size change requires the regenerating of
146 // all previews
147 m_finishedItems.clear();
148 startUpdating();
149 }
150 }
151 }
152
153 QSize KFileItemModelRolesUpdater::iconSize() const
154 {
155 return m_iconSize;
156 }
157
158 void KFileItemModelRolesUpdater::setVisibleIndexRange(int index, int count)
159 {
160 if (index < 0) {
161 index = 0;
162 }
163 if (count < 0) {
164 count = 0;
165 }
166
167 if (index == m_firstVisibleIndex && count == m_lastVisibleIndex - m_firstVisibleIndex + 1) {
168 // The range has not been changed
169 return;
170 }
171
172 m_firstVisibleIndex = index;
173 m_lastVisibleIndex = qMin(index + count - 1, m_model->count() - 1);
174
175 startUpdating();
176 }
177
178 void KFileItemModelRolesUpdater::setMaximumVisibleItems(int count)
179 {
180 m_maximumVisibleItems = count;
181 }
182
183 void KFileItemModelRolesUpdater::setPreviewsShown(bool show)
184 {
185 if (show == m_previewShown) {
186 return;
187 }
188
189 m_previewShown = show;
190 if (!show) {
191 m_clearPreviews = true;
192 }
193
194 updateAllPreviews();
195 }
196
197 bool KFileItemModelRolesUpdater::previewsShown() const
198 {
199 return m_previewShown;
200 }
201
202 void KFileItemModelRolesUpdater::setEnlargeSmallPreviews(bool enlarge)
203 {
204 if (enlarge != m_enlargeSmallPreviews) {
205 m_enlargeSmallPreviews = enlarge;
206 if (m_previewShown) {
207 updateAllPreviews();
208 }
209 }
210 }
211
212 bool KFileItemModelRolesUpdater::enlargeSmallPreviews() const
213 {
214 return m_enlargeSmallPreviews;
215 }
216
217 void KFileItemModelRolesUpdater::setEnabledPlugins(const QStringList& list)
218 {
219 if (m_enabledPlugins != list) {
220 m_enabledPlugins = list;
221 if (m_previewShown) {
222 updateAllPreviews();
223 }
224 }
225 }
226
227 void KFileItemModelRolesUpdater::setPaused(bool paused)
228 {
229 if (paused == (m_state == Paused)) {
230 return;
231 }
232
233 if (paused) {
234 m_state = Paused;
235 killPreviewJob();
236 } else {
237 const bool updatePreviews = (m_iconSizeChangedDuringPausing && m_previewShown) ||
238 m_previewChangedDuringPausing;
239 const bool resolveAll = updatePreviews || m_rolesChangedDuringPausing;
240 if (resolveAll) {
241 m_finishedItems.clear();
242 }
243
244 m_iconSizeChangedDuringPausing = false;
245 m_previewChangedDuringPausing = false;
246 m_rolesChangedDuringPausing = false;
247
248 if (!m_pendingSortRoleItems.isEmpty()) {
249 m_state = ResolvingSortRole;
250 resolveNextSortRole();
251 } else {
252 m_state = Idle;
253 }
254
255 startUpdating();
256 }
257 }
258
259 void KFileItemModelRolesUpdater::setRoles(const QSet<QByteArray>& roles)
260 {
261 if (m_roles != roles) {
262 m_roles = roles;
263
264 #ifdef HAVE_BALOO
265 // Check whether there is at least one role that must be resolved
266 // with the help of Baloo. If this is the case, a (quite expensive)
267 // resolving will be done in KFileItemModelRolesUpdater::rolesData() and
268 // the role gets watched for changes.
269 const KBalooRolesProvider& rolesProvider = KBalooRolesProvider::instance();
270 bool hasBalooRole = false;
271 QSetIterator<QByteArray> it(roles);
272 while (it.hasNext()) {
273 const QByteArray& role = it.next();
274 if (rolesProvider.roles().contains(role)) {
275 hasBalooRole = true;
276 break;
277 }
278 }
279
280 if (hasBalooRole && !m_balooFileMonitor) {
281 m_balooFileMonitor = new Baloo::FileMonitor(this);
282 connect(m_balooFileMonitor, &Baloo::FileMonitor::fileMetaDataChanged,
283 this, &KFileItemModelRolesUpdater::applyChangedBalooRoles);
284 } else if (!hasBalooRole && m_balooFileMonitor) {
285 delete m_balooFileMonitor;
286 m_balooFileMonitor = 0;
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::slotItemsInserted(const KItemRangeList& itemRanges)
314 {
315 QElapsedTimer timer;
316 timer.start();
317
318 // Determine the sort role synchronously for as many items as possible.
319 if (m_resolvableRoles.contains(m_model->sortRole())) {
320 int insertedCount = 0;
321 foreach (const KItemRange& range, itemRanges) {
322 const int lastIndex = insertedCount + range.index + range.count - 1;
323 for (int i = insertedCount + range.index; i <= lastIndex; ++i) {
324 if (timer.elapsed() < MaxBlockTimeout) {
325 applySortRole(i);
326 } else {
327 m_pendingSortRoleItems.insert(m_model->fileItem(i));
328 }
329 }
330 insertedCount += range.count;
331 }
332
333 applySortProgressToModel();
334
335 // If there are still items whose sort role is unknown, check if the
336 // asynchronous determination of the sort role is already in progress,
337 // and start it if that is not the case.
338 if (!m_pendingSortRoleItems.isEmpty() && m_state != ResolvingSortRole) {
339 killPreviewJob();
340 m_state = ResolvingSortRole;
341 resolveNextSortRole();
342 }
343 }
344
345 startUpdating();
346 }
347
348 void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList& itemRanges)
349 {
350 Q_UNUSED(itemRanges);
351
352 const bool allItemsRemoved = (m_model->count() == 0);
353
354 #ifdef HAVE_BALOO
355 if (m_balooFileMonitor) {
356 // Don't let the FileWatcher watch for removed items
357 if (allItemsRemoved) {
358 m_balooFileMonitor->clear();
359 } else {
360 QStringList newFileList;
361 foreach (const QString& itemUrl, m_balooFileMonitor->files()) {
362 if (m_model->index(itemUrl) >= 0) {
363 newFileList.append(itemUrl);
364 }
365 }
366 m_balooFileMonitor->setFiles(newFileList);
367 }
368 }
369 #endif
370
371 if (allItemsRemoved) {
372 m_state = Idle;
373
374 m_finishedItems.clear();
375 m_pendingSortRoleItems.clear();
376 m_pendingIndexes.clear();
377 m_pendingPreviewItems.clear();
378 m_recentlyChangedItems.clear();
379 m_recentlyChangedItemsTimer->stop();
380 m_changedItems.clear();
381
382 killPreviewJob();
383 } else {
384 // Only remove the items from m_finishedItems. They will be removed
385 // from the other sets later on.
386 QSet<KFileItem>::iterator it = m_finishedItems.begin();
387 while (it != m_finishedItems.end()) {
388 if (m_model->index(*it) < 0) {
389 it = m_finishedItems.erase(it);
390 } else {
391 ++it;
392 }
393 }
394
395 // The visible items might have changed.
396 startUpdating();
397 }
398 }
399
400 void KFileItemModelRolesUpdater::slotItemsMoved(const KItemRange& itemRange, QList<int> movedToIndexes)
401 {
402 Q_UNUSED(itemRange);
403 Q_UNUSED(movedToIndexes);
404
405 // The visible items might have changed.
406 startUpdating();
407 }
408
409 void KFileItemModelRolesUpdater::slotItemsChanged(const KItemRangeList& itemRanges,
410 const QSet<QByteArray>& roles)
411 {
412 Q_UNUSED(roles);
413
414 // Find out if slotItemsChanged() has been done recently. If that is the
415 // case, resolving the roles is postponed until a timer has exceeded
416 // to prevent expensive repeated updates if files are updated frequently.
417 const bool itemsChangedRecently = m_recentlyChangedItemsTimer->isActive();
418
419 QSet<KFileItem>& targetSet = itemsChangedRecently ? m_recentlyChangedItems : m_changedItems;
420
421 foreach (const KItemRange& itemRange, itemRanges) {
422 int index = itemRange.index;
423 for (int count = itemRange.count; count > 0; --count) {
424 const KFileItem item = m_model->fileItem(index);
425 targetSet.insert(item);
426 ++index;
427 }
428 }
429
430 m_recentlyChangedItemsTimer->start();
431
432 if (!itemsChangedRecently) {
433 updateChangedItems();
434 }
435 }
436
437 void KFileItemModelRolesUpdater::slotSortRoleChanged(const QByteArray& current,
438 const QByteArray& previous)
439 {
440 Q_UNUSED(current);
441 Q_UNUSED(previous);
442
443 if (m_resolvableRoles.contains(current)) {
444 m_pendingSortRoleItems.clear();
445 m_finishedItems.clear();
446
447 const int count = m_model->count();
448 QElapsedTimer timer;
449 timer.start();
450
451 // Determine the sort role synchronously for as many items as possible.
452 for (int index = 0; index < count; ++index) {
453 if (timer.elapsed() < MaxBlockTimeout) {
454 applySortRole(index);
455 } else {
456 m_pendingSortRoleItems.insert(m_model->fileItem(index));
457 }
458 }
459
460 applySortProgressToModel();
461
462 if (!m_pendingSortRoleItems.isEmpty()) {
463 // Trigger the asynchronous determination of the sort role.
464 killPreviewJob();
465 m_state = ResolvingSortRole;
466 resolveNextSortRole();
467 }
468 } else {
469 m_state = Idle;
470 m_pendingSortRoleItems.clear();
471 applySortProgressToModel();
472 }
473 }
474
475 void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem& item, const QPixmap& pixmap)
476 {
477 if (m_state != PreviewJobRunning) {
478 return;
479 }
480
481 m_changedItems.remove(item);
482
483 const int index = m_model->index(item);
484 if (index < 0) {
485 return;
486 }
487
488 QPixmap scaledPixmap = pixmap;
489
490 const QString mimeType = item.mimetype();
491 const int slashIndex = mimeType.indexOf(QLatin1Char('/'));
492 const QString mimeTypeGroup = mimeType.left(slashIndex);
493 if (mimeTypeGroup == QLatin1String("image")) {
494 if (m_enlargeSmallPreviews) {
495 KPixmapModifier::applyFrame(scaledPixmap, m_iconSize);
496 } else {
497 // Assure that small previews don't get enlarged. Instead they
498 // should be shown centered within the frame.
499 const QSize contentSize = KPixmapModifier::sizeInsideFrame(m_iconSize);
500 const bool enlargingRequired = scaledPixmap.width() < contentSize.width() &&
501 scaledPixmap.height() < contentSize.height();
502 if (enlargingRequired) {
503 QSize frameSize = scaledPixmap.size();
504 frameSize.scale(m_iconSize, Qt::KeepAspectRatio);
505
506 QPixmap largeFrame(frameSize);
507 largeFrame.fill(Qt::transparent);
508
509 KPixmapModifier::applyFrame(largeFrame, frameSize);
510
511 QPainter painter(&largeFrame);
512 painter.drawPixmap((largeFrame.width() - scaledPixmap.width()) / 2,
513 (largeFrame.height() - scaledPixmap.height()) / 2,
514 scaledPixmap);
515 scaledPixmap = largeFrame;
516 } else {
517 // The image must be shrinked as it is too large to fit into
518 // the available icon size
519 KPixmapModifier::applyFrame(scaledPixmap, m_iconSize);
520 }
521 }
522 } else {
523 KPixmapModifier::scale(scaledPixmap, m_iconSize);
524 }
525
526 QHash<QByteArray, QVariant> data = rolesData(item);
527
528 const QStringList overlays = data["iconOverlays"].toStringList();
529 // Strangely KFileItem::overlays() returns empty string-values, so
530 // we need to check first whether an overlay must be drawn at all.
531 // It is more efficient to do it here, as KIconLoader::drawOverlays()
532 // assumes that an overlay will be drawn and has some additional
533 // setup time.
534 foreach (const QString& overlay, overlays) {
535 if (!overlay.isEmpty()) {
536 // There is at least one overlay, draw all overlays above m_pixmap
537 // and cancel the check
538 KIconLoader::global()->drawOverlays(overlays, scaledPixmap, KIconLoader::Desktop);
539 break;
540 }
541 }
542
543 data.insert("iconPixmap", scaledPixmap);
544
545 disconnect(m_model, &KFileItemModel::itemsChanged,
546 this, &KFileItemModelRolesUpdater::slotItemsChanged);
547 m_model->setData(index, data);
548 connect(m_model, &KFileItemModel::itemsChanged,
549 this, &KFileItemModelRolesUpdater::slotItemsChanged);
550
551 m_finishedItems.insert(item);
552 }
553
554 void KFileItemModelRolesUpdater::slotPreviewFailed(const KFileItem& item)
555 {
556 if (m_state != PreviewJobRunning) {
557 return;
558 }
559
560 m_changedItems.remove(item);
561
562 const int index = m_model->index(item);
563 if (index >= 0) {
564 QHash<QByteArray, QVariant> data;
565 data.insert("iconPixmap", QPixmap());
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 applyResolvedRoles(index, ResolveAll);
574 m_finishedItems.insert(item);
575 }
576 }
577
578 void KFileItemModelRolesUpdater::slotPreviewJobFinished()
579 {
580 m_previewJob = 0;
581
582 if (m_state != PreviewJobRunning) {
583 return;
584 }
585
586 m_state = Idle;
587
588 if (!m_pendingPreviewItems.isEmpty()) {
589 startPreviewJob();
590 } else {
591 if (!m_changedItems.isEmpty()) {
592 updateChangedItems();
593 }
594 }
595 }
596
597 void KFileItemModelRolesUpdater::resolveNextSortRole()
598 {
599 if (m_state != ResolvingSortRole) {
600 return;
601 }
602
603 QSet<KFileItem>::iterator it = m_pendingSortRoleItems.begin();
604 while (it != m_pendingSortRoleItems.end()) {
605 const KFileItem item = *it;
606 const int index = m_model->index(item);
607
608 // Continue if the sort role has already been determined for the
609 // item, and the item has not been changed recently.
610 if (!m_changedItems.contains(item) && m_model->data(index).contains(m_model->sortRole())) {
611 it = m_pendingSortRoleItems.erase(it);
612 continue;
613 }
614
615 applySortRole(index);
616 m_pendingSortRoleItems.erase(it);
617 break;
618 }
619
620 if (!m_pendingSortRoleItems.isEmpty()) {
621 applySortProgressToModel();
622 QTimer::singleShot(0, this, SLOT(resolveNextSortRole()));
623 } else {
624 m_state = Idle;
625
626 // Prevent that we try to update the items twice.
627 disconnect(m_model, &KFileItemModel::itemsMoved,
628 this, &KFileItemModelRolesUpdater::slotItemsMoved);
629 applySortProgressToModel();
630 connect(m_model, &KFileItemModel::itemsMoved,
631 this, &KFileItemModelRolesUpdater::slotItemsMoved);
632 startUpdating();
633 }
634 }
635
636 void KFileItemModelRolesUpdater::resolveNextPendingRoles()
637 {
638 if (m_state != ResolvingAllRoles) {
639 return;
640 }
641
642 while (!m_pendingIndexes.isEmpty()) {
643 const int index = m_pendingIndexes.takeFirst();
644 const KFileItem item = m_model->fileItem(index);
645
646 if (m_finishedItems.contains(item)) {
647 continue;
648 }
649
650 applyResolvedRoles(index, ResolveAll);
651 m_finishedItems.insert(item);
652 m_changedItems.remove(item);
653 break;
654 }
655
656 if (!m_pendingIndexes.isEmpty()) {
657 QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
658 } else {
659 m_state = Idle;
660
661 if (m_clearPreviews) {
662 // Only go through the list if there are items which might still have previews.
663 if (m_finishedItems.count() != m_model->count()) {
664 QHash<QByteArray, QVariant> data;
665 data.insert("iconPixmap", QPixmap());
666
667 disconnect(m_model, &KFileItemModel::itemsChanged,
668 this, &KFileItemModelRolesUpdater::slotItemsChanged);
669 for (int index = 0; index <= m_model->count(); ++index) {
670 if (m_model->data(index).contains("iconPixmap")) {
671 m_model->setData(index, data);
672 }
673 }
674 connect(m_model, &KFileItemModel::itemsChanged,
675 this, &KFileItemModelRolesUpdater::slotItemsChanged);
676
677 }
678 m_clearPreviews = false;
679 }
680
681 if (!m_changedItems.isEmpty()) {
682 updateChangedItems();
683 }
684 }
685 }
686
687 void KFileItemModelRolesUpdater::resolveRecentlyChangedItems()
688 {
689 m_changedItems += m_recentlyChangedItems;
690 m_recentlyChangedItems.clear();
691 updateChangedItems();
692 }
693
694 void KFileItemModelRolesUpdater::applyChangedBalooRoles(const QString& itemUrl)
695 {
696 #ifdef HAVE_BALOO
697 const KFileItem item = m_model->fileItem(itemUrl);
698
699 if (item.isNull()) {
700 // itemUrl is not in the model anymore, probably because
701 // the corresponding file has been deleted in the meantime.
702 return;
703 }
704
705 Baloo::File file(item.localPath());
706 file.load();
707
708 const KBalooRolesProvider& rolesProvider = KBalooRolesProvider::instance();
709 QHash<QByteArray, QVariant> data;
710
711 foreach (const QByteArray& role, rolesProvider.roles()) {
712 // Overwrite all the role values with an empty QVariant, because the roles
713 // provider doesn't overwrite it when the property value list is empty.
714 // See bug 322348
715 data.insert(role, QVariant());
716 }
717
718 QHashIterator<QByteArray, QVariant> it(rolesProvider.roleValues(file, m_roles));
719 while (it.hasNext()) {
720 it.next();
721 data.insert(it.key(), it.value());
722 }
723
724 disconnect(m_model, &KFileItemModel::itemsChanged,
725 this, &KFileItemModelRolesUpdater::slotItemsChanged);
726 const int index = m_model->index(item);
727 m_model->setData(index, data);
728 connect(m_model, &KFileItemModel::itemsChanged,
729 this, &KFileItemModelRolesUpdater::slotItemsChanged);
730 #else
731 #ifndef Q_CC_MSVC
732 Q_UNUSED(itemUrl);
733 #endif
734 #endif
735 }
736
737 void KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived(const QString& path, int count)
738 {
739 const bool getSizeRole = m_roles.contains("size");
740 const bool getIsExpandableRole = m_roles.contains("isExpandable");
741
742 if (getSizeRole || getIsExpandableRole) {
743 const int index = m_model->index(QUrl::fromLocalFile(path));
744 if (index >= 0) {
745 QHash<QByteArray, QVariant> data;
746
747 if (getSizeRole) {
748 data.insert("size", count);
749 }
750 if (getIsExpandableRole) {
751 data.insert("isExpandable", count > 0);
752 }
753
754 disconnect(m_model, &KFileItemModel::itemsChanged,
755 this, &KFileItemModelRolesUpdater::slotItemsChanged);
756 m_model->setData(index, data);
757 connect(m_model, &KFileItemModel::itemsChanged,
758 this, &KFileItemModelRolesUpdater::slotItemsChanged);
759 }
760 }
761 }
762
763 void KFileItemModelRolesUpdater::startUpdating()
764 {
765 if (m_state == Paused) {
766 return;
767 }
768
769 if (m_finishedItems.count() == m_model->count()) {
770 // All roles have been resolved already.
771 m_state = Idle;
772 return;
773 }
774
775 // Terminate all updates that are currently active.
776 killPreviewJob();
777 m_pendingIndexes.clear();
778
779 QElapsedTimer timer;
780 timer.start();
781
782 // Determine the icons for the visible items synchronously.
783 updateVisibleIcons();
784
785 // A detailed update of the items in and near the visible area
786 // only makes sense if sorting is finished.
787 if (m_state == ResolvingSortRole) {
788 return;
789 }
790
791 // Start the preview job or the asynchronous resolving of all roles.
792 QList<int> indexes = indexesToResolve();
793
794 if (m_previewShown) {
795 m_pendingPreviewItems.clear();
796 m_pendingPreviewItems.reserve(indexes.count());
797
798 foreach (int index, indexes) {
799 const KFileItem item = m_model->fileItem(index);
800 if (!m_finishedItems.contains(item)) {
801 m_pendingPreviewItems.append(item);
802 }
803 }
804
805 startPreviewJob();
806 } else {
807 m_pendingIndexes = indexes;
808 // Trigger the asynchronous resolving of all roles.
809 m_state = ResolvingAllRoles;
810 QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
811 }
812 }
813
814 void KFileItemModelRolesUpdater::updateVisibleIcons()
815 {
816 int lastVisibleIndex = m_lastVisibleIndex;
817 if (lastVisibleIndex <= 0) {
818 // Guess a reasonable value for the last visible index if the view
819 // has not told us about the real value yet.
820 lastVisibleIndex = qMin(m_firstVisibleIndex + m_maximumVisibleItems, m_model->count() - 1);
821 if (lastVisibleIndex <= 0) {
822 lastVisibleIndex = qMin(200, m_model->count() - 1);
823 }
824 }
825
826 QElapsedTimer timer;
827 timer.start();
828
829 // Try to determine the final icons for all visible items.
830 int index;
831 for (index = m_firstVisibleIndex; index <= lastVisibleIndex && timer.elapsed() < MaxBlockTimeout; ++index) {
832 applyResolvedRoles(index, ResolveFast);
833 }
834
835 // KFileItemListView::initializeItemListWidget(KItemListWidget*) will load
836 // preliminary icons (i.e., without mime type determination) for the
837 // remaining items.
838 }
839
840 void KFileItemModelRolesUpdater::startPreviewJob()
841 {
842 m_state = PreviewJobRunning;
843
844 if (m_pendingPreviewItems.isEmpty()) {
845 QTimer::singleShot(0, this, SLOT(slotPreviewJobFinished()));
846 return;
847 }
848
849 // PreviewJob internally caches items always with the size of
850 // 128 x 128 pixels or 256 x 256 pixels. A (slow) downscaling is done
851 // by PreviewJob if a smaller size is requested. For images KFileItemModelRolesUpdater must
852 // do a downscaling anyhow because of the frame, so in this case only the provided
853 // cache sizes are requested.
854 const QSize cacheSize = (m_iconSize.width() > 128) || (m_iconSize.height() > 128)
855 ? QSize(256, 256) : QSize(128, 128);
856
857 // KIO::filePreview() will request the MIME-type of all passed items, which (in the
858 // worst case) might block the application for several seconds. To prevent such
859 // a blocking, we only pass items with known mime type to the preview job.
860 const int count = m_pendingPreviewItems.count();
861 KFileItemList itemSubSet;
862 itemSubSet.reserve(count);
863
864 if (m_pendingPreviewItems.first().isMimeTypeKnown()) {
865 // Some mime types are known already, probably because they were
866 // determined when loading the icons for the visible items. Start
867 // a preview job for all items at the beginning of the list which
868 // have a known mime type.
869 do {
870 itemSubSet.append(m_pendingPreviewItems.takeFirst());
871 } while (!m_pendingPreviewItems.isEmpty() && m_pendingPreviewItems.first().isMimeTypeKnown());
872 } else {
873 // Determine mime types for MaxBlockTimeout ms, and start a preview
874 // job for the corresponding items.
875 QElapsedTimer timer;
876 timer.start();
877
878 do {
879 const KFileItem item = m_pendingPreviewItems.takeFirst();
880 item.determineMimeType();
881 itemSubSet.append(item);
882 } while (!m_pendingPreviewItems.isEmpty() && timer.elapsed() < MaxBlockTimeout);
883 }
884
885 KIO::PreviewJob* job = new KIO::PreviewJob(itemSubSet, cacheSize, &m_enabledPlugins);
886
887 job->setIgnoreMaximumSize(itemSubSet.first().isLocalFile());
888 if (job->ui()) {
889 KJobWidgets::setWindow(job, qApp->activeWindow());
890 }
891
892 connect(job, &KIO::PreviewJob::gotPreview,
893 this, &KFileItemModelRolesUpdater::slotGotPreview);
894 connect(job, &KIO::PreviewJob::failed,
895 this, &KFileItemModelRolesUpdater::slotPreviewFailed);
896 connect(job, &KIO::PreviewJob::finished,
897 this, &KFileItemModelRolesUpdater::slotPreviewJobFinished);
898
899 m_previewJob = job;
900 }
901
902 void KFileItemModelRolesUpdater::updateChangedItems()
903 {
904 if (m_state == Paused) {
905 return;
906 }
907
908 if (m_changedItems.isEmpty()) {
909 return;
910 }
911
912 m_finishedItems -= m_changedItems;
913
914 if (m_resolvableRoles.contains(m_model->sortRole())) {
915 m_pendingSortRoleItems += m_changedItems;
916
917 if (m_state != ResolvingSortRole) {
918 // Stop the preview job if necessary, and trigger the
919 // asynchronous determination of the sort role.
920 killPreviewJob();
921 m_state = ResolvingSortRole;
922 QTimer::singleShot(0, this, SLOT(resolveNextSortRole()));
923 }
924
925 return;
926 }
927
928 QList<int> visibleChangedIndexes;
929 QList<int> invisibleChangedIndexes;
930
931 foreach (const KFileItem& item, m_changedItems) {
932 const int index = m_model->index(item);
933
934 if (index < 0) {
935 m_changedItems.remove(item);
936 continue;
937 }
938
939 if (index >= m_firstVisibleIndex && index <= m_lastVisibleIndex) {
940 visibleChangedIndexes.append(index);
941 } else {
942 invisibleChangedIndexes.append(index);
943 }
944 }
945
946 std::sort(visibleChangedIndexes.begin(), visibleChangedIndexes.end());
947
948 if (m_previewShown) {
949 foreach (int index, visibleChangedIndexes) {
950 m_pendingPreviewItems.append(m_model->fileItem(index));
951 }
952
953 foreach (int index, invisibleChangedIndexes) {
954 m_pendingPreviewItems.append(m_model->fileItem(index));
955 }
956
957 if (!m_previewJob) {
958 startPreviewJob();
959 }
960 } else {
961 const bool resolvingInProgress = !m_pendingIndexes.isEmpty();
962 m_pendingIndexes = visibleChangedIndexes + m_pendingIndexes + invisibleChangedIndexes;
963 if (!resolvingInProgress) {
964 // Trigger the asynchronous resolving of the changed roles.
965 m_state = ResolvingAllRoles;
966 QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
967 }
968 }
969 }
970
971 void KFileItemModelRolesUpdater::applySortRole(int index)
972 {
973 QHash<QByteArray, QVariant> data;
974 const KFileItem item = m_model->fileItem(index);
975
976 if (m_model->sortRole() == "type") {
977 if (!item.isMimeTypeKnown()) {
978 item.determineMimeType();
979 }
980
981 data.insert("type", item.mimeComment());
982 } else if (m_model->sortRole() == "size" && item.isLocalFile() && item.isDir()) {
983 const QString path = item.localPath();
984 data.insert("size", m_directoryContentsCounter->countDirectoryContentsSynchronously(path));
985 } else {
986 // Probably the sort role is a baloo role - just determine all roles.
987 data = rolesData(item);
988 }
989
990 disconnect(m_model, &KFileItemModel::itemsChanged,
991 this, &KFileItemModelRolesUpdater::slotItemsChanged);
992 m_model->setData(index, data);
993 connect(m_model, &KFileItemModel::itemsChanged,
994 this, &KFileItemModelRolesUpdater::slotItemsChanged);
995 }
996
997 void KFileItemModelRolesUpdater::applySortProgressToModel()
998 {
999 // Inform the model about the progress of the resolved items,
1000 // so that it can give an indication when the sorting has been finished.
1001 const int resolvedCount = m_model->count() - m_pendingSortRoleItems.count();
1002 m_model->emitSortProgress(resolvedCount);
1003 }
1004
1005 bool KFileItemModelRolesUpdater::applyResolvedRoles(int index, ResolveHint hint)
1006 {
1007 const KFileItem item = m_model->fileItem(index);
1008 const bool resolveAll = (hint == ResolveAll);
1009
1010 bool iconChanged = false;
1011 if (!item.isMimeTypeKnown() || !item.isFinalIconKnown()) {
1012 item.determineMimeType();
1013 iconChanged = true;
1014 } else if (!m_model->data(index).contains("iconName")) {
1015 iconChanged = true;
1016 }
1017
1018 if (iconChanged || resolveAll || m_clearPreviews) {
1019 if (index < 0) {
1020 return false;
1021 }
1022
1023 QHash<QByteArray, QVariant> data;
1024 if (resolveAll) {
1025 data = rolesData(item);
1026 }
1027
1028 data.insert("iconName", item.iconName());
1029
1030 if (m_clearPreviews) {
1031 data.insert("iconPixmap", QPixmap());
1032 }
1033
1034 disconnect(m_model, &KFileItemModel::itemsChanged,
1035 this, &KFileItemModelRolesUpdater::slotItemsChanged);
1036 m_model->setData(index, data);
1037 connect(m_model, &KFileItemModel::itemsChanged,
1038 this, &KFileItemModelRolesUpdater::slotItemsChanged);
1039 return true;
1040 }
1041
1042 return false;
1043 }
1044
1045 QHash<QByteArray, QVariant> KFileItemModelRolesUpdater::rolesData(const KFileItem& item)
1046 {
1047 QHash<QByteArray, QVariant> data;
1048
1049 const bool getSizeRole = m_roles.contains("size");
1050 const bool getIsExpandableRole = m_roles.contains("isExpandable");
1051
1052 if ((getSizeRole || getIsExpandableRole) && item.isDir()) {
1053 if (item.isLocalFile()) {
1054 // Tell m_directoryContentsCounter that we want to count the items
1055 // inside the directory. The result will be received in slotDirectoryContentsCountReceived.
1056 const QString path = item.localPath();
1057 m_directoryContentsCounter->addDirectory(path);
1058 } else if (getSizeRole) {
1059 data.insert("size", -1); // -1 indicates an unknown number of items
1060 }
1061 }
1062
1063 if (m_roles.contains("type")) {
1064 data.insert("type", item.mimeComment());
1065 }
1066
1067 data.insert("iconOverlays", item.overlays());
1068
1069 #ifdef HAVE_BALOO
1070 if (m_balooFileMonitor) {
1071 m_balooFileMonitor->addFile(item.localPath());
1072 applyChangedBalooRoles(item.localPath());
1073 }
1074 #endif
1075 return data;
1076 }
1077
1078 void KFileItemModelRolesUpdater::updateAllPreviews()
1079 {
1080 if (m_state == Paused) {
1081 m_previewChangedDuringPausing = true;
1082 } else {
1083 m_finishedItems.clear();
1084 startUpdating();
1085 }
1086 }
1087
1088 void KFileItemModelRolesUpdater::killPreviewJob()
1089 {
1090 if (m_previewJob) {
1091 disconnect(m_previewJob, &KIO::PreviewJob::gotPreview,
1092 this, &KFileItemModelRolesUpdater::slotGotPreview);
1093 disconnect(m_previewJob, &KIO::PreviewJob::failed,
1094 this, &KFileItemModelRolesUpdater::slotPreviewFailed);
1095 disconnect(m_previewJob, &KIO::PreviewJob::finished,
1096 this, &KFileItemModelRolesUpdater::slotPreviewJobFinished);
1097 m_previewJob->kill();
1098 m_previewJob = 0;
1099 m_pendingPreviewItems.clear();
1100 }
1101 }
1102
1103 QList<int> KFileItemModelRolesUpdater::indexesToResolve() const
1104 {
1105 const int count = m_model->count();
1106
1107 QList<int> result;
1108 result.reserve(ResolveAllItemsLimit);
1109
1110 // Add visible items.
1111 for (int i = m_firstVisibleIndex; i <= m_lastVisibleIndex; ++i) {
1112 result.append(i);
1113 }
1114
1115 // We need a reasonable upper limit for number of items to resolve after
1116 // and before the visible range. m_maximumVisibleItems can be quite large
1117 // when using Compace View.
1118 const int readAheadItems = qMin(ReadAheadPages * m_maximumVisibleItems, ResolveAllItemsLimit / 2);
1119
1120 // Add items after the visible range.
1121 const int endExtendedVisibleRange = qMin(m_lastVisibleIndex + readAheadItems, count - 1);
1122 for (int i = m_lastVisibleIndex + 1; i <= endExtendedVisibleRange; ++i) {
1123 result.append(i);
1124 }
1125
1126 // Add items before the visible range in reverse order.
1127 const int beginExtendedVisibleRange = qMax(0, m_firstVisibleIndex - readAheadItems);
1128 for (int i = m_firstVisibleIndex - 1; i >= beginExtendedVisibleRange; --i) {
1129 result.append(i);
1130 }
1131
1132 // Add items on the last page.
1133 const int beginLastPage = qMax(qMin(endExtendedVisibleRange + 1, count - 1), count - m_maximumVisibleItems);
1134 for (int i = beginLastPage; i < count; ++i) {
1135 result.append(i);
1136 }
1137
1138 // Add items on the first page.
1139 const int endFirstPage = qMin(qMax(beginExtendedVisibleRange - 1, 0), m_maximumVisibleItems);
1140 for (int i = 0; i <= endFirstPage; ++i) {
1141 result.append(i);
1142 }
1143
1144 // Continue adding items until ResolveAllItemsLimit is reached.
1145 int remainingItems = ResolveAllItemsLimit - result.count();
1146
1147 for (int i = endExtendedVisibleRange + 1; i < beginLastPage && remainingItems > 0; ++i) {
1148 result.append(i);
1149 --remainingItems;
1150 }
1151
1152 for (int i = beginExtendedVisibleRange - 1; i > endFirstPage && remainingItems > 0; --i) {
1153 result.append(i);
1154 --remainingItems;
1155 }
1156
1157 return result;
1158 }
1159