]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemmodelrolesupdater.cpp
Allow showing Nepomuk metadata inside views
[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 #include "kpixmapmodifier_p.h"
24
25 #include <KConfig>
26 #include <KConfigGroup>
27 #include <KDebug>
28 #include <KFileItem>
29 #include <KGlobal>
30 #include <KIO/PreviewJob>
31 #include <QPainter>
32 #include <QPixmap>
33 #include <QElapsedTimer>
34 #include <QTimer>
35
36 #ifdef HAVE_NEPOMUK
37 #include "knepomukrolesprovider_p.h"
38 #endif
39
40 // Required includes for subItemsCount():
41 #ifdef Q_WS_WIN
42 #include <QDir>
43 #else
44 #include <dirent.h>
45 #include <QFile>
46 #endif
47
48 // #define KFILEITEMMODELROLESUPDATER_DEBUG
49
50 namespace {
51 // Maximum time in ms that the KFileItemModelRolesUpdater
52 // may perform a blocking operation
53 const int MaxBlockTimeout = 200;
54
55 // Maximum number of items that will get resolved synchronously.
56 // The value should roughly represent the number of maximum visible
57 // items, as it does not make sense to resolve more items synchronously
58 // and probably reach the MaxBlockTimeout because of invisible items.
59 const int MaxResolveItemsCount = 100;
60 }
61
62 KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QObject* parent) :
63 QObject(parent),
64 m_paused(false),
65 m_previewChangedDuringPausing(false),
66 m_iconSizeChangedDuringPausing(false),
67 m_rolesChangedDuringPausing(false),
68 m_previewShown(false),
69 m_clearPreviews(false),
70 m_model(model),
71 m_iconSize(),
72 m_firstVisibleIndex(0),
73 m_lastVisibleIndex(-1),
74 m_roles(),
75 m_enabledPlugins(),
76 m_pendingVisibleItems(),
77 m_pendingInvisibleItems(),
78 m_previewJobs(),
79 m_changedItemsTimer(0),
80 m_changedItems()
81 #ifdef HAVE_NEPOMUK
82 , m_resolveNepomukRoles(false)
83 #endif
84
85 {
86 Q_ASSERT(model);
87
88 const KConfigGroup globalConfig(KGlobal::config(), "PreviewSettings");
89 m_enabledPlugins = globalConfig.readEntry("Plugins", QStringList()
90 << "directorythumbnail"
91 << "imagethumbnail"
92 << "jpegthumbnail");
93
94 connect(m_model, SIGNAL(itemsInserted(KItemRangeList)),
95 this, SLOT(slotItemsInserted(KItemRangeList)));
96 connect(m_model, SIGNAL(itemsRemoved(KItemRangeList)),
97 this, SLOT(slotItemsRemoved(KItemRangeList)));
98 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
99 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
100
101 // Use a timer to prevent that each call of slotItemsChanged() results in a synchronous
102 // resolving of the roles. Postpone the resolving until no update has been done for 1 second.
103 m_changedItemsTimer = new QTimer(this);
104 m_changedItemsTimer->setInterval(1000);
105 m_changedItemsTimer->setSingleShot(true);
106 connect(m_changedItemsTimer, SIGNAL(timeout()), this, SLOT(resolveChangedItems()));
107 }
108
109 KFileItemModelRolesUpdater::~KFileItemModelRolesUpdater()
110 {
111 }
112
113 void KFileItemModelRolesUpdater::setIconSize(const QSize& size)
114 {
115 if (size != m_iconSize) {
116 m_iconSize = size;
117 if (m_paused) {
118 m_iconSizeChangedDuringPausing = true;
119 } else if (m_previewShown) {
120 // An icon size change requires the regenerating of
121 // all previews
122 sortAndResolveAllRoles();
123 } else {
124 sortAndResolvePendingRoles();
125 }
126 }
127 }
128
129 QSize KFileItemModelRolesUpdater::iconSize() const
130 {
131 return m_iconSize;
132 }
133
134 void KFileItemModelRolesUpdater::setVisibleIndexRange(int index, int count)
135 {
136 if (index < 0) {
137 index = 0;
138 }
139 if (count < 0) {
140 count = 0;
141 }
142
143 if (index == m_firstVisibleIndex && count == m_lastVisibleIndex - m_firstVisibleIndex + 1) {
144 // The range has not been changed
145 return;
146 }
147
148 m_firstVisibleIndex = index;
149 m_lastVisibleIndex = qMin(index + count - 1, m_model->count() - 1);
150
151 if (hasPendingRoles() && !m_paused) {
152 sortAndResolvePendingRoles();
153 }
154 }
155
156 void KFileItemModelRolesUpdater::setPreviewShown(bool show)
157 {
158 if (show == m_previewShown) {
159 return;
160 }
161
162 m_previewShown = show;
163 if (!show) {
164 m_clearPreviews = true;
165 }
166
167 if (m_paused) {
168 m_previewChangedDuringPausing = true;
169 } else {
170 sortAndResolveAllRoles();
171 }
172 }
173
174 bool KFileItemModelRolesUpdater::isPreviewShown() const
175 {
176 return m_previewShown;
177 }
178
179 void KFileItemModelRolesUpdater::setEnabledPlugins(const QStringList& list)
180 {
181 if (m_enabledPlugins == list) {
182 return;
183 }
184
185 m_enabledPlugins = list;
186 if (m_previewShown) {
187 if (m_paused) {
188 m_previewChangedDuringPausing = true;
189 } else {
190 sortAndResolveAllRoles();
191 }
192 }
193 }
194
195 void KFileItemModelRolesUpdater::setPaused(bool paused)
196 {
197 if (paused == m_paused) {
198 return;
199 }
200
201 m_paused = paused;
202 if (paused) {
203 if (hasPendingRoles()) {
204 foreach (KJob* job, m_previewJobs) {
205 job->kill();
206 }
207 Q_ASSERT(m_previewJobs.isEmpty());
208 }
209 } else {
210 const bool resolveAll = (m_iconSizeChangedDuringPausing && m_previewShown) ||
211 m_previewChangedDuringPausing ||
212 m_rolesChangedDuringPausing;
213 if (resolveAll) {
214 sortAndResolveAllRoles();
215 } else {
216 sortAndResolvePendingRoles();
217 }
218
219 m_iconSizeChangedDuringPausing = false;
220 m_previewChangedDuringPausing = false;
221 m_rolesChangedDuringPausing = false;
222 }
223 }
224
225 void KFileItemModelRolesUpdater::setRoles(const QSet<QByteArray>& roles)
226 {
227 if (m_roles != roles) {
228 m_roles = roles;
229
230 #ifdef HAVE_NEPOMUK
231 // Check whether there is at least one role that must be resolved
232 // with the help of Nepomuk. If this is the case, m_resolveNepomukRoles
233 // will be set to true and the (quite expensive) resolving will be done
234 // in KFileItemModelRolesUpdater::rolesData().
235 const KNepomukRolesProvider& rolesProvider = KNepomukRolesProvider::instance();
236 m_resolveNepomukRoles = false;
237 QSetIterator<QByteArray> it(roles);
238 while (it.hasNext()) {
239 const QByteArray& role = it.next();
240 if (rolesProvider.isNepomukRole(role)) {
241 m_resolveNepomukRoles = true;
242 break;
243 }
244 }
245 #endif
246
247 if (m_paused) {
248 m_rolesChangedDuringPausing = true;
249 } else {
250 sortAndResolveAllRoles();
251 }
252 }
253 }
254
255 QSet<QByteArray> KFileItemModelRolesUpdater::roles() const
256 {
257 return m_roles;
258 }
259
260 bool KFileItemModelRolesUpdater::isPaused() const
261 {
262 return m_paused;
263 }
264
265 QStringList KFileItemModelRolesUpdater::enabledPlugins() const
266 {
267 return m_enabledPlugins;
268 }
269
270 void KFileItemModelRolesUpdater::slotItemsInserted(const KItemRangeList& itemRanges)
271 {
272 startUpdating(itemRanges);
273 }
274
275 void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList& itemRanges)
276 {
277 Q_UNUSED(itemRanges);
278 m_firstVisibleIndex = 0;
279 m_lastVisibleIndex = -1;
280 if (!hasPendingRoles()) {
281 return;
282 }
283
284 if (m_model->count() == 0) {
285 // Most probably a directory change is done. Clear all pending items
286 // and also kill all ongoing preview-jobs.
287 resetPendingRoles();
288
289 m_changedItems.clear();
290 m_changedItemsTimer->stop();
291 } else {
292 // Remove all items from m_pendingVisibleItems and m_pendingInvisibleItems
293 // that are not part of the model anymore. The items from m_changedItems
294 // don't need to be handled here, removed items are just skipped in
295 // resolveChangedItems().
296 for (int i = 0; i <= 1; ++i) {
297 QSet<KFileItem>& pendingItems = (i == 0) ? m_pendingVisibleItems : m_pendingInvisibleItems;
298 QMutableSetIterator<KFileItem> it(pendingItems);
299 while (it.hasNext()) {
300 const KFileItem item = it.next();
301 if (m_model->index(item) < 0) {
302 pendingItems.remove(item);
303 }
304 }
305 }
306 }
307 }
308
309 void KFileItemModelRolesUpdater::slotItemsChanged(const KItemRangeList& itemRanges,
310 const QSet<QByteArray>& roles)
311 {
312 Q_UNUSED(roles);
313
314 if (m_changedItemsTimer->isActive()) {
315 // A call of slotItemsChanged() has been done recently. Postpone the resolving
316 // of the roles until the timer has exceeded.
317 foreach (const KItemRange& itemRange, itemRanges) {
318 int index = itemRange.index;
319 for (int count = itemRange.count; count > 0; --count) {
320 m_changedItems.insert(m_model->fileItem(index));
321 ++index;
322 }
323 }
324 } else {
325 // No call of slotItemsChanged() has been done recently, resolve the roles now.
326 startUpdating(itemRanges);
327 }
328 m_changedItemsTimer->start();
329 }
330
331 void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem& item, const QPixmap& pixmap)
332 {
333 m_pendingVisibleItems.remove(item);
334 m_pendingInvisibleItems.remove(item);
335
336 const int index = m_model->index(item);
337 if (index < 0) {
338 return;
339 }
340
341 QPixmap scaledPixmap = pixmap;
342
343 const QString mimeType = item.mimetype();
344 const int slashIndex = mimeType.indexOf(QLatin1Char('/'));
345 const QString mimeTypeGroup = mimeType.left(slashIndex);
346 if (mimeTypeGroup == QLatin1String("image")) {
347 KPixmapModifier::applyFrame(scaledPixmap, m_iconSize);
348 } else {
349 KPixmapModifier::scale(scaledPixmap, m_iconSize);
350 }
351
352 QHash<QByteArray, QVariant> data = rolesData(item);
353 data.insert("iconPixmap", scaledPixmap);
354
355 disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
356 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
357 m_model->setData(index, data);
358 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
359 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
360 }
361
362 void KFileItemModelRolesUpdater::slotPreviewFailed(const KFileItem& item)
363 {
364 m_pendingVisibleItems.remove(item);
365 m_pendingInvisibleItems.remove(item);
366
367 const bool clearPreviews = m_clearPreviews;
368 m_clearPreviews = true;
369 applyResolvedRoles(item, ResolveAll);
370 m_clearPreviews = clearPreviews;
371 }
372
373 void KFileItemModelRolesUpdater::slotPreviewJobFinished(KJob* job)
374 {
375 #ifdef KFILEITEMMODELROLESUPDATER_DEBUG
376 kDebug() << "Preview job finished. Pending visible:" << m_pendingVisibleItems.count() << "invisible:" << m_pendingInvisibleItems.count();
377 #endif
378
379 m_previewJobs.removeOne(job);
380 if (!m_previewJobs.isEmpty() || !hasPendingRoles()) {
381 return;
382 }
383
384 const KFileItemList visibleItems = sortedItems(m_pendingVisibleItems);
385 startPreviewJob(visibleItems + m_pendingInvisibleItems.toList());
386 }
387
388 void KFileItemModelRolesUpdater::resolveNextPendingRoles()
389 {
390 if (m_paused) {
391 return;
392 }
393
394 if (m_previewShown) {
395 // The preview has been turned on since the last run. Skip
396 // resolving further pending roles as this is done as soon
397 // as a preview has been received.
398 return;
399 }
400
401 int resolvedCount = 0;
402 bool changed = false;
403 for (int i = 0; i <= 1; ++i) {
404 QSet<KFileItem>& pendingItems = (i == 0) ? m_pendingVisibleItems : m_pendingInvisibleItems;
405 QSetIterator<KFileItem> it(pendingItems);
406 while (it.hasNext() && !changed && resolvedCount < MaxResolveItemsCount) {
407 const KFileItem item = it.next();
408 pendingItems.remove(item);
409 changed = applyResolvedRoles(item, ResolveAll);
410 ++resolvedCount;
411 }
412 }
413
414 if (hasPendingRoles()) {
415 QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
416 } else {
417 m_clearPreviews = false;
418 }
419
420 #ifdef KFILEITEMMODELROLESUPDATER_DEBUG
421 static int callCount = 0;
422 ++callCount;
423 if (callCount % 100 == 0) {
424 kDebug() << "Remaining visible roles to resolve:" << m_pendingVisibleItems.count()
425 << "invisible:" << m_pendingInvisibleItems.count();
426 }
427 #endif
428 }
429
430 void KFileItemModelRolesUpdater::resolveChangedItems()
431 {
432 if (m_changedItems.isEmpty()) {
433 return;
434 }
435
436 KItemRangeList itemRanges;
437
438 QSetIterator<KFileItem> it(m_changedItems);
439 while (it.hasNext()) {
440 const KFileItem& item = it.next();
441 const int index = m_model->index(item);
442 if (index >= 0) {
443 itemRanges.append(KItemRange(index, 1));
444 }
445 }
446 m_changedItems.clear();
447
448 startUpdating(itemRanges);
449 }
450
451 void KFileItemModelRolesUpdater::startUpdating(const KItemRangeList& itemRanges)
452 {
453 // If no valid index range is given assume that all items are visible.
454 // A cleanup will be done later as soon as the index range has been set.
455 const bool hasValidIndexRange = (m_lastVisibleIndex >= 0);
456
457 if (hasValidIndexRange) {
458 // Move all current pending visible items that are not visible anymore
459 // to the pending invisible items.
460 QSetIterator<KFileItem> it(m_pendingVisibleItems);
461 while (it.hasNext()) {
462 const KFileItem item = it.next();
463 const int index = m_model->index(item);
464 if (index < m_firstVisibleIndex || index > m_lastVisibleIndex) {
465 m_pendingVisibleItems.remove(item);
466 m_pendingInvisibleItems.insert(item);
467 }
468 }
469 }
470
471 int rangesCount = 0;
472
473 foreach (const KItemRange& range, itemRanges) {
474 rangesCount += range.count;
475
476 // Add the inserted items to the pending visible and invisible items
477 const int lastIndex = range.index + range.count - 1;
478 for (int i = range.index; i <= lastIndex; ++i) {
479 const KFileItem item = m_model->fileItem(i);
480 if (!hasValidIndexRange || (i >= m_firstVisibleIndex && i <= m_lastVisibleIndex)) {
481 m_pendingVisibleItems.insert(item);
482 } else {
483 m_pendingInvisibleItems.insert(item);
484 }
485 }
486 }
487
488 resolvePendingRoles();
489 }
490
491 void KFileItemModelRolesUpdater::startPreviewJob(const KFileItemList& items)
492 {
493 if (items.isEmpty() || m_paused) {
494 return;
495 }
496
497 // PreviewJob internally caches items always with the size of
498 // 128 x 128 pixels or 256 x 256 pixels. A (slow) downscaling is done
499 // by PreviewJob if a smaller size is requested. For images KFileItemModelRolesUpdater must
500 // do a downscaling anyhow because of the frame, so in this case only the provided
501 // cache sizes are requested.
502 const QSize cacheSize = (m_iconSize.width() > 128) || (m_iconSize.height() > 128)
503 ? QSize(256, 256) : QSize(128, 128);
504
505 // KIO::filePreview() will request the MIME-type of all passed items, which (in the
506 // worst case) might block the application for several seconds. To prevent such
507 // a blocking the MIME-type of the items will determined until the MaxBlockTimeout
508 // has been reached and only those items will get passed. As soon as the MIME-type
509 // has been resolved once KIO::filePreview() can already access the resolved
510 // MIME-type in a fast way.
511 QElapsedTimer timer;
512 timer.start();
513 KFileItemList itemSubSet;
514 for (int i = 0; i < items.count(); ++i) {
515 KFileItem item = items.at(i);
516 item.determineMimeType();
517 itemSubSet.append(items.at(i));
518 if (timer.elapsed() > MaxBlockTimeout) {
519 #ifdef KFILEITEMMODELROLESUPDATER_DEBUG
520 kDebug() << "Maximum time of" << MaxBlockTimeout << "ms exceeded, creating only previews for"
521 << (i + 1) << "items," << (items.count() - (i + 1)) << "will be resolved later";
522 #endif
523 break;
524 }
525 }
526 KJob* job = KIO::filePreview(itemSubSet, cacheSize, &m_enabledPlugins);
527
528 connect(job, SIGNAL(gotPreview(KFileItem,QPixmap)),
529 this, SLOT(slotGotPreview(KFileItem,QPixmap)));
530 connect(job, SIGNAL(failed(KFileItem)),
531 this, SLOT(slotPreviewFailed(KFileItem)));
532 connect(job, SIGNAL(finished(KJob*)),
533 this, SLOT(slotPreviewJobFinished(KJob*)));
534
535 m_previewJobs.append(job);
536 }
537
538
539 bool KFileItemModelRolesUpdater::hasPendingRoles() const
540 {
541 return !m_pendingVisibleItems.isEmpty() || !m_pendingInvisibleItems.isEmpty();
542 }
543
544 void KFileItemModelRolesUpdater::resolvePendingRoles()
545 {
546 int resolvedCount = 0;
547
548 const bool hasSlowRoles = m_previewShown
549 || m_roles.contains("size")
550 || m_roles.contains("type")
551 || m_roles.contains("isExpandable");
552 const ResolveHint resolveHint = hasSlowRoles ? ResolveFast : ResolveAll;
553
554 // Resolving the MIME type can be expensive. Assure that not more than MaxBlockTimeout ms are
555 // spend for resolving them synchronously. Usually this is more than enough to determine
556 // all visible items, but there are corner cases where this limit gets easily exceeded.
557 QElapsedTimer timer;
558 timer.start();
559
560 // Resolve the MIME type of all visible items
561 QSetIterator<KFileItem> visibleIt(m_pendingVisibleItems);
562 while (visibleIt.hasNext()) {
563 const KFileItem item = visibleIt.next();
564 applyResolvedRoles(item, resolveHint);
565 if (!hasSlowRoles) {
566 Q_ASSERT(!m_pendingInvisibleItems.contains(item));
567 // All roles have been resolved already by applyResolvedRoles()
568 m_pendingVisibleItems.remove(item);
569 }
570 ++resolvedCount;
571
572 if (timer.elapsed() > MaxBlockTimeout) {
573 break;
574 }
575 }
576
577 // Resolve the MIME type of the invisible items at least until the timeout
578 // has been exceeded or the maximum number of items has been reached
579 KFileItemList invisibleItems;
580 if (m_lastVisibleIndex >= 0) {
581 // The visible range is valid, don't care about the order how the MIME
582 // type of invisible items get resolved
583 invisibleItems = m_pendingInvisibleItems.toList();
584 } else {
585 // The visible range is temporary invalid (e.g. happens when loading
586 // a directory) so take care to sort the currently invisible items where
587 // a part will get visible later
588 invisibleItems = sortedItems(m_pendingInvisibleItems);
589 }
590
591 int index = 0;
592 while (resolvedCount < MaxResolveItemsCount && index < invisibleItems.count() && timer.elapsed() <= MaxBlockTimeout) {
593 const KFileItem item = invisibleItems.at(index);
594 applyResolvedRoles(item, resolveHint);
595
596 if (!hasSlowRoles) {
597 // All roles have been resolved already by applyResolvedRoles()
598 m_pendingInvisibleItems.remove(item);
599 }
600 ++index;
601 ++resolvedCount;
602 }
603
604 if (m_previewShown) {
605 KFileItemList items = sortedItems(m_pendingVisibleItems);
606 items += invisibleItems;
607 startPreviewJob(items);
608 } else {
609 QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
610 }
611
612 #ifdef KFILEITEMMODELROLESUPDATER_DEBUG
613 if (timer.elapsed() > MaxBlockTimeout) {
614 kDebug() << "Maximum time of" << MaxBlockTimeout
615 << "ms exceeded, skipping items... Remaining visible:" << m_pendingVisibleItems.count()
616 << "invisible:" << m_pendingInvisibleItems.count();
617 }
618 kDebug() << "[TIME] Resolved pending roles:" << timer.elapsed();
619 #endif
620 }
621
622 void KFileItemModelRolesUpdater::resetPendingRoles()
623 {
624 m_pendingVisibleItems.clear();
625 m_pendingInvisibleItems.clear();
626
627 foreach (KJob* job, m_previewJobs) {
628 job->kill();
629 }
630 Q_ASSERT(m_previewJobs.isEmpty());
631 }
632
633 void KFileItemModelRolesUpdater::sortAndResolveAllRoles()
634 {
635 if (m_paused) {
636 return;
637 }
638
639 resetPendingRoles();
640 Q_ASSERT(m_pendingVisibleItems.isEmpty());
641 Q_ASSERT(m_pendingInvisibleItems.isEmpty());
642
643 if (m_model->count() == 0) {
644 return;
645 }
646
647 // Determine all visible items
648 Q_ASSERT(m_firstVisibleIndex >= 0);
649 for (int i = m_firstVisibleIndex; i <= m_lastVisibleIndex; ++i) {
650 const KFileItem item = m_model->fileItem(i);
651 if (!item.isNull()) {
652 m_pendingVisibleItems.insert(item);
653 }
654 }
655
656 // Determine all invisible items
657 for (int i = 0; i < m_firstVisibleIndex; ++i) {
658 const KFileItem item = m_model->fileItem(i);
659 if (!item.isNull()) {
660 m_pendingInvisibleItems.insert(item);
661 }
662 }
663 for (int i = m_lastVisibleIndex + 1; i < m_model->count(); ++i) {
664 const KFileItem item = m_model->fileItem(i);
665 if (!item.isNull()) {
666 m_pendingInvisibleItems.insert(item);
667 }
668 }
669
670 resolvePendingRoles();
671 }
672
673 void KFileItemModelRolesUpdater::sortAndResolvePendingRoles()
674 {
675 Q_ASSERT(!m_paused);
676 if (m_model->count() == 0) {
677 return;
678 }
679
680 // If no valid index range is given assume that all items are visible.
681 // A cleanup will be done later as soon as the index range has been set.
682 const bool hasValidIndexRange = (m_lastVisibleIndex >= 0);
683
684 // Trigger a preview generation of all pending items. Assure that the visible
685 // pending items get generated first.
686 QSet<KFileItem> pendingItems;
687 pendingItems += m_pendingVisibleItems;
688 pendingItems += m_pendingInvisibleItems;
689
690 resetPendingRoles();
691 Q_ASSERT(m_pendingVisibleItems.isEmpty());
692 Q_ASSERT(m_pendingInvisibleItems.isEmpty());
693
694 QSetIterator<KFileItem> it(pendingItems);
695 while (it.hasNext()) {
696 const KFileItem item = it.next();
697 if (item.isNull()) {
698 continue;
699 }
700
701 const int index = m_model->index(item);
702 if (!hasValidIndexRange || (index >= m_firstVisibleIndex && index <= m_lastVisibleIndex)) {
703 m_pendingVisibleItems.insert(item);
704 } else {
705 m_pendingInvisibleItems.insert(item);
706 }
707 }
708
709 resolvePendingRoles();
710 }
711
712 bool KFileItemModelRolesUpdater::applyResolvedRoles(const KFileItem& item, ResolveHint hint)
713 {
714 if (item.isNull()) {
715 return false;
716 }
717
718 const bool resolveAll = (hint == ResolveAll);
719
720 bool mimeTypeChanged = false;
721 if (!item.isMimeTypeKnown()) {
722 item.determineMimeType();
723 mimeTypeChanged = true;
724 }
725
726 if (mimeTypeChanged || resolveAll || m_clearPreviews) {
727 const int index = m_model->index(item);
728 if (index < 0) {
729 return false;
730 }
731
732 QHash<QByteArray, QVariant> data;
733 if (resolveAll) {
734 data = rolesData(item);
735 }
736
737 data.insert("iconName", item.iconName());
738
739 if (m_clearPreviews) {
740 data.insert("iconPixmap", QPixmap());
741 }
742
743 disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
744 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
745 m_model->setData(index, data);
746 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
747 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
748 return true;
749 }
750
751 return false;
752 }
753
754 QHash<QByteArray, QVariant> KFileItemModelRolesUpdater::rolesData(const KFileItem& item) const
755 {
756 QHash<QByteArray, QVariant> data;
757
758 const bool getSizeRole = m_roles.contains("size");
759 const bool getIsExpandableRole = m_roles.contains("isExpandable");
760
761 if ((getSizeRole || getIsExpandableRole) && item.isDir()) {
762 if (item.isLocalFile()) {
763 const QString path = item.localPath();
764 const int count = subItemsCount(path);
765 if (getSizeRole) {
766 data.insert("size", count);
767 }
768 if (getIsExpandableRole) {
769 data.insert("isExpandable", count > 0);
770 }
771 } else if (getSizeRole) {
772 data.insert("size", -1); // -1 indicates an unknown number of items
773 }
774 }
775
776 if (m_roles.contains("type")) {
777 data.insert("type", item.mimeComment());
778 }
779
780 data.insert("iconOverlays", item.overlays());
781
782 #ifdef HAVE_NEPOMUK
783 if (m_resolveNepomukRoles) {
784 const KNepomukRolesProvider& rolesProvider = KNepomukRolesProvider::instance();
785 QHashIterator<QByteArray, QVariant> it(rolesProvider.roleValues(item.url(), m_roles));
786 while (it.hasNext()) {
787 it.next();
788 data.insert(it.key(), it.value());
789 }
790 }
791 #endif
792
793 return data;
794 }
795
796 KFileItemList KFileItemModelRolesUpdater::sortedItems(const QSet<KFileItem>& items) const
797 {
798 KFileItemList itemList;
799 if (items.isEmpty()) {
800 return itemList;
801 }
802
803 #ifdef KFILEITEMMODELROLESUPDATER_DEBUG
804 QElapsedTimer timer;
805 timer.start();
806 #endif
807
808 QList<int> indexes;
809 indexes.reserve(items.count());
810
811 QSetIterator<KFileItem> it(items);
812 while (it.hasNext()) {
813 const KFileItem item = it.next();
814 const int index = m_model->index(item);
815 if (index >= 0) {
816 indexes.append(index);
817 }
818 }
819 qSort(indexes);
820
821 itemList.reserve(items.count());
822 foreach (int index, indexes) {
823 itemList.append(m_model->fileItem(index));
824 }
825
826 #ifdef KFILEITEMMODELROLESUPDATER_DEBUG
827 kDebug() << "[TIME] Sorting of items:" << timer.elapsed();
828 #endif
829 return itemList;
830 }
831
832 int KFileItemModelRolesUpdater::subItemsCount(const QString& path) const
833 {
834 const bool countHiddenFiles = m_model->showHiddenFiles();
835 const bool showFoldersOnly = m_model->showFoldersOnly();
836
837 #ifdef Q_WS_WIN
838 QDir dir(path);
839 QDir::Filters filters = QDir::NoDotAndDotDot | QDir::System;
840 if (countHiddenFiles) {
841 filters |= QDir::Hidden;
842 }
843 if (showFoldersOnly) {
844 filters |= QDir::Dirs;
845 } else {
846 filters |= QDir::AllEntries;
847 }
848 return dir.entryList(filters).count();
849 #else
850 // Taken from kdelibs/kio/kio/kdirmodel.cpp
851 // Copyright (C) 2006 David Faure <faure@kde.org>
852
853 int count = -1;
854 DIR* dir = ::opendir(QFile::encodeName(path));
855 if (dir) {
856 count = 0;
857 struct dirent *dirEntry = 0;
858 while ((dirEntry = ::readdir(dir))) { // krazy:exclude=syscalls
859 if (dirEntry->d_name[0] == '.') {
860 if (dirEntry->d_name[1] == '\0' || !countHiddenFiles) {
861 // Skip "." or hidden files
862 continue;
863 }
864 if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') {
865 // Skip ".."
866 continue;
867 }
868 }
869
870 // If only directories are counted, consider an unknown file type also
871 // as directory instead of trying to do an expensive stat() (see bug 292642).
872 if (!showFoldersOnly || dirEntry->d_type == DT_DIR || dirEntry->d_type == DT_UNKNOWN) {
873 ++count;
874 }
875 }
876 ::closedir(dir);
877 }
878 return count;
879 #endif
880 }
881
882 #include "kfileitemmodelrolesupdater.moc"