]> cloud.milkyroute.net Git - dolphin.git/blob - src/kfilepreviewgenerator.cpp
make the background transparent and apply the window-text color to the text color...
[dolphin.git] / src / kfilepreviewgenerator.cpp
1 /***************************************************************************
2 * Copyright (C) 2008 by Peter Penz <peter.penz@gmx.at> *
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 "kfilepreviewgenerator.h"
21
22 #include <kfileitem.h>
23 #include <kiconeffect.h>
24 #include <kio/previewjob.h>
25 #include <kdirlister.h>
26 #include <kdirmodel.h>
27 #include <kdirsortfilterproxymodel.h>
28 #include <kmimetyperesolver.h>
29 #include <konqmimedata.h>
30
31 #include <QApplication>
32 #include <QAbstractItemView>
33 #include <QClipboard>
34 #include <QColor>
35 #include <QList>
36 #include <QListView>
37 #include <QPainter>
38 #include <QPixmap>
39 #include <QScrollBar>
40 #include <QIcon>
41
42 #ifdef Q_WS_X11
43 # include <QX11Info>
44 # include <X11/Xlib.h>
45 # include <X11/extensions/Xrender.h>
46 #endif
47
48 /**
49 * If the passed item view is an instance of QListView, expensive
50 * layout operations are blocked in the constructor and are unblocked
51 * again in the destructor.
52 *
53 * This helper class is a workaround for the following huge performance
54 * problem when having directories with several 1000 items:
55 * - each change of an icon emits a dataChanged() signal from the model
56 * - QListView iterates through all items on each dataChanged() signal
57 * and invokes QItemDelegate::sizeHint()
58 * - the sizeHint() implementation of KFileItemDelegate is quite complex,
59 * invoking it 1000 times for each icon change might block the UI
60 *
61 * QListView does not invoke QItemDelegate::sizeHint() when the
62 * uniformItemSize property has been set to true, so this property is
63 * set before exchanging a block of icons. It is important to reset
64 * it again before the event loop is entered, otherwise QListView
65 * would not get the correct size hints after dispatching the layoutChanged()
66 * signal.
67 */
68 class LayoutBlocker {
69 public:
70 LayoutBlocker(QAbstractItemView* view) :
71 m_uniformSizes(false),
72 m_view(qobject_cast<QListView*>(view))
73 {
74 if (m_view != 0) {
75 m_uniformSizes = m_view->uniformItemSizes();
76 m_view->setUniformItemSizes(true);
77 }
78 }
79
80 ~LayoutBlocker()
81 {
82 if (m_view != 0) {
83 m_view->setUniformItemSizes(m_uniformSizes);
84 }
85 }
86
87 private:
88 bool m_uniformSizes;
89 QListView* m_view;
90 };
91
92 class KFilePreviewGenerator::Private
93 {
94 public:
95 Private(KFilePreviewGenerator* parent,
96 QAbstractItemView* view,
97 KDirSortFilterProxyModel* model);
98 ~Private();
99
100 /**
101 * Generates previews for the items \a items asynchronously.
102 */
103 void generatePreviews(const KFileItemList& items);
104
105 /**
106 * Adds the preview \a pixmap for the item \a item to the preview
107 * queue and starts a timer which will dispatch the preview queue
108 * later.
109 */
110 void addToPreviewQueue(const KFileItem& item, const QPixmap& pixmap);
111
112 /**
113 * Is invoked when the preview job has been finished and
114 * removes the job from the m_previewJobs list.
115 */
116 void slotPreviewJobFinished(KJob* job);
117
118 /** Synchronizes the item icon with the clipboard of cut items. */
119 void updateCutItems();
120
121 /**
122 * Dispatches the preview queue block by block within
123 * time slices.
124 */
125 void dispatchPreviewQueue();
126
127 /**
128 * Pauses all preview jobs and invokes KFilePreviewGenerator::resumePreviews()
129 * after a short delay. Is invoked as soon as the user has moved
130 * a scrollbar.
131 */
132 void pausePreviews();
133
134 /**
135 * Resumes the previews that have been paused after moving the
136 * scrollbar. The previews for the current visible area are
137 * generated first.
138 */
139 void resumePreviews();
140
141 /**
142 * Returns true, if the item \a item has been cut into
143 * the clipboard.
144 */
145 bool isCutItem(const KFileItem& item) const;
146
147 /** Applies an item effect to all cut items. */
148 void applyCutItemEffect();
149
150 /**
151 * Applies a frame around the icon. False is returned if
152 * no frame has been added because the icon is too small.
153 */
154 bool applyImageFrame(QPixmap& icon);
155
156 /**
157 * Resizes the icon to \a maxSize if the icon size does not
158 * fit into the maximum size. The aspect ratio of the icon
159 * is kept.
160 */
161 void limitToSize(QPixmap& icon, const QSize& maxSize);
162
163 /**
164 * Starts a new preview job for the items \a to m_previewJobs
165 * and triggers the preview timer.
166 */
167 void startPreviewJob(const KFileItemList& items);
168
169 /** Kills all ongoing preview jobs. */
170 void killPreviewJobs();
171
172 /**
173 * Orders the items \a items in a way that the visible items
174 * are moved to the front of the list. When passing this
175 * list to a preview job, the visible items will get generated
176 * first.
177 */
178 void orderItems(KFileItemList& items);
179
180 /** Remembers the pixmap for an item specified by an URL. */
181 struct ItemInfo
182 {
183 KUrl url;
184 QPixmap pixmap;
185 };
186
187 bool m_showPreview;
188
189 /**
190 * True, if m_pendingItems and m_dispatchedItems should be
191 * cleared when the preview jobs have been finished.
192 */
193 bool m_clearItemQueues;
194
195 /**
196 * True if a selection has been done which should cut items.
197 */
198 bool m_hasCutSelection;
199
200 int m_pendingVisiblePreviews;
201
202 QAbstractItemView* m_view;
203 QTimer* m_previewTimer;
204 QTimer* m_scrollAreaTimer;
205 QList<KJob*> m_previewJobs;
206 KDirModel* m_dirModel;
207 KDirSortFilterProxyModel* m_proxyModel;
208
209 KMimeTypeResolver* m_mimeTypeResolver;
210
211 QList<ItemInfo> m_cutItemsCache;
212 QList<ItemInfo> m_previews;
213
214 /**
215 * Contains all items where a preview must be generated, but
216 * where the preview job has not dispatched the items yet.
217 */
218 KFileItemList m_pendingItems;
219
220 /**
221 * Contains all items, where a preview has already been
222 * generated by the preview jobs.
223 */
224 KFileItemList m_dispatchedItems;
225
226 private:
227 KFilePreviewGenerator* const q;
228
229 };
230
231 KFilePreviewGenerator::Private::Private(KFilePreviewGenerator* parent,
232 QAbstractItemView* view,
233 KDirSortFilterProxyModel* model) :
234 m_showPreview(true),
235 m_clearItemQueues(true),
236 m_hasCutSelection(false),
237 m_pendingVisiblePreviews(0),
238 m_view(view),
239 m_previewTimer(0),
240 m_scrollAreaTimer(0),
241 m_previewJobs(),
242 m_dirModel(0),
243 m_proxyModel(model),
244 m_mimeTypeResolver(0),
245 m_cutItemsCache(),
246 m_previews(),
247 m_pendingItems(),
248 m_dispatchedItems(),
249 q(parent)
250 {
251 if (!m_view->iconSize().isValid()) {
252 m_showPreview = false;
253 }
254
255 m_dirModel = static_cast<KDirModel*>(m_proxyModel->sourceModel());
256 connect(m_dirModel->dirLister(), SIGNAL(newItems(const KFileItemList&)),
257 q, SLOT(generatePreviews(const KFileItemList&)));
258
259 QClipboard* clipboard = QApplication::clipboard();
260 connect(clipboard, SIGNAL(dataChanged()),
261 q, SLOT(updateCutItems()));
262
263 m_previewTimer = new QTimer(q);
264 m_previewTimer->setSingleShot(true);
265 connect(m_previewTimer, SIGNAL(timeout()), q, SLOT(dispatchPreviewQueue()));
266
267 // Whenever the scrollbar values have been changed, the pending previews should
268 // be reordered in a way that the previews for the visible items are generated
269 // first. The reordering is done with a small delay, so that during moving the
270 // scrollbars the CPU load is kept low.
271 m_scrollAreaTimer = new QTimer(q);
272 m_scrollAreaTimer->setSingleShot(true);
273 m_scrollAreaTimer->setInterval(200);
274 connect(m_scrollAreaTimer, SIGNAL(timeout()),
275 q, SLOT(resumePreviews()));
276 connect(m_view->horizontalScrollBar(), SIGNAL(valueChanged(int)),
277 q, SLOT(pausePreviews()));
278 connect(m_view->verticalScrollBar(), SIGNAL(valueChanged(int)),
279 q, SLOT(pausePreviews()));
280 }
281
282 KFilePreviewGenerator::Private::~Private()
283 {
284 killPreviewJobs();
285 m_pendingItems.clear();
286 m_dispatchedItems.clear();
287 if (m_mimeTypeResolver != 0) {
288 m_mimeTypeResolver->deleteLater();
289 m_mimeTypeResolver = 0;
290 }
291 }
292
293 void KFilePreviewGenerator::Private::generatePreviews(const KFileItemList& items)
294 {
295 applyCutItemEffect();
296
297 if (!m_showPreview) {
298 return;
299 }
300
301 KFileItemList orderedItems = items;
302 orderItems(orderedItems);
303
304 foreach (const KFileItem& item, orderedItems) {
305 m_pendingItems.append(item);
306 }
307
308 startPreviewJob(orderedItems);
309 }
310
311 void KFilePreviewGenerator::Private::addToPreviewQueue(const KFileItem& item, const QPixmap& pixmap)
312 {
313 if (!m_showPreview) {
314 // the preview has been canceled in the meantime
315 return;
316 }
317 const KUrl url = item.url();
318
319 // check whether the item is part of the directory lister (it is possible
320 // that a preview from an old directory lister is received)
321 KDirLister* dirLister = m_dirModel->dirLister();
322 bool isOldPreview = true;
323 const KUrl::List dirs = dirLister->directories();
324 const QString itemDir = url.directory();
325 foreach (const KUrl& url, dirs) {
326 if (url.path() == itemDir) {
327 isOldPreview = false;
328 break;
329 }
330 }
331 if (isOldPreview) {
332 return;
333 }
334
335 QPixmap icon = pixmap;
336
337 const QString mimeType = item.mimetype();
338 const QString mimeTypeGroup = mimeType.left(mimeType.indexOf('/'));
339 if ((mimeTypeGroup != "image") || !applyImageFrame(icon)) {
340 limitToSize(icon, m_view->iconSize());
341 }
342
343 if (m_hasCutSelection && isCutItem(item)) {
344 // Remember the current icon in the cache for cut items before
345 // the disabled effect is applied. This makes it possible restoring
346 // the uncut version again when cutting other items.
347 QList<ItemInfo>::iterator begin = m_cutItemsCache.begin();
348 QList<ItemInfo>::iterator end = m_cutItemsCache.end();
349 for (QList<ItemInfo>::iterator it = begin; it != end; ++it) {
350 if ((*it).url == item.url()) {
351 (*it).pixmap = icon;
352 break;
353 }
354 }
355
356 // apply the disabled effect to the icon for marking it as "cut item"
357 // and apply the icon to the item
358 KIconEffect iconEffect;
359 icon = iconEffect.apply(icon, KIconLoader::Desktop, KIconLoader::DisabledState);
360 }
361
362 // remember the preview and URL, so that it can be applied to the model
363 // in KFilePreviewGenerator::dispatchPreviewQueue()
364 ItemInfo preview;
365 preview.url = url;
366 preview.pixmap = icon;
367 m_previews.append(preview);
368
369 m_dispatchedItems.append(item);
370 }
371
372 void KFilePreviewGenerator::Private::slotPreviewJobFinished(KJob* job)
373 {
374 const int index = m_previewJobs.indexOf(job);
375 m_previewJobs.removeAt(index);
376
377 if ((m_previewJobs.count() == 0) && m_clearItemQueues) {
378 m_pendingItems.clear();
379 m_dispatchedItems.clear();
380 m_pendingVisiblePreviews = 0;
381 QMetaObject::invokeMethod(q, "dispatchPreviewQueue", Qt::QueuedConnection);
382 }
383 }
384
385 void KFilePreviewGenerator::Private::updateCutItems()
386 {
387 // restore the icons of all previously selected items to the
388 // original state...
389 foreach (const ItemInfo& cutItem, m_cutItemsCache) {
390 const QModelIndex index = m_dirModel->indexForUrl(cutItem.url);
391 if (index.isValid()) {
392 m_dirModel->setData(index, QIcon(cutItem.pixmap), Qt::DecorationRole);
393 }
394 }
395 m_cutItemsCache.clear();
396
397 // ... and apply an item effect to all currently cut items
398 applyCutItemEffect();
399 }
400
401 void KFilePreviewGenerator::Private::dispatchPreviewQueue()
402 {
403 const int previewsCount = m_previews.count();
404 if (previewsCount > 0) {
405 // Applying the previews to the model must be done step by step
406 // in larger blocks: Applying a preview immediately when getting the signal
407 // 'gotPreview()' from the PreviewJob is too expensive, as a relayout
408 // of the view would be triggered for each single preview.
409 LayoutBlocker blocker(m_view);
410 for (int i = 0; i < previewsCount; ++i) {
411 const ItemInfo& preview = m_previews.first();
412
413 const QModelIndex idx = m_dirModel->indexForUrl(preview.url);
414 if (idx.isValid() && (idx.column() == 0)) {
415 m_dirModel->setData(idx, QIcon(preview.pixmap), Qt::DecorationRole);
416 }
417
418 m_previews.pop_front();
419 if (m_pendingVisiblePreviews > 0) {
420 --m_pendingVisiblePreviews;
421 }
422 }
423 }
424
425 if (m_pendingVisiblePreviews > 0) {
426 // As long as there are pending previews for visible items, poll
427 // the preview queue each 200 ms. If there are no pending previews,
428 // the queue is dispatched in slotPreviewJobFinished().
429 m_previewTimer->start(200);
430 }
431 }
432
433 void KFilePreviewGenerator::Private::pausePreviews()
434 {
435 foreach (KJob* job, m_previewJobs) {
436 Q_ASSERT(job != 0);
437 job->suspend();
438 }
439 m_scrollAreaTimer->start();
440 }
441
442 void KFilePreviewGenerator::Private::resumePreviews()
443 {
444 // Before creating new preview jobs the m_pendingItems queue must be
445 // cleaned up by removing the already dispatched items. Implementation
446 // note: The order of the m_dispatchedItems queue and the m_pendingItems
447 // queue is usually equal. So even when having a lot of elements the
448 // nested loop is no performance bottle neck, as the inner loop is only
449 // entered once in most cases.
450 foreach (const KFileItem& item, m_dispatchedItems) {
451 KFileItemList::iterator begin = m_pendingItems.begin();
452 KFileItemList::iterator end = m_pendingItems.end();
453 for (KFileItemList::iterator it = begin; it != end; ++it) {
454 if ((*it).url() == item.url()) {
455 m_pendingItems.erase(it);
456 break;
457 }
458 }
459 }
460 m_dispatchedItems.clear();
461
462 m_pendingVisiblePreviews = 0;
463 dispatchPreviewQueue();
464
465 KFileItemList orderedItems = m_pendingItems;
466 orderItems(orderedItems);
467
468 // Kill all suspended preview jobs. Usually when a preview job
469 // has been finished, slotPreviewJobFinished() clears all item queues.
470 // This is not wanted in this case, as a new job is created afterwards
471 // for m_pendingItems.
472 m_clearItemQueues = false;
473 killPreviewJobs();
474 m_clearItemQueues = true;
475
476 startPreviewJob(orderedItems);
477 }
478
479 bool KFilePreviewGenerator::Private::isCutItem(const KFileItem& item) const
480 {
481 const QMimeData* mimeData = QApplication::clipboard()->mimeData();
482 const KUrl::List cutUrls = KUrl::List::fromMimeData(mimeData);
483
484 const KUrl itemUrl = item.url();
485 foreach (const KUrl& url, cutUrls) {
486 if (url == itemUrl) {
487 return true;
488 }
489 }
490
491 return false;
492 }
493
494 void KFilePreviewGenerator::Private::applyCutItemEffect()
495 {
496 const QMimeData* mimeData = QApplication::clipboard()->mimeData();
497 m_hasCutSelection = KonqMimeData::decodeIsCutSelection(mimeData);
498 if (!m_hasCutSelection) {
499 return;
500 }
501
502 KFileItemList items;
503 KDirLister* dirLister = m_dirModel->dirLister();
504 const KUrl::List dirs = dirLister->directories();
505 foreach (const KUrl& url, dirs) {
506 items << dirLister->itemsForDir(url);
507 }
508
509 foreach (const KFileItem& item, items) {
510 if (isCutItem(item)) {
511 const QModelIndex index = m_dirModel->indexForItem(item);
512 const QVariant value = m_dirModel->data(index, Qt::DecorationRole);
513 if (value.type() == QVariant::Icon) {
514 const QIcon icon(qvariant_cast<QIcon>(value));
515 const QSize actualSize = icon.actualSize(m_view->iconSize());
516 QPixmap pixmap = icon.pixmap(actualSize);
517
518 // remember current pixmap for the item to be able
519 // to restore it when other items get cut
520 ItemInfo cutItem;
521 cutItem.url = item.url();
522 cutItem.pixmap = pixmap;
523 m_cutItemsCache.append(cutItem);
524
525 // apply icon effect to the cut item
526 KIconEffect iconEffect;
527 pixmap = iconEffect.apply(pixmap, KIconLoader::Desktop, KIconLoader::DisabledState);
528 m_dirModel->setData(index, QIcon(pixmap), Qt::DecorationRole);
529 }
530 }
531 }
532 }
533
534 bool KFilePreviewGenerator::Private::applyImageFrame(QPixmap& icon)
535 {
536 const QSize maxSize = m_view->iconSize();
537 const bool applyFrame = (maxSize.width() > KIconLoader::SizeSmallMedium) &&
538 (maxSize.height() > KIconLoader::SizeSmallMedium) &&
539 ((icon.width() > KIconLoader::SizeLarge) ||
540 (icon.height() > KIconLoader::SizeLarge));
541 if (!applyFrame) {
542 // the maximum size or the image itself is too small for a frame
543 return false;
544 }
545
546 const int frame = 4;
547 const int doubleFrame = frame * 2;
548
549 // resize the icon to the maximum size minus the space required for the frame
550 limitToSize(icon, QSize(maxSize.width() - doubleFrame, maxSize.height() - doubleFrame));
551
552 QPainter painter;
553 const QPalette palette = m_view->palette();
554 QPixmap framedIcon(icon.size().width() + doubleFrame, icon.size().height() + doubleFrame);
555 framedIcon.fill(palette.color(QPalette::Normal, QPalette::Base));
556 const int width = framedIcon.width() - 1;
557 const int height = framedIcon.height() - 1;
558
559 painter.begin(&framedIcon);
560 painter.drawPixmap(frame, frame, icon);
561
562 // add a border
563 painter.setPen(palette.color(QPalette::Text));
564 painter.drawRect(0, 0, width, height);
565 painter.drawRect(1, 1, width - 2, height - 2);
566
567 painter.setCompositionMode(QPainter::CompositionMode_Plus);
568 QColor blendColor = palette.color(QPalette::Normal, QPalette::Base);
569
570 blendColor.setAlpha(255 - 32);
571 painter.setPen(blendColor);
572 painter.drawRect(0, 0, width, height);
573
574 blendColor.setAlpha(255 - 64);
575 painter.setPen(blendColor);
576 painter.drawRect(1, 1, width - 2, height - 2);
577 painter.end();
578
579 icon = framedIcon;
580
581 return true;
582 }
583
584 void KFilePreviewGenerator::Private::limitToSize(QPixmap& icon, const QSize& maxSize)
585 {
586 if ((icon.width() > maxSize.width()) || (icon.height() > maxSize.height())) {
587 #ifdef Q_WS_X11
588 // Assume that the texture size limit is 2048x2048
589 if ((icon.width() <= 2048) && (icon.height() <= 2048)) {
590 QSize size = icon.size();
591 size.scale(maxSize, Qt::KeepAspectRatio);
592
593 const qreal factor = size.width() / qreal(icon.width());
594
595 XTransform xform = {{
596 { XDoubleToFixed(1 / factor), 0, 0 },
597 { 0, XDoubleToFixed(1 / factor), 0 },
598 { 0, 0, XDoubleToFixed(1) }
599 }};
600
601 QPixmap pixmap(size);
602 pixmap.fill(Qt::transparent);
603
604 Display *dpy = QX11Info::display();
605 XRenderSetPictureFilter(dpy, icon.x11PictureHandle(), FilterBilinear, 0, 0);
606 XRenderSetPictureTransform(dpy, icon.x11PictureHandle(), &xform);
607 XRenderComposite(dpy, PictOpOver, icon.x11PictureHandle(), None, pixmap.x11PictureHandle(),
608 0, 0, 0, 0, 0, 0, pixmap.width(), pixmap.height());
609 icon = pixmap;
610 } else {
611 icon = icon.scaled(maxSize, Qt::KeepAspectRatio, Qt::FastTransformation);
612 }
613 #else
614 icon = icon.scaled(maxSize, Qt::KeepAspectRatio, Qt::FastTransformation);
615 #endif
616 }
617 }
618
619 void KFilePreviewGenerator::Private::startPreviewJob(const KFileItemList& items)
620 {
621 if (items.count() == 0) {
622 return;
623 }
624
625 const QMimeData* mimeData = QApplication::clipboard()->mimeData();
626 m_hasCutSelection = KonqMimeData::decodeIsCutSelection(mimeData);
627
628 const QSize size = m_view->iconSize();
629
630 // PreviewJob internally caches items always with the size of
631 // 128 x 128 pixels or 256 x 256 pixels. A downscaling is done
632 // by PreviewJob if a smaller size is requested. As the KFilePreviewGenerator must
633 // do a downscaling anyhow because of the frame, only the provided
634 // cache sizes are requested.
635 const int cacheSize = (size.width() > 128) || (size.height() > 128) ? 256 : 128;
636 KIO::PreviewJob* job = KIO::filePreview(items, cacheSize, cacheSize);
637 connect(job, SIGNAL(gotPreview(const KFileItem&, const QPixmap&)),
638 q, SLOT(addToPreviewQueue(const KFileItem&, const QPixmap&)));
639 connect(job, SIGNAL(finished(KJob*)),
640 q, SLOT(slotPreviewJobFinished(KJob*)));
641
642 m_previewJobs.append(job);
643 m_previewTimer->start(200);
644 }
645
646 void KFilePreviewGenerator::Private::killPreviewJobs()
647 {
648 foreach (KJob* job, m_previewJobs) {
649 Q_ASSERT(job != 0);
650 job->kill();
651 }
652 m_previewJobs.clear();
653 }
654
655 void KFilePreviewGenerator::Private::orderItems(KFileItemList& items)
656 {
657 // Order the items in a way that the preview for the visible items
658 // is generated first, as this improves the feeled performance a lot.
659 //
660 // Implementation note: 2 different algorithms are used for the sorting.
661 // Algorithm 1 is faster when having a lot of items in comparison
662 // to the number of rows in the model. Algorithm 2 is faster
663 // when having quite less items in comparison to the number of rows in
664 // the model. Choosing the right algorithm is important when having directories
665 // with several hundreds or thousands of items.
666
667 const int itemCount = items.count();
668 const int rowCount = m_proxyModel->rowCount();
669 const QRect visibleArea = m_view->viewport()->rect();
670
671 int insertPos = 0;
672 if (itemCount * 10 > rowCount) {
673 // Algorithm 1: The number of items is > 10 % of the row count. Parse all rows
674 // and check whether the received row is part of the item list.
675 for (int row = 0; row < rowCount; ++row) {
676 const QModelIndex proxyIndex = m_proxyModel->index(row, 0);
677 const QRect itemRect = m_view->visualRect(proxyIndex);
678 const QModelIndex dirIndex = m_proxyModel->mapToSource(proxyIndex);
679
680 KFileItem item = m_dirModel->itemForIndex(dirIndex); // O(1)
681 const KUrl url = item.url();
682
683 // check whether the item is part of the item list 'items'
684 int index = -1;
685 for (int i = 0; i < itemCount; ++i) {
686 if (items.at(i).url() == url) {
687 index = i;
688 break;
689 }
690 }
691
692 if ((index > 0) && itemRect.intersects(visibleArea)) {
693 // The current item is (at least partly) visible. Move it
694 // to the front of the list, so that the preview is
695 // generated earlier.
696 items.removeAt(index);
697 items.insert(insertPos, item);
698 ++insertPos;
699 ++m_pendingVisiblePreviews;
700 }
701 }
702 } else {
703 // Algorithm 2: The number of items is <= 10 % of the row count. In this case iterate
704 // all items and receive the corresponding row from the item.
705 for (int i = 0; i < itemCount; ++i) {
706 const QModelIndex dirIndex = m_dirModel->indexForItem(items.at(i)); // O(n) (n = number of rows)
707 const QModelIndex proxyIndex = m_proxyModel->mapFromSource(dirIndex);
708 const QRect itemRect = m_view->visualRect(proxyIndex);
709
710 if (itemRect.intersects(visibleArea)) {
711 // The current item is (at least partly) visible. Move it
712 // to the front of the list, so that the preview is
713 // generated earlier.
714 items.insert(insertPos, items.at(i));
715 items.removeAt(i + 1);
716 ++insertPos;
717 ++m_pendingVisiblePreviews;
718 }
719 }
720 }
721 }
722
723 KFilePreviewGenerator::KFilePreviewGenerator(QAbstractItemView* parent, KDirSortFilterProxyModel* model) :
724 QObject(parent),
725 d(new Private(this, parent, model))
726 {
727 }
728
729 KFilePreviewGenerator::~KFilePreviewGenerator()
730 {
731 delete d;
732 }
733
734 void KFilePreviewGenerator::setShowPreview(bool show)
735 {
736 if (show && !d->m_view->iconSize().isValid()) {
737 // the view must provide an icon size, otherwise the showing
738 // off previews will get ignored
739 return;
740 }
741
742 if (d->m_showPreview != show) {
743 d->m_showPreview = show;
744 d->m_cutItemsCache.clear();
745 d->updateCutItems();
746 if (show) {
747 updatePreviews();
748 }
749 }
750
751 if (show && (d->m_mimeTypeResolver != 0)) {
752 // don't resolve the MIME types if the preview is turned on
753 d->m_mimeTypeResolver->deleteLater();
754 d->m_mimeTypeResolver = 0;
755 } else if (!show && (d->m_mimeTypeResolver == 0)) {
756 // the preview is turned off: resolve the MIME-types so that
757 // the icons gets updated
758 d->m_mimeTypeResolver = new KMimeTypeResolver(d->m_view, d->m_dirModel);
759 }
760 }
761
762 bool KFilePreviewGenerator::showPreview() const
763 {
764 return d->m_showPreview;
765 }
766
767 void KFilePreviewGenerator::updatePreviews()
768 {
769 if (!d->m_showPreview) {
770 return;
771 }
772
773 d->killPreviewJobs();
774 d->m_cutItemsCache.clear();
775 d->m_pendingItems.clear();
776 d->m_dispatchedItems.clear();
777
778 KFileItemList itemList;
779 const int rowCount = d->m_dirModel->rowCount();
780 for (int row = 0; row < rowCount; ++row) {
781 const QModelIndex index = d->m_dirModel->index(row, 0);
782 KFileItem item = d->m_dirModel->itemForIndex(index);
783 itemList.append(item);
784 }
785
786 d->generatePreviews(itemList);
787 d->updateCutItems();
788 }
789
790 void KFilePreviewGenerator::cancelPreviews()
791 {
792 d->killPreviewJobs();
793 d->m_cutItemsCache.clear();
794 d->m_pendingItems.clear();
795 d->m_dispatchedItems.clear();
796 }
797
798 #include "kfilepreviewgenerator.moc"