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