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