]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemmodelrolesupdater.cpp
Prevent flickering when updating items
[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 1 second.
95 m_changedItemsTimer = new QTimer(this);
96 m_changedItemsTimer->setInterval(1000);
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 m_changedItems.clear();
422
423 startUpdating(itemRanges);
424 }
425
426 void KFileItemModelRolesUpdater::startUpdating(const KItemRangeList& itemRanges)
427 {
428 // If no valid index range is given assume that all items are visible.
429 // A cleanup will be done later as soon as the index range has been set.
430 const bool hasValidIndexRange = (m_lastVisibleIndex >= 0);
431
432 if (hasValidIndexRange) {
433 // Move all current pending visible items that are not visible anymore
434 // to the pending invisible items.
435 QSetIterator<KFileItem> it(m_pendingVisibleItems);
436 while (it.hasNext()) {
437 const KFileItem item = it.next();
438 const int index = m_model->index(item);
439 if (index < m_firstVisibleIndex || index > m_lastVisibleIndex) {
440 m_pendingVisibleItems.remove(item);
441 m_pendingInvisibleItems.insert(item);
442 }
443 }
444 }
445
446 int rangesCount = 0;
447
448 foreach (const KItemRange& range, itemRanges) {
449 rangesCount += range.count;
450
451 // Add the inserted items to the pending visible and invisible items
452 const int lastIndex = range.index + range.count - 1;
453 for (int i = range.index; i <= lastIndex; ++i) {
454 const KFileItem item = m_model->fileItem(i);
455 if (!hasValidIndexRange || (i >= m_firstVisibleIndex && i <= m_lastVisibleIndex)) {
456 m_pendingVisibleItems.insert(item);
457 } else {
458 m_pendingInvisibleItems.insert(item);
459 }
460 }
461 }
462
463 resolvePendingRoles();
464 }
465
466 void KFileItemModelRolesUpdater::startPreviewJob(const KFileItemList& items)
467 {
468 if (items.isEmpty() || m_paused) {
469 return;
470 }
471
472 // PreviewJob internally caches items always with the size of
473 // 128 x 128 pixels or 256 x 256 pixels. A (slow) downscaling is done
474 // by PreviewJob if a smaller size is requested. For images KFileItemModelRolesUpdater must
475 // do a downscaling anyhow because of the frame, so in this case only the provided
476 // cache sizes are requested.
477 const QSize cacheSize = (m_iconSize.width() > 128) || (m_iconSize.height() > 128)
478 ? QSize(256, 256) : QSize(128, 128);
479
480 // KIO::filePreview() will request the MIME-type of all passed items, which (in the
481 // worst case) might block the application for several seconds. To prevent such
482 // a blocking the MIME-type of the items will determined until the MaxBlockTimeout
483 // has been reached and only those items will get passed. As soon as the MIME-type
484 // has been resolved once KIO::filePreview() can already access the resolved
485 // MIME-type in a fast way.
486 QElapsedTimer timer;
487 timer.start();
488 KFileItemList itemSubSet;
489 for (int i = 0; i < items.count(); ++i) {
490 KFileItem item = items.at(i);
491 item.determineMimeType();
492 itemSubSet.append(items.at(i));
493 if (timer.elapsed() > MaxBlockTimeout) {
494 #ifdef KFILEITEMMODELROLESUPDATER_DEBUG
495 kDebug() << "Maximum time of" << MaxBlockTimeout << "ms exceeded, creating only previews for"
496 << (i + 1) << "items," << (items.count() - (i + 1)) << "will be resolved later";
497 #endif
498 break;
499 }
500 }
501 KJob* job = KIO::filePreview(itemSubSet, cacheSize, &m_enabledPlugins);
502
503 connect(job, SIGNAL(gotPreview(KFileItem,QPixmap)),
504 this, SLOT(slotGotPreview(KFileItem,QPixmap)));
505 connect(job, SIGNAL(failed(KFileItem)),
506 this, SLOT(slotPreviewFailed(KFileItem)));
507 connect(job, SIGNAL(finished(KJob*)),
508 this, SLOT(slotPreviewJobFinished(KJob*)));
509
510 m_previewJobs.append(job);
511 }
512
513
514 bool KFileItemModelRolesUpdater::hasPendingRoles() const
515 {
516 return !m_pendingVisibleItems.isEmpty() || !m_pendingInvisibleItems.isEmpty();
517 }
518
519 void KFileItemModelRolesUpdater::resolvePendingRoles()
520 {
521 int resolvedCount = 0;
522
523 const bool hasSlowRoles = m_previewShown
524 || m_roles.contains("size")
525 || m_roles.contains("type")
526 || m_roles.contains("isExpandable");
527 const ResolveHint resolveHint = hasSlowRoles ? ResolveFast : ResolveAll;
528
529 // Resolving the MIME type can be expensive. Assure that not more than MaxBlockTimeout ms are
530 // spend for resolving them synchronously. Usually this is more than enough to determine
531 // all visible items, but there are corner cases where this limit gets easily exceeded.
532 QElapsedTimer timer;
533 timer.start();
534
535 // Resolve the MIME type of all visible items
536 QSetIterator<KFileItem> visibleIt(m_pendingVisibleItems);
537 while (visibleIt.hasNext()) {
538 const KFileItem item = visibleIt.next();
539 applyResolvedRoles(item, resolveHint);
540 if (!hasSlowRoles) {
541 Q_ASSERT(!m_pendingInvisibleItems.contains(item));
542 // All roles have been resolved already by applyResolvedRoles()
543 m_pendingVisibleItems.remove(item);
544 }
545 ++resolvedCount;
546
547 if (timer.elapsed() > MaxBlockTimeout) {
548 break;
549 }
550 }
551
552 // Resolve the MIME type of the invisible items at least until the timeout
553 // has been exceeded or the maximum number of items has been reached
554 KFileItemList invisibleItems;
555 if (m_lastVisibleIndex >= 0) {
556 // The visible range is valid, don't care about the order how the MIME
557 // type of invisible items get resolved
558 invisibleItems = m_pendingInvisibleItems.toList();
559 } else {
560 // The visible range is temporary invalid (e.g. happens when loading
561 // a directory) so take care to sort the currently invisible items where
562 // a part will get visible later
563 invisibleItems = sortedItems(m_pendingInvisibleItems);
564 }
565
566 int index = 0;
567 while (resolvedCount < MaxResolveItemsCount && index < invisibleItems.count() && timer.elapsed() <= MaxBlockTimeout) {
568 const KFileItem item = invisibleItems.at(index);
569 applyResolvedRoles(item, resolveHint);
570
571 if (!hasSlowRoles) {
572 // All roles have been resolved already by applyResolvedRoles()
573 m_pendingInvisibleItems.remove(item);
574 }
575 ++index;
576 ++resolvedCount;
577 }
578
579 if (m_previewShown) {
580 KFileItemList items = sortedItems(m_pendingVisibleItems);
581 items += invisibleItems;
582 startPreviewJob(items);
583 } else {
584 QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
585 }
586
587 #ifdef KFILEITEMMODELROLESUPDATER_DEBUG
588 if (timer.elapsed() > MaxBlockTimeout) {
589 kDebug() << "Maximum time of" << MaxBlockTimeout
590 << "ms exceeded, skipping items... Remaining visible:" << m_pendingVisibleItems.count()
591 << "invisible:" << m_pendingInvisibleItems.count();
592 }
593 kDebug() << "[TIME] Resolved pending roles:" << timer.elapsed();
594 #endif
595 }
596
597 void KFileItemModelRolesUpdater::resetPendingRoles()
598 {
599 m_pendingVisibleItems.clear();
600 m_pendingInvisibleItems.clear();
601
602 foreach (KJob* job, m_previewJobs) {
603 job->kill();
604 }
605 Q_ASSERT(m_previewJobs.isEmpty());
606 }
607
608 void KFileItemModelRolesUpdater::sortAndResolveAllRoles()
609 {
610 if (m_paused) {
611 return;
612 }
613
614 resetPendingRoles();
615 Q_ASSERT(m_pendingVisibleItems.isEmpty());
616 Q_ASSERT(m_pendingInvisibleItems.isEmpty());
617
618 if (m_model->count() == 0) {
619 return;
620 }
621
622 // Determine all visible items
623 Q_ASSERT(m_firstVisibleIndex >= 0);
624 for (int i = m_firstVisibleIndex; i <= m_lastVisibleIndex; ++i) {
625 const KFileItem item = m_model->fileItem(i);
626 if (!item.isNull()) {
627 m_pendingVisibleItems.insert(item);
628 }
629 }
630
631 // Determine all invisible items
632 for (int i = 0; i < m_firstVisibleIndex; ++i) {
633 const KFileItem item = m_model->fileItem(i);
634 if (!item.isNull()) {
635 m_pendingInvisibleItems.insert(item);
636 }
637 }
638 for (int i = m_lastVisibleIndex + 1; i < m_model->count(); ++i) {
639 const KFileItem item = m_model->fileItem(i);
640 if (!item.isNull()) {
641 m_pendingInvisibleItems.insert(item);
642 }
643 }
644
645 resolvePendingRoles();
646 }
647
648 void KFileItemModelRolesUpdater::sortAndResolvePendingRoles()
649 {
650 Q_ASSERT(!m_paused);
651 if (m_model->count() == 0) {
652 return;
653 }
654
655 // If no valid index range is given assume that all items are visible.
656 // A cleanup will be done later as soon as the index range has been set.
657 const bool hasValidIndexRange = (m_lastVisibleIndex >= 0);
658
659 // Trigger a preview generation of all pending items. Assure that the visible
660 // pending items get generated first.
661 QSet<KFileItem> pendingItems;
662 pendingItems += m_pendingVisibleItems;
663 pendingItems += m_pendingInvisibleItems;
664
665 resetPendingRoles();
666 Q_ASSERT(m_pendingVisibleItems.isEmpty());
667 Q_ASSERT(m_pendingInvisibleItems.isEmpty());
668
669 QSetIterator<KFileItem> it(pendingItems);
670 while (it.hasNext()) {
671 const KFileItem item = it.next();
672 if (item.isNull()) {
673 continue;
674 }
675
676 const int index = m_model->index(item);
677 if (!hasValidIndexRange || (index >= m_firstVisibleIndex && index <= m_lastVisibleIndex)) {
678 m_pendingVisibleItems.insert(item);
679 } else {
680 m_pendingInvisibleItems.insert(item);
681 }
682 }
683
684 resolvePendingRoles();
685 }
686
687 bool KFileItemModelRolesUpdater::applyResolvedRoles(const KFileItem& item, ResolveHint hint)
688 {
689 if (item.isNull()) {
690 return false;
691 }
692
693 const bool resolveAll = (hint == ResolveAll);
694
695 bool mimeTypeChanged = false;
696 if (!item.isMimeTypeKnown()) {
697 item.determineMimeType();
698 mimeTypeChanged = true;
699 }
700
701 if (mimeTypeChanged || resolveAll || m_clearPreviews) {
702 const int index = m_model->index(item);
703 if (index < 0) {
704 return false;
705 }
706
707 QHash<QByteArray, QVariant> data;
708 if (resolveAll) {
709 data = rolesData(item);
710 }
711
712 data.insert("iconName", item.iconName());
713
714 if (m_clearPreviews) {
715 data.insert("iconPixmap", QPixmap());
716 }
717
718 disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
719 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
720 m_model->setData(index, data);
721 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
722 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
723 return true;
724 }
725
726 return false;
727 }
728
729 QHash<QByteArray, QVariant> KFileItemModelRolesUpdater::rolesData(const KFileItem& item) const
730 {
731 QHash<QByteArray, QVariant> data;
732
733 const bool getSizeRole = m_roles.contains("size");
734 const bool getIsExpandableRole = m_roles.contains("isExpandable");
735
736 if ((getSizeRole || getIsExpandableRole) && item.isDir()) {
737 if (item.isLocalFile()) {
738 const QString path = item.localPath();
739 const int count = subItemsCount(path);
740 if (getSizeRole) {
741 data.insert("size", count);
742 }
743 if (getIsExpandableRole) {
744 data.insert("isExpandable", count > 0);
745 }
746 } else if (getSizeRole) {
747 data.insert("size", -1); // -1 indicates an unknown number of items
748 }
749 }
750
751 if (m_roles.contains("type")) {
752 data.insert("type", item.mimeComment());
753 }
754
755 data.insert("iconOverlays", item.overlays());
756
757 return data;
758 }
759
760 KFileItemList KFileItemModelRolesUpdater::sortedItems(const QSet<KFileItem>& items) const
761 {
762 KFileItemList itemList;
763 if (items.isEmpty()) {
764 return itemList;
765 }
766
767 #ifdef KFILEITEMMODELROLESUPDATER_DEBUG
768 QElapsedTimer timer;
769 timer.start();
770 #endif
771
772 QList<int> indexes;
773 indexes.reserve(items.count());
774
775 QSetIterator<KFileItem> it(items);
776 while (it.hasNext()) {
777 const KFileItem item = it.next();
778 const int index = m_model->index(item);
779 if (index >= 0) {
780 indexes.append(index);
781 }
782 }
783 qSort(indexes);
784
785 itemList.reserve(items.count());
786 foreach (int index, indexes) {
787 itemList.append(m_model->fileItem(index));
788 }
789
790 #ifdef KFILEITEMMODELROLESUPDATER_DEBUG
791 kDebug() << "[TIME] Sorting of items:" << timer.elapsed();
792 #endif
793 return itemList;
794 }
795
796 int KFileItemModelRolesUpdater::subItemsCount(const QString& path) const
797 {
798 const bool countHiddenFiles = m_model->showHiddenFiles();
799 const bool showFoldersOnly = m_model->showFoldersOnly();
800
801 #ifdef Q_WS_WIN
802 QDir dir(path);
803 QDir::Filters filters = QDir::NoDotAndDotDot | QDir::System;
804 if (countHiddenFiles) {
805 filters |= QDir::Hidden;
806 }
807 if (showFoldersOnly) {
808 filters |= QDir::Dirs;
809 } else {
810 filters |= QDir::AllEntries;
811 }
812 return dir.entryList(filters).count();
813 #else
814 // Taken from kdelibs/kio/kio/kdirmodel.cpp
815 // Copyright (C) 2006 David Faure <faure@kde.org>
816
817 int count = -1;
818 DIR* dir = ::opendir(QFile::encodeName(path));
819 if (dir) {
820 count = 0;
821 struct dirent *dirEntry = 0;
822 while ((dirEntry = ::readdir(dir))) { // krazy:exclude=syscalls
823 if (dirEntry->d_name[0] == '.') {
824 if (dirEntry->d_name[1] == '\0' || !countHiddenFiles) {
825 // Skip "." or hidden files
826 continue;
827 }
828 if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') {
829 // Skip ".."
830 continue;
831 }
832 }
833
834 // If only directories are counted, consider an unknown file type also
835 // as directory instead of trying to do an expensive stat() (see bug 292642).
836 if (!showFoldersOnly || dirEntry->d_type == DT_DIR || dirEntry->d_type == DT_UNKNOWN) {
837 ++count;
838 }
839 }
840 ::closedir(dir);
841 }
842 return count;
843 #endif
844 }
845
846 #include "kfileitemmodelrolesupdater.moc"