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