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