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 <KIO/JobUiDelegate>
30 #include <KIO/PreviewJob>
32 #include "private/kpixmapmodifier.h"
33 #include "private/kdirectorycontentscounter.h"
35 #include <QApplication>
38 #include <QElapsedTimer>
44 #include "private/knepomukrolesprovider.h"
45 #include <Nepomuk2/ResourceWatcher>
46 #include <Nepomuk2/ResourceManager>
49 // #define KFILEITEMMODELROLESUPDATER_DEBUG
52 // Maximum time in ms that the KFileItemModelRolesUpdater
53 // may perform a blocking operation
54 const int MaxBlockTimeout
= 200;
56 // If the number of items is smaller than ResolveAllItemsLimit,
57 // the roles of all items will be resolved.
58 const int ResolveAllItemsLimit
= 500;
60 // Not only the visible area, but up to ReadAheadPages before and after
61 // this area will be resolved.
62 const int ReadAheadPages
= 5;
65 KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel
* model
, QObject
* parent
) :
68 m_previewChangedDuringPausing(false),
69 m_iconSizeChangedDuringPausing(false),
70 m_rolesChangedDuringPausing(false),
71 m_previewShown(false),
72 m_enlargeSmallPreviews(true),
73 m_clearPreviews(false),
77 m_firstVisibleIndex(0),
78 m_lastVisibleIndex(-1),
79 m_maximumVisibleItems(50),
83 m_pendingSortRoleItems(),
85 m_pendingPreviewItems(),
87 m_recentlyChangedItemsTimer(0),
88 m_recentlyChangedItems(),
90 m_directoryContentsCounter(0)
92 , m_nepomukResourceWatcher(0),
98 const KConfigGroup
globalConfig(KGlobal::config(), "PreviewSettings");
99 m_enabledPlugins
= globalConfig
.readEntry("Plugins", QStringList()
100 << "directorythumbnail"
104 connect(m_model
, SIGNAL(itemsInserted(KItemRangeList
)),
105 this, SLOT(slotItemsInserted(KItemRangeList
)));
106 connect(m_model
, SIGNAL(itemsRemoved(KItemRangeList
)),
107 this, SLOT(slotItemsRemoved(KItemRangeList
)));
108 connect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
109 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
110 connect(m_model
, SIGNAL(itemsMoved(KItemRange
,QList
<int>)),
111 this, SLOT(slotItemsMoved(KItemRange
,QList
<int>)));
112 connect(m_model
, SIGNAL(sortRoleChanged(QByteArray
,QByteArray
)),
113 this, SLOT(slotSortRoleChanged(QByteArray
,QByteArray
)));
115 // Use a timer to prevent that each call of slotItemsChanged() results in a synchronous
116 // resolving of the roles. Postpone the resolving until no update has been done for 1 second.
117 m_recentlyChangedItemsTimer
= new QTimer(this);
118 m_recentlyChangedItemsTimer
->setInterval(1000);
119 m_recentlyChangedItemsTimer
->setSingleShot(true);
120 connect(m_recentlyChangedItemsTimer
, SIGNAL(timeout()), this, SLOT(resolveRecentlyChangedItems()));
122 m_resolvableRoles
.insert("size");
123 m_resolvableRoles
.insert("type");
124 m_resolvableRoles
.insert("isExpandable");
126 m_resolvableRoles
+= KNepomukRolesProvider::instance().roles();
129 m_directoryContentsCounter
= new KDirectoryContentsCounter(m_model
, this);
130 connect(m_directoryContentsCounter
, SIGNAL(result(QString
,int)),
131 this, SLOT(slotDirectoryContentsCountReceived(QString
,int)));
134 KFileItemModelRolesUpdater::~KFileItemModelRolesUpdater()
139 void KFileItemModelRolesUpdater::setIconSize(const QSize
& size
)
141 if (size
!= m_iconSize
) {
143 if (m_state
== Paused
) {
144 m_iconSizeChangedDuringPausing
= true;
145 } else if (m_previewShown
) {
146 // An icon size change requires the regenerating of
148 m_finishedItems
.clear();
154 QSize
KFileItemModelRolesUpdater::iconSize() const
159 void KFileItemModelRolesUpdater::setVisibleIndexRange(int index
, int count
)
168 if (index
== m_firstVisibleIndex
&& count
== m_lastVisibleIndex
- m_firstVisibleIndex
+ 1) {
169 // The range has not been changed
173 m_firstVisibleIndex
= index
;
174 m_lastVisibleIndex
= qMin(index
+ count
- 1, m_model
->count() - 1);
179 void KFileItemModelRolesUpdater::setMaximumVisibleItems(int count
)
181 m_maximumVisibleItems
= count
;
184 void KFileItemModelRolesUpdater::setPreviewsShown(bool show
)
186 if (show
== m_previewShown
) {
190 m_previewShown
= show
;
192 m_clearPreviews
= true;
198 bool KFileItemModelRolesUpdater::previewsShown() const
200 return m_previewShown
;
203 void KFileItemModelRolesUpdater::setEnlargeSmallPreviews(bool enlarge
)
205 if (enlarge
!= m_enlargeSmallPreviews
) {
206 m_enlargeSmallPreviews
= enlarge
;
207 if (m_previewShown
) {
213 bool KFileItemModelRolesUpdater::enlargeSmallPreviews() const
215 return m_enlargeSmallPreviews
;
218 void KFileItemModelRolesUpdater::setEnabledPlugins(const QStringList
& list
)
220 if (m_enabledPlugins
!= list
) {
221 m_enabledPlugins
= list
;
222 if (m_previewShown
) {
228 void KFileItemModelRolesUpdater::setPaused(bool paused
)
230 if (paused
== (m_state
== Paused
)) {
238 const bool updatePreviews
= (m_iconSizeChangedDuringPausing
&& m_previewShown
) ||
239 m_previewChangedDuringPausing
;
240 const bool resolveAll
= updatePreviews
|| m_rolesChangedDuringPausing
;
242 m_finishedItems
.clear();
245 m_iconSizeChangedDuringPausing
= false;
246 m_previewChangedDuringPausing
= false;
247 m_rolesChangedDuringPausing
= false;
249 if (!m_pendingSortRoleItems
.isEmpty()) {
250 m_state
= ResolvingSortRole
;
251 resolveNextSortRole();
260 void KFileItemModelRolesUpdater::setRoles(const QSet
<QByteArray
>& roles
)
262 if (m_roles
!= roles
) {
266 if (Nepomuk2::ResourceManager::instance()->initialized()) {
267 // Check whether there is at least one role that must be resolved
268 // with the help of Nepomuk. If this is the case, a (quite expensive)
269 // resolving will be done in KFileItemModelRolesUpdater::rolesData() and
270 // the role gets watched for changes.
271 const KNepomukRolesProvider
& rolesProvider
= KNepomukRolesProvider::instance();
272 bool hasNepomukRole
= false;
273 QSetIterator
<QByteArray
> it(roles
);
274 while (it
.hasNext()) {
275 const QByteArray
& role
= it
.next();
276 if (rolesProvider
.roles().contains(role
)) {
277 hasNepomukRole
= true;
282 if (hasNepomukRole
&& !m_nepomukResourceWatcher
) {
283 Q_ASSERT(m_nepomukUriItems
.isEmpty());
285 m_nepomukResourceWatcher
= new Nepomuk2::ResourceWatcher(this);
286 connect(m_nepomukResourceWatcher
, SIGNAL(propertyChanged(Nepomuk2::Resource
,Nepomuk2::Types::Property
,QVariantList
,QVariantList
)),
287 this, SLOT(applyChangedNepomukRoles(Nepomuk2::Resource
,Nepomuk2::Types::Property
)));
288 } else if (!hasNepomukRole
&& m_nepomukResourceWatcher
) {
289 delete m_nepomukResourceWatcher
;
290 m_nepomukResourceWatcher
= 0;
291 m_nepomukUriItems
.clear();
296 if (m_state
== Paused
) {
297 m_rolesChangedDuringPausing
= true;
304 QSet
<QByteArray
> KFileItemModelRolesUpdater::roles() const
309 bool KFileItemModelRolesUpdater::isPaused() const
311 return m_state
== Paused
;
314 QStringList
KFileItemModelRolesUpdater::enabledPlugins() const
316 return m_enabledPlugins
;
319 void KFileItemModelRolesUpdater::slotItemsInserted(const KItemRangeList
& itemRanges
)
324 // Determine the sort role synchronously for as many items as possible.
325 if (m_resolvableRoles
.contains(m_model
->sortRole())) {
326 int insertedCount
= 0;
327 foreach (const KItemRange
& range
, itemRanges
) {
328 const int lastIndex
= insertedCount
+ range
.index
+ range
.count
- 1;
329 for (int i
= insertedCount
+ range
.index
; i
<= lastIndex
; ++i
) {
330 if (timer
.elapsed() < MaxBlockTimeout
) {
333 m_pendingSortRoleItems
.insert(m_model
->fileItem(i
));
336 insertedCount
+= range
.count
;
339 applySortProgressToModel();
341 // If there are still items whose sort role is unknown, check if the
342 // asynchronous determination of the sort role is already in progress,
343 // and start it if that is not the case.
344 if (!m_pendingSortRoleItems
.isEmpty() && m_state
!= ResolvingSortRole
) {
346 m_state
= ResolvingSortRole
;
347 resolveNextSortRole();
354 void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList
& itemRanges
)
356 Q_UNUSED(itemRanges
);
358 const bool allItemsRemoved
= (m_model
->count() == 0);
361 if (m_nepomukResourceWatcher
) {
362 // Don't let the ResourceWatcher watch for removed items
363 if (allItemsRemoved
) {
364 m_nepomukResourceWatcher
->setResources(QList
<Nepomuk2::Resource
>());
365 m_nepomukResourceWatcher
->stop();
366 m_nepomukUriItems
.clear();
368 QList
<Nepomuk2::Resource
> newResources
;
369 const QList
<Nepomuk2::Resource
> oldResources
= m_nepomukResourceWatcher
->resources();
370 foreach (const Nepomuk2::Resource
& resource
, oldResources
) {
371 const QUrl uri
= resource
.uri();
372 const KUrl itemUrl
= m_nepomukUriItems
.value(uri
);
373 if (m_model
->index(itemUrl
) >= 0) {
374 newResources
.append(resource
);
376 m_nepomukUriItems
.remove(uri
);
379 m_nepomukResourceWatcher
->setResources(newResources
);
380 if (newResources
.isEmpty()) {
381 Q_ASSERT(m_nepomukUriItems
.isEmpty());
382 m_nepomukResourceWatcher
->stop();
388 if (allItemsRemoved
) {
391 m_finishedItems
.clear();
392 m_pendingSortRoleItems
.clear();
393 m_pendingIndexes
.clear();
394 m_pendingPreviewItems
.clear();
395 m_recentlyChangedItems
.clear();
396 m_recentlyChangedItemsTimer
->stop();
397 m_changedItems
.clear();
401 // Only remove the items from m_finishedItems. They will be removed
402 // from the other sets later on.
403 QSet
<KFileItem
>::iterator it
= m_finishedItems
.begin();
404 while (it
!= m_finishedItems
.end()) {
405 if (m_model
->index(*it
) < 0) {
406 it
= m_finishedItems
.erase(it
);
412 // The visible items might have changed.
417 void KFileItemModelRolesUpdater::slotItemsMoved(const KItemRange
& itemRange
, QList
<int> movedToIndexes
)
420 Q_UNUSED(movedToIndexes
);
422 // The visible items might have changed.
426 void KFileItemModelRolesUpdater::slotItemsChanged(const KItemRangeList
& itemRanges
,
427 const QSet
<QByteArray
>& roles
)
431 // Find out if slotItemsChanged() has been done recently. If that is the
432 // case, resolving the roles is postponed until a timer has exceeded
433 // to prevent expensive repeated updates if files are updated frequently.
434 const bool itemsChangedRecently
= m_recentlyChangedItemsTimer
->isActive();
436 QSet
<KFileItem
>& targetSet
= itemsChangedRecently
? m_recentlyChangedItems
: m_changedItems
;
438 foreach (const KItemRange
& itemRange
, itemRanges
) {
439 int index
= itemRange
.index
;
440 for (int count
= itemRange
.count
; count
> 0; --count
) {
441 const KFileItem item
= m_model
->fileItem(index
);
442 targetSet
.insert(item
);
447 m_recentlyChangedItemsTimer
->start();
449 if (!itemsChangedRecently
) {
450 updateChangedItems();
454 void KFileItemModelRolesUpdater::slotSortRoleChanged(const QByteArray
& current
,
455 const QByteArray
& previous
)
460 if (m_resolvableRoles
.contains(current
)) {
461 m_pendingSortRoleItems
.clear();
462 m_finishedItems
.clear();
464 const int count
= m_model
->count();
468 // Determine the sort role synchronously for as many items as possible.
469 for (int index
= 0; index
< count
; ++index
) {
470 if (timer
.elapsed() < MaxBlockTimeout
) {
471 applySortRole(index
);
473 m_pendingSortRoleItems
.insert(m_model
->fileItem(index
));
477 applySortProgressToModel();
479 if (!m_pendingSortRoleItems
.isEmpty()) {
480 // Trigger the asynchronous determination of the sort role.
482 m_state
= ResolvingSortRole
;
483 resolveNextSortRole();
487 m_pendingSortRoleItems
.clear();
488 applySortProgressToModel();
492 void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem
& item
, const QPixmap
& pixmap
)
494 if (m_state
!= PreviewJobRunning
) {
498 m_changedItems
.remove(item
);
500 const int index
= m_model
->index(item
);
505 QPixmap scaledPixmap
= pixmap
;
507 const QString mimeType
= item
.mimetype();
508 const int slashIndex
= mimeType
.indexOf(QLatin1Char('/'));
509 const QString mimeTypeGroup
= mimeType
.left(slashIndex
);
510 if (mimeTypeGroup
== QLatin1String("image")) {
511 if (m_enlargeSmallPreviews
) {
512 KPixmapModifier::applyFrame(scaledPixmap
, m_iconSize
);
514 // Assure that small previews don't get enlarged. Instead they
515 // should be shown centered within the frame.
516 const QSize contentSize
= KPixmapModifier::sizeInsideFrame(m_iconSize
);
517 const bool enlargingRequired
= scaledPixmap
.width() < contentSize
.width() &&
518 scaledPixmap
.height() < contentSize
.height();
519 if (enlargingRequired
) {
520 QSize frameSize
= scaledPixmap
.size();
521 frameSize
.scale(m_iconSize
, Qt::KeepAspectRatio
);
523 QPixmap
largeFrame(frameSize
);
524 largeFrame
.fill(Qt::transparent
);
526 KPixmapModifier::applyFrame(largeFrame
, frameSize
);
528 QPainter
painter(&largeFrame
);
529 painter
.drawPixmap((largeFrame
.width() - scaledPixmap
.width()) / 2,
530 (largeFrame
.height() - scaledPixmap
.height()) / 2,
532 scaledPixmap
= largeFrame
;
534 // The image must be shrinked as it is too large to fit into
535 // the available icon size
536 KPixmapModifier::applyFrame(scaledPixmap
, m_iconSize
);
540 KPixmapModifier::scale(scaledPixmap
, m_iconSize
);
543 QHash
<QByteArray
, QVariant
> data
= rolesData(item
);
545 const QStringList overlays
= data
["iconOverlays"].toStringList();
546 // Strangely KFileItem::overlays() returns empty string-values, so
547 // we need to check first whether an overlay must be drawn at all.
548 // It is more efficient to do it here, as KIconLoader::drawOverlays()
549 // assumes that an overlay will be drawn and has some additional
551 foreach (const QString
& overlay
, overlays
) {
552 if (!overlay
.isEmpty()) {
553 // There is at least one overlay, draw all overlays above m_pixmap
554 // and cancel the check
555 KIconLoader::global()->drawOverlays(overlays
, scaledPixmap
, KIconLoader::Desktop
);
560 data
.insert("iconPixmap", scaledPixmap
);
562 disconnect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
563 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
564 m_model
->setData(index
, data
);
565 connect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
566 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
568 m_finishedItems
.insert(item
);
571 void KFileItemModelRolesUpdater::slotPreviewFailed(const KFileItem
& item
)
573 if (m_state
!= PreviewJobRunning
) {
577 m_changedItems
.remove(item
);
579 const int index
= m_model
->index(item
);
581 QHash
<QByteArray
, QVariant
> data
;
582 data
.insert("iconPixmap", QPixmap());
584 disconnect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
585 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
586 m_model
->setData(index
, data
);
587 connect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
588 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
590 applyResolvedRoles(item
, ResolveAll
);
591 m_finishedItems
.insert(item
);
595 void KFileItemModelRolesUpdater::slotPreviewJobFinished()
599 if (m_state
!= PreviewJobRunning
) {
605 if (!m_pendingPreviewItems
.isEmpty()) {
608 if (!m_changedItems
.isEmpty()) {
609 updateChangedItems();
614 void KFileItemModelRolesUpdater::resolveNextSortRole()
616 if (m_state
!= ResolvingSortRole
) {
620 QSet
<KFileItem
>::iterator it
= m_pendingSortRoleItems
.begin();
621 while (it
!= m_pendingSortRoleItems
.end()) {
622 const KFileItem item
= *it
;
623 const int index
= m_model
->index(item
);
625 // Continue if the sort role has already been determined for the
626 // item, and the item has not been changed recently.
627 if (!m_changedItems
.contains(item
) && m_model
->data(index
).contains(m_model
->sortRole())) {
628 it
= m_pendingSortRoleItems
.erase(it
);
632 applySortRole(index
);
633 m_pendingSortRoleItems
.erase(it
);
637 if (!m_pendingSortRoleItems
.isEmpty()) {
638 applySortProgressToModel();
639 QTimer::singleShot(0, this, SLOT(resolveNextSortRole()));
643 // Prevent that we try to update the items twice.
644 disconnect(m_model
, SIGNAL(itemsMoved(KItemRange
,QList
<int>)),
645 this, SLOT(slotItemsMoved(KItemRange
,QList
<int>)));
646 applySortProgressToModel();
647 connect(m_model
, SIGNAL(itemsMoved(KItemRange
,QList
<int>)),
648 this, SLOT(slotItemsMoved(KItemRange
,QList
<int>)));
653 void KFileItemModelRolesUpdater::resolveNextPendingRoles()
655 if (m_state
!= ResolvingAllRoles
) {
659 while (!m_pendingIndexes
.isEmpty()) {
660 const int index
= m_pendingIndexes
.takeFirst();
661 const KFileItem item
= m_model
->fileItem(index
);
663 if (m_finishedItems
.contains(item
)) {
667 applyResolvedRoles(item
, ResolveAll
);
668 m_finishedItems
.insert(item
);
669 m_changedItems
.remove(item
);
673 if (!m_pendingIndexes
.isEmpty()) {
674 QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
678 if (m_clearPreviews
) {
679 // Only go through the list if there are items which might still have previews.
680 if (m_finishedItems
.count() != m_model
->count()) {
681 QHash
<QByteArray
, QVariant
> data
;
682 data
.insert("iconPixmap", QPixmap());
684 disconnect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
685 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
686 for (int index
= 0; index
<= m_model
->count(); ++index
) {
687 if (m_model
->data(index
).contains("iconPixmap")) {
688 m_model
->setData(index
, data
);
691 connect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
692 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
695 m_clearPreviews
= false;
698 if (!m_changedItems
.isEmpty()) {
699 updateChangedItems();
704 void KFileItemModelRolesUpdater::resolveRecentlyChangedItems()
706 m_changedItems
+= m_recentlyChangedItems
;
707 m_recentlyChangedItems
.clear();
708 updateChangedItems();
711 void KFileItemModelRolesUpdater::applyChangedNepomukRoles(const Nepomuk2::Resource
& resource
, const Nepomuk2::Types::Property
& property
)
714 if (!Nepomuk2::ResourceManager::instance()->initialized()) {
718 const KUrl itemUrl
= m_nepomukUriItems
.value(resource
.uri());
719 const KFileItem item
= m_model
->fileItem(itemUrl
);
722 // itemUrl is not in the model anymore, probably because
723 // the corresponding file has been deleted in the meantime.
727 QHash
<QByteArray
, QVariant
> data
= rolesData(item
);
729 const KNepomukRolesProvider
& rolesProvider
= KNepomukRolesProvider::instance();
730 const QByteArray role
= rolesProvider
.roleForPropertyUri(property
.uri());
731 if (!role
.isEmpty() && m_roles
.contains(role
)) {
732 // Overwrite the changed role value with an empty QVariant, because the roles
733 // provider doesn't overwrite it when the property value list is empty.
735 data
.insert(role
, QVariant());
738 QHashIterator
<QByteArray
, QVariant
> it(rolesProvider
.roleValues(resource
, m_roles
));
739 while (it
.hasNext()) {
741 data
.insert(it
.key(), it
.value());
744 disconnect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
745 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
746 const int index
= m_model
->index(item
);
747 m_model
->setData(index
, data
);
748 connect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
749 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
757 void KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived(const QString
& path
, int count
)
759 const bool getSizeRole
= m_roles
.contains("size");
760 const bool getIsExpandableRole
= m_roles
.contains("isExpandable");
762 if (getSizeRole
|| getIsExpandableRole
) {
763 const int index
= m_model
->index(KUrl(path
));
766 QHash
<QByteArray
, QVariant
> data
;
769 data
.insert("size", count
);
771 if (getIsExpandableRole
) {
772 data
.insert("isExpandable", count
> 0);
775 disconnect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
776 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
777 m_model
->setData(index
, data
);
778 connect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
779 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
784 void KFileItemModelRolesUpdater::startUpdating()
786 if (m_state
== Paused
) {
790 if (m_finishedItems
.count() == m_model
->count()) {
791 // All roles have been resolved already.
796 // Terminate all updates that are currently active.
798 m_pendingIndexes
.clear();
803 // Determine the icons for the visible items synchronously.
804 updateVisibleIcons();
806 // A detailed update of the items in and near the visible area
807 // only makes sense if sorting is finished.
808 if (m_state
== ResolvingSortRole
) {
812 // Start the preview job or the asynchronous resolving of all roles.
813 QList
<int> indexes
= indexesToResolve();
815 if (m_previewShown
) {
816 m_pendingPreviewItems
.clear();
817 m_pendingPreviewItems
.reserve(indexes
.count());
819 foreach (int index
, indexes
) {
820 const KFileItem item
= m_model
->fileItem(index
);
821 if (!m_finishedItems
.contains(item
)) {
822 m_pendingPreviewItems
.append(item
);
828 m_pendingIndexes
= indexes
;
829 // Trigger the asynchronous resolving of all roles.
830 m_state
= ResolvingAllRoles
;
831 QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
835 void KFileItemModelRolesUpdater::updateVisibleIcons()
837 int lastVisibleIndex
= m_lastVisibleIndex
;
838 if (lastVisibleIndex
<= 0) {
839 // Guess a reasonable value for the last visible index if the view
840 // has not told us about the real value yet.
841 lastVisibleIndex
= qMin(m_firstVisibleIndex
+ m_maximumVisibleItems
, m_model
->count() - 1);
842 if (lastVisibleIndex
<= 0) {
843 lastVisibleIndex
= qMin(200, m_model
->count() - 1);
850 // Try to determine the final icons for all visible items.
852 for (index
= m_firstVisibleIndex
; index
<= lastVisibleIndex
&& timer
.elapsed() < MaxBlockTimeout
; ++index
) {
853 const KFileItem item
= m_model
->fileItem(index
);
854 applyResolvedRoles(item
, ResolveFast
);
857 // KFileItemListView::initializeItemListWidget(KItemListWidget*) will load
858 // preliminary icons (i.e., without mime type determination) for the
862 void KFileItemModelRolesUpdater::startPreviewJob()
864 m_state
= PreviewJobRunning
;
866 if (m_pendingPreviewItems
.isEmpty()) {
867 QTimer::singleShot(0, this, SLOT(slotPreviewJobFinished()));
871 // PreviewJob internally caches items always with the size of
872 // 128 x 128 pixels or 256 x 256 pixels. A (slow) downscaling is done
873 // by PreviewJob if a smaller size is requested. For images KFileItemModelRolesUpdater must
874 // do a downscaling anyhow because of the frame, so in this case only the provided
875 // cache sizes are requested.
876 const QSize cacheSize
= (m_iconSize
.width() > 128) || (m_iconSize
.height() > 128)
877 ? QSize(256, 256) : QSize(128, 128);
879 // KIO::filePreview() will request the MIME-type of all passed items, which (in the
880 // worst case) might block the application for several seconds. To prevent such
881 // a blocking, we only pass items with known mime type to the preview job.
882 const int count
= m_pendingPreviewItems
.count();
883 KFileItemList itemSubSet
;
884 itemSubSet
.reserve(count
);
886 if (m_pendingPreviewItems
.first().isMimeTypeKnown()) {
887 // Some mime types are known already, probably because they were
888 // determined when loading the icons for the visible items. Start
889 // a preview job for all items at the beginning of the list which
890 // have a known mime type.
892 itemSubSet
.append(m_pendingPreviewItems
.takeFirst());
893 } while (!m_pendingPreviewItems
.isEmpty() && m_pendingPreviewItems
.first().isMimeTypeKnown());
895 // Determine mime types for MaxBlockTimeout ms, and start a preview
896 // job for the corresponding items.
901 const KFileItem item
= m_pendingPreviewItems
.takeFirst();
902 item
.determineMimeType();
903 itemSubSet
.append(item
);
904 } while (!m_pendingPreviewItems
.isEmpty() && timer
.elapsed() < MaxBlockTimeout
);
907 KIO::PreviewJob
* job
= new KIO::PreviewJob(itemSubSet
, cacheSize
, &m_enabledPlugins
);
909 job
->setIgnoreMaximumSize(itemSubSet
.first().isLocalFile());
911 job
->ui()->setWindow(qApp
->activeWindow());
914 connect(job
, SIGNAL(gotPreview(KFileItem
,QPixmap
)),
915 this, SLOT(slotGotPreview(KFileItem
,QPixmap
)));
916 connect(job
, SIGNAL(failed(KFileItem
)),
917 this, SLOT(slotPreviewFailed(KFileItem
)));
918 connect(job
, SIGNAL(finished(KJob
*)),
919 this, SLOT(slotPreviewJobFinished()));
924 void KFileItemModelRolesUpdater::updateChangedItems()
926 if (m_state
== Paused
) {
930 if (m_changedItems
.isEmpty()) {
934 m_finishedItems
-= m_changedItems
;
936 if (m_resolvableRoles
.contains(m_model
->sortRole())) {
937 m_pendingSortRoleItems
+= m_changedItems
;
939 if (m_state
!= ResolvingSortRole
) {
940 // Stop the preview job if necessary, and trigger the
941 // asynchronous determination of the sort role.
943 m_state
= ResolvingSortRole
;
944 QTimer::singleShot(0, this, SLOT(resolveNextSortRole()));
950 QList
<int> visibleChangedIndexes
;
951 QList
<int> invisibleChangedIndexes
;
953 foreach (const KFileItem
& item
, m_changedItems
) {
954 const int index
= m_model
->index(item
);
957 m_changedItems
.remove(item
);
961 if (index
>= m_firstVisibleIndex
&& index
<= m_lastVisibleIndex
) {
962 visibleChangedIndexes
.append(index
);
964 invisibleChangedIndexes
.append(index
);
968 std::sort(visibleChangedIndexes
.begin(), visibleChangedIndexes
.end());
970 if (m_previewShown
) {
971 foreach (int index
, visibleChangedIndexes
) {
972 m_pendingPreviewItems
.append(m_model
->fileItem(index
));
975 foreach (int index
, invisibleChangedIndexes
) {
976 m_pendingPreviewItems
.append(m_model
->fileItem(index
));
983 const bool resolvingInProgress
= !m_pendingIndexes
.isEmpty();
984 m_pendingIndexes
= visibleChangedIndexes
+ m_pendingIndexes
+ invisibleChangedIndexes
;
985 if (!resolvingInProgress
) {
986 // Trigger the asynchronous resolving of the changed roles.
987 m_state
= ResolvingAllRoles
;
988 QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
993 void KFileItemModelRolesUpdater::applySortRole(int index
)
995 QHash
<QByteArray
, QVariant
> data
;
996 const KFileItem item
= m_model
->fileItem(index
);
998 if (m_model
->sortRole() == "type") {
999 if (!item
.isMimeTypeKnown()) {
1000 item
.determineMimeType();
1003 data
.insert("type", item
.mimeComment());
1004 } else if (m_model
->sortRole() == "size" && item
.isLocalFile() && item
.isDir()) {
1005 const QString path
= item
.localPath();
1006 data
.insert("size", m_directoryContentsCounter
->countDirectoryContentsSynchronously(path
));
1008 // Probably the sort role is a Nepomuk role - just determine all roles.
1009 data
= rolesData(item
);
1012 disconnect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
1013 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
1014 m_model
->setData(index
, data
);
1015 connect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
1016 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
1019 void KFileItemModelRolesUpdater::applySortProgressToModel()
1021 // Inform the model about the progress of the resolved items,
1022 // so that it can give an indication when the sorting has been finished.
1023 const int resolvedCount
= m_model
->count() - m_pendingSortRoleItems
.count();
1024 m_model
->emitSortProgress(resolvedCount
);
1027 bool KFileItemModelRolesUpdater::applyResolvedRoles(const KFileItem
& item
, ResolveHint hint
)
1029 if (item
.isNull()) {
1033 const bool resolveAll
= (hint
== ResolveAll
);
1035 bool iconChanged
= false;
1036 if (!item
.isMimeTypeKnown() || !item
.isFinalIconKnown()) {
1037 item
.determineMimeType();
1040 const int index
= m_model
->index(item
);
1041 if (!m_model
->data(index
).contains("iconName")) {
1046 if (iconChanged
|| resolveAll
|| m_clearPreviews
) {
1047 const int index
= m_model
->index(item
);
1052 QHash
<QByteArray
, QVariant
> data
;
1054 data
= rolesData(item
);
1057 data
.insert("iconName", item
.iconName());
1059 if (m_clearPreviews
) {
1060 data
.insert("iconPixmap", QPixmap());
1063 disconnect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
1064 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
1065 m_model
->setData(index
, data
);
1066 connect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
1067 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
1074 QHash
<QByteArray
, QVariant
> KFileItemModelRolesUpdater::rolesData(const KFileItem
& item
)
1076 QHash
<QByteArray
, QVariant
> data
;
1078 const bool getSizeRole
= m_roles
.contains("size");
1079 const bool getIsExpandableRole
= m_roles
.contains("isExpandable");
1081 if ((getSizeRole
|| getIsExpandableRole
) && item
.isDir()) {
1082 if (item
.isLocalFile()) {
1083 // Tell m_directoryContentsCounter that we want to count the items
1084 // inside the directory. The result will be received in slotDirectoryContentsCountReceived.
1085 const QString path
= item
.localPath();
1086 m_directoryContentsCounter
->addDirectory(path
);
1087 } else if (getSizeRole
) {
1088 data
.insert("size", -1); // -1 indicates an unknown number of items
1092 if (m_roles
.contains("type")) {
1093 data
.insert("type", item
.mimeComment());
1096 data
.insert("iconOverlays", item
.overlays());
1099 if (m_nepomukResourceWatcher
) {
1100 const KNepomukRolesProvider
& rolesProvider
= KNepomukRolesProvider::instance();
1101 Nepomuk2::Resource
resource(item
.nepomukUri());
1102 QHashIterator
<QByteArray
, QVariant
> it(rolesProvider
.roleValues(resource
, m_roles
));
1103 while (it
.hasNext()) {
1105 data
.insert(it
.key(), it
.value());
1108 QUrl uri
= resource
.uri();
1109 if (uri
.isEmpty()) {
1110 // TODO: Is there another way to explicitly create a resource?
1111 // We need a resource to be able to track it for changes.
1112 resource
.setRating(0);
1113 uri
= resource
.uri();
1115 if (!uri
.isEmpty() && !m_nepomukUriItems
.contains(uri
)) {
1116 m_nepomukResourceWatcher
->addResource(resource
);
1118 if (m_nepomukUriItems
.isEmpty()) {
1119 m_nepomukResourceWatcher
->start();
1122 m_nepomukUriItems
.insert(uri
, item
.url());
1130 void KFileItemModelRolesUpdater::updateAllPreviews()
1132 if (m_state
== Paused
) {
1133 m_previewChangedDuringPausing
= true;
1135 m_finishedItems
.clear();
1140 void KFileItemModelRolesUpdater::killPreviewJob()
1143 disconnect(m_previewJob
, SIGNAL(gotPreview(KFileItem
,QPixmap
)),
1144 this, SLOT(slotGotPreview(KFileItem
,QPixmap
)));
1145 disconnect(m_previewJob
, SIGNAL(failed(KFileItem
)),
1146 this, SLOT(slotPreviewFailed(KFileItem
)));
1147 disconnect(m_previewJob
, SIGNAL(finished(KJob
*)),
1148 this, SLOT(slotPreviewJobFinished()));
1149 m_previewJob
->kill();
1151 m_pendingPreviewItems
.clear();
1155 QList
<int> KFileItemModelRolesUpdater::indexesToResolve() const
1157 const int count
= m_model
->count();
1160 result
.reserve(ResolveAllItemsLimit
);
1162 // Add visible items.
1163 for (int i
= m_firstVisibleIndex
; i
<= m_lastVisibleIndex
; ++i
) {
1167 // We need a reasonable upper limit for number of items to resolve after
1168 // and before the visible range. m_maximumVisibleItems can be quite large
1169 // when using Compace View.
1170 const int readAheadItems
= qMin(ReadAheadPages
* m_maximumVisibleItems
, ResolveAllItemsLimit
/ 2);
1172 // Add items after the visible range.
1173 const int endExtendedVisibleRange
= qMin(m_lastVisibleIndex
+ readAheadItems
, count
- 1);
1174 for (int i
= m_lastVisibleIndex
+ 1; i
<= endExtendedVisibleRange
; ++i
) {
1178 // Add items before the visible range in reverse order.
1179 const int beginExtendedVisibleRange
= qMax(0, m_firstVisibleIndex
- readAheadItems
);
1180 for (int i
= m_firstVisibleIndex
- 1; i
>= beginExtendedVisibleRange
; --i
) {
1184 // Add items on the last page.
1185 const int beginLastPage
= qMax(qMin(endExtendedVisibleRange
+ 1, count
- 1), count
- m_maximumVisibleItems
);
1186 for (int i
= beginLastPage
; i
< count
; ++i
) {
1190 // Add items on the first page.
1191 const int endFirstPage
= qMin(qMax(beginExtendedVisibleRange
- 1, 0), m_maximumVisibleItems
);
1192 for (int i
= 0; i
<= endFirstPage
; ++i
) {
1196 // Continue adding items until ResolveAllItemsLimit is reached.
1197 int remainingItems
= ResolveAllItemsLimit
- result
.count();
1199 for (int i
= endExtendedVisibleRange
+ 1; i
< beginLastPage
&& remainingItems
> 0; ++i
) {
1204 for (int i
= beginExtendedVisibleRange
- 1; i
> endFirstPage
&& remainingItems
> 0; --i
) {
1212 #include "kfileitemmodelrolesupdater.moc"