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>
30 #include <KIO/JobUiDelegate>
31 #include <KIO/PreviewJob>
33 #include "private/kpixmapmodifier.h"
35 #include <QApplication>
38 #include <QElapsedTimer>
44 #include "private/knepomukrolesprovider.h"
45 #include <Nepomuk2/ResourceWatcher>
46 #include <Nepomuk2/ResourceManager>
49 // Required includes for subItemsCount():
57 // #define KFILEITEMMODELROLESUPDATER_DEBUG
60 // Maximum time in ms that the KFileItemModelRolesUpdater
61 // may perform a blocking operation
62 const int MaxBlockTimeout
= 200;
64 // If the number of items is smaller than ResolveAllItemsLimit,
65 // the roles of all items will be resolved.
66 const int ResolveAllItemsLimit
= 500;
68 // Not only the visible area, but up to ReadAheadPages before and after
69 // this area will be resolved.
70 const int ReadAheadPages
= 5;
73 KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel
* model
, QObject
* parent
) :
76 m_previewChangedDuringPausing(false),
77 m_iconSizeChangedDuringPausing(false),
78 m_rolesChangedDuringPausing(false),
79 m_previewShown(false),
80 m_enlargeSmallPreviews(true),
81 m_clearPreviews(false),
85 m_firstVisibleIndex(0),
86 m_lastVisibleIndex(-1),
87 m_maximumVisibleItems(50),
91 m_pendingSortRoleItems(),
92 m_hasUnknownIcons(false),
93 m_firstIndexWithoutIcon(0),
95 m_pendingPreviewItems(),
97 m_recentlyChangedItemsTimer(0),
98 m_recentlyChangedItems(),
103 , m_nepomukResourceWatcher(0),
109 const KConfigGroup
globalConfig(KGlobal::config(), "PreviewSettings");
110 m_enabledPlugins
= globalConfig
.readEntry("Plugins", QStringList()
111 << "directorythumbnail"
115 connect(m_model
, SIGNAL(itemsInserted(KItemRangeList
)),
116 this, SLOT(slotItemsInserted(KItemRangeList
)));
117 connect(m_model
, SIGNAL(itemsRemoved(KItemRangeList
)),
118 this, SLOT(slotItemsRemoved(KItemRangeList
)));
119 connect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
120 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
121 connect(m_model
, SIGNAL(itemsMoved(KItemRange
,QList
<int>)),
122 this, SLOT(slotItemsMoved(KItemRange
,QList
<int>)));
123 connect(m_model
, SIGNAL(sortRoleChanged(QByteArray
,QByteArray
)),
124 this, SLOT(slotSortRoleChanged(QByteArray
,QByteArray
)));
126 // Use a timer to prevent that each call of slotItemsChanged() results in a synchronous
127 // resolving of the roles. Postpone the resolving until no update has been done for 1 second.
128 m_recentlyChangedItemsTimer
= new QTimer(this);
129 m_recentlyChangedItemsTimer
->setInterval(1000);
130 m_recentlyChangedItemsTimer
->setSingleShot(true);
131 connect(m_recentlyChangedItemsTimer
, SIGNAL(timeout()), this, SLOT(resolveRecentlyChangedItems()));
133 m_resolvableRoles
.insert("size");
134 m_resolvableRoles
.insert("type");
135 m_resolvableRoles
.insert("isExpandable");
137 m_resolvableRoles
+= KNepomukRolesProvider::instance().roles();
140 // When folders are expandable or the item-count is shown for folders, it is necessary
141 // to watch the number of items of the sub-folder to be able to react on changes.
142 m_dirWatcher
= new KDirWatch(this);
143 connect(m_dirWatcher
, SIGNAL(dirty(QString
)), this, SLOT(slotDirWatchDirty(QString
)));
146 KFileItemModelRolesUpdater::~KFileItemModelRolesUpdater()
151 void KFileItemModelRolesUpdater::setIconSize(const QSize
& size
)
153 if (size
!= m_iconSize
) {
155 if (m_state
== Paused
) {
156 m_iconSizeChangedDuringPausing
= true;
157 } else if (m_previewShown
) {
158 // An icon size change requires the regenerating of
160 m_finishedItems
.clear();
166 QSize
KFileItemModelRolesUpdater::iconSize() const
171 void KFileItemModelRolesUpdater::setVisibleIndexRange(int index
, int count
)
180 if (index
== m_firstVisibleIndex
&& count
== m_lastVisibleIndex
- m_firstVisibleIndex
+ 1) {
181 // The range has not been changed
185 m_firstVisibleIndex
= index
;
186 m_lastVisibleIndex
= qMin(index
+ count
- 1, m_model
->count() - 1);
191 void KFileItemModelRolesUpdater::setMaximumVisibleItems(int count
)
193 m_maximumVisibleItems
= count
;
196 void KFileItemModelRolesUpdater::setPreviewsShown(bool show
)
198 if (show
== m_previewShown
) {
202 m_previewShown
= show
;
204 m_clearPreviews
= true;
210 bool KFileItemModelRolesUpdater::previewsShown() const
212 return m_previewShown
;
215 void KFileItemModelRolesUpdater::setEnlargeSmallPreviews(bool enlarge
)
217 if (enlarge
!= m_enlargeSmallPreviews
) {
218 m_enlargeSmallPreviews
= enlarge
;
219 if (m_previewShown
) {
225 bool KFileItemModelRolesUpdater::enlargeSmallPreviews() const
227 return m_enlargeSmallPreviews
;
230 void KFileItemModelRolesUpdater::setEnabledPlugins(const QStringList
& list
)
232 if (m_enabledPlugins
!= list
) {
233 m_enabledPlugins
= list
;
234 if (m_previewShown
) {
240 void KFileItemModelRolesUpdater::setPaused(bool paused
)
242 if (paused
== (m_state
== Paused
)) {
250 const bool updatePreviews
= (m_iconSizeChangedDuringPausing
&& m_previewShown
) ||
251 m_previewChangedDuringPausing
;
252 const bool resolveAll
= updatePreviews
|| m_rolesChangedDuringPausing
;
254 m_finishedItems
.clear();
257 m_iconSizeChangedDuringPausing
= false;
258 m_previewChangedDuringPausing
= false;
259 m_rolesChangedDuringPausing
= false;
261 if (!m_pendingSortRoleItems
.isEmpty()) {
262 m_state
= ResolvingSortRole
;
263 resolveNextSortRole();
272 void KFileItemModelRolesUpdater::setRoles(const QSet
<QByteArray
>& roles
)
274 if (m_roles
!= roles
) {
278 if (Nepomuk2::ResourceManager::instance()->initialized()) {
279 // Check whether there is at least one role that must be resolved
280 // with the help of Nepomuk. If this is the case, a (quite expensive)
281 // resolving will be done in KFileItemModelRolesUpdater::rolesData() and
282 // the role gets watched for changes.
283 const KNepomukRolesProvider
& rolesProvider
= KNepomukRolesProvider::instance();
284 bool hasNepomukRole
= false;
285 QSetIterator
<QByteArray
> it(roles
);
286 while (it
.hasNext()) {
287 const QByteArray
& role
= it
.next();
288 if (rolesProvider
.roles().contains(role
)) {
289 hasNepomukRole
= true;
294 if (hasNepomukRole
&& !m_nepomukResourceWatcher
) {
295 Q_ASSERT(m_nepomukUriItems
.isEmpty());
297 m_nepomukResourceWatcher
= new Nepomuk2::ResourceWatcher(this);
298 connect(m_nepomukResourceWatcher
, SIGNAL(propertyChanged(Nepomuk2::Resource
,Nepomuk2::Types::Property
,QVariantList
,QVariantList
)),
299 this, SLOT(applyChangedNepomukRoles(Nepomuk2::Resource
)));
300 } else if (!hasNepomukRole
&& m_nepomukResourceWatcher
) {
301 delete m_nepomukResourceWatcher
;
302 m_nepomukResourceWatcher
= 0;
303 m_nepomukUriItems
.clear();
308 if (m_state
== Paused
) {
309 m_rolesChangedDuringPausing
= true;
316 QSet
<QByteArray
> KFileItemModelRolesUpdater::roles() const
321 bool KFileItemModelRolesUpdater::isPaused() const
323 return m_state
== Paused
;
326 QStringList
KFileItemModelRolesUpdater::enabledPlugins() const
328 return m_enabledPlugins
;
331 void KFileItemModelRolesUpdater::slotItemsInserted(const KItemRangeList
& itemRanges
)
336 const int firstInsertedIndex
= itemRanges
.first().index
;
337 m_firstIndexWithoutIcon
= qMin(m_firstIndexWithoutIcon
, firstInsertedIndex
);
338 m_hasUnknownIcons
= true;
340 // Determine the sort role synchronously for as many items as possible.
341 if (m_resolvableRoles
.contains(m_model
->sortRole())) {
342 int insertedCount
= 0;
343 foreach (const KItemRange
& range
, itemRanges
) {
344 const int lastIndex
= insertedCount
+ range
.index
+ range
.count
- 1;
345 for (int i
= insertedCount
+ range
.index
; i
<= lastIndex
; ++i
) {
346 if (timer
.elapsed() < MaxBlockTimeout
) {
349 m_pendingSortRoleItems
.insert(m_model
->fileItem(i
));
352 insertedCount
+= range
.count
;
355 applySortProgressToModel();
357 // If there are still items whose sort role is unknown, check if the
358 // asynchronous determination of the sort role is already in progress,
359 // and start it if that is not the case.
360 if (!m_pendingSortRoleItems
.isEmpty() && m_state
!= ResolvingSortRole
) {
362 m_state
= ResolvingSortRole
;
363 resolveNextSortRole();
370 void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList
& itemRanges
)
372 Q_UNUSED(itemRanges
);
374 const bool allItemsRemoved
= (m_model
->count() == 0);
376 if (m_hasUnknownIcons
) {
377 const int firstRemovedIndex
= itemRanges
.first().index
;
378 m_firstIndexWithoutIcon
= qMin(m_firstIndexWithoutIcon
, firstRemovedIndex
);
381 if (!m_watchedDirs
.isEmpty()) {
382 // Don't let KDirWatch watch for removed items
383 if (allItemsRemoved
) {
384 foreach (const QString
& path
, m_watchedDirs
) {
385 m_dirWatcher
->removeDir(path
);
387 m_watchedDirs
.clear();
389 QMutableSetIterator
<QString
> it(m_watchedDirs
);
390 while (it
.hasNext()) {
391 const QString
& path
= it
.next();
392 if (m_model
->index(KUrl(path
)) < 0) {
393 m_dirWatcher
->removeDir(path
);
401 if (m_nepomukResourceWatcher
) {
402 // Don't let the ResourceWatcher watch for removed items
403 if (allItemsRemoved
) {
404 m_nepomukResourceWatcher
->setResources(QList
<Nepomuk2::Resource
>());
405 m_nepomukResourceWatcher
->stop();
406 m_nepomukUriItems
.clear();
408 QList
<Nepomuk2::Resource
> newResources
;
409 const QList
<Nepomuk2::Resource
> oldResources
= m_nepomukResourceWatcher
->resources();
410 foreach (const Nepomuk2::Resource
& resource
, oldResources
) {
411 const QUrl uri
= resource
.uri();
412 const KUrl itemUrl
= m_nepomukUriItems
.value(uri
);
413 if (m_model
->index(itemUrl
) >= 0) {
414 newResources
.append(resource
);
416 m_nepomukUriItems
.remove(uri
);
419 m_nepomukResourceWatcher
->setResources(newResources
);
420 if (newResources
.isEmpty()) {
421 Q_ASSERT(m_nepomukUriItems
.isEmpty());
422 m_nepomukResourceWatcher
->stop();
428 if (allItemsRemoved
) {
431 m_finishedItems
.clear();
432 m_pendingSortRoleItems
.clear();
433 m_pendingIndexes
.clear();
434 m_pendingPreviewItems
.clear();
435 m_recentlyChangedItems
.clear();
436 m_recentlyChangedItemsTimer
->stop();
437 m_changedItems
.clear();
441 // Only remove the items from m_finishedItems. They will be removed
442 // from the other sets later on.
443 QSet
<KFileItem
>::iterator it
= m_finishedItems
.begin();
444 while (it
!= m_finishedItems
.end()) {
445 if (m_model
->index(*it
) < 0) {
446 it
= m_finishedItems
.erase(it
);
452 // The visible items might have changed.
457 void KFileItemModelRolesUpdater::slotItemsMoved(const KItemRange
& itemRange
, QList
<int> movedToIndexes
)
460 Q_UNUSED(movedToIndexes
);
462 if (m_hasUnknownIcons
) {
463 const int firstMovedIndex
= itemRange
.index
;
464 m_firstIndexWithoutIcon
= qMin(m_firstIndexWithoutIcon
, firstMovedIndex
);
467 // The visible items might have changed.
471 void KFileItemModelRolesUpdater::slotItemsChanged(const KItemRangeList
& itemRanges
,
472 const QSet
<QByteArray
>& roles
)
476 // Find out if slotItemsChanged() has been done recently. If that is the
477 // case, resolving the roles is postponed until a timer has exceeded
478 // to prevent expensive repeated updates if files are updated frequently.
479 const bool itemsChangedRecently
= m_recentlyChangedItemsTimer
->isActive();
481 QSet
<KFileItem
>& targetSet
= itemsChangedRecently
? m_recentlyChangedItems
: m_changedItems
;
483 foreach (const KItemRange
& itemRange
, itemRanges
) {
484 int index
= itemRange
.index
;
485 for (int count
= itemRange
.count
; count
> 0; --count
) {
486 const KFileItem item
= m_model
->fileItem(index
);
487 targetSet
.insert(item
);
492 m_recentlyChangedItemsTimer
->start();
494 if (!itemsChangedRecently
) {
495 updateChangedItems();
499 void KFileItemModelRolesUpdater::slotSortRoleChanged(const QByteArray
& current
,
500 const QByteArray
& previous
)
505 if (m_resolvableRoles
.contains(current
)) {
506 m_pendingSortRoleItems
.clear();
507 m_finishedItems
.clear();
509 const int count
= m_model
->count();
513 // Determine the sort role synchronously for as many items as possible.
514 for (int index
= 0; index
< count
; ++index
) {
515 if (timer
.elapsed() < MaxBlockTimeout
) {
516 applySortRole(index
);
518 m_pendingSortRoleItems
.insert(m_model
->fileItem(index
));
522 applySortProgressToModel();
524 if (!m_pendingSortRoleItems
.isEmpty()) {
525 // Trigger the asynchronous determination of the sort role.
527 m_state
= ResolvingSortRole
;
528 resolveNextSortRole();
532 m_pendingSortRoleItems
.clear();
533 applySortProgressToModel();
537 void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem
& item
, const QPixmap
& pixmap
)
539 if (m_state
!= PreviewJobRunning
) {
543 m_changedItems
.remove(item
);
545 const int index
= m_model
->index(item
);
550 QPixmap scaledPixmap
= pixmap
;
552 const QString mimeType
= item
.mimetype();
553 const int slashIndex
= mimeType
.indexOf(QLatin1Char('/'));
554 const QString mimeTypeGroup
= mimeType
.left(slashIndex
);
555 if (mimeTypeGroup
== QLatin1String("image")) {
556 if (m_enlargeSmallPreviews
) {
557 KPixmapModifier::applyFrame(scaledPixmap
, m_iconSize
);
559 // Assure that small previews don't get enlarged. Instead they
560 // should be shown centered within the frame.
561 const QSize contentSize
= KPixmapModifier::sizeInsideFrame(m_iconSize
);
562 const bool enlargingRequired
= scaledPixmap
.width() < contentSize
.width() &&
563 scaledPixmap
.height() < contentSize
.height();
564 if (enlargingRequired
) {
565 QSize frameSize
= scaledPixmap
.size();
566 frameSize
.scale(m_iconSize
, Qt::KeepAspectRatio
);
568 QPixmap
largeFrame(frameSize
);
569 largeFrame
.fill(Qt::transparent
);
571 KPixmapModifier::applyFrame(largeFrame
, frameSize
);
573 QPainter
painter(&largeFrame
);
574 painter
.drawPixmap((largeFrame
.width() - scaledPixmap
.width()) / 2,
575 (largeFrame
.height() - scaledPixmap
.height()) / 2,
577 scaledPixmap
= largeFrame
;
579 // The image must be shrinked as it is too large to fit into
580 // the available icon size
581 KPixmapModifier::applyFrame(scaledPixmap
, m_iconSize
);
585 KPixmapModifier::scale(scaledPixmap
, m_iconSize
);
588 QHash
<QByteArray
, QVariant
> data
= rolesData(item
);
589 data
.insert("iconPixmap", scaledPixmap
);
591 disconnect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
592 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
593 m_model
->setData(index
, data
);
594 connect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
595 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
597 m_finishedItems
.insert(item
);
600 void KFileItemModelRolesUpdater::slotPreviewFailed(const KFileItem
& item
)
602 if (m_state
!= PreviewJobRunning
) {
606 m_changedItems
.remove(item
);
608 const int index
= m_model
->index(item
);
610 QHash
<QByteArray
, QVariant
> data
;
611 data
.insert("iconPixmap", QPixmap());
613 disconnect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
614 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
615 m_model
->setData(index
, data
);
616 connect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
617 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
619 applyResolvedRoles(item
, ResolveAll
);
620 m_finishedItems
.insert(item
);
624 void KFileItemModelRolesUpdater::slotPreviewJobFinished()
628 if (m_state
!= PreviewJobRunning
) {
634 if (!m_pendingPreviewItems
.isEmpty()) {
637 if (!m_changedItems
.isEmpty()) {
638 updateChangedItems();
643 void KFileItemModelRolesUpdater::resolveNextSortRole()
645 if (m_state
!= ResolvingSortRole
) {
649 QSet
<KFileItem
>::iterator it
= m_pendingSortRoleItems
.begin();
650 while (it
!= m_pendingSortRoleItems
.end()) {
651 const KFileItem item
= *it
;
652 const int index
= m_model
->index(item
);
654 // Continue if the sort role has already been determined for the
655 // item, and the item has not been changed recently.
656 if (!m_changedItems
.contains(item
) && m_model
->data(index
).contains(m_model
->sortRole())) {
657 it
= m_pendingSortRoleItems
.erase(it
);
661 applySortRole(index
);
662 m_pendingSortRoleItems
.erase(it
);
666 if (!m_pendingSortRoleItems
.isEmpty()) {
667 applySortProgressToModel();
668 QTimer::singleShot(0, this, SLOT(resolveNextSortRole()));
672 // Prevent that we try to update the items twice.
673 disconnect(m_model
, SIGNAL(itemsMoved(KItemRange
,QList
<int>)),
674 this, SLOT(slotItemsMoved(KItemRange
,QList
<int>)));
675 applySortProgressToModel();
676 connect(m_model
, SIGNAL(itemsMoved(KItemRange
,QList
<int>)),
677 this, SLOT(slotItemsMoved(KItemRange
,QList
<int>)));
682 void KFileItemModelRolesUpdater::resolveNextPendingRoles()
684 if (m_state
!= ResolvingAllRoles
) {
688 while (!m_pendingIndexes
.isEmpty()) {
689 const int index
= m_pendingIndexes
.takeFirst();
690 const KFileItem item
= m_model
->fileItem(index
);
692 if (m_finishedItems
.contains(item
)) {
696 applyResolvedRoles(item
, ResolveAll
);
697 m_finishedItems
.insert(item
);
698 m_changedItems
.remove(item
);
702 if (!m_pendingIndexes
.isEmpty()) {
703 QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
707 if (m_clearPreviews
) {
708 // Only go through the list if there are items which might still have previews.
709 if (m_finishedItems
.count() != m_model
->count()) {
710 QHash
<QByteArray
, QVariant
> data
;
711 data
.insert("iconPixmap", QPixmap());
713 disconnect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
714 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
715 for (int index
= 0; index
<= m_model
->count(); ++index
) {
716 if (m_model
->data(index
).contains("iconPixmap")) {
717 m_model
->setData(index
, data
);
720 connect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
721 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
724 m_clearPreviews
= false;
727 if (!m_changedItems
.isEmpty()) {
728 updateChangedItems();
733 void KFileItemModelRolesUpdater::resolveRecentlyChangedItems()
735 m_changedItems
+= m_recentlyChangedItems
;
736 m_recentlyChangedItems
.clear();
737 updateChangedItems();
740 void KFileItemModelRolesUpdater::applyChangedNepomukRoles(const Nepomuk2::Resource
& resource
)
743 if (!Nepomuk2::ResourceManager::instance()->initialized()) {
747 const KUrl itemUrl
= m_nepomukUriItems
.value(resource
.uri());
748 const KFileItem item
= m_model
->fileItem(itemUrl
);
751 // itemUrl is not in the model anymore, probably because
752 // the corresponding file has been deleted in the meantime.
756 QHash
<QByteArray
, QVariant
> data
= rolesData(item
);
758 const KNepomukRolesProvider
& rolesProvider
= KNepomukRolesProvider::instance();
759 QHashIterator
<QByteArray
, QVariant
> it(rolesProvider
.roleValues(resource
, m_roles
));
760 while (it
.hasNext()) {
762 data
.insert(it
.key(), it
.value());
765 disconnect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
766 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
767 const int index
= m_model
->index(item
);
768 m_model
->setData(index
, data
);
769 connect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
770 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
778 void KFileItemModelRolesUpdater::slotDirWatchDirty(const QString
& path
)
780 const bool getSizeRole
= m_roles
.contains("size");
781 const bool getIsExpandableRole
= m_roles
.contains("isExpandable");
783 if (getSizeRole
|| getIsExpandableRole
) {
784 const int index
= m_model
->index(KUrl(path
));
786 if (!m_model
->fileItem(index
).isDir()) {
787 // If INotify is used, KDirWatch issues the dirty() signal
788 // also for changed files inside the directory, even if we
789 // don't enable this behavior explicitly (see bug 309740).
793 QHash
<QByteArray
, QVariant
> data
;
795 const int count
= subItemsCount(path
);
797 data
.insert("size", count
);
799 if (getIsExpandableRole
) {
800 data
.insert("isExpandable", count
> 0);
803 // Note that we do not block the itemsChanged signal here.
804 // This ensures that a new preview will be generated.
805 m_model
->setData(index
, data
);
810 void KFileItemModelRolesUpdater::startUpdating()
812 if (m_state
== Paused
) {
816 if (m_finishedItems
.count() == m_model
->count()) {
817 // All roles have been resolved already.
822 // Terminate all updates that are currently active.
824 m_pendingIndexes
.clear();
829 // Determine the icons for the visible items synchronously.
830 updateVisibleIcons();
832 // Try to do at least a fast icon loading (without determining the
833 // mime type) for all items, to reduce the risk that the user sees
834 // "unknown" icons when scrolling.
835 if (m_hasUnknownIcons
) {
836 updateAllIconsFast(MaxBlockTimeout
- timer
.elapsed());
839 // A detailed update of the items in and near the visible area
840 // only makes sense if sorting is finished.
841 if (m_state
== ResolvingSortRole
) {
845 // Start the preview job or the asynchronous resolving of all roles.
846 QList
<int> indexes
= indexesToResolve();
848 if (m_previewShown
) {
849 m_pendingPreviewItems
.clear();
850 m_pendingPreviewItems
.reserve(indexes
.count());
852 foreach (int index
, indexes
) {
853 const KFileItem item
= m_model
->fileItem(index
);
854 if (!m_finishedItems
.contains(item
)) {
855 m_pendingPreviewItems
.append(item
);
861 m_pendingIndexes
= indexes
;
862 // Trigger the asynchronous resolving of all roles.
863 m_state
= ResolvingAllRoles
;
864 QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
868 void KFileItemModelRolesUpdater::updateVisibleIcons()
870 int lastVisibleIndex
= m_lastVisibleIndex
;
871 if (lastVisibleIndex
<= 0) {
872 // Guess a reasonable value for the last visible index if the view
873 // has not told us about the real value yet.
874 lastVisibleIndex
= qMin(m_firstVisibleIndex
+ m_maximumVisibleItems
, m_model
->count() - 1);
875 if (lastVisibleIndex
<= 0) {
876 lastVisibleIndex
= qMin(200, m_model
->count() - 1);
883 // Try to determine the final icons for all visible items.
885 for (index
= m_firstVisibleIndex
; index
<= lastVisibleIndex
&& timer
.elapsed() < MaxBlockTimeout
; ++index
) {
886 const KFileItem item
= m_model
->fileItem(index
);
887 applyResolvedRoles(item
, ResolveFast
);
890 if (index
> lastVisibleIndex
) {
894 // If this didn't work before MaxBlockTimeout was reached, at least
895 // prevent that the user sees 'unknown' icons.
896 disconnect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
897 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
899 while (index
<= lastVisibleIndex
) {
900 if (!m_model
->data(index
).contains("iconName")) {
901 const KFileItem item
= m_model
->fileItem(index
);
902 QHash
<QByteArray
, QVariant
> data
;
903 data
.insert("iconName", item
.iconName());
904 m_model
->setData(index
, data
);
909 connect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
910 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
913 void KFileItemModelRolesUpdater::updateAllIconsFast(int timeout
)
922 disconnect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
923 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
925 const int count
= m_model
->count();
926 while (m_firstIndexWithoutIcon
< count
&& timer
.elapsed() < timeout
) {
927 if (!m_model
->data(m_firstIndexWithoutIcon
).contains("iconName")) {
928 const KFileItem item
= m_model
->fileItem(m_firstIndexWithoutIcon
);
929 QHash
<QByteArray
, QVariant
> data
;
930 data
.insert("iconName", item
.iconName());
931 m_model
->setData(m_firstIndexWithoutIcon
, data
);
933 ++m_firstIndexWithoutIcon
;
936 if (m_firstIndexWithoutIcon
== count
) {
937 m_hasUnknownIcons
= false;
940 connect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
941 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
944 void KFileItemModelRolesUpdater::startPreviewJob()
946 m_state
= PreviewJobRunning
;
948 if (m_pendingPreviewItems
.isEmpty()) {
949 QTimer::singleShot(0, this, SLOT(slotPreviewJobFinished()));
953 // PreviewJob internally caches items always with the size of
954 // 128 x 128 pixels or 256 x 256 pixels. A (slow) downscaling is done
955 // by PreviewJob if a smaller size is requested. For images KFileItemModelRolesUpdater must
956 // do a downscaling anyhow because of the frame, so in this case only the provided
957 // cache sizes are requested.
958 const QSize cacheSize
= (m_iconSize
.width() > 128) || (m_iconSize
.height() > 128)
959 ? QSize(256, 256) : QSize(128, 128);
961 // KIO::filePreview() will request the MIME-type of all passed items, which (in the
962 // worst case) might block the application for several seconds. To prevent such
963 // a blocking, we only pass items with known mime type to the preview job.
964 const int count
= m_pendingPreviewItems
.count();
965 KFileItemList itemSubSet
;
966 itemSubSet
.reserve(count
);
968 if (m_pendingPreviewItems
.first().isMimeTypeKnown()) {
969 // Some mime types are known already, probably because they were
970 // determined when loading the icons for the visible items. Start
971 // a preview job for all items at the beginning of the list which
972 // have a known mime type.
974 itemSubSet
.append(m_pendingPreviewItems
.takeFirst());
975 } while (!m_pendingPreviewItems
.isEmpty() && m_pendingPreviewItems
.first().isMimeTypeKnown());
977 // Determine mime types for MaxBlockTimeout ms, and start a preview
978 // job for the corresponding items.
983 const KFileItem item
= m_pendingPreviewItems
.takeFirst();
984 item
.determineMimeType();
985 itemSubSet
.append(item
);
986 } while (!m_pendingPreviewItems
.isEmpty() && timer
.elapsed() < MaxBlockTimeout
);
989 KIO::PreviewJob
* job
= new KIO::PreviewJob(itemSubSet
, cacheSize
, &m_enabledPlugins
);
991 job
->setIgnoreMaximumSize(itemSubSet
.first().isLocalFile());
993 job
->ui()->setWindow(qApp
->activeWindow());
996 connect(job
, SIGNAL(gotPreview(KFileItem
,QPixmap
)),
997 this, SLOT(slotGotPreview(KFileItem
,QPixmap
)));
998 connect(job
, SIGNAL(failed(KFileItem
)),
999 this, SLOT(slotPreviewFailed(KFileItem
)));
1000 connect(job
, SIGNAL(finished(KJob
*)),
1001 this, SLOT(slotPreviewJobFinished()));
1006 void KFileItemModelRolesUpdater::updateChangedItems()
1008 if (m_state
== Paused
) {
1012 if (m_changedItems
.isEmpty()) {
1016 m_finishedItems
-= m_changedItems
;
1018 if (m_resolvableRoles
.contains(m_model
->sortRole())) {
1019 m_pendingSortRoleItems
+= m_changedItems
;
1021 if (m_state
!= ResolvingSortRole
) {
1022 // Stop the preview job if necessary, and trigger the
1023 // asynchronous determination of the sort role.
1025 m_state
= ResolvingSortRole
;
1026 QTimer::singleShot(0, this, SLOT(resolveNextSortRole()));
1032 QList
<int> visibleChangedIndexes
;
1033 QList
<int> invisibleChangedIndexes
;
1035 foreach (const KFileItem
& item
, m_changedItems
) {
1036 const int index
= m_model
->index(item
);
1039 m_changedItems
.remove(item
);
1043 if (index
>= m_firstVisibleIndex
&& index
<= m_lastVisibleIndex
) {
1044 visibleChangedIndexes
.append(index
);
1046 invisibleChangedIndexes
.append(index
);
1050 std::sort(visibleChangedIndexes
.begin(), visibleChangedIndexes
.end());
1052 if (m_previewShown
) {
1053 foreach (int index
, visibleChangedIndexes
) {
1054 m_pendingPreviewItems
.append(m_model
->fileItem(index
));
1057 foreach (int index
, invisibleChangedIndexes
) {
1058 m_pendingPreviewItems
.append(m_model
->fileItem(index
));
1061 if (!m_previewJob
) {
1065 const bool resolvingInProgress
= !m_pendingIndexes
.isEmpty();
1066 m_pendingIndexes
= visibleChangedIndexes
+ m_pendingIndexes
+ invisibleChangedIndexes
;
1067 if (!resolvingInProgress
) {
1068 // Trigger the asynchronous resolving of the changed roles.
1069 m_state
= ResolvingAllRoles
;
1070 QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
1075 void KFileItemModelRolesUpdater::applySortRole(int index
)
1077 QHash
<QByteArray
, QVariant
> data
;
1078 const KFileItem item
= m_model
->fileItem(index
);
1080 if (m_model
->sortRole() == "type") {
1081 if (!item
.isMimeTypeKnown()) {
1082 item
.determineMimeType();
1085 data
.insert("type", item
.mimeComment());
1086 } else if (m_model
->sortRole() == "size" && item
.isLocalFile() && item
.isDir()) {
1087 const QString path
= item
.localPath();
1088 data
.insert("size", subItemsCount(path
));
1090 // Probably the sort role is a Nepomuk role - just determine all roles.
1091 data
= rolesData(item
);
1094 disconnect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
1095 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
1096 m_model
->setData(index
, data
);
1097 connect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
1098 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
1101 void KFileItemModelRolesUpdater::applySortProgressToModel()
1103 // Inform the model about the progress of the resolved items,
1104 // so that it can give an indication when the sorting has been finished.
1105 const int resolvedCount
= m_model
->count() - m_pendingSortRoleItems
.count();
1106 m_model
->emitSortProgress(resolvedCount
);
1109 bool KFileItemModelRolesUpdater::applyResolvedRoles(const KFileItem
& item
, ResolveHint hint
)
1111 if (item
.isNull()) {
1115 const bool resolveAll
= (hint
== ResolveAll
);
1117 bool iconChanged
= false;
1118 if (!item
.isMimeTypeKnown() || !item
.isFinalIconKnown()) {
1119 item
.determineMimeType();
1122 const int index
= m_model
->index(item
);
1123 if (!m_model
->data(index
).contains("iconName")) {
1128 if (iconChanged
|| resolveAll
|| m_clearPreviews
) {
1129 const int index
= m_model
->index(item
);
1134 QHash
<QByteArray
, QVariant
> data
;
1136 data
= rolesData(item
);
1139 data
.insert("iconName", item
.iconName());
1141 if (m_clearPreviews
) {
1142 data
.insert("iconPixmap", QPixmap());
1145 disconnect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
1146 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
1147 m_model
->setData(index
, data
);
1148 connect(m_model
, SIGNAL(itemsChanged(KItemRangeList
,QSet
<QByteArray
>)),
1149 this, SLOT(slotItemsChanged(KItemRangeList
,QSet
<QByteArray
>)));
1156 QHash
<QByteArray
, QVariant
> KFileItemModelRolesUpdater::rolesData(const KFileItem
& item
) const
1158 QHash
<QByteArray
, QVariant
> data
;
1160 const bool getSizeRole
= m_roles
.contains("size");
1161 const bool getIsExpandableRole
= m_roles
.contains("isExpandable");
1163 if ((getSizeRole
|| getIsExpandableRole
) && item
.isDir()) {
1164 if (item
.isLocalFile()) {
1165 const QString path
= item
.localPath();
1166 const int count
= subItemsCount(path
);
1168 data
.insert("size", count
);
1170 if (getIsExpandableRole
) {
1171 data
.insert("isExpandable", count
> 0);
1174 if (!m_dirWatcher
->contains(path
)) {
1175 m_dirWatcher
->addDir(path
);
1176 m_watchedDirs
.insert(path
);
1178 } else if (getSizeRole
) {
1179 data
.insert("size", -1); // -1 indicates an unknown number of items
1183 if (m_roles
.contains("type")) {
1184 data
.insert("type", item
.mimeComment());
1187 data
.insert("iconOverlays", item
.overlays());
1190 if (m_nepomukResourceWatcher
) {
1191 const KNepomukRolesProvider
& rolesProvider
= KNepomukRolesProvider::instance();
1192 Nepomuk2::Resource
resource(item
.nepomukUri());
1193 QHashIterator
<QByteArray
, QVariant
> it(rolesProvider
.roleValues(resource
, m_roles
));
1194 while (it
.hasNext()) {
1196 data
.insert(it
.key(), it
.value());
1199 QUrl uri
= resource
.uri();
1200 if (uri
.isEmpty()) {
1201 // TODO: Is there another way to explicitly create a resource?
1202 // We need a resource to be able to track it for changes.
1203 resource
.setRating(0);
1204 uri
= resource
.uri();
1206 if (!uri
.isEmpty() && !m_nepomukUriItems
.contains(uri
)) {
1207 m_nepomukResourceWatcher
->addResource(resource
);
1209 if (m_nepomukUriItems
.isEmpty()) {
1210 m_nepomukResourceWatcher
->start();
1213 m_nepomukUriItems
.insert(uri
, item
.url());
1221 int KFileItemModelRolesUpdater::subItemsCount(const QString
& path
) const
1223 const bool countHiddenFiles
= m_model
->showHiddenFiles();
1224 const bool showFoldersOnly
= m_model
->showDirectoriesOnly();
1228 QDir::Filters filters
= QDir::NoDotAndDotDot
| QDir::System
;
1229 if (countHiddenFiles
) {
1230 filters
|= QDir::Hidden
;
1232 if (showFoldersOnly
) {
1233 filters
|= QDir::Dirs
;
1235 filters
|= QDir::AllEntries
;
1237 return dir
.entryList(filters
).count();
1239 // Taken from kdelibs/kio/kio/kdirmodel.cpp
1240 // Copyright (C) 2006 David Faure <faure@kde.org>
1243 DIR* dir
= ::opendir(QFile::encodeName(path
));
1244 if (dir
) { // krazy:exclude=syscalls
1246 struct dirent
*dirEntry
= 0;
1247 while ((dirEntry
= ::readdir(dir
))) {
1248 if (dirEntry
->d_name
[0] == '.') {
1249 if (dirEntry
->d_name
[1] == '\0' || !countHiddenFiles
) {
1250 // Skip "." or hidden files
1253 if (dirEntry
->d_name
[1] == '.' && dirEntry
->d_name
[2] == '\0') {
1259 // If only directories are counted, consider an unknown file type and links also
1260 // as directory instead of trying to do an expensive stat()
1261 // (see bugs 292642 and 299997).
1262 const bool countEntry
= !showFoldersOnly
||
1263 dirEntry
->d_type
== DT_DIR
||
1264 dirEntry
->d_type
== DT_LNK
||
1265 dirEntry
->d_type
== DT_UNKNOWN
;
1276 void KFileItemModelRolesUpdater::updateAllPreviews()
1278 if (m_state
== Paused
) {
1279 m_previewChangedDuringPausing
= true;
1281 m_finishedItems
.clear();
1286 void KFileItemModelRolesUpdater::killPreviewJob()
1289 disconnect(m_previewJob
, SIGNAL(gotPreview(KFileItem
,QPixmap
)),
1290 this, SLOT(slotGotPreview(KFileItem
,QPixmap
)));
1291 disconnect(m_previewJob
, SIGNAL(failed(KFileItem
)),
1292 this, SLOT(slotPreviewFailed(KFileItem
)));
1293 disconnect(m_previewJob
, SIGNAL(finished(KJob
*)),
1294 this, SLOT(slotPreviewJobFinished()));
1295 m_previewJob
->kill();
1297 m_pendingPreviewItems
.clear();
1301 QList
<int> KFileItemModelRolesUpdater::indexesToResolve() const
1303 const int count
= m_model
->count();
1306 result
.reserve(ResolveAllItemsLimit
);
1308 // Add visible items.
1309 for (int i
= m_firstVisibleIndex
; i
<= m_lastVisibleIndex
; ++i
) {
1313 // We need a reasonable upper limit for number of items to resolve after
1314 // and before the visible range. m_maximumVisibleItems can be quite large
1315 // when using Compace View.
1316 const int readAheadItems
= qMin(ReadAheadPages
* m_maximumVisibleItems
, ResolveAllItemsLimit
/ 2);
1318 // Add items after the visible range.
1319 const int endExtendedVisibleRange
= qMin(m_lastVisibleIndex
+ readAheadItems
, count
- 1);
1320 for (int i
= m_lastVisibleIndex
+ 1; i
<= endExtendedVisibleRange
; ++i
) {
1324 // Add items before the visible range in reverse order.
1325 const int beginExtendedVisibleRange
= qMax(0, m_firstVisibleIndex
- readAheadItems
);
1326 for (int i
= m_firstVisibleIndex
- 1; i
>= beginExtendedVisibleRange
; --i
) {
1330 // Add items on the last page.
1331 const int beginLastPage
= qMax(qMin(endExtendedVisibleRange
+ 1, count
- 1), count
- m_maximumVisibleItems
);
1332 for (int i
= beginLastPage
; i
< count
; ++i
) {
1336 // Add items on the first page.
1337 const int endFirstPage
= qMin(qMax(beginExtendedVisibleRange
- 1, 0), m_maximumVisibleItems
);
1338 for (int i
= 0; i
<= endFirstPage
; ++i
) {
1342 // Continue adding items until ResolveAllItemsLimit is reached.
1343 int remainingItems
= ResolveAllItemsLimit
- result
.count();
1345 for (int i
= endExtendedVisibleRange
+ 1; i
< beginLastPage
&& remainingItems
> 0; ++i
) {
1350 for (int i
= beginExtendedVisibleRange
- 1; i
> endFirstPage
&& remainingItems
> 0; --i
) {
1358 #include "kfileitemmodelrolesupdater.moc"