]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemmodelrolesupdater.cpp
Merge remote-tracking branch 'origin/KDE/4.11'
[dolphin.git] / src / kitemviews / kfileitemmodelrolesupdater.cpp
1 /***************************************************************************
2 * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18 ***************************************************************************/
19
20 #include "kfileitemmodelrolesupdater.h"
21
22 #include "kfileitemmodel.h"
23
24 #include <KConfig>
25 #include <KConfigGroup>
26 #include <KDebug>
27 #include <KFileItem>
28 #include <KGlobal>
29 #include <KIO/JobUiDelegate>
30 #include <KIO/PreviewJob>
31
32 #include "private/kpixmapmodifier.h"
33 #include "private/kdirectorycontentscounter.h"
34
35 #include <QApplication>
36 #include <QPainter>
37 #include <QPixmap>
38 #include <QElapsedTimer>
39 #include <QTimer>
40
41 #include <algorithm>
42
43 #ifdef HAVE_NEPOMUK
44 #include "private/knepomukrolesprovider.h"
45 #include <Nepomuk2/ResourceWatcher>
46 #include <Nepomuk2/ResourceManager>
47 #endif
48
49 // #define KFILEITEMMODELROLESUPDATER_DEBUG
50
51 namespace {
52 // Maximum time in ms that the KFileItemModelRolesUpdater
53 // may perform a blocking operation
54 const int MaxBlockTimeout = 200;
55
56 // If the number of items is smaller than ResolveAllItemsLimit,
57 // the roles of all items will be resolved.
58 const int ResolveAllItemsLimit = 500;
59
60 // Not only the visible area, but up to ReadAheadPages before and after
61 // this area will be resolved.
62 const int ReadAheadPages = 5;
63 }
64
65 KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QObject* parent) :
66 QObject(parent),
67 m_state(Idle),
68 m_previewChangedDuringPausing(false),
69 m_iconSizeChangedDuringPausing(false),
70 m_rolesChangedDuringPausing(false),
71 m_previewShown(false),
72 m_enlargeSmallPreviews(true),
73 m_clearPreviews(false),
74 m_finishedItems(),
75 m_model(model),
76 m_iconSize(),
77 m_firstVisibleIndex(0),
78 m_lastVisibleIndex(-1),
79 m_maximumVisibleItems(50),
80 m_roles(),
81 m_resolvableRoles(),
82 m_enabledPlugins(),
83 m_pendingSortRoleItems(),
84 m_pendingIndexes(),
85 m_pendingPreviewItems(),
86 m_previewJob(),
87 m_recentlyChangedItemsTimer(0),
88 m_recentlyChangedItems(),
89 m_changedItems(),
90 m_directoryContentsCounter(0)
91 #ifdef HAVE_NEPOMUK
92 , m_nepomukResourceWatcher(0),
93 m_nepomukUriItems()
94 #endif
95 {
96 Q_ASSERT(model);
97
98 const KConfigGroup globalConfig(KGlobal::config(), "PreviewSettings");
99 m_enabledPlugins = globalConfig.readEntry("Plugins", QStringList()
100 << "directorythumbnail"
101 << "imagethumbnail"
102 << "jpegthumbnail");
103
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)));
114
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()));
121
122 m_resolvableRoles.insert("size");
123 m_resolvableRoles.insert("type");
124 m_resolvableRoles.insert("isExpandable");
125 #ifdef HAVE_NEPOMUK
126 m_resolvableRoles += KNepomukRolesProvider::instance().roles();
127 #endif
128
129 m_directoryContentsCounter = new KDirectoryContentsCounter(m_model, this);
130 connect(m_directoryContentsCounter, SIGNAL(result(QString,int)),
131 this, SLOT(slotDirectoryContentsCountReceived(QString,int)));
132 }
133
134 KFileItemModelRolesUpdater::~KFileItemModelRolesUpdater()
135 {
136 killPreviewJob();
137 }
138
139 void KFileItemModelRolesUpdater::setIconSize(const QSize& size)
140 {
141 if (size != m_iconSize) {
142 m_iconSize = size;
143 if (m_state == Paused) {
144 m_iconSizeChangedDuringPausing = true;
145 } else if (m_previewShown) {
146 // An icon size change requires the regenerating of
147 // all previews
148 m_finishedItems.clear();
149 startUpdating();
150 }
151 }
152 }
153
154 QSize KFileItemModelRolesUpdater::iconSize() const
155 {
156 return m_iconSize;
157 }
158
159 void KFileItemModelRolesUpdater::setVisibleIndexRange(int index, int count)
160 {
161 if (index < 0) {
162 index = 0;
163 }
164 if (count < 0) {
165 count = 0;
166 }
167
168 if (index == m_firstVisibleIndex && count == m_lastVisibleIndex - m_firstVisibleIndex + 1) {
169 // The range has not been changed
170 return;
171 }
172
173 m_firstVisibleIndex = index;
174 m_lastVisibleIndex = qMin(index + count - 1, m_model->count() - 1);
175
176 startUpdating();
177 }
178
179 void KFileItemModelRolesUpdater::setMaximumVisibleItems(int count)
180 {
181 m_maximumVisibleItems = count;
182 }
183
184 void KFileItemModelRolesUpdater::setPreviewsShown(bool show)
185 {
186 if (show == m_previewShown) {
187 return;
188 }
189
190 m_previewShown = show;
191 if (!show) {
192 m_clearPreviews = true;
193 }
194
195 updateAllPreviews();
196 }
197
198 bool KFileItemModelRolesUpdater::previewsShown() const
199 {
200 return m_previewShown;
201 }
202
203 void KFileItemModelRolesUpdater::setEnlargeSmallPreviews(bool enlarge)
204 {
205 if (enlarge != m_enlargeSmallPreviews) {
206 m_enlargeSmallPreviews = enlarge;
207 if (m_previewShown) {
208 updateAllPreviews();
209 }
210 }
211 }
212
213 bool KFileItemModelRolesUpdater::enlargeSmallPreviews() const
214 {
215 return m_enlargeSmallPreviews;
216 }
217
218 void KFileItemModelRolesUpdater::setEnabledPlugins(const QStringList& list)
219 {
220 if (m_enabledPlugins != list) {
221 m_enabledPlugins = list;
222 if (m_previewShown) {
223 updateAllPreviews();
224 }
225 }
226 }
227
228 void KFileItemModelRolesUpdater::setPaused(bool paused)
229 {
230 if (paused == (m_state == Paused)) {
231 return;
232 }
233
234 if (paused) {
235 m_state = Paused;
236 killPreviewJob();
237 } else {
238 const bool updatePreviews = (m_iconSizeChangedDuringPausing && m_previewShown) ||
239 m_previewChangedDuringPausing;
240 const bool resolveAll = updatePreviews || m_rolesChangedDuringPausing;
241 if (resolveAll) {
242 m_finishedItems.clear();
243 }
244
245 m_iconSizeChangedDuringPausing = false;
246 m_previewChangedDuringPausing = false;
247 m_rolesChangedDuringPausing = false;
248
249 if (!m_pendingSortRoleItems.isEmpty()) {
250 m_state = ResolvingSortRole;
251 resolveNextSortRole();
252 } else {
253 m_state = Idle;
254 }
255
256 startUpdating();
257 }
258 }
259
260 void KFileItemModelRolesUpdater::setRoles(const QSet<QByteArray>& roles)
261 {
262 if (m_roles != roles) {
263 m_roles = roles;
264
265 #ifdef HAVE_NEPOMUK
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;
278 break;
279 }
280 }
281
282 if (hasNepomukRole && !m_nepomukResourceWatcher) {
283 Q_ASSERT(m_nepomukUriItems.isEmpty());
284
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();
292 }
293 }
294 #endif
295
296 if (m_state == Paused) {
297 m_rolesChangedDuringPausing = true;
298 } else {
299 startUpdating();
300 }
301 }
302 }
303
304 QSet<QByteArray> KFileItemModelRolesUpdater::roles() const
305 {
306 return m_roles;
307 }
308
309 bool KFileItemModelRolesUpdater::isPaused() const
310 {
311 return m_state == Paused;
312 }
313
314 QStringList KFileItemModelRolesUpdater::enabledPlugins() const
315 {
316 return m_enabledPlugins;
317 }
318
319 void KFileItemModelRolesUpdater::slotItemsInserted(const KItemRangeList& itemRanges)
320 {
321 QElapsedTimer timer;
322 timer.start();
323
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) {
331 applySortRole(i);
332 } else {
333 m_pendingSortRoleItems.insert(m_model->fileItem(i));
334 }
335 }
336 insertedCount += range.count;
337 }
338
339 applySortProgressToModel();
340
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) {
345 killPreviewJob();
346 m_state = ResolvingSortRole;
347 resolveNextSortRole();
348 }
349 }
350
351 startUpdating();
352 }
353
354 void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList& itemRanges)
355 {
356 Q_UNUSED(itemRanges);
357
358 const bool allItemsRemoved = (m_model->count() == 0);
359
360 #ifdef HAVE_NEPOMUK
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();
367 } else {
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);
375 } else {
376 m_nepomukUriItems.remove(uri);
377 }
378 }
379 m_nepomukResourceWatcher->setResources(newResources);
380 if (newResources.isEmpty()) {
381 Q_ASSERT(m_nepomukUriItems.isEmpty());
382 m_nepomukResourceWatcher->stop();
383 }
384 }
385 }
386 #endif
387
388 if (allItemsRemoved) {
389 m_state = Idle;
390
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();
398
399 killPreviewJob();
400 } else {
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);
407 } else {
408 ++it;
409 }
410 }
411
412 // The visible items might have changed.
413 startUpdating();
414 }
415 }
416
417 void KFileItemModelRolesUpdater::slotItemsMoved(const KItemRange& itemRange, QList<int> movedToIndexes)
418 {
419 Q_UNUSED(itemRange);
420 Q_UNUSED(movedToIndexes);
421
422 // The visible items might have changed.
423 startUpdating();
424 }
425
426 void KFileItemModelRolesUpdater::slotItemsChanged(const KItemRangeList& itemRanges,
427 const QSet<QByteArray>& roles)
428 {
429 Q_UNUSED(roles);
430
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();
435
436 QSet<KFileItem>& targetSet = itemsChangedRecently ? m_recentlyChangedItems : m_changedItems;
437
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);
443 ++index;
444 }
445 }
446
447 m_recentlyChangedItemsTimer->start();
448
449 if (!itemsChangedRecently) {
450 updateChangedItems();
451 }
452 }
453
454 void KFileItemModelRolesUpdater::slotSortRoleChanged(const QByteArray& current,
455 const QByteArray& previous)
456 {
457 Q_UNUSED(current);
458 Q_UNUSED(previous);
459
460 if (m_resolvableRoles.contains(current)) {
461 m_pendingSortRoleItems.clear();
462 m_finishedItems.clear();
463
464 const int count = m_model->count();
465 QElapsedTimer timer;
466 timer.start();
467
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);
472 } else {
473 m_pendingSortRoleItems.insert(m_model->fileItem(index));
474 }
475 }
476
477 applySortProgressToModel();
478
479 if (!m_pendingSortRoleItems.isEmpty()) {
480 // Trigger the asynchronous determination of the sort role.
481 killPreviewJob();
482 m_state = ResolvingSortRole;
483 resolveNextSortRole();
484 }
485 } else {
486 m_state = Idle;
487 m_pendingSortRoleItems.clear();
488 applySortProgressToModel();
489 }
490 }
491
492 void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem& item, const QPixmap& pixmap)
493 {
494 if (m_state != PreviewJobRunning) {
495 return;
496 }
497
498 m_changedItems.remove(item);
499
500 const int index = m_model->index(item);
501 if (index < 0) {
502 return;
503 }
504
505 QPixmap scaledPixmap = pixmap;
506
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);
513 } else {
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);
522
523 QPixmap largeFrame(frameSize);
524 largeFrame.fill(Qt::transparent);
525
526 KPixmapModifier::applyFrame(largeFrame, frameSize);
527
528 QPainter painter(&largeFrame);
529 painter.drawPixmap((largeFrame.width() - scaledPixmap.width()) / 2,
530 (largeFrame.height() - scaledPixmap.height()) / 2,
531 scaledPixmap);
532 scaledPixmap = largeFrame;
533 } else {
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);
537 }
538 }
539 } else {
540 KPixmapModifier::scale(scaledPixmap, m_iconSize);
541 }
542
543 QHash<QByteArray, QVariant> data = rolesData(item);
544
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
550 // setup time.
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);
556 break;
557 }
558 }
559
560 data.insert("iconPixmap", scaledPixmap);
561
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>)));
567
568 m_finishedItems.insert(item);
569 }
570
571 void KFileItemModelRolesUpdater::slotPreviewFailed(const KFileItem& item)
572 {
573 if (m_state != PreviewJobRunning) {
574 return;
575 }
576
577 m_changedItems.remove(item);
578
579 const int index = m_model->index(item);
580 if (index >= 0) {
581 QHash<QByteArray, QVariant> data;
582 data.insert("iconPixmap", QPixmap());
583
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>)));
589
590 applyResolvedRoles(item, ResolveAll);
591 m_finishedItems.insert(item);
592 }
593 }
594
595 void KFileItemModelRolesUpdater::slotPreviewJobFinished()
596 {
597 m_previewJob = 0;
598
599 if (m_state != PreviewJobRunning) {
600 return;
601 }
602
603 m_state = Idle;
604
605 if (!m_pendingPreviewItems.isEmpty()) {
606 startPreviewJob();
607 } else {
608 if (!m_changedItems.isEmpty()) {
609 updateChangedItems();
610 }
611 }
612 }
613
614 void KFileItemModelRolesUpdater::resolveNextSortRole()
615 {
616 if (m_state != ResolvingSortRole) {
617 return;
618 }
619
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);
624
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);
629 continue;
630 }
631
632 applySortRole(index);
633 m_pendingSortRoleItems.erase(it);
634 break;
635 }
636
637 if (!m_pendingSortRoleItems.isEmpty()) {
638 applySortProgressToModel();
639 QTimer::singleShot(0, this, SLOT(resolveNextSortRole()));
640 } else {
641 m_state = Idle;
642
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>)));
649 startUpdating();
650 }
651 }
652
653 void KFileItemModelRolesUpdater::resolveNextPendingRoles()
654 {
655 if (m_state != ResolvingAllRoles) {
656 return;
657 }
658
659 while (!m_pendingIndexes.isEmpty()) {
660 const int index = m_pendingIndexes.takeFirst();
661 const KFileItem item = m_model->fileItem(index);
662
663 if (m_finishedItems.contains(item)) {
664 continue;
665 }
666
667 applyResolvedRoles(item, ResolveAll);
668 m_finishedItems.insert(item);
669 m_changedItems.remove(item);
670 break;
671 }
672
673 if (!m_pendingIndexes.isEmpty()) {
674 QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
675 } else {
676 m_state = Idle;
677
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());
683
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);
689 }
690 }
691 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
692 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
693
694 }
695 m_clearPreviews = false;
696 }
697
698 if (!m_changedItems.isEmpty()) {
699 updateChangedItems();
700 }
701 }
702 }
703
704 void KFileItemModelRolesUpdater::resolveRecentlyChangedItems()
705 {
706 m_changedItems += m_recentlyChangedItems;
707 m_recentlyChangedItems.clear();
708 updateChangedItems();
709 }
710
711 void KFileItemModelRolesUpdater::applyChangedNepomukRoles(const Nepomuk2::Resource& resource, const Nepomuk2::Types::Property& property)
712 {
713 #ifdef HAVE_NEPOMUK
714 if (!Nepomuk2::ResourceManager::instance()->initialized()) {
715 return;
716 }
717
718 const KUrl itemUrl = m_nepomukUriItems.value(resource.uri());
719 const KFileItem item = m_model->fileItem(itemUrl);
720
721 if (item.isNull()) {
722 // itemUrl is not in the model anymore, probably because
723 // the corresponding file has been deleted in the meantime.
724 return;
725 }
726
727 QHash<QByteArray, QVariant> data = rolesData(item);
728
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.
734 // See bug 322348
735 data.insert(role, QVariant());
736 }
737
738 QHashIterator<QByteArray, QVariant> it(rolesProvider.roleValues(resource, m_roles));
739 while (it.hasNext()) {
740 it.next();
741 data.insert(it.key(), it.value());
742 }
743
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>)));
750 #else
751 #ifndef Q_CC_MSVC
752 Q_UNUSED(resource);
753 #endif
754 #endif
755 }
756
757 void KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived(const QString& path, int count)
758 {
759 const bool getSizeRole = m_roles.contains("size");
760 const bool getIsExpandableRole = m_roles.contains("isExpandable");
761
762 if (getSizeRole || getIsExpandableRole) {
763 const int index = m_model->index(KUrl(path));
764 if (index >= 0) {
765
766 QHash<QByteArray, QVariant> data;
767
768 if (getSizeRole) {
769 data.insert("size", count);
770 }
771 if (getIsExpandableRole) {
772 data.insert("isExpandable", count > 0);
773 }
774
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>)));
780 }
781 }
782 }
783
784 void KFileItemModelRolesUpdater::startUpdating()
785 {
786 if (m_state == Paused) {
787 return;
788 }
789
790 if (m_finishedItems.count() == m_model->count()) {
791 // All roles have been resolved already.
792 m_state = Idle;
793 return;
794 }
795
796 // Terminate all updates that are currently active.
797 killPreviewJob();
798 m_pendingIndexes.clear();
799
800 QElapsedTimer timer;
801 timer.start();
802
803 // Determine the icons for the visible items synchronously.
804 updateVisibleIcons();
805
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) {
809 return;
810 }
811
812 // Start the preview job or the asynchronous resolving of all roles.
813 QList<int> indexes = indexesToResolve();
814
815 if (m_previewShown) {
816 m_pendingPreviewItems.clear();
817 m_pendingPreviewItems.reserve(indexes.count());
818
819 foreach (int index, indexes) {
820 const KFileItem item = m_model->fileItem(index);
821 if (!m_finishedItems.contains(item)) {
822 m_pendingPreviewItems.append(item);
823 }
824 }
825
826 startPreviewJob();
827 } else {
828 m_pendingIndexes = indexes;
829 // Trigger the asynchronous resolving of all roles.
830 m_state = ResolvingAllRoles;
831 QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
832 }
833 }
834
835 void KFileItemModelRolesUpdater::updateVisibleIcons()
836 {
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);
844 }
845 }
846
847 QElapsedTimer timer;
848 timer.start();
849
850 // Try to determine the final icons for all visible items.
851 int index;
852 for (index = m_firstVisibleIndex; index <= lastVisibleIndex && timer.elapsed() < MaxBlockTimeout; ++index) {
853 const KFileItem item = m_model->fileItem(index);
854 applyResolvedRoles(item, ResolveFast);
855 }
856
857 // KFileItemListView::initializeItemListWidget(KItemListWidget*) will load
858 // preliminary icons (i.e., without mime type determination) for the
859 // remaining items.
860 }
861
862 void KFileItemModelRolesUpdater::startPreviewJob()
863 {
864 m_state = PreviewJobRunning;
865
866 if (m_pendingPreviewItems.isEmpty()) {
867 QTimer::singleShot(0, this, SLOT(slotPreviewJobFinished()));
868 return;
869 }
870
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);
878
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);
885
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.
891 do {
892 itemSubSet.append(m_pendingPreviewItems.takeFirst());
893 } while (!m_pendingPreviewItems.isEmpty() && m_pendingPreviewItems.first().isMimeTypeKnown());
894 } else {
895 // Determine mime types for MaxBlockTimeout ms, and start a preview
896 // job for the corresponding items.
897 QElapsedTimer timer;
898 timer.start();
899
900 do {
901 const KFileItem item = m_pendingPreviewItems.takeFirst();
902 item.determineMimeType();
903 itemSubSet.append(item);
904 } while (!m_pendingPreviewItems.isEmpty() && timer.elapsed() < MaxBlockTimeout);
905 }
906
907 KIO::PreviewJob* job = new KIO::PreviewJob(itemSubSet, cacheSize, &m_enabledPlugins);
908
909 job->setIgnoreMaximumSize(itemSubSet.first().isLocalFile());
910 if (job->ui()) {
911 job->ui()->setWindow(qApp->activeWindow());
912 }
913
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()));
920
921 m_previewJob = job;
922 }
923
924 void KFileItemModelRolesUpdater::updateChangedItems()
925 {
926 if (m_state == Paused) {
927 return;
928 }
929
930 if (m_changedItems.isEmpty()) {
931 return;
932 }
933
934 m_finishedItems -= m_changedItems;
935
936 if (m_resolvableRoles.contains(m_model->sortRole())) {
937 m_pendingSortRoleItems += m_changedItems;
938
939 if (m_state != ResolvingSortRole) {
940 // Stop the preview job if necessary, and trigger the
941 // asynchronous determination of the sort role.
942 killPreviewJob();
943 m_state = ResolvingSortRole;
944 QTimer::singleShot(0, this, SLOT(resolveNextSortRole()));
945 }
946
947 return;
948 }
949
950 QList<int> visibleChangedIndexes;
951 QList<int> invisibleChangedIndexes;
952
953 foreach (const KFileItem& item, m_changedItems) {
954 const int index = m_model->index(item);
955
956 if (index < 0) {
957 m_changedItems.remove(item);
958 continue;
959 }
960
961 if (index >= m_firstVisibleIndex && index <= m_lastVisibleIndex) {
962 visibleChangedIndexes.append(index);
963 } else {
964 invisibleChangedIndexes.append(index);
965 }
966 }
967
968 std::sort(visibleChangedIndexes.begin(), visibleChangedIndexes.end());
969
970 if (m_previewShown) {
971 foreach (int index, visibleChangedIndexes) {
972 m_pendingPreviewItems.append(m_model->fileItem(index));
973 }
974
975 foreach (int index, invisibleChangedIndexes) {
976 m_pendingPreviewItems.append(m_model->fileItem(index));
977 }
978
979 if (!m_previewJob) {
980 startPreviewJob();
981 }
982 } else {
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()));
989 }
990 }
991 }
992
993 void KFileItemModelRolesUpdater::applySortRole(int index)
994 {
995 QHash<QByteArray, QVariant> data;
996 const KFileItem item = m_model->fileItem(index);
997
998 if (m_model->sortRole() == "type") {
999 if (!item.isMimeTypeKnown()) {
1000 item.determineMimeType();
1001 }
1002
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));
1007 } else {
1008 // Probably the sort role is a Nepomuk role - just determine all roles.
1009 data = rolesData(item);
1010 }
1011
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>)));
1017 }
1018
1019 void KFileItemModelRolesUpdater::applySortProgressToModel()
1020 {
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);
1025 }
1026
1027 bool KFileItemModelRolesUpdater::applyResolvedRoles(const KFileItem& item, ResolveHint hint)
1028 {
1029 if (item.isNull()) {
1030 return false;
1031 }
1032
1033 const bool resolveAll = (hint == ResolveAll);
1034
1035 bool iconChanged = false;
1036 if (!item.isMimeTypeKnown() || !item.isFinalIconKnown()) {
1037 item.determineMimeType();
1038 iconChanged = true;
1039 } else {
1040 const int index = m_model->index(item);
1041 if (!m_model->data(index).contains("iconName")) {
1042 iconChanged = true;
1043 }
1044 }
1045
1046 if (iconChanged || resolveAll || m_clearPreviews) {
1047 const int index = m_model->index(item);
1048 if (index < 0) {
1049 return false;
1050 }
1051
1052 QHash<QByteArray, QVariant> data;
1053 if (resolveAll) {
1054 data = rolesData(item);
1055 }
1056
1057 data.insert("iconName", item.iconName());
1058
1059 if (m_clearPreviews) {
1060 data.insert("iconPixmap", QPixmap());
1061 }
1062
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>)));
1068 return true;
1069 }
1070
1071 return false;
1072 }
1073
1074 QHash<QByteArray, QVariant> KFileItemModelRolesUpdater::rolesData(const KFileItem& item)
1075 {
1076 QHash<QByteArray, QVariant> data;
1077
1078 const bool getSizeRole = m_roles.contains("size");
1079 const bool getIsExpandableRole = m_roles.contains("isExpandable");
1080
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
1089 }
1090 }
1091
1092 if (m_roles.contains("type")) {
1093 data.insert("type", item.mimeComment());
1094 }
1095
1096 data.insert("iconOverlays", item.overlays());
1097
1098 #ifdef HAVE_NEPOMUK
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()) {
1104 it.next();
1105 data.insert(it.key(), it.value());
1106 }
1107
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();
1114 }
1115 if (!uri.isEmpty() && !m_nepomukUriItems.contains(uri)) {
1116 m_nepomukResourceWatcher->addResource(resource);
1117
1118 if (m_nepomukUriItems.isEmpty()) {
1119 m_nepomukResourceWatcher->start();
1120 }
1121
1122 m_nepomukUriItems.insert(uri, item.url());
1123 }
1124 }
1125 #endif
1126
1127 return data;
1128 }
1129
1130 void KFileItemModelRolesUpdater::updateAllPreviews()
1131 {
1132 if (m_state == Paused) {
1133 m_previewChangedDuringPausing = true;
1134 } else {
1135 m_finishedItems.clear();
1136 startUpdating();
1137 }
1138 }
1139
1140 void KFileItemModelRolesUpdater::killPreviewJob()
1141 {
1142 if (m_previewJob) {
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();
1150 m_previewJob = 0;
1151 m_pendingPreviewItems.clear();
1152 }
1153 }
1154
1155 QList<int> KFileItemModelRolesUpdater::indexesToResolve() const
1156 {
1157 const int count = m_model->count();
1158
1159 QList<int> result;
1160 result.reserve(ResolveAllItemsLimit);
1161
1162 // Add visible items.
1163 for (int i = m_firstVisibleIndex; i <= m_lastVisibleIndex; ++i) {
1164 result.append(i);
1165 }
1166
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);
1171
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) {
1175 result.append(i);
1176 }
1177
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) {
1181 result.append(i);
1182 }
1183
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) {
1187 result.append(i);
1188 }
1189
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) {
1193 result.append(i);
1194 }
1195
1196 // Continue adding items until ResolveAllItemsLimit is reached.
1197 int remainingItems = ResolveAllItemsLimit - result.count();
1198
1199 for (int i = endExtendedVisibleRange + 1; i < beginLastPage && remainingItems > 0; ++i) {
1200 result.append(i);
1201 --remainingItems;
1202 }
1203
1204 for (int i = beginExtendedVisibleRange - 1; i > endFirstPage && remainingItems > 0; --i) {
1205 result.append(i);
1206 --remainingItems;
1207 }
1208
1209 return result;
1210 }
1211
1212 #include "kfileitemmodelrolesupdater.moc"