1 /***************************************************************************
2 * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
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. *
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. *
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 ***************************************************************************/
20 #include "kfileitemmodelrolesupdater.h"
22 #include "kfileitemmodel.h"
25 #include <KConfigGroup>
29 #include <KIconLoader>
30 #include <KJobWidgets>
31 #include <KIO/JobUiDelegate>
32 #include <KIO/PreviewJob>
34 #include "private/kpixmapmodifier.h"
35 #include "private/kdirectorycontentscounter.h"
37 #include <QApplication>
40 #include <QElapsedTimer>
46 #include "private/kbaloorolesprovider.h"
47 #include <KF5/Baloo/file.h>
48 #include <KF5/Baloo/filefetchjob.h>
49 #include <KF5/Baloo/filemonitor.h>
52 // #define KFILEITEMMODELROLESUPDATER_DEBUG
55 // Maximum time in ms that the KFileItemModelRolesUpdater
56 // may perform a blocking operation
57 const int MaxBlockTimeout
= 200;
59 // If the number of items is smaller than ResolveAllItemsLimit,
60 // the roles of all items will be resolved.
61 const int ResolveAllItemsLimit
= 500;
63 // Not only the visible area, but up to ReadAheadPages before and after
64 // this area will be resolved.
65 const int ReadAheadPages
= 5;
68 KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel
* model
, QObject
* parent
) :
71 m_previewChangedDuringPausing(false),
72 m_iconSizeChangedDuringPausing(false),
73 m_rolesChangedDuringPausing(false),
74 m_previewShown(false),
75 m_enlargeSmallPreviews(true),
76 m_clearPreviews(false),
80 m_firstVisibleIndex(0),
81 m_lastVisibleIndex(-1),
82 m_maximumVisibleItems(50),
86 m_pendingSortRoleItems(),
88 m_pendingPreviewItems(),
90 m_recentlyChangedItemsTimer(0),
91 m_recentlyChangedItems(),
93 m_directoryContentsCounter(0)
95 , m_balooFileMonitor(0)
100 const KConfigGroup
globalConfig(KGlobal::config(), "PreviewSettings");
101 m_enabledPlugins
= globalConfig
.readEntry("Plugins", QStringList()
102 << "directorythumbnail"
106 connect(m_model
, &KFileItemModel::itemsInserted
,
107 this, &KFileItemModelRolesUpdater::slotItemsInserted
);
108 connect(m_model
, &KFileItemModel::itemsRemoved
,
109 this, &KFileItemModelRolesUpdater::slotItemsRemoved
);
110 connect(m_model
, &KFileItemModel::itemsChanged
,
111 this, &KFileItemModelRolesUpdater::slotItemsChanged
);
112 connect(m_model
, &KFileItemModel::itemsMoved
,
113 this, &KFileItemModelRolesUpdater::slotItemsMoved
);
114 connect(m_model
, &KFileItemModel::sortRoleChanged
,
115 this, &KFileItemModelRolesUpdater::slotSortRoleChanged
);
117 // Use a timer to prevent that each call of slotItemsChanged() results in a synchronous
118 // resolving of the roles. Postpone the resolving until no update has been done for 1 second.
119 m_recentlyChangedItemsTimer
= new QTimer(this);
120 m_recentlyChangedItemsTimer
->setInterval(1000);
121 m_recentlyChangedItemsTimer
->setSingleShot(true);
122 connect(m_recentlyChangedItemsTimer
, &QTimer::timeout
, this, &KFileItemModelRolesUpdater::resolveRecentlyChangedItems
);
124 m_resolvableRoles
.insert("size");
125 m_resolvableRoles
.insert("type");
126 m_resolvableRoles
.insert("isExpandable");
128 m_resolvableRoles
+= KBalooRolesProvider::instance().roles();
131 m_directoryContentsCounter
= new KDirectoryContentsCounter(m_model
, this);
132 connect(m_directoryContentsCounter
, &KDirectoryContentsCounter::result
,
133 this, &KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived
);
136 KFileItemModelRolesUpdater::~KFileItemModelRolesUpdater()
141 void KFileItemModelRolesUpdater::setIconSize(const QSize
& size
)
143 if (size
!= m_iconSize
) {
145 if (m_state
== Paused
) {
146 m_iconSizeChangedDuringPausing
= true;
147 } else if (m_previewShown
) {
148 // An icon size change requires the regenerating of
150 m_finishedItems
.clear();
156 QSize
KFileItemModelRolesUpdater::iconSize() const
161 void KFileItemModelRolesUpdater::setVisibleIndexRange(int index
, int count
)
170 if (index
== m_firstVisibleIndex
&& count
== m_lastVisibleIndex
- m_firstVisibleIndex
+ 1) {
171 // The range has not been changed
175 m_firstVisibleIndex
= index
;
176 m_lastVisibleIndex
= qMin(index
+ count
- 1, m_model
->count() - 1);
181 void KFileItemModelRolesUpdater::setMaximumVisibleItems(int count
)
183 m_maximumVisibleItems
= count
;
186 void KFileItemModelRolesUpdater::setPreviewsShown(bool show
)
188 if (show
== m_previewShown
) {
192 m_previewShown
= show
;
194 m_clearPreviews
= true;
200 bool KFileItemModelRolesUpdater::previewsShown() const
202 return m_previewShown
;
205 void KFileItemModelRolesUpdater::setEnlargeSmallPreviews(bool enlarge
)
207 if (enlarge
!= m_enlargeSmallPreviews
) {
208 m_enlargeSmallPreviews
= enlarge
;
209 if (m_previewShown
) {
215 bool KFileItemModelRolesUpdater::enlargeSmallPreviews() const
217 return m_enlargeSmallPreviews
;
220 void KFileItemModelRolesUpdater::setEnabledPlugins(const QStringList
& list
)
222 if (m_enabledPlugins
!= list
) {
223 m_enabledPlugins
= list
;
224 if (m_previewShown
) {
230 void KFileItemModelRolesUpdater::setPaused(bool paused
)
232 if (paused
== (m_state
== Paused
)) {
240 const bool updatePreviews
= (m_iconSizeChangedDuringPausing
&& m_previewShown
) ||
241 m_previewChangedDuringPausing
;
242 const bool resolveAll
= updatePreviews
|| m_rolesChangedDuringPausing
;
244 m_finishedItems
.clear();
247 m_iconSizeChangedDuringPausing
= false;
248 m_previewChangedDuringPausing
= false;
249 m_rolesChangedDuringPausing
= false;
251 if (!m_pendingSortRoleItems
.isEmpty()) {
252 m_state
= ResolvingSortRole
;
253 resolveNextSortRole();
262 void KFileItemModelRolesUpdater::setRoles(const QSet
<QByteArray
>& roles
)
264 if (m_roles
!= roles
) {
268 // Check whether there is at least one role that must be resolved
269 // with the help of Baloo. If this is the case, a (quite expensive)
270 // resolving will be done in KFileItemModelRolesUpdater::rolesData() and
271 // the role gets watched for changes.
272 const KBalooRolesProvider
& rolesProvider
= KBalooRolesProvider::instance();
273 bool hasBalooRole
= false;
274 QSetIterator
<QByteArray
> it(roles
);
275 while (it
.hasNext()) {
276 const QByteArray
& role
= it
.next();
277 if (rolesProvider
.roles().contains(role
)) {
283 if (hasBalooRole
&& !m_balooFileMonitor
) {
284 m_balooFileMonitor
= new Baloo::FileMonitor(this);
285 connect(m_balooFileMonitor
, &Baloo::FileMonitor::fileMetaDataChanged
,
286 this, &KFileItemModelRolesUpdater::applyChangedBalooRoles
);
287 } else if (!hasBalooRole
&& m_balooFileMonitor
) {
288 delete m_balooFileMonitor
;
289 m_balooFileMonitor
= 0;
293 if (m_state
== Paused
) {
294 m_rolesChangedDuringPausing
= true;
301 QSet
<QByteArray
> KFileItemModelRolesUpdater::roles() const
306 bool KFileItemModelRolesUpdater::isPaused() const
308 return m_state
== Paused
;
311 QStringList
KFileItemModelRolesUpdater::enabledPlugins() const
313 return m_enabledPlugins
;
316 void KFileItemModelRolesUpdater::slotItemsInserted(const KItemRangeList
& itemRanges
)
321 // Determine the sort role synchronously for as many items as possible.
322 if (m_resolvableRoles
.contains(m_model
->sortRole())) {
323 int insertedCount
= 0;
324 foreach (const KItemRange
& range
, itemRanges
) {
325 const int lastIndex
= insertedCount
+ range
.index
+ range
.count
- 1;
326 for (int i
= insertedCount
+ range
.index
; i
<= lastIndex
; ++i
) {
327 if (timer
.elapsed() < MaxBlockTimeout
) {
330 m_pendingSortRoleItems
.insert(m_model
->fileItem(i
));
333 insertedCount
+= range
.count
;
336 applySortProgressToModel();
338 // If there are still items whose sort role is unknown, check if the
339 // asynchronous determination of the sort role is already in progress,
340 // and start it if that is not the case.
341 if (!m_pendingSortRoleItems
.isEmpty() && m_state
!= ResolvingSortRole
) {
343 m_state
= ResolvingSortRole
;
344 resolveNextSortRole();
351 void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList
& itemRanges
)
353 Q_UNUSED(itemRanges
);
355 const bool allItemsRemoved
= (m_model
->count() == 0);
358 if (m_balooFileMonitor
) {
359 // Don't let the FileWatcher watch for removed items
360 if (allItemsRemoved
) {
361 m_balooFileMonitor
->clear();
363 QStringList newFileList
;
364 foreach (const QString
& itemUrl
, m_balooFileMonitor
->files()) {
365 if (m_model
->index(itemUrl
) >= 0) {
366 newFileList
.append(itemUrl
);
369 m_balooFileMonitor
->setFiles(newFileList
);
374 if (allItemsRemoved
) {
377 m_finishedItems
.clear();
378 m_pendingSortRoleItems
.clear();
379 m_pendingIndexes
.clear();
380 m_pendingPreviewItems
.clear();
381 m_recentlyChangedItems
.clear();
382 m_recentlyChangedItemsTimer
->stop();
383 m_changedItems
.clear();
387 // Only remove the items from m_finishedItems. They will be removed
388 // from the other sets later on.
389 QSet
<KFileItem
>::iterator it
= m_finishedItems
.begin();
390 while (it
!= m_finishedItems
.end()) {
391 if (m_model
->index(*it
) < 0) {
392 it
= m_finishedItems
.erase(it
);
398 // The visible items might have changed.
403 void KFileItemModelRolesUpdater::slotItemsMoved(const KItemRange
& itemRange
, QList
<int> movedToIndexes
)
406 Q_UNUSED(movedToIndexes
);
408 // The visible items might have changed.
412 void KFileItemModelRolesUpdater::slotItemsChanged(const KItemRangeList
& itemRanges
,
413 const QSet
<QByteArray
>& roles
)
417 // Find out if slotItemsChanged() has been done recently. If that is the
418 // case, resolving the roles is postponed until a timer has exceeded
419 // to prevent expensive repeated updates if files are updated frequently.
420 const bool itemsChangedRecently
= m_recentlyChangedItemsTimer
->isActive();
422 QSet
<KFileItem
>& targetSet
= itemsChangedRecently
? m_recentlyChangedItems
: m_changedItems
;
424 foreach (const KItemRange
& itemRange
, itemRanges
) {
425 int index
= itemRange
.index
;
426 for (int count
= itemRange
.count
; count
> 0; --count
) {
427 const KFileItem item
= m_model
->fileItem(index
);
428 targetSet
.insert(item
);
433 m_recentlyChangedItemsTimer
->start();
435 if (!itemsChangedRecently
) {
436 updateChangedItems();
440 void KFileItemModelRolesUpdater::slotSortRoleChanged(const QByteArray
& current
,
441 const QByteArray
& previous
)
446 if (m_resolvableRoles
.contains(current
)) {
447 m_pendingSortRoleItems
.clear();
448 m_finishedItems
.clear();
450 const int count
= m_model
->count();
454 // Determine the sort role synchronously for as many items as possible.
455 for (int index
= 0; index
< count
; ++index
) {
456 if (timer
.elapsed() < MaxBlockTimeout
) {
457 applySortRole(index
);
459 m_pendingSortRoleItems
.insert(m_model
->fileItem(index
));
463 applySortProgressToModel();
465 if (!m_pendingSortRoleItems
.isEmpty()) {
466 // Trigger the asynchronous determination of the sort role.
468 m_state
= ResolvingSortRole
;
469 resolveNextSortRole();
473 m_pendingSortRoleItems
.clear();
474 applySortProgressToModel();
478 void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem
& item
, const QPixmap
& pixmap
)
480 if (m_state
!= PreviewJobRunning
) {
484 m_changedItems
.remove(item
);
486 const int index
= m_model
->index(item
);
491 QPixmap scaledPixmap
= pixmap
;
493 const QString mimeType
= item
.mimetype();
494 const int slashIndex
= mimeType
.indexOf(QLatin1Char('/'));
495 const QString mimeTypeGroup
= mimeType
.left(slashIndex
);
496 if (mimeTypeGroup
== QLatin1String("image")) {
497 if (m_enlargeSmallPreviews
) {
498 KPixmapModifier::applyFrame(scaledPixmap
, m_iconSize
);
500 // Assure that small previews don't get enlarged. Instead they
501 // should be shown centered within the frame.
502 const QSize contentSize
= KPixmapModifier::sizeInsideFrame(m_iconSize
);
503 const bool enlargingRequired
= scaledPixmap
.width() < contentSize
.width() &&
504 scaledPixmap
.height() < contentSize
.height();
505 if (enlargingRequired
) {
506 QSize frameSize
= scaledPixmap
.size();
507 frameSize
.scale(m_iconSize
, Qt::KeepAspectRatio
);
509 QPixmap
largeFrame(frameSize
);
510 largeFrame
.fill(Qt::transparent
);
512 KPixmapModifier::applyFrame(largeFrame
, frameSize
);
514 QPainter
painter(&largeFrame
);
515 painter
.drawPixmap((largeFrame
.width() - scaledPixmap
.width()) / 2,
516 (largeFrame
.height() - scaledPixmap
.height()) / 2,
518 scaledPixmap
= largeFrame
;
520 // The image must be shrinked as it is too large to fit into
521 // the available icon size
522 KPixmapModifier::applyFrame(scaledPixmap
, m_iconSize
);
526 KPixmapModifier::scale(scaledPixmap
, m_iconSize
);
529 QHash
<QByteArray
, QVariant
> data
= rolesData(item
);
531 const QStringList overlays
= data
["iconOverlays"].toStringList();
532 // Strangely KFileItem::overlays() returns empty string-values, so
533 // we need to check first whether an overlay must be drawn at all.
534 // It is more efficient to do it here, as KIconLoader::drawOverlays()
535 // assumes that an overlay will be drawn and has some additional
537 foreach (const QString
& overlay
, overlays
) {
538 if (!overlay
.isEmpty()) {
539 // There is at least one overlay, draw all overlays above m_pixmap
540 // and cancel the check
541 KIconLoader::global()->drawOverlays(overlays
, scaledPixmap
, KIconLoader::Desktop
);
546 data
.insert("iconPixmap", scaledPixmap
);
548 disconnect(m_model
, &KFileItemModel::itemsChanged
,
549 this, &KFileItemModelRolesUpdater::slotItemsChanged
);
550 m_model
->setData(index
, data
);
551 connect(m_model
, &KFileItemModel::itemsChanged
,
552 this, &KFileItemModelRolesUpdater::slotItemsChanged
);
554 m_finishedItems
.insert(item
);
557 void KFileItemModelRolesUpdater::slotPreviewFailed(const KFileItem
& item
)
559 if (m_state
!= PreviewJobRunning
) {
563 m_changedItems
.remove(item
);
565 const int index
= m_model
->index(item
);
567 QHash
<QByteArray
, QVariant
> data
;
568 data
.insert("iconPixmap", QPixmap());
570 disconnect(m_model
, &KFileItemModel::itemsChanged
,
571 this, &KFileItemModelRolesUpdater::slotItemsChanged
);
572 m_model
->setData(index
, data
);
573 connect(m_model
, &KFileItemModel::itemsChanged
,
574 this, &KFileItemModelRolesUpdater::slotItemsChanged
);
576 applyResolvedRoles(index
, ResolveAll
);
577 m_finishedItems
.insert(item
);
581 void KFileItemModelRolesUpdater::slotPreviewJobFinished()
585 if (m_state
!= PreviewJobRunning
) {
591 if (!m_pendingPreviewItems
.isEmpty()) {
594 if (!m_changedItems
.isEmpty()) {
595 updateChangedItems();
600 void KFileItemModelRolesUpdater::resolveNextSortRole()
602 if (m_state
!= ResolvingSortRole
) {
606 QSet
<KFileItem
>::iterator it
= m_pendingSortRoleItems
.begin();
607 while (it
!= m_pendingSortRoleItems
.end()) {
608 const KFileItem item
= *it
;
609 const int index
= m_model
->index(item
);
611 // Continue if the sort role has already been determined for the
612 // item, and the item has not been changed recently.
613 if (!m_changedItems
.contains(item
) && m_model
->data(index
).contains(m_model
->sortRole())) {
614 it
= m_pendingSortRoleItems
.erase(it
);
618 applySortRole(index
);
619 m_pendingSortRoleItems
.erase(it
);
623 if (!m_pendingSortRoleItems
.isEmpty()) {
624 applySortProgressToModel();
625 QTimer::singleShot(0, this, SLOT(resolveNextSortRole()));
629 // Prevent that we try to update the items twice.
630 disconnect(m_model
, &KFileItemModel::itemsMoved
,
631 this, &KFileItemModelRolesUpdater::slotItemsMoved
);
632 applySortProgressToModel();
633 connect(m_model
, &KFileItemModel::itemsMoved
,
634 this, &KFileItemModelRolesUpdater::slotItemsMoved
);
639 void KFileItemModelRolesUpdater::resolveNextPendingRoles()
641 if (m_state
!= ResolvingAllRoles
) {
645 while (!m_pendingIndexes
.isEmpty()) {
646 const int index
= m_pendingIndexes
.takeFirst();
647 const KFileItem item
= m_model
->fileItem(index
);
649 if (m_finishedItems
.contains(item
)) {
653 applyResolvedRoles(index
, ResolveAll
);
654 m_finishedItems
.insert(item
);
655 m_changedItems
.remove(item
);
659 if (!m_pendingIndexes
.isEmpty()) {
660 QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
664 if (m_clearPreviews
) {
665 // Only go through the list if there are items which might still have previews.
666 if (m_finishedItems
.count() != m_model
->count()) {
667 QHash
<QByteArray
, QVariant
> data
;
668 data
.insert("iconPixmap", QPixmap());
670 disconnect(m_model
, &KFileItemModel::itemsChanged
,
671 this, &KFileItemModelRolesUpdater::slotItemsChanged
);
672 for (int index
= 0; index
<= m_model
->count(); ++index
) {
673 if (m_model
->data(index
).contains("iconPixmap")) {
674 m_model
->setData(index
, data
);
677 connect(m_model
, &KFileItemModel::itemsChanged
,
678 this, &KFileItemModelRolesUpdater::slotItemsChanged
);
681 m_clearPreviews
= false;
684 if (!m_changedItems
.isEmpty()) {
685 updateChangedItems();
690 void KFileItemModelRolesUpdater::resolveRecentlyChangedItems()
692 m_changedItems
+= m_recentlyChangedItems
;
693 m_recentlyChangedItems
.clear();
694 updateChangedItems();
697 void KFileItemModelRolesUpdater::applyChangedBalooRoles(const QString
& itemUrl
)
700 const KFileItem item
= m_model
->fileItem(itemUrl
);
703 // itemUrl is not in the model anymore, probably because
704 // the corresponding file has been deleted in the meantime.
708 Baloo::FileFetchJob
* job
= new Baloo::FileFetchJob(item
.localPath());
709 connect(job
, &Baloo::FileFetchJob::finished
, this, &KFileItemModelRolesUpdater::applyChangedBalooRolesJobFinished
);
710 job
->setProperty("item", QVariant::fromValue(item
));
719 void KFileItemModelRolesUpdater::applyChangedBalooRolesJobFinished(KJob
* kjob
)
722 const KFileItem item
= kjob
->property("item").value
<KFileItem
>();
724 const KBalooRolesProvider
& rolesProvider
= KBalooRolesProvider::instance();
725 QHash
<QByteArray
, QVariant
> data
;
727 foreach (const QByteArray
& role
, rolesProvider
.roles()) {
728 // Overwrite all the role values with an empty QVariant, because the roles
729 // provider doesn't overwrite it when the property value list is empty.
731 data
.insert(role
, QVariant());
734 Baloo::FileFetchJob
* job
= static_cast<Baloo::FileFetchJob
*>(kjob
);
735 QHashIterator
<QByteArray
, QVariant
> it(rolesProvider
.roleValues(job
->file(), m_roles
));
736 while (it
.hasNext()) {
738 data
.insert(it
.key(), it
.value());
741 disconnect(m_model
, &KFileItemModel::itemsChanged
,
742 this, &KFileItemModelRolesUpdater::slotItemsChanged
);
743 const int index
= m_model
->index(item
);
744 m_model
->setData(index
, data
);
745 connect(m_model
, &KFileItemModel::itemsChanged
,
746 this, &KFileItemModelRolesUpdater::slotItemsChanged
);
750 void KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived(const QString
& path
, int count
)
752 const bool getSizeRole
= m_roles
.contains("size");
753 const bool getIsExpandableRole
= m_roles
.contains("isExpandable");
755 if (getSizeRole
|| getIsExpandableRole
) {
756 const int index
= m_model
->index(KUrl(path
));
758 QHash
<QByteArray
, QVariant
> data
;
761 data
.insert("size", count
);
763 if (getIsExpandableRole
) {
764 data
.insert("isExpandable", count
> 0);
767 disconnect(m_model
, &KFileItemModel::itemsChanged
,
768 this, &KFileItemModelRolesUpdater::slotItemsChanged
);
769 m_model
->setData(index
, data
);
770 connect(m_model
, &KFileItemModel::itemsChanged
,
771 this, &KFileItemModelRolesUpdater::slotItemsChanged
);
776 void KFileItemModelRolesUpdater::startUpdating()
778 if (m_state
== Paused
) {
782 if (m_finishedItems
.count() == m_model
->count()) {
783 // All roles have been resolved already.
788 // Terminate all updates that are currently active.
790 m_pendingIndexes
.clear();
795 // Determine the icons for the visible items synchronously.
796 updateVisibleIcons();
798 // A detailed update of the items in and near the visible area
799 // only makes sense if sorting is finished.
800 if (m_state
== ResolvingSortRole
) {
804 // Start the preview job or the asynchronous resolving of all roles.
805 QList
<int> indexes
= indexesToResolve();
807 if (m_previewShown
) {
808 m_pendingPreviewItems
.clear();
809 m_pendingPreviewItems
.reserve(indexes
.count());
811 foreach (int index
, indexes
) {
812 const KFileItem item
= m_model
->fileItem(index
);
813 if (!m_finishedItems
.contains(item
)) {
814 m_pendingPreviewItems
.append(item
);
820 m_pendingIndexes
= indexes
;
821 // Trigger the asynchronous resolving of all roles.
822 m_state
= ResolvingAllRoles
;
823 QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
827 void KFileItemModelRolesUpdater::updateVisibleIcons()
829 int lastVisibleIndex
= m_lastVisibleIndex
;
830 if (lastVisibleIndex
<= 0) {
831 // Guess a reasonable value for the last visible index if the view
832 // has not told us about the real value yet.
833 lastVisibleIndex
= qMin(m_firstVisibleIndex
+ m_maximumVisibleItems
, m_model
->count() - 1);
834 if (lastVisibleIndex
<= 0) {
835 lastVisibleIndex
= qMin(200, m_model
->count() - 1);
842 // Try to determine the final icons for all visible items.
844 for (index
= m_firstVisibleIndex
; index
<= lastVisibleIndex
&& timer
.elapsed() < MaxBlockTimeout
; ++index
) {
845 applyResolvedRoles(index
, ResolveFast
);
848 // KFileItemListView::initializeItemListWidget(KItemListWidget*) will load
849 // preliminary icons (i.e., without mime type determination) for the
853 void KFileItemModelRolesUpdater::startPreviewJob()
855 m_state
= PreviewJobRunning
;
857 if (m_pendingPreviewItems
.isEmpty()) {
858 QTimer::singleShot(0, this, SLOT(slotPreviewJobFinished()));
862 // PreviewJob internally caches items always with the size of
863 // 128 x 128 pixels or 256 x 256 pixels. A (slow) downscaling is done
864 // by PreviewJob if a smaller size is requested. For images KFileItemModelRolesUpdater must
865 // do a downscaling anyhow because of the frame, so in this case only the provided
866 // cache sizes are requested.
867 const QSize cacheSize
= (m_iconSize
.width() > 128) || (m_iconSize
.height() > 128)
868 ? QSize(256, 256) : QSize(128, 128);
870 // KIO::filePreview() will request the MIME-type of all passed items, which (in the
871 // worst case) might block the application for several seconds. To prevent such
872 // a blocking, we only pass items with known mime type to the preview job.
873 const int count
= m_pendingPreviewItems
.count();
874 KFileItemList itemSubSet
;
875 itemSubSet
.reserve(count
);
877 if (m_pendingPreviewItems
.first().isMimeTypeKnown()) {
878 // Some mime types are known already, probably because they were
879 // determined when loading the icons for the visible items. Start
880 // a preview job for all items at the beginning of the list which
881 // have a known mime type.
883 itemSubSet
.append(m_pendingPreviewItems
.takeFirst());
884 } while (!m_pendingPreviewItems
.isEmpty() && m_pendingPreviewItems
.first().isMimeTypeKnown());
886 // Determine mime types for MaxBlockTimeout ms, and start a preview
887 // job for the corresponding items.
892 const KFileItem item
= m_pendingPreviewItems
.takeFirst();
893 item
.determineMimeType();
894 itemSubSet
.append(item
);
895 } while (!m_pendingPreviewItems
.isEmpty() && timer
.elapsed() < MaxBlockTimeout
);
898 KIO::PreviewJob
* job
= new KIO::PreviewJob(itemSubSet
, cacheSize
, &m_enabledPlugins
);
900 job
->setIgnoreMaximumSize(itemSubSet
.first().isLocalFile());
902 KJobWidgets::setWindow(job
, qApp
->activeWindow());
905 connect(job
, &KIO::PreviewJob::gotPreview
,
906 this, &KFileItemModelRolesUpdater::slotGotPreview
);
907 connect(job
, &KIO::PreviewJob::failed
,
908 this, &KFileItemModelRolesUpdater::slotPreviewFailed
);
909 connect(job
, &KIO::PreviewJob::finished
,
910 this, &KFileItemModelRolesUpdater::slotPreviewJobFinished
);
915 void KFileItemModelRolesUpdater::updateChangedItems()
917 if (m_state
== Paused
) {
921 if (m_changedItems
.isEmpty()) {
925 m_finishedItems
-= m_changedItems
;
927 if (m_resolvableRoles
.contains(m_model
->sortRole())) {
928 m_pendingSortRoleItems
+= m_changedItems
;
930 if (m_state
!= ResolvingSortRole
) {
931 // Stop the preview job if necessary, and trigger the
932 // asynchronous determination of the sort role.
934 m_state
= ResolvingSortRole
;
935 QTimer::singleShot(0, this, SLOT(resolveNextSortRole()));
941 QList
<int> visibleChangedIndexes
;
942 QList
<int> invisibleChangedIndexes
;
944 foreach (const KFileItem
& item
, m_changedItems
) {
945 const int index
= m_model
->index(item
);
948 m_changedItems
.remove(item
);
952 if (index
>= m_firstVisibleIndex
&& index
<= m_lastVisibleIndex
) {
953 visibleChangedIndexes
.append(index
);
955 invisibleChangedIndexes
.append(index
);
959 std::sort(visibleChangedIndexes
.begin(), visibleChangedIndexes
.end());
961 if (m_previewShown
) {
962 foreach (int index
, visibleChangedIndexes
) {
963 m_pendingPreviewItems
.append(m_model
->fileItem(index
));
966 foreach (int index
, invisibleChangedIndexes
) {
967 m_pendingPreviewItems
.append(m_model
->fileItem(index
));
974 const bool resolvingInProgress
= !m_pendingIndexes
.isEmpty();
975 m_pendingIndexes
= visibleChangedIndexes
+ m_pendingIndexes
+ invisibleChangedIndexes
;
976 if (!resolvingInProgress
) {
977 // Trigger the asynchronous resolving of the changed roles.
978 m_state
= ResolvingAllRoles
;
979 QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
984 void KFileItemModelRolesUpdater::applySortRole(int index
)
986 QHash
<QByteArray
, QVariant
> data
;
987 const KFileItem item
= m_model
->fileItem(index
);
989 if (m_model
->sortRole() == "type") {
990 if (!item
.isMimeTypeKnown()) {
991 item
.determineMimeType();
994 data
.insert("type", item
.mimeComment());
995 } else if (m_model
->sortRole() == "size" && item
.isLocalFile() && item
.isDir()) {
996 const QString path
= item
.localPath();
997 data
.insert("size", m_directoryContentsCounter
->countDirectoryContentsSynchronously(path
));
999 // Probably the sort role is a baloo role - just determine all roles.
1000 data
= rolesData(item
);
1003 disconnect(m_model
, &KFileItemModel::itemsChanged
,
1004 this, &KFileItemModelRolesUpdater::slotItemsChanged
);
1005 m_model
->setData(index
, data
);
1006 connect(m_model
, &KFileItemModel::itemsChanged
,
1007 this, &KFileItemModelRolesUpdater::slotItemsChanged
);
1010 void KFileItemModelRolesUpdater::applySortProgressToModel()
1012 // Inform the model about the progress of the resolved items,
1013 // so that it can give an indication when the sorting has been finished.
1014 const int resolvedCount
= m_model
->count() - m_pendingSortRoleItems
.count();
1015 m_model
->emitSortProgress(resolvedCount
);
1018 bool KFileItemModelRolesUpdater::applyResolvedRoles(int index
, ResolveHint hint
)
1020 const KFileItem item
= m_model
->fileItem(index
);
1021 const bool resolveAll
= (hint
== ResolveAll
);
1023 bool iconChanged
= false;
1024 if (!item
.isMimeTypeKnown() || !item
.isFinalIconKnown()) {
1025 item
.determineMimeType();
1027 } else if (!m_model
->data(index
).contains("iconName")) {
1031 if (iconChanged
|| resolveAll
|| m_clearPreviews
) {
1036 QHash
<QByteArray
, QVariant
> data
;
1038 data
= rolesData(item
);
1041 data
.insert("iconName", item
.iconName());
1043 if (m_clearPreviews
) {
1044 data
.insert("iconPixmap", QPixmap());
1047 disconnect(m_model
, &KFileItemModel::itemsChanged
,
1048 this, &KFileItemModelRolesUpdater::slotItemsChanged
);
1049 m_model
->setData(index
, data
);
1050 connect(m_model
, &KFileItemModel::itemsChanged
,
1051 this, &KFileItemModelRolesUpdater::slotItemsChanged
);
1058 QHash
<QByteArray
, QVariant
> KFileItemModelRolesUpdater::rolesData(const KFileItem
& item
)
1060 QHash
<QByteArray
, QVariant
> data
;
1062 const bool getSizeRole
= m_roles
.contains("size");
1063 const bool getIsExpandableRole
= m_roles
.contains("isExpandable");
1065 if ((getSizeRole
|| getIsExpandableRole
) && item
.isDir()) {
1066 if (item
.isLocalFile()) {
1067 // Tell m_directoryContentsCounter that we want to count the items
1068 // inside the directory. The result will be received in slotDirectoryContentsCountReceived.
1069 const QString path
= item
.localPath();
1070 m_directoryContentsCounter
->addDirectory(path
);
1071 } else if (getSizeRole
) {
1072 data
.insert("size", -1); // -1 indicates an unknown number of items
1076 if (m_roles
.contains("type")) {
1077 data
.insert("type", item
.mimeComment());
1080 data
.insert("iconOverlays", item
.overlays());
1083 if (m_balooFileMonitor
) {
1084 m_balooFileMonitor
->addFile(item
.localPath());
1085 applyChangedBalooRoles(item
.localPath());
1091 void KFileItemModelRolesUpdater::updateAllPreviews()
1093 if (m_state
== Paused
) {
1094 m_previewChangedDuringPausing
= true;
1096 m_finishedItems
.clear();
1101 void KFileItemModelRolesUpdater::killPreviewJob()
1104 disconnect(m_previewJob
, &KIO::PreviewJob::gotPreview
,
1105 this, &KFileItemModelRolesUpdater::slotGotPreview
);
1106 disconnect(m_previewJob
, &KIO::PreviewJob::failed
,
1107 this, &KFileItemModelRolesUpdater::slotPreviewFailed
);
1108 disconnect(m_previewJob
, &KIO::PreviewJob::finished
,
1109 this, &KFileItemModelRolesUpdater::slotPreviewJobFinished
);
1110 m_previewJob
->kill();
1112 m_pendingPreviewItems
.clear();
1116 QList
<int> KFileItemModelRolesUpdater::indexesToResolve() const
1118 const int count
= m_model
->count();
1121 result
.reserve(ResolveAllItemsLimit
);
1123 // Add visible items.
1124 for (int i
= m_firstVisibleIndex
; i
<= m_lastVisibleIndex
; ++i
) {
1128 // We need a reasonable upper limit for number of items to resolve after
1129 // and before the visible range. m_maximumVisibleItems can be quite large
1130 // when using Compace View.
1131 const int readAheadItems
= qMin(ReadAheadPages
* m_maximumVisibleItems
, ResolveAllItemsLimit
/ 2);
1133 // Add items after the visible range.
1134 const int endExtendedVisibleRange
= qMin(m_lastVisibleIndex
+ readAheadItems
, count
- 1);
1135 for (int i
= m_lastVisibleIndex
+ 1; i
<= endExtendedVisibleRange
; ++i
) {
1139 // Add items before the visible range in reverse order.
1140 const int beginExtendedVisibleRange
= qMax(0, m_firstVisibleIndex
- readAheadItems
);
1141 for (int i
= m_firstVisibleIndex
- 1; i
>= beginExtendedVisibleRange
; --i
) {
1145 // Add items on the last page.
1146 const int beginLastPage
= qMax(qMin(endExtendedVisibleRange
+ 1, count
- 1), count
- m_maximumVisibleItems
);
1147 for (int i
= beginLastPage
; i
< count
; ++i
) {
1151 // Add items on the first page.
1152 const int endFirstPage
= qMin(qMax(beginExtendedVisibleRange
- 1, 0), m_maximumVisibleItems
);
1153 for (int i
= 0; i
<= endFirstPage
; ++i
) {
1157 // Continue adding items until ResolveAllItemsLimit is reached.
1158 int remainingItems
= ResolveAllItemsLimit
- result
.count();
1160 for (int i
= endExtendedVisibleRange
+ 1; i
< beginLastPage
&& remainingItems
> 0; ++i
) {
1165 for (int i
= beginExtendedVisibleRange
- 1; i
> endFirstPage
&& remainingItems
> 0; --i
) {
1173 #include "kfileitemmodelrolesupdater.moc"