]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemmodelrolesupdater.cpp
Fix memory leak in KFileItemModel
[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
24 #include <KConfig>
25 #include <KConfigGroup>
26 #include <KDebug>
27 #include <KDirWatch>
28 #include <KFileItem>
29 #include <KGlobal>
30 #include <KIO/JobUiDelegate>
31 #include <KIO/PreviewJob>
32
33 #include "private/kpixmapmodifier.h"
34
35 #include <QApplication>
36 #include <QPainter>
37 #include <QPixmap>
38 #include <QElapsedTimer>
39 #include <QTimer>
40
41 #include <algorithm>
42
43 #ifdef HAVE_NEPOMUK
44 #include "private/knepomukrolesprovider.h"
45 #include <Nepomuk2/ResourceWatcher>
46 #include <Nepomuk2/ResourceManager>
47 #endif
48
49 // Required includes for subItemsCount():
50 #ifdef Q_WS_WIN
51 #include <QDir>
52 #else
53 #include <dirent.h>
54 #include <QFile>
55 #endif
56
57 // #define KFILEITEMMODELROLESUPDATER_DEBUG
58
59 namespace {
60 // Maximum time in ms that the KFileItemModelRolesUpdater
61 // may perform a blocking operation
62 const int MaxBlockTimeout = 200;
63
64 // If the number of items is smaller than ResolveAllItemsLimit,
65 // the roles of all items will be resolved.
66 const int ResolveAllItemsLimit = 500;
67
68 // Not only the visible area, but up to ReadAheadPages before and after
69 // this area will be resolved.
70 const int ReadAheadPages = 5;
71 }
72
73 KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QObject* parent) :
74 QObject(parent),
75 m_state(Idle),
76 m_previewChangedDuringPausing(false),
77 m_iconSizeChangedDuringPausing(false),
78 m_rolesChangedDuringPausing(false),
79 m_previewShown(false),
80 m_enlargeSmallPreviews(true),
81 m_clearPreviews(false),
82 m_finishedItems(),
83 m_model(model),
84 m_iconSize(),
85 m_firstVisibleIndex(0),
86 m_lastVisibleIndex(-1),
87 m_maximumVisibleItems(50),
88 m_roles(),
89 m_resolvableRoles(),
90 m_enabledPlugins(),
91 m_pendingSortRoleItems(),
92 m_hasUnknownIcons(false),
93 m_firstIndexWithoutIcon(0),
94 m_pendingIndexes(),
95 m_pendingPreviewItems(),
96 m_previewJob(),
97 m_recentlyChangedItemsTimer(0),
98 m_recentlyChangedItems(),
99 m_changedItems(),
100 m_dirWatcher(0),
101 m_watchedDirs()
102 #ifdef HAVE_NEPOMUK
103 , m_nepomukResourceWatcher(0),
104 m_nepomukUriItems()
105 #endif
106 {
107 Q_ASSERT(model);
108
109 const KConfigGroup globalConfig(KGlobal::config(), "PreviewSettings");
110 m_enabledPlugins = globalConfig.readEntry("Plugins", QStringList()
111 << "directorythumbnail"
112 << "imagethumbnail"
113 << "jpegthumbnail");
114
115 connect(m_model, SIGNAL(itemsInserted(KItemRangeList)),
116 this, SLOT(slotItemsInserted(KItemRangeList)));
117 connect(m_model, SIGNAL(itemsRemoved(KItemRangeList)),
118 this, SLOT(slotItemsRemoved(KItemRangeList)));
119 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
120 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
121 connect(m_model, SIGNAL(itemsMoved(KItemRange,QList<int>)),
122 this, SLOT(slotItemsMoved(KItemRange,QList<int>)));
123 connect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)),
124 this, SLOT(slotSortRoleChanged(QByteArray,QByteArray)));
125
126 // Use a timer to prevent that each call of slotItemsChanged() results in a synchronous
127 // resolving of the roles. Postpone the resolving until no update has been done for 1 second.
128 m_recentlyChangedItemsTimer = new QTimer(this);
129 m_recentlyChangedItemsTimer->setInterval(1000);
130 m_recentlyChangedItemsTimer->setSingleShot(true);
131 connect(m_recentlyChangedItemsTimer, SIGNAL(timeout()), this, SLOT(resolveRecentlyChangedItems()));
132
133 m_resolvableRoles.insert("size");
134 m_resolvableRoles.insert("type");
135 m_resolvableRoles.insert("isExpandable");
136 #ifdef HAVE_NEPOMUK
137 m_resolvableRoles += KNepomukRolesProvider::instance().roles();
138 #endif
139
140 // When folders are expandable or the item-count is shown for folders, it is necessary
141 // to watch the number of items of the sub-folder to be able to react on changes.
142 m_dirWatcher = new KDirWatch(this);
143 connect(m_dirWatcher, SIGNAL(dirty(QString)), this, SLOT(slotDirWatchDirty(QString)));
144 }
145
146 KFileItemModelRolesUpdater::~KFileItemModelRolesUpdater()
147 {
148 killPreviewJob();
149 }
150
151 void KFileItemModelRolesUpdater::setIconSize(const QSize& size)
152 {
153 if (size != m_iconSize) {
154 m_iconSize = size;
155 if (m_state == Paused) {
156 m_iconSizeChangedDuringPausing = true;
157 } else if (m_previewShown) {
158 // An icon size change requires the regenerating of
159 // all previews
160 m_finishedItems.clear();
161 startUpdating();
162 }
163 }
164 }
165
166 QSize KFileItemModelRolesUpdater::iconSize() const
167 {
168 return m_iconSize;
169 }
170
171 void KFileItemModelRolesUpdater::setVisibleIndexRange(int index, int count)
172 {
173 if (index < 0) {
174 index = 0;
175 }
176 if (count < 0) {
177 count = 0;
178 }
179
180 if (index == m_firstVisibleIndex && count == m_lastVisibleIndex - m_firstVisibleIndex + 1) {
181 // The range has not been changed
182 return;
183 }
184
185 m_firstVisibleIndex = index;
186 m_lastVisibleIndex = qMin(index + count - 1, m_model->count() - 1);
187
188 startUpdating();
189 }
190
191 void KFileItemModelRolesUpdater::setMaximumVisibleItems(int count)
192 {
193 m_maximumVisibleItems = count;
194 }
195
196 void KFileItemModelRolesUpdater::setPreviewsShown(bool show)
197 {
198 if (show == m_previewShown) {
199 return;
200 }
201
202 m_previewShown = show;
203 if (!show) {
204 m_clearPreviews = true;
205 }
206
207 updateAllPreviews();
208 }
209
210 bool KFileItemModelRolesUpdater::previewsShown() const
211 {
212 return m_previewShown;
213 }
214
215 void KFileItemModelRolesUpdater::setEnlargeSmallPreviews(bool enlarge)
216 {
217 if (enlarge != m_enlargeSmallPreviews) {
218 m_enlargeSmallPreviews = enlarge;
219 if (m_previewShown) {
220 updateAllPreviews();
221 }
222 }
223 }
224
225 bool KFileItemModelRolesUpdater::enlargeSmallPreviews() const
226 {
227 return m_enlargeSmallPreviews;
228 }
229
230 void KFileItemModelRolesUpdater::setEnabledPlugins(const QStringList& list)
231 {
232 if (m_enabledPlugins != list) {
233 m_enabledPlugins = list;
234 if (m_previewShown) {
235 updateAllPreviews();
236 }
237 }
238 }
239
240 void KFileItemModelRolesUpdater::setPaused(bool paused)
241 {
242 if (paused == (m_state == Paused)) {
243 return;
244 }
245
246 if (paused) {
247 m_state = Paused;
248 killPreviewJob();
249 } else {
250 const bool updatePreviews = (m_iconSizeChangedDuringPausing && m_previewShown) ||
251 m_previewChangedDuringPausing;
252 const bool resolveAll = updatePreviews || m_rolesChangedDuringPausing;
253 if (resolveAll) {
254 m_finishedItems.clear();
255 }
256
257 m_iconSizeChangedDuringPausing = false;
258 m_previewChangedDuringPausing = false;
259 m_rolesChangedDuringPausing = false;
260
261 if (!m_pendingSortRoleItems.isEmpty()) {
262 m_state = ResolvingSortRole;
263 resolveNextSortRole();
264 } else {
265 m_state = Idle;
266 }
267
268 startUpdating();
269 }
270 }
271
272 void KFileItemModelRolesUpdater::setRoles(const QSet<QByteArray>& roles)
273 {
274 if (m_roles != roles) {
275 m_roles = roles;
276
277 #ifdef HAVE_NEPOMUK
278 if (Nepomuk2::ResourceManager::instance()->initialized()) {
279 // Check whether there is at least one role that must be resolved
280 // with the help of Nepomuk. If this is the case, a (quite expensive)
281 // resolving will be done in KFileItemModelRolesUpdater::rolesData() and
282 // the role gets watched for changes.
283 const KNepomukRolesProvider& rolesProvider = KNepomukRolesProvider::instance();
284 bool hasNepomukRole = false;
285 QSetIterator<QByteArray> it(roles);
286 while (it.hasNext()) {
287 const QByteArray& role = it.next();
288 if (rolesProvider.roles().contains(role)) {
289 hasNepomukRole = true;
290 break;
291 }
292 }
293
294 if (hasNepomukRole && !m_nepomukResourceWatcher) {
295 Q_ASSERT(m_nepomukUriItems.isEmpty());
296
297 m_nepomukResourceWatcher = new Nepomuk2::ResourceWatcher(this);
298 connect(m_nepomukResourceWatcher, SIGNAL(propertyChanged(Nepomuk2::Resource,Nepomuk2::Types::Property,QVariantList,QVariantList)),
299 this, SLOT(applyChangedNepomukRoles(Nepomuk2::Resource)));
300 } else if (!hasNepomukRole && m_nepomukResourceWatcher) {
301 delete m_nepomukResourceWatcher;
302 m_nepomukResourceWatcher = 0;
303 m_nepomukUriItems.clear();
304 }
305 }
306 #endif
307
308 if (m_state == Paused) {
309 m_rolesChangedDuringPausing = true;
310 } else {
311 startUpdating();
312 }
313 }
314 }
315
316 QSet<QByteArray> KFileItemModelRolesUpdater::roles() const
317 {
318 return m_roles;
319 }
320
321 bool KFileItemModelRolesUpdater::isPaused() const
322 {
323 return m_state == Paused;
324 }
325
326 QStringList KFileItemModelRolesUpdater::enabledPlugins() const
327 {
328 return m_enabledPlugins;
329 }
330
331 void KFileItemModelRolesUpdater::slotItemsInserted(const KItemRangeList& itemRanges)
332 {
333 QElapsedTimer timer;
334 timer.start();
335
336 const int firstInsertedIndex = itemRanges.first().index;
337 m_firstIndexWithoutIcon = qMin(m_firstIndexWithoutIcon, firstInsertedIndex);
338 m_hasUnknownIcons = true;
339
340 // Determine the sort role synchronously for as many items as possible.
341 if (m_resolvableRoles.contains(m_model->sortRole())) {
342 int insertedCount = 0;
343 foreach (const KItemRange& range, itemRanges) {
344 const int lastIndex = insertedCount + range.index + range.count - 1;
345 for (int i = insertedCount + range.index; i <= lastIndex; ++i) {
346 if (timer.elapsed() < MaxBlockTimeout) {
347 applySortRole(i);
348 } else {
349 m_pendingSortRoleItems.insert(m_model->fileItem(i));
350 }
351 }
352 insertedCount += range.count;
353 }
354
355 applySortProgressToModel();
356
357 // If there are still items whose sort role is unknown, check if the
358 // asynchronous determination of the sort role is already in progress,
359 // and start it if that is not the case.
360 if (!m_pendingSortRoleItems.isEmpty() && m_state != ResolvingSortRole) {
361 killPreviewJob();
362 m_state = ResolvingSortRole;
363 resolveNextSortRole();
364 }
365 }
366
367 startUpdating();
368 }
369
370 void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList& itemRanges)
371 {
372 Q_UNUSED(itemRanges);
373
374 const bool allItemsRemoved = (m_model->count() == 0);
375
376 if (m_hasUnknownIcons) {
377 const int firstRemovedIndex = itemRanges.first().index;
378 m_firstIndexWithoutIcon = qMin(m_firstIndexWithoutIcon, firstRemovedIndex);
379 }
380
381 if (!m_watchedDirs.isEmpty()) {
382 // Don't let KDirWatch watch for removed items
383 if (allItemsRemoved) {
384 foreach (const QString& path, m_watchedDirs) {
385 m_dirWatcher->removeDir(path);
386 }
387 m_watchedDirs.clear();
388 } else {
389 QMutableSetIterator<QString> it(m_watchedDirs);
390 while (it.hasNext()) {
391 const QString& path = it.next();
392 if (m_model->index(KUrl(path)) < 0) {
393 m_dirWatcher->removeDir(path);
394 it.remove();
395 }
396 }
397 }
398 }
399
400 #ifdef HAVE_NEPOMUK
401 if (m_nepomukResourceWatcher) {
402 // Don't let the ResourceWatcher watch for removed items
403 if (allItemsRemoved) {
404 m_nepomukResourceWatcher->setResources(QList<Nepomuk2::Resource>());
405 m_nepomukResourceWatcher->stop();
406 m_nepomukUriItems.clear();
407 } else {
408 QList<Nepomuk2::Resource> newResources;
409 const QList<Nepomuk2::Resource> oldResources = m_nepomukResourceWatcher->resources();
410 foreach (const Nepomuk2::Resource& resource, oldResources) {
411 const QUrl uri = resource.uri();
412 const KUrl itemUrl = m_nepomukUriItems.value(uri);
413 if (m_model->index(itemUrl) >= 0) {
414 newResources.append(resource);
415 } else {
416 m_nepomukUriItems.remove(uri);
417 }
418 }
419 m_nepomukResourceWatcher->setResources(newResources);
420 if (newResources.isEmpty()) {
421 Q_ASSERT(m_nepomukUriItems.isEmpty());
422 m_nepomukResourceWatcher->stop();
423 }
424 }
425 }
426 #endif
427
428 if (allItemsRemoved) {
429 m_state = Idle;
430
431 m_finishedItems.clear();
432 m_pendingSortRoleItems.clear();
433 m_pendingIndexes.clear();
434 m_pendingPreviewItems.clear();
435 m_recentlyChangedItems.clear();
436 m_recentlyChangedItemsTimer->stop();
437 m_changedItems.clear();
438
439 killPreviewJob();
440 } else {
441 // Only remove the items from m_finishedItems. They will be removed
442 // from the other sets later on.
443 QSet<KFileItem>::iterator it = m_finishedItems.begin();
444 while (it != m_finishedItems.end()) {
445 if (m_model->index(*it) < 0) {
446 it = m_finishedItems.erase(it);
447 } else {
448 ++it;
449 }
450 }
451
452 // The visible items might have changed.
453 startUpdating();
454 }
455 }
456
457 void KFileItemModelRolesUpdater::slotItemsMoved(const KItemRange& itemRange, QList<int> movedToIndexes)
458 {
459 Q_UNUSED(itemRange);
460 Q_UNUSED(movedToIndexes);
461
462 if (m_hasUnknownIcons) {
463 const int firstMovedIndex = itemRange.index;
464 m_firstIndexWithoutIcon = qMin(m_firstIndexWithoutIcon, firstMovedIndex);
465 }
466
467 // The visible items might have changed.
468 startUpdating();
469 }
470
471 void KFileItemModelRolesUpdater::slotItemsChanged(const KItemRangeList& itemRanges,
472 const QSet<QByteArray>& roles)
473 {
474 Q_UNUSED(roles);
475
476 // Find out if slotItemsChanged() has been done recently. If that is the
477 // case, resolving the roles is postponed until a timer has exceeded
478 // to prevent expensive repeated updates if files are updated frequently.
479 const bool itemsChangedRecently = m_recentlyChangedItemsTimer->isActive();
480
481 QSet<KFileItem>& targetSet = itemsChangedRecently ? m_recentlyChangedItems : m_changedItems;
482
483 foreach (const KItemRange& itemRange, itemRanges) {
484 int index = itemRange.index;
485 for (int count = itemRange.count; count > 0; --count) {
486 const KFileItem item = m_model->fileItem(index);
487 targetSet.insert(item);
488 ++index;
489 }
490 }
491
492 m_recentlyChangedItemsTimer->start();
493
494 if (!itemsChangedRecently) {
495 updateChangedItems();
496 }
497 }
498
499 void KFileItemModelRolesUpdater::slotSortRoleChanged(const QByteArray& current,
500 const QByteArray& previous)
501 {
502 Q_UNUSED(current);
503 Q_UNUSED(previous);
504
505 if (m_resolvableRoles.contains(current)) {
506 m_pendingSortRoleItems.clear();
507 m_finishedItems.clear();
508
509 const int count = m_model->count();
510 QElapsedTimer timer;
511 timer.start();
512
513 // Determine the sort role synchronously for as many items as possible.
514 for (int index = 0; index < count; ++index) {
515 if (timer.elapsed() < MaxBlockTimeout) {
516 applySortRole(index);
517 } else {
518 m_pendingSortRoleItems.insert(m_model->fileItem(index));
519 }
520 }
521
522 applySortProgressToModel();
523
524 if (!m_pendingSortRoleItems.isEmpty()) {
525 // Trigger the asynchronous determination of the sort role.
526 killPreviewJob();
527 m_state = ResolvingSortRole;
528 resolveNextSortRole();
529 }
530 } else {
531 m_state = Idle;
532 m_pendingSortRoleItems.clear();
533 applySortProgressToModel();
534 }
535 }
536
537 void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem& item, const QPixmap& pixmap)
538 {
539 if (m_state != PreviewJobRunning) {
540 return;
541 }
542
543 m_changedItems.remove(item);
544
545 const int index = m_model->index(item);
546 if (index < 0) {
547 return;
548 }
549
550 QPixmap scaledPixmap = pixmap;
551
552 const QString mimeType = item.mimetype();
553 const int slashIndex = mimeType.indexOf(QLatin1Char('/'));
554 const QString mimeTypeGroup = mimeType.left(slashIndex);
555 if (mimeTypeGroup == QLatin1String("image")) {
556 if (m_enlargeSmallPreviews) {
557 KPixmapModifier::applyFrame(scaledPixmap, m_iconSize);
558 } else {
559 // Assure that small previews don't get enlarged. Instead they
560 // should be shown centered within the frame.
561 const QSize contentSize = KPixmapModifier::sizeInsideFrame(m_iconSize);
562 const bool enlargingRequired = scaledPixmap.width() < contentSize.width() &&
563 scaledPixmap.height() < contentSize.height();
564 if (enlargingRequired) {
565 QSize frameSize = scaledPixmap.size();
566 frameSize.scale(m_iconSize, Qt::KeepAspectRatio);
567
568 QPixmap largeFrame(frameSize);
569 largeFrame.fill(Qt::transparent);
570
571 KPixmapModifier::applyFrame(largeFrame, frameSize);
572
573 QPainter painter(&largeFrame);
574 painter.drawPixmap((largeFrame.width() - scaledPixmap.width()) / 2,
575 (largeFrame.height() - scaledPixmap.height()) / 2,
576 scaledPixmap);
577 scaledPixmap = largeFrame;
578 } else {
579 // The image must be shrinked as it is too large to fit into
580 // the available icon size
581 KPixmapModifier::applyFrame(scaledPixmap, m_iconSize);
582 }
583 }
584 } else {
585 KPixmapModifier::scale(scaledPixmap, m_iconSize);
586 }
587
588 QHash<QByteArray, QVariant> data = rolesData(item);
589 data.insert("iconPixmap", scaledPixmap);
590
591 disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
592 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
593 m_model->setData(index, data);
594 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
595 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
596
597 m_finishedItems.insert(item);
598 }
599
600 void KFileItemModelRolesUpdater::slotPreviewFailed(const KFileItem& item)
601 {
602 if (m_state != PreviewJobRunning) {
603 return;
604 }
605
606 m_changedItems.remove(item);
607
608 const int index = m_model->index(item);
609 if (index >= 0) {
610 QHash<QByteArray, QVariant> data;
611 data.insert("iconPixmap", QPixmap());
612
613 disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
614 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
615 m_model->setData(index, data);
616 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
617 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
618
619 applyResolvedRoles(item, ResolveAll);
620 m_finishedItems.insert(item);
621 }
622 }
623
624 void KFileItemModelRolesUpdater::slotPreviewJobFinished()
625 {
626 m_previewJob = 0;
627
628 if (m_state != PreviewJobRunning) {
629 return;
630 }
631
632 m_state = Idle;
633
634 if (!m_pendingPreviewItems.isEmpty()) {
635 startPreviewJob();
636 } else {
637 if (!m_changedItems.isEmpty()) {
638 updateChangedItems();
639 }
640 }
641 }
642
643 void KFileItemModelRolesUpdater::resolveNextSortRole()
644 {
645 if (m_state != ResolvingSortRole) {
646 return;
647 }
648
649 QSet<KFileItem>::iterator it = m_pendingSortRoleItems.begin();
650 while (it != m_pendingSortRoleItems.end()) {
651 const KFileItem item = *it;
652 const int index = m_model->index(item);
653
654 // Continue if the sort role has already been determined for the
655 // item, and the item has not been changed recently.
656 if (!m_changedItems.contains(item) && m_model->data(index).contains(m_model->sortRole())) {
657 it = m_pendingSortRoleItems.erase(it);
658 continue;
659 }
660
661 applySortRole(index);
662 m_pendingSortRoleItems.erase(it);
663 break;
664 }
665
666 if (!m_pendingSortRoleItems.isEmpty()) {
667 applySortProgressToModel();
668 QTimer::singleShot(0, this, SLOT(resolveNextSortRole()));
669 } else {
670 m_state = Idle;
671
672 // Prevent that we try to update the items twice.
673 disconnect(m_model, SIGNAL(itemsMoved(KItemRange,QList<int>)),
674 this, SLOT(slotItemsMoved(KItemRange,QList<int>)));
675 applySortProgressToModel();
676 connect(m_model, SIGNAL(itemsMoved(KItemRange,QList<int>)),
677 this, SLOT(slotItemsMoved(KItemRange,QList<int>)));
678 startUpdating();
679 }
680 }
681
682 void KFileItemModelRolesUpdater::resolveNextPendingRoles()
683 {
684 if (m_state != ResolvingAllRoles) {
685 return;
686 }
687
688 while (!m_pendingIndexes.isEmpty()) {
689 const int index = m_pendingIndexes.takeFirst();
690 const KFileItem item = m_model->fileItem(index);
691
692 if (m_finishedItems.contains(item)) {
693 continue;
694 }
695
696 applyResolvedRoles(item, ResolveAll);
697 m_finishedItems.insert(item);
698 m_changedItems.remove(item);
699 break;
700 }
701
702 if (!m_pendingIndexes.isEmpty()) {
703 QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
704 } else {
705 m_state = Idle;
706
707 if (m_clearPreviews) {
708 // Only go through the list if there are items which might still have previews.
709 if (m_finishedItems.count() != m_model->count()) {
710 QHash<QByteArray, QVariant> data;
711 data.insert("iconPixmap", QPixmap());
712
713 disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
714 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
715 for (int index = 0; index <= m_model->count(); ++index) {
716 if (m_model->data(index).contains("iconPixmap")) {
717 m_model->setData(index, data);
718 }
719 }
720 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
721 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
722
723 }
724 m_clearPreviews = false;
725 }
726
727 if (!m_changedItems.isEmpty()) {
728 updateChangedItems();
729 }
730 }
731 }
732
733 void KFileItemModelRolesUpdater::resolveRecentlyChangedItems()
734 {
735 m_changedItems += m_recentlyChangedItems;
736 m_recentlyChangedItems.clear();
737 updateChangedItems();
738 }
739
740 void KFileItemModelRolesUpdater::applyChangedNepomukRoles(const Nepomuk2::Resource& resource)
741 {
742 #ifdef HAVE_NEPOMUK
743 if (!Nepomuk2::ResourceManager::instance()->initialized()) {
744 return;
745 }
746
747 const KUrl itemUrl = m_nepomukUriItems.value(resource.uri());
748 const KFileItem item = m_model->fileItem(itemUrl);
749
750 if (item.isNull()) {
751 // itemUrl is not in the model anymore, probably because
752 // the corresponding file has been deleted in the meantime.
753 return;
754 }
755
756 QHash<QByteArray, QVariant> data = rolesData(item);
757
758 const KNepomukRolesProvider& rolesProvider = KNepomukRolesProvider::instance();
759 QHashIterator<QByteArray, QVariant> it(rolesProvider.roleValues(resource, m_roles));
760 while (it.hasNext()) {
761 it.next();
762 data.insert(it.key(), it.value());
763 }
764
765 disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
766 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
767 const int index = m_model->index(item);
768 m_model->setData(index, data);
769 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
770 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
771 #else
772 #ifndef Q_CC_MSVC
773 Q_UNUSED(resource);
774 #endif
775 #endif
776 }
777
778 void KFileItemModelRolesUpdater::slotDirWatchDirty(const QString& path)
779 {
780 const bool getSizeRole = m_roles.contains("size");
781 const bool getIsExpandableRole = m_roles.contains("isExpandable");
782
783 if (getSizeRole || getIsExpandableRole) {
784 const int index = m_model->index(KUrl(path));
785 if (index >= 0) {
786 if (!m_model->fileItem(index).isDir()) {
787 // If INotify is used, KDirWatch issues the dirty() signal
788 // also for changed files inside the directory, even if we
789 // don't enable this behavior explicitly (see bug 309740).
790 return;
791 }
792
793 QHash<QByteArray, QVariant> data;
794
795 const int count = subItemsCount(path);
796 if (getSizeRole) {
797 data.insert("size", count);
798 }
799 if (getIsExpandableRole) {
800 data.insert("isExpandable", count > 0);
801 }
802
803 // Note that we do not block the itemsChanged signal here.
804 // This ensures that a new preview will be generated.
805 m_model->setData(index, data);
806 }
807 }
808 }
809
810 void KFileItemModelRolesUpdater::startUpdating()
811 {
812 if (m_state == Paused) {
813 return;
814 }
815
816 if (m_finishedItems.count() == m_model->count()) {
817 // All roles have been resolved already.
818 m_state = Idle;
819 return;
820 }
821
822 // Terminate all updates that are currently active.
823 killPreviewJob();
824 m_pendingIndexes.clear();
825
826 QElapsedTimer timer;
827 timer.start();
828
829 // Determine the icons for the visible items synchronously.
830 updateVisibleIcons();
831
832 // Try to do at least a fast icon loading (without determining the
833 // mime type) for all items, to reduce the risk that the user sees
834 // "unknown" icons when scrolling.
835 if (m_hasUnknownIcons) {
836 updateAllIconsFast(MaxBlockTimeout - timer.elapsed());
837 }
838
839 // A detailed update of the items in and near the visible area
840 // only makes sense if sorting is finished.
841 if (m_state == ResolvingSortRole) {
842 return;
843 }
844
845 // Start the preview job or the asynchronous resolving of all roles.
846 QList<int> indexes = indexesToResolve();
847
848 if (m_previewShown) {
849 m_pendingPreviewItems.clear();
850 m_pendingPreviewItems.reserve(indexes.count());
851
852 foreach (int index, indexes) {
853 const KFileItem item = m_model->fileItem(index);
854 if (!m_finishedItems.contains(item)) {
855 m_pendingPreviewItems.append(item);
856 }
857 }
858
859 startPreviewJob();
860 } else {
861 m_pendingIndexes = indexes;
862 // Trigger the asynchronous resolving of all roles.
863 m_state = ResolvingAllRoles;
864 QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
865 }
866 }
867
868 void KFileItemModelRolesUpdater::updateVisibleIcons()
869 {
870 int lastVisibleIndex = m_lastVisibleIndex;
871 if (lastVisibleIndex <= 0) {
872 // Guess a reasonable value for the last visible index if the view
873 // has not told us about the real value yet.
874 lastVisibleIndex = qMin(m_firstVisibleIndex + m_maximumVisibleItems, m_model->count() - 1);
875 if (lastVisibleIndex <= 0) {
876 lastVisibleIndex = qMin(200, m_model->count() - 1);
877 }
878 }
879
880 QElapsedTimer timer;
881 timer.start();
882
883 // Try to determine the final icons for all visible items.
884 int index;
885 for (index = m_firstVisibleIndex; index <= lastVisibleIndex && timer.elapsed() < MaxBlockTimeout; ++index) {
886 const KFileItem item = m_model->fileItem(index);
887 applyResolvedRoles(item, ResolveFast);
888 }
889
890 if (index > lastVisibleIndex) {
891 return;
892 }
893
894 // If this didn't work before MaxBlockTimeout was reached, at least
895 // prevent that the user sees 'unknown' icons.
896 disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
897 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
898
899 while (index <= lastVisibleIndex) {
900 if (!m_model->data(index).contains("iconName")) {
901 const KFileItem item = m_model->fileItem(index);
902 QHash<QByteArray, QVariant> data;
903 data.insert("iconName", item.iconName());
904 m_model->setData(index, data);
905 }
906 ++index;
907 }
908
909 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
910 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
911 }
912
913 void KFileItemModelRolesUpdater::updateAllIconsFast(int timeout)
914 {
915 if (timeout <= 0) {
916 return;
917 }
918
919 QElapsedTimer timer;
920 timer.start();
921
922 disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
923 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
924
925 const int count = m_model->count();
926 while (m_firstIndexWithoutIcon < count && timer.elapsed() < timeout) {
927 if (!m_model->data(m_firstIndexWithoutIcon).contains("iconName")) {
928 const KFileItem item = m_model->fileItem(m_firstIndexWithoutIcon);
929 QHash<QByteArray, QVariant> data;
930 data.insert("iconName", item.iconName());
931 m_model->setData(m_firstIndexWithoutIcon, data);
932 }
933 ++m_firstIndexWithoutIcon;
934 }
935
936 if (m_firstIndexWithoutIcon == count) {
937 m_hasUnknownIcons = false;
938 }
939
940 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
941 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
942 }
943
944 void KFileItemModelRolesUpdater::startPreviewJob()
945 {
946 m_state = PreviewJobRunning;
947
948 if (m_pendingPreviewItems.isEmpty()) {
949 QTimer::singleShot(0, this, SLOT(slotPreviewJobFinished()));
950 return;
951 }
952
953 // PreviewJob internally caches items always with the size of
954 // 128 x 128 pixels or 256 x 256 pixels. A (slow) downscaling is done
955 // by PreviewJob if a smaller size is requested. For images KFileItemModelRolesUpdater must
956 // do a downscaling anyhow because of the frame, so in this case only the provided
957 // cache sizes are requested.
958 const QSize cacheSize = (m_iconSize.width() > 128) || (m_iconSize.height() > 128)
959 ? QSize(256, 256) : QSize(128, 128);
960
961 // KIO::filePreview() will request the MIME-type of all passed items, which (in the
962 // worst case) might block the application for several seconds. To prevent such
963 // a blocking, we only pass items with known mime type to the preview job.
964 const int count = m_pendingPreviewItems.count();
965 KFileItemList itemSubSet;
966 itemSubSet.reserve(count);
967
968 if (m_pendingPreviewItems.first().isMimeTypeKnown()) {
969 // Some mime types are known already, probably because they were
970 // determined when loading the icons for the visible items. Start
971 // a preview job for all items at the beginning of the list which
972 // have a known mime type.
973 do {
974 itemSubSet.append(m_pendingPreviewItems.takeFirst());
975 } while (!m_pendingPreviewItems.isEmpty() && m_pendingPreviewItems.first().isMimeTypeKnown());
976 } else {
977 // Determine mime types for MaxBlockTimeout ms, and start a preview
978 // job for the corresponding items.
979 QElapsedTimer timer;
980 timer.start();
981
982 do {
983 const KFileItem item = m_pendingPreviewItems.takeFirst();
984 item.determineMimeType();
985 itemSubSet.append(item);
986 } while (!m_pendingPreviewItems.isEmpty() && timer.elapsed() < MaxBlockTimeout);
987 }
988
989 KIO::PreviewJob* job = new KIO::PreviewJob(itemSubSet, cacheSize, &m_enabledPlugins);
990
991 job->setIgnoreMaximumSize(itemSubSet.first().isLocalFile());
992 if (job->ui()) {
993 job->ui()->setWindow(qApp->activeWindow());
994 }
995
996 connect(job, SIGNAL(gotPreview(KFileItem,QPixmap)),
997 this, SLOT(slotGotPreview(KFileItem,QPixmap)));
998 connect(job, SIGNAL(failed(KFileItem)),
999 this, SLOT(slotPreviewFailed(KFileItem)));
1000 connect(job, SIGNAL(finished(KJob*)),
1001 this, SLOT(slotPreviewJobFinished()));
1002
1003 m_previewJob = job;
1004 }
1005
1006 void KFileItemModelRolesUpdater::updateChangedItems()
1007 {
1008 if (m_state == Paused) {
1009 return;
1010 }
1011
1012 if (m_changedItems.isEmpty()) {
1013 return;
1014 }
1015
1016 m_finishedItems -= m_changedItems;
1017
1018 if (m_resolvableRoles.contains(m_model->sortRole())) {
1019 m_pendingSortRoleItems += m_changedItems;
1020
1021 if (m_state != ResolvingSortRole) {
1022 // Stop the preview job if necessary, and trigger the
1023 // asynchronous determination of the sort role.
1024 killPreviewJob();
1025 m_state = ResolvingSortRole;
1026 QTimer::singleShot(0, this, SLOT(resolveNextSortRole()));
1027 }
1028
1029 return;
1030 }
1031
1032 QList<int> visibleChangedIndexes;
1033 QList<int> invisibleChangedIndexes;
1034
1035 foreach (const KFileItem& item, m_changedItems) {
1036 const int index = m_model->index(item);
1037
1038 if (index < 0) {
1039 m_changedItems.remove(item);
1040 continue;
1041 }
1042
1043 if (index >= m_firstVisibleIndex && index <= m_lastVisibleIndex) {
1044 visibleChangedIndexes.append(index);
1045 } else {
1046 invisibleChangedIndexes.append(index);
1047 }
1048 }
1049
1050 std::sort(visibleChangedIndexes.begin(), visibleChangedIndexes.end());
1051
1052 if (m_previewShown) {
1053 foreach (int index, visibleChangedIndexes) {
1054 m_pendingPreviewItems.append(m_model->fileItem(index));
1055 }
1056
1057 foreach (int index, invisibleChangedIndexes) {
1058 m_pendingPreviewItems.append(m_model->fileItem(index));
1059 }
1060
1061 if (!m_previewJob) {
1062 startPreviewJob();
1063 }
1064 } else {
1065 const bool resolvingInProgress = !m_pendingIndexes.isEmpty();
1066 m_pendingIndexes = visibleChangedIndexes + m_pendingIndexes + invisibleChangedIndexes;
1067 if (!resolvingInProgress) {
1068 // Trigger the asynchronous resolving of the changed roles.
1069 m_state = ResolvingAllRoles;
1070 QTimer::singleShot(0, this, SLOT(resolveNextPendingRoles()));
1071 }
1072 }
1073 }
1074
1075 void KFileItemModelRolesUpdater::applySortRole(int index)
1076 {
1077 QHash<QByteArray, QVariant> data;
1078 const KFileItem item = m_model->fileItem(index);
1079
1080 if (m_model->sortRole() == "type") {
1081 if (!item.isMimeTypeKnown()) {
1082 item.determineMimeType();
1083 }
1084
1085 data.insert("type", item.mimeComment());
1086 } else if (m_model->sortRole() == "size" && item.isLocalFile() && item.isDir()) {
1087 const QString path = item.localPath();
1088 data.insert("size", subItemsCount(path));
1089 } else {
1090 // Probably the sort role is a Nepomuk role - just determine all roles.
1091 data = rolesData(item);
1092 }
1093
1094 disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
1095 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
1096 m_model->setData(index, data);
1097 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
1098 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
1099 }
1100
1101 void KFileItemModelRolesUpdater::applySortProgressToModel()
1102 {
1103 // Inform the model about the progress of the resolved items,
1104 // so that it can give an indication when the sorting has been finished.
1105 const int resolvedCount = m_model->count() - m_pendingSortRoleItems.count();
1106 m_model->emitSortProgress(resolvedCount);
1107 }
1108
1109 bool KFileItemModelRolesUpdater::applyResolvedRoles(const KFileItem& item, ResolveHint hint)
1110 {
1111 if (item.isNull()) {
1112 return false;
1113 }
1114
1115 const bool resolveAll = (hint == ResolveAll);
1116
1117 bool iconChanged = false;
1118 if (!item.isMimeTypeKnown() || !item.isFinalIconKnown()) {
1119 item.determineMimeType();
1120 iconChanged = true;
1121 } else {
1122 const int index = m_model->index(item);
1123 if (!m_model->data(index).contains("iconName")) {
1124 iconChanged = true;
1125 }
1126 }
1127
1128 if (iconChanged || resolveAll || m_clearPreviews) {
1129 const int index = m_model->index(item);
1130 if (index < 0) {
1131 return false;
1132 }
1133
1134 QHash<QByteArray, QVariant> data;
1135 if (resolveAll) {
1136 data = rolesData(item);
1137 }
1138
1139 data.insert("iconName", item.iconName());
1140
1141 if (m_clearPreviews) {
1142 data.insert("iconPixmap", QPixmap());
1143 }
1144
1145 disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
1146 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
1147 m_model->setData(index, data);
1148 connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)),
1149 this, SLOT(slotItemsChanged(KItemRangeList,QSet<QByteArray>)));
1150 return true;
1151 }
1152
1153 return false;
1154 }
1155
1156 QHash<QByteArray, QVariant> KFileItemModelRolesUpdater::rolesData(const KFileItem& item) const
1157 {
1158 QHash<QByteArray, QVariant> data;
1159
1160 const bool getSizeRole = m_roles.contains("size");
1161 const bool getIsExpandableRole = m_roles.contains("isExpandable");
1162
1163 if ((getSizeRole || getIsExpandableRole) && item.isDir()) {
1164 if (item.isLocalFile()) {
1165 const QString path = item.localPath();
1166 const int count = subItemsCount(path);
1167 if (getSizeRole) {
1168 data.insert("size", count);
1169 }
1170 if (getIsExpandableRole) {
1171 data.insert("isExpandable", count > 0);
1172 }
1173
1174 if (!m_dirWatcher->contains(path)) {
1175 m_dirWatcher->addDir(path);
1176 m_watchedDirs.insert(path);
1177 }
1178 } else if (getSizeRole) {
1179 data.insert("size", -1); // -1 indicates an unknown number of items
1180 }
1181 }
1182
1183 if (m_roles.contains("type")) {
1184 data.insert("type", item.mimeComment());
1185 }
1186
1187 data.insert("iconOverlays", item.overlays());
1188
1189 #ifdef HAVE_NEPOMUK
1190 if (m_nepomukResourceWatcher) {
1191 const KNepomukRolesProvider& rolesProvider = KNepomukRolesProvider::instance();
1192 Nepomuk2::Resource resource(item.nepomukUri());
1193 QHashIterator<QByteArray, QVariant> it(rolesProvider.roleValues(resource, m_roles));
1194 while (it.hasNext()) {
1195 it.next();
1196 data.insert(it.key(), it.value());
1197 }
1198
1199 QUrl uri = resource.uri();
1200 if (uri.isEmpty()) {
1201 // TODO: Is there another way to explicitly create a resource?
1202 // We need a resource to be able to track it for changes.
1203 resource.setRating(0);
1204 uri = resource.uri();
1205 }
1206 if (!uri.isEmpty() && !m_nepomukUriItems.contains(uri)) {
1207 m_nepomukResourceWatcher->addResource(resource);
1208
1209 if (m_nepomukUriItems.isEmpty()) {
1210 m_nepomukResourceWatcher->start();
1211 }
1212
1213 m_nepomukUriItems.insert(uri, item.url());
1214 }
1215 }
1216 #endif
1217
1218 return data;
1219 }
1220
1221 int KFileItemModelRolesUpdater::subItemsCount(const QString& path) const
1222 {
1223 const bool countHiddenFiles = m_model->showHiddenFiles();
1224 const bool showFoldersOnly = m_model->showDirectoriesOnly();
1225
1226 #ifdef Q_WS_WIN
1227 QDir dir(path);
1228 QDir::Filters filters = QDir::NoDotAndDotDot | QDir::System;
1229 if (countHiddenFiles) {
1230 filters |= QDir::Hidden;
1231 }
1232 if (showFoldersOnly) {
1233 filters |= QDir::Dirs;
1234 } else {
1235 filters |= QDir::AllEntries;
1236 }
1237 return dir.entryList(filters).count();
1238 #else
1239 // Taken from kdelibs/kio/kio/kdirmodel.cpp
1240 // Copyright (C) 2006 David Faure <faure@kde.org>
1241
1242 int count = -1;
1243 DIR* dir = ::opendir(QFile::encodeName(path));
1244 if (dir) { // krazy:exclude=syscalls
1245 count = 0;
1246 struct dirent *dirEntry = 0;
1247 while ((dirEntry = ::readdir(dir))) {
1248 if (dirEntry->d_name[0] == '.') {
1249 if (dirEntry->d_name[1] == '\0' || !countHiddenFiles) {
1250 // Skip "." or hidden files
1251 continue;
1252 }
1253 if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') {
1254 // Skip ".."
1255 continue;
1256 }
1257 }
1258
1259 // If only directories are counted, consider an unknown file type and links also
1260 // as directory instead of trying to do an expensive stat()
1261 // (see bugs 292642 and 299997).
1262 const bool countEntry = !showFoldersOnly ||
1263 dirEntry->d_type == DT_DIR ||
1264 dirEntry->d_type == DT_LNK ||
1265 dirEntry->d_type == DT_UNKNOWN;
1266 if (countEntry) {
1267 ++count;
1268 }
1269 }
1270 ::closedir(dir);
1271 }
1272 return count;
1273 #endif
1274 }
1275
1276 void KFileItemModelRolesUpdater::updateAllPreviews()
1277 {
1278 if (m_state == Paused) {
1279 m_previewChangedDuringPausing = true;
1280 } else {
1281 m_finishedItems.clear();
1282 startUpdating();
1283 }
1284 }
1285
1286 void KFileItemModelRolesUpdater::killPreviewJob()
1287 {
1288 if (m_previewJob) {
1289 disconnect(m_previewJob, SIGNAL(gotPreview(KFileItem,QPixmap)),
1290 this, SLOT(slotGotPreview(KFileItem,QPixmap)));
1291 disconnect(m_previewJob, SIGNAL(failed(KFileItem)),
1292 this, SLOT(slotPreviewFailed(KFileItem)));
1293 disconnect(m_previewJob, SIGNAL(finished(KJob*)),
1294 this, SLOT(slotPreviewJobFinished()));
1295 m_previewJob->kill();
1296 m_previewJob = 0;
1297 m_pendingPreviewItems.clear();
1298 }
1299 }
1300
1301 QList<int> KFileItemModelRolesUpdater::indexesToResolve() const
1302 {
1303 const int count = m_model->count();
1304
1305 QList<int> result;
1306 result.reserve(ResolveAllItemsLimit);
1307
1308 // Add visible items.
1309 for (int i = m_firstVisibleIndex; i <= m_lastVisibleIndex; ++i) {
1310 result.append(i);
1311 }
1312
1313 // We need a reasonable upper limit for number of items to resolve after
1314 // and before the visible range. m_maximumVisibleItems can be quite large
1315 // when using Compace View.
1316 const int readAheadItems = qMin(ReadAheadPages * m_maximumVisibleItems, ResolveAllItemsLimit / 2);
1317
1318 // Add items after the visible range.
1319 const int endExtendedVisibleRange = qMin(m_lastVisibleIndex + readAheadItems, count - 1);
1320 for (int i = m_lastVisibleIndex + 1; i <= endExtendedVisibleRange; ++i) {
1321 result.append(i);
1322 }
1323
1324 // Add items before the visible range in reverse order.
1325 const int beginExtendedVisibleRange = qMax(0, m_firstVisibleIndex - readAheadItems);
1326 for (int i = m_firstVisibleIndex - 1; i >= beginExtendedVisibleRange; --i) {
1327 result.append(i);
1328 }
1329
1330 // Add items on the last page.
1331 const int beginLastPage = qMax(qMin(endExtendedVisibleRange + 1, count - 1), count - m_maximumVisibleItems);
1332 for (int i = beginLastPage; i < count; ++i) {
1333 result.append(i);
1334 }
1335
1336 // Add items on the first page.
1337 const int endFirstPage = qMin(qMax(beginExtendedVisibleRange - 1, 0), m_maximumVisibleItems);
1338 for (int i = 0; i <= endFirstPage; ++i) {
1339 result.append(i);
1340 }
1341
1342 // Continue adding items until ResolveAllItemsLimit is reached.
1343 int remainingItems = ResolveAllItemsLimit - result.count();
1344
1345 for (int i = endExtendedVisibleRange + 1; i < beginLastPage && remainingItems > 0; ++i) {
1346 result.append(i);
1347 --remainingItems;
1348 }
1349
1350 for (int i = beginExtendedVisibleRange - 1; i > endFirstPage && remainingItems > 0; --i) {
1351 result.append(i);
1352 --remainingItems;
1353 }
1354
1355 return result;
1356 }
1357
1358 #include "kfileitemmodelrolesupdater.moc"