SVN_SILENT shhh.
[kdelibs.git] / kfile / kfilepreviewgenerator.cpp
blob836517aa3f984bc4293a98734d2f4d0695b9e76d
1 /*******************************************************************************
2 * Copyright (C) 2008 by Peter Penz <peter.penz@gmx.at> *
3 * *
4 * This library is free software; you can redistribute it and/or *
5 * modify it under the terms of the GNU Library General Public *
6 * License as published by the Free Software Foundation; either *
7 * version 2 of the License, or (at your option) any later version. *
8 * *
9 * This library 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 GNU *
12 * Library General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU Library General Public License *
15 * along with this library; see the file COPYING.LIB. If not, write to *
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, *
17 * Boston, MA 02110-1301, USA. *
18 *******************************************************************************/
20 #include "kfilepreviewgenerator.h"
22 #include "../kio/kio/defaultviewadapter_p.h"
23 #include "../kio/kio/imagefilter_p.h"
24 #include <kfileitem.h>
25 #include <kiconeffect.h>
26 #include <kio/previewjob.h>
27 #include <kdirlister.h>
28 #include <kdirmodel.h>
29 #include <kmimetyperesolver.h>
31 #include <QApplication>
32 #include <QAbstractItemView>
33 #include <QAbstractProxyModel>
34 #include <QClipboard>
35 #include <QColor>
36 #include <QList>
37 #include <QListView>
38 #include <QPainter>
39 #include <QPixmap>
40 #include <QScrollBar>
41 #include <QIcon>
43 #ifdef Q_WS_X11
44 # include <QX11Info>
45 # include <X11/Xlib.h>
46 # include <X11/extensions/Xrender.h>
47 #endif
49 /**
50 * If the passed item view is an instance of QListView, expensive
51 * layout operations are blocked in the constructor and are unblocked
52 * again in the destructor.
54 * This helper class is a workaround for the following huge performance
55 * problem when having directories with several 1000 items:
56 * - each change of an icon emits a dataChanged() signal from the model
57 * - QListView iterates through all items on each dataChanged() signal
58 * and invokes QItemDelegate::sizeHint()
59 * - the sizeHint() implementation of KFileItemDelegate is quite complex,
60 * invoking it 1000 times for each icon change might block the UI
62 * QListView does not invoke QItemDelegate::sizeHint() when the
63 * uniformItemSize property has been set to true, so this property is
64 * set before exchanging a block of icons. It is important to reset
65 * it again before the event loop is entered, otherwise QListView
66 * would not get the correct size hints after dispatching the layoutChanged()
67 * signal.
69 class LayoutBlocker {
70 public:
71 LayoutBlocker(QAbstractItemView* view) :
72 m_uniformSizes(false),
73 m_view(qobject_cast<QListView*>(view))
75 if (m_view != 0) {
76 m_uniformSizes = m_view->uniformItemSizes();
77 m_view->setUniformItemSizes(true);
81 ~LayoutBlocker()
83 if (m_view != 0) {
84 m_view->setUniformItemSizes(m_uniformSizes);
88 private:
89 bool m_uniformSizes;
90 QListView* m_view;
93 class TileSet
95 public:
96 enum Tile { TopLeftCorner = 0, TopSide, TopRightCorner, LeftSide, Center,
97 RightSide, BottomLeftCorner, BottomSide, BottomRightCorner,
98 NumTiles };
100 TileSet()
102 QImage image(8 * 3, 8 * 3, QImage::Format_ARGB32_Premultiplied);
104 QPainter p(&image);
105 p.setCompositionMode(QPainter::CompositionMode_Source);
106 p.fillRect(image.rect(), Qt::transparent);
107 p.fillRect(image.rect().adjusted(3, 3, -3, -3), Qt::black);
108 p.end();
110 KIO::ImageFilter::shadowBlur(image, 3, Qt::black);
112 QPixmap pixmap = QPixmap::fromImage(image);
113 m_tiles[TopLeftCorner] = pixmap.copy(0, 0, 8, 8);
114 m_tiles[TopSide] = pixmap.copy(8, 0, 8, 8);
115 m_tiles[TopRightCorner] = pixmap.copy(16, 0, 8, 8);
116 m_tiles[LeftSide] = pixmap.copy(0, 8, 8, 8);
117 m_tiles[Center] = pixmap.copy(8, 8, 8, 8);
118 m_tiles[RightSide] = pixmap.copy(16, 8, 8, 8);
119 m_tiles[BottomLeftCorner] = pixmap.copy(0, 16, 8, 8);
120 m_tiles[BottomSide] = pixmap.copy(8, 16, 8, 8);
121 m_tiles[BottomRightCorner] = pixmap.copy(16, 16, 8, 8);
124 void paint(QPainter *p, const QRect &r)
126 p->drawPixmap(r.topLeft(), m_tiles[TopLeftCorner]);
127 if (r.width() - 16 > 0)
128 p->drawTiledPixmap(r.x() + 8, r.y(), r.width() - 16, 8, m_tiles[TopSide]);
129 p->drawPixmap(r.right() - 8 + 1, r.y(), m_tiles[TopRightCorner]);
130 if (r.height() - 16 > 0)
132 p->drawTiledPixmap(r.x(), r.y() + 8, 8, r.height() - 16, m_tiles[LeftSide]);
133 if (r.width() - 16 > 0)
134 p->drawTiledPixmap(r.x() + 8, r.y() + 8, r.width() - 16, r.height() - 16, m_tiles[Center]);
135 p->drawTiledPixmap(r.right() - 8 + 1, r.y() + 8, 8, r.height() - 16, m_tiles[RightSide]);
137 p->drawPixmap(r.x(), r.bottom() - 8 + 1, m_tiles[BottomLeftCorner]);
138 if (r.width() - 16 > 0)
139 p->drawTiledPixmap(r.x() + 8, r.bottom() - 8 + 1, r.width() - 16, 8, m_tiles[BottomSide]);
140 p->drawPixmap(r.right() - 8 + 1, r.bottom() - 8 + 1, m_tiles[BottomRightCorner]);
143 private:
144 QPixmap m_tiles[NumTiles];
147 class KFilePreviewGenerator::Private
149 public:
150 Private(KFilePreviewGenerator* parent,
151 KAbstractViewAdapter* viewAdapter,
152 QAbstractItemModel* model);
153 ~Private();
156 * Generates previews for the items \a items asynchronously.
158 void generatePreviews(const KFileItemList& items);
161 * Adds the preview \a pixmap for the item \a item to the preview
162 * queue and starts a timer which will dispatch the preview queue
163 * later.
165 void addToPreviewQueue(const KFileItem& item, const QPixmap& pixmap);
168 * Is invoked when the preview job has been finished and
169 * removes the job from the m_previewJobs list.
171 void slotPreviewJobFinished(KJob* job);
173 /** Synchronizes the item icon with the clipboard of cut items. */
174 void updateCutItems();
177 * Dispatches the preview queue block by block within
178 * time slices.
180 void dispatchPreviewQueue();
183 * Pauses all preview jobs and invokes KFilePreviewGenerator::resumePreviews()
184 * after a short delay. Is invoked as soon as the user has moved
185 * a scrollbar.
187 void pausePreviews();
190 * Resumes the previews that have been paused after moving the
191 * scrollbar. The previews for the current visible area are
192 * generated first.
194 void resumePreviews();
197 * Returns true, if the item \a item has been cut into
198 * the clipboard.
200 bool isCutItem(const KFileItem& item) const;
202 /** Applies an item effect to all cut items. */
203 void applyCutItemEffect();
206 * Applies a frame around the icon. False is returned if
207 * no frame has been added because the icon is too small.
209 bool applyImageFrame(QPixmap& icon);
212 * Resizes the icon to \a maxSize if the icon size does not
213 * fit into the maximum size. The aspect ratio of the icon
214 * is kept.
216 void limitToSize(QPixmap& icon, const QSize& maxSize);
219 * Starts a new preview job for the items \a to m_previewJobs
220 * and triggers the preview timer.
222 void startPreviewJob(const KFileItemList& items);
224 /** Kills all ongoing preview jobs. */
225 void killPreviewJobs();
228 * Orders the items \a items in a way that the visible items
229 * are moved to the front of the list. When passing this
230 * list to a preview job, the visible items will get generated
231 * first.
233 void orderItems(KFileItemList& items);
236 * Returns true, if \a mimeData represents a selection that has
237 * been cut.
239 bool decodeIsCutSelection(const QMimeData* mimeData);
241 /** Remembers the pixmap for an item specified by an URL. */
242 struct ItemInfo
244 KUrl url;
245 QPixmap pixmap;
248 bool m_previewShown;
251 * True, if m_pendingItems and m_dispatchedItems should be
252 * cleared when the preview jobs have been finished.
254 bool m_clearItemQueues;
257 * True if a selection has been done which should cut items.
259 bool m_hasCutSelection;
261 int m_pendingVisiblePreviews;
263 KAbstractViewAdapter* m_viewAdapter;
264 QAbstractItemView* m_itemView;
265 QTimer* m_previewTimer;
266 QTimer* m_scrollAreaTimer;
267 QList<KJob*> m_previewJobs;
268 KDirModel* m_dirModel;
269 QAbstractProxyModel* m_proxyModel;
271 KMimeTypeResolver* m_mimeTypeResolver;
273 QList<ItemInfo> m_cutItemsCache;
274 QList<ItemInfo> m_previews;
277 * Contains all items where a preview must be generated, but
278 * where the preview job has not dispatched the items yet.
280 KFileItemList m_pendingItems;
283 * Contains all items, where a preview has already been
284 * generated by the preview jobs.
286 KFileItemList m_dispatchedItems;
288 QStringList m_enabledPlugins;
290 TileSet *m_tileSet;
292 private:
293 KFilePreviewGenerator* const q;
297 KFilePreviewGenerator::Private::Private(KFilePreviewGenerator* parent,
298 KAbstractViewAdapter* viewAdapter,
299 QAbstractItemModel* model) :
300 m_previewShown(true),
301 m_clearItemQueues(true),
302 m_hasCutSelection(false),
303 m_pendingVisiblePreviews(0),
304 m_viewAdapter(viewAdapter),
305 m_itemView(0),
306 m_previewTimer(0),
307 m_scrollAreaTimer(0),
308 m_previewJobs(),
309 m_dirModel(0),
310 m_proxyModel(0),
311 m_mimeTypeResolver(0),
312 m_cutItemsCache(),
313 m_previews(),
314 m_pendingItems(),
315 m_dispatchedItems(),
316 m_tileSet(0),
317 q(parent)
319 if (!m_viewAdapter->iconSize().isValid()) {
320 m_previewShown = false;
323 m_proxyModel = qobject_cast<QAbstractProxyModel*>(model);
324 m_dirModel = (m_proxyModel == 0) ?
325 qobject_cast<KDirModel*>(model) :
326 qobject_cast<KDirModel*>(m_proxyModel->sourceModel());
327 if (m_dirModel == 0) {
328 // previews can only get generated for directory models
329 m_previewShown = false;
330 } else {
331 connect(m_dirModel->dirLister(), SIGNAL(newItems(const KFileItemList&)),
332 q, SLOT(generatePreviews(const KFileItemList&)));
335 QClipboard* clipboard = QApplication::clipboard();
336 connect(clipboard, SIGNAL(dataChanged()),
337 q, SLOT(updateCutItems()));
339 m_previewTimer = new QTimer(q);
340 m_previewTimer->setSingleShot(true);
341 connect(m_previewTimer, SIGNAL(timeout()), q, SLOT(dispatchPreviewQueue()));
343 // Whenever the scrollbar values have been changed, the pending previews should
344 // be reordered in a way that the previews for the visible items are generated
345 // first. The reordering is done with a small delay, so that during moving the
346 // scrollbars the CPU load is kept low.
347 m_scrollAreaTimer = new QTimer(q);
348 m_scrollAreaTimer->setSingleShot(true);
349 m_scrollAreaTimer->setInterval(200);
350 connect(m_scrollAreaTimer, SIGNAL(timeout()),
351 q, SLOT(resumePreviews()));
352 m_viewAdapter->connect(KAbstractViewAdapter::ScrollBarValueChanged,
353 q, SLOT(pausePreviews()));
356 KFilePreviewGenerator::Private::~Private()
358 killPreviewJobs();
359 m_pendingItems.clear();
360 m_dispatchedItems.clear();
361 if (m_mimeTypeResolver != 0) {
362 m_mimeTypeResolver->deleteLater();
363 m_mimeTypeResolver = 0;
365 delete m_tileSet;
368 void KFilePreviewGenerator::Private::generatePreviews(const KFileItemList& items)
370 applyCutItemEffect();
372 if (!m_previewShown) {
373 return;
376 KFileItemList orderedItems = items;
377 orderItems(orderedItems);
379 foreach (const KFileItem& item, orderedItems) {
380 m_pendingItems.append(item);
383 startPreviewJob(orderedItems);
386 void KFilePreviewGenerator::Private::addToPreviewQueue(const KFileItem& item, const QPixmap& pixmap)
388 if (!m_previewShown) {
389 // the preview has been canceled in the meantime
390 return;
392 const KUrl url = item.url();
394 // check whether the item is part of the directory lister (it is possible
395 // that a preview from an old directory lister is received)
396 KDirLister* dirLister = m_dirModel->dirLister();
397 bool isOldPreview = true;
398 const KUrl::List dirs = dirLister->directories();
399 const QString itemDir = url.directory();
400 foreach (const KUrl& url, dirs) {
401 if (url.path() == itemDir) {
402 isOldPreview = false;
403 break;
406 if (isOldPreview) {
407 return;
410 QPixmap icon = pixmap;
412 const QString mimeType = item.mimetype();
413 const QString mimeTypeGroup = mimeType.left(mimeType.indexOf('/'));
414 if ((mimeTypeGroup != "image") || !applyImageFrame(icon)) {
415 limitToSize(icon, m_viewAdapter->iconSize());
418 if (m_hasCutSelection && isCutItem(item)) {
419 // Remember the current icon in the cache for cut items before
420 // the disabled effect is applied. This makes it possible restoring
421 // the uncut version again when cutting other items.
422 QList<ItemInfo>::iterator begin = m_cutItemsCache.begin();
423 QList<ItemInfo>::iterator end = m_cutItemsCache.end();
424 for (QList<ItemInfo>::iterator it = begin; it != end; ++it) {
425 if ((*it).url == item.url()) {
426 (*it).pixmap = icon;
427 break;
431 // apply the disabled effect to the icon for marking it as "cut item"
432 // and apply the icon to the item
433 KIconEffect iconEffect;
434 icon = iconEffect.apply(icon, KIconLoader::Desktop, KIconLoader::DisabledState);
437 // remember the preview and URL, so that it can be applied to the model
438 // in KFilePreviewGenerator::dispatchPreviewQueue()
439 ItemInfo preview;
440 preview.url = url;
441 preview.pixmap = icon;
442 m_previews.append(preview);
444 m_dispatchedItems.append(item);
447 void KFilePreviewGenerator::Private::slotPreviewJobFinished(KJob* job)
449 const int index = m_previewJobs.indexOf(job);
450 m_previewJobs.removeAt(index);
452 if ((m_previewJobs.count() == 0) && m_clearItemQueues) {
453 m_pendingItems.clear();
454 m_dispatchedItems.clear();
455 m_pendingVisiblePreviews = 0;
456 QMetaObject::invokeMethod(q, "dispatchPreviewQueue", Qt::QueuedConnection);
460 void KFilePreviewGenerator::Private::updateCutItems()
462 // restore the icons of all previously selected items to the
463 // original state...
464 foreach (const ItemInfo& cutItem, m_cutItemsCache) {
465 const QModelIndex index = m_dirModel->indexForUrl(cutItem.url);
466 if (index.isValid()) {
467 m_dirModel->setData(index, QIcon(cutItem.pixmap), Qt::DecorationRole);
470 m_cutItemsCache.clear();
472 // ... and apply an item effect to all currently cut items
473 applyCutItemEffect();
476 void KFilePreviewGenerator::Private::dispatchPreviewQueue()
478 const int previewsCount = m_previews.count();
479 if (previewsCount > 0) {
480 // Applying the previews to the model must be done step by step
481 // in larger blocks: Applying a preview immediately when getting the signal
482 // 'gotPreview()' from the PreviewJob is too expensive, as a relayout
483 // of the view would be triggered for each single preview.
484 LayoutBlocker blocker(m_itemView);
485 for (int i = 0; i < previewsCount; ++i) {
486 const ItemInfo& preview = m_previews.first();
488 const QModelIndex idx = m_dirModel->indexForUrl(preview.url);
489 if (idx.isValid() && (idx.column() == 0)) {
490 m_dirModel->setData(idx, QIcon(preview.pixmap), Qt::DecorationRole);
493 m_previews.pop_front();
494 if (m_pendingVisiblePreviews > 0) {
495 --m_pendingVisiblePreviews;
500 if (m_pendingVisiblePreviews > 0) {
501 // As long as there are pending previews for visible items, poll
502 // the preview queue each 200 ms. If there are no pending previews,
503 // the queue is dispatched in slotPreviewJobFinished().
504 m_previewTimer->start(200);
508 void KFilePreviewGenerator::Private::pausePreviews()
510 foreach (KJob* job, m_previewJobs) {
511 Q_ASSERT(job != 0);
512 job->suspend();
514 m_scrollAreaTimer->start();
517 void KFilePreviewGenerator::Private::resumePreviews()
519 // Before creating new preview jobs the m_pendingItems queue must be
520 // cleaned up by removing the already dispatched items. Implementation
521 // note: The order of the m_dispatchedItems queue and the m_pendingItems
522 // queue is usually equal. So even when having a lot of elements the
523 // nested loop is no performance bottle neck, as the inner loop is only
524 // entered once in most cases.
525 foreach (const KFileItem& item, m_dispatchedItems) {
526 KFileItemList::iterator begin = m_pendingItems.begin();
527 KFileItemList::iterator end = m_pendingItems.end();
528 for (KFileItemList::iterator it = begin; it != end; ++it) {
529 if ((*it).url() == item.url()) {
530 m_pendingItems.erase(it);
531 break;
535 m_dispatchedItems.clear();
537 m_pendingVisiblePreviews = 0;
538 dispatchPreviewQueue();
540 KFileItemList orderedItems = m_pendingItems;
541 orderItems(orderedItems);
543 // Kill all suspended preview jobs. Usually when a preview job
544 // has been finished, slotPreviewJobFinished() clears all item queues.
545 // This is not wanted in this case, as a new job is created afterwards
546 // for m_pendingItems.
547 m_clearItemQueues = false;
548 killPreviewJobs();
549 m_clearItemQueues = true;
551 startPreviewJob(orderedItems);
554 bool KFilePreviewGenerator::Private::isCutItem(const KFileItem& item) const
556 const QMimeData* mimeData = QApplication::clipboard()->mimeData();
557 const KUrl::List cutUrls = KUrl::List::fromMimeData(mimeData);
559 const KUrl itemUrl = item.url();
560 foreach (const KUrl& url, cutUrls) {
561 if (url == itemUrl) {
562 return true;
566 return false;
569 void KFilePreviewGenerator::Private::applyCutItemEffect()
571 const QMimeData* mimeData = QApplication::clipboard()->mimeData();
572 m_hasCutSelection = decodeIsCutSelection(mimeData);
573 if (!m_hasCutSelection) {
574 return;
577 KFileItemList items;
578 KDirLister* dirLister = m_dirModel->dirLister();
579 const KUrl::List dirs = dirLister->directories();
580 foreach (const KUrl& url, dirs) {
581 items << dirLister->itemsForDir(url);
584 foreach (const KFileItem& item, items) {
585 if (isCutItem(item)) {
586 const QModelIndex index = m_dirModel->indexForItem(item);
587 const QVariant value = m_dirModel->data(index, Qt::DecorationRole);
588 if (value.type() == QVariant::Icon) {
589 const QIcon icon(qvariant_cast<QIcon>(value));
590 const QSize actualSize = icon.actualSize(m_viewAdapter->iconSize());
591 QPixmap pixmap = icon.pixmap(actualSize);
593 // remember current pixmap for the item to be able
594 // to restore it when other items get cut
595 ItemInfo cutItem;
596 cutItem.url = item.url();
597 cutItem.pixmap = pixmap;
598 m_cutItemsCache.append(cutItem);
600 // apply icon effect to the cut item
601 KIconEffect iconEffect;
602 pixmap = iconEffect.apply(pixmap, KIconLoader::Desktop, KIconLoader::DisabledState);
603 m_dirModel->setData(index, QIcon(pixmap), Qt::DecorationRole);
609 bool KFilePreviewGenerator::Private::applyImageFrame(QPixmap& icon)
611 const QSize maxSize = m_viewAdapter->iconSize();
612 const bool applyFrame = (maxSize.width() > KIconLoader::SizeSmallMedium) &&
613 (maxSize.height() > KIconLoader::SizeSmallMedium) &&
614 ((icon.width() > KIconLoader::SizeLarge) ||
615 (icon.height() > KIconLoader::SizeLarge));
616 if (!applyFrame) {
617 // the maximum size or the image itself is too small for a frame
618 return false;
621 const int tloffset = 2;
622 const int broffset = 4;
623 const int doubleFrame = tloffset + broffset;
625 // resize the icon to the maximum size minus the space required for the frame
626 limitToSize(icon, QSize(maxSize.width() - doubleFrame, maxSize.height() - doubleFrame));
628 if (!m_tileSet) {
629 m_tileSet = new TileSet;
632 QPixmap framedIcon(icon.size().width() + doubleFrame, icon.size().height() + doubleFrame);
633 framedIcon.fill(Qt::transparent);
635 QPainter painter;
636 painter.begin(&framedIcon);
637 painter.setCompositionMode(QPainter::CompositionMode_Source);
638 m_tileSet->paint(&painter, framedIcon.rect());
639 painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
640 painter.drawPixmap(tloffset, tloffset, icon);
641 painter.end();
643 icon = framedIcon;
644 return true;
647 void KFilePreviewGenerator::Private::limitToSize(QPixmap& icon, const QSize& maxSize)
649 if ((icon.width() > maxSize.width()) || (icon.height() > maxSize.height())) {
650 #ifdef Q_WS_X11
651 // Assume that the texture size limit is 2048x2048
652 if ((icon.width() <= 2048) && (icon.height() <= 2048)) {
653 QSize size = icon.size();
654 size.scale(maxSize, Qt::KeepAspectRatio);
656 const qreal factor = size.width() / qreal(icon.width());
658 XTransform xform = {{
659 { XDoubleToFixed(1 / factor), 0, 0 },
660 { 0, XDoubleToFixed(1 / factor), 0 },
661 { 0, 0, XDoubleToFixed(1) }
664 QPixmap pixmap(size);
665 pixmap.fill(Qt::transparent);
667 Display* dpy = QX11Info::display();
668 XRenderSetPictureFilter(dpy, icon.x11PictureHandle(), FilterBilinear, 0, 0);
669 XRenderSetPictureTransform(dpy, icon.x11PictureHandle(), &xform);
670 XRenderComposite(dpy, PictOpOver, icon.x11PictureHandle(), None, pixmap.x11PictureHandle(),
671 0, 0, 0, 0, 0, 0, pixmap.width(), pixmap.height());
672 icon = pixmap;
673 } else {
674 icon = icon.scaled(maxSize, Qt::KeepAspectRatio, Qt::FastTransformation);
676 #else
677 icon = icon.scaled(maxSize, Qt::KeepAspectRatio, Qt::FastTransformation);
678 #endif
682 void KFilePreviewGenerator::Private::startPreviewJob(const KFileItemList& items)
684 if (items.count() == 0) {
685 return;
688 const QMimeData* mimeData = QApplication::clipboard()->mimeData();
689 m_hasCutSelection = decodeIsCutSelection(mimeData);
691 const QSize size = m_viewAdapter->iconSize();
693 // PreviewJob internally caches items always with the size of
694 // 128 x 128 pixels or 256 x 256 pixels. A downscaling is done
695 // by PreviewJob if a smaller size is requested. As the KFilePreviewGenerator must
696 // do a downscaling anyhow because of the frame, only the provided
697 // cache sizes are requested.
698 const int cacheSize = (size.width() > 128) || (size.height() > 128) ? 256 : 128;
699 KIO::PreviewJob* job = KIO::filePreview(items, cacheSize, cacheSize, 0, 70, true, true, &m_enabledPlugins);
700 connect(job, SIGNAL(gotPreview(const KFileItem&, const QPixmap&)),
701 q, SLOT(addToPreviewQueue(const KFileItem&, const QPixmap&)));
702 connect(job, SIGNAL(finished(KJob*)),
703 q, SLOT(slotPreviewJobFinished(KJob*)));
705 m_previewJobs.append(job);
706 m_previewTimer->start(200);
709 void KFilePreviewGenerator::Private::killPreviewJobs()
711 foreach (KJob* job, m_previewJobs) {
712 Q_ASSERT(job != 0);
713 job->kill();
715 m_previewJobs.clear();
718 void KFilePreviewGenerator::Private::orderItems(KFileItemList& items)
720 // Order the items in a way that the preview for the visible items
721 // is generated first, as this improves the feeled performance a lot.
723 // Implementation note: 2 different algorithms are used for the sorting.
724 // Algorithm 1 is faster when having a lot of items in comparison
725 // to the number of rows in the model. Algorithm 2 is faster
726 // when having quite less items in comparison to the number of rows in
727 // the model. Choosing the right algorithm is important when having directories
728 // with several hundreds or thousands of items.
731 const bool hasProxy = (m_proxyModel != 0);
732 const int itemCount = items.count();
733 const int rowCount = hasProxy ? m_proxyModel->rowCount() : m_dirModel->rowCount();
734 const QRect visibleArea = m_viewAdapter->visibleArea();
736 QModelIndex dirIndex;
737 QRect itemRect;
738 int insertPos = 0;
739 if (itemCount * 10 > rowCount) {
740 // Algorithm 1: The number of items is > 10 % of the row count. Parse all rows
741 // and check whether the received row is part of the item list.
742 for (int row = 0; row < rowCount; ++row) {
743 if (hasProxy) {
744 const QModelIndex proxyIndex = m_proxyModel->index(row, 0);
745 itemRect = m_viewAdapter->visualRect(proxyIndex);
746 dirIndex = m_proxyModel->mapToSource(proxyIndex);
747 } else {
748 dirIndex = m_dirModel->index(row, 0);
749 itemRect = m_viewAdapter->visualRect(dirIndex);
752 KFileItem item = m_dirModel->itemForIndex(dirIndex); // O(1)
753 const KUrl url = item.url();
755 // check whether the item is part of the item list 'items'
756 int index = -1;
757 for (int i = 0; i < itemCount; ++i) {
758 if (items.at(i).url() == url) {
759 index = i;
760 break;
764 if ((index > 0) && itemRect.intersects(visibleArea)) {
765 // The current item is (at least partly) visible. Move it
766 // to the front of the list, so that the preview is
767 // generated earlier.
768 items.removeAt(index);
769 items.insert(insertPos, item);
770 ++insertPos;
771 ++m_pendingVisiblePreviews;
774 } else {
775 // Algorithm 2: The number of items is <= 10 % of the row count. In this case iterate
776 // all items and receive the corresponding row from the item.
777 for (int i = 0; i < itemCount; ++i) {
778 dirIndex = m_dirModel->indexForItem(items.at(i)); // O(n) (n = number of rows)
779 if (hasProxy) {
780 const QModelIndex proxyIndex = m_proxyModel->mapFromSource(dirIndex);
781 itemRect = m_viewAdapter->visualRect(proxyIndex);
782 } else {
783 itemRect = m_viewAdapter->visualRect(dirIndex);
786 if (itemRect.intersects(visibleArea)) {
787 // The current item is (at least partly) visible. Move it
788 // to the front of the list, so that the preview is
789 // generated earlier.
790 items.insert(insertPos, items.at(i));
791 items.removeAt(i + 1);
792 ++insertPos;
793 ++m_pendingVisiblePreviews;
799 bool KFilePreviewGenerator::Private::decodeIsCutSelection(const QMimeData* mimeData)
801 const QByteArray data = mimeData->data("application/x-kde-cutselection");
802 if (data.isEmpty()) {
803 return false;
804 } else {
805 return data.at(0) == '1';
809 KFilePreviewGenerator::KFilePreviewGenerator(QAbstractItemView* parent) :
810 QObject(parent),
811 d(new Private(this, new KIO::DefaultViewAdapter(parent, this), parent->model()))
813 d->m_itemView = parent;
816 KFilePreviewGenerator::KFilePreviewGenerator(KAbstractViewAdapter* parent, QAbstractProxyModel* model) :
817 QObject(parent),
818 d(new Private(this, parent, model))
822 KFilePreviewGenerator::~KFilePreviewGenerator()
824 delete d;
827 void KFilePreviewGenerator::setPreviewShown(bool show)
829 if (show && (!d->m_viewAdapter->iconSize().isValid() || (d->m_dirModel == 0))) {
830 // the view must provide an icon size and a directory model,
831 // otherwise the showing the previews will get ignored
832 return;
835 if (d->m_previewShown != show) {
836 d->m_previewShown = show;
837 d->m_cutItemsCache.clear();
838 d->updateCutItems();
839 if (show) {
840 updatePreviews();
844 if (show && (d->m_mimeTypeResolver != 0)) {
845 // don't resolve the MIME types if the preview is turned on
846 d->m_mimeTypeResolver->deleteLater();
847 d->m_mimeTypeResolver = 0;
848 } else if (!show && (d->m_mimeTypeResolver == 0)) {
849 // the preview is turned off: resolve the MIME-types so that
850 // the icons gets updated
851 d->m_mimeTypeResolver = new KMimeTypeResolver(d->m_viewAdapter);
855 bool KFilePreviewGenerator::isPreviewShown() const
857 return d->m_previewShown;
860 void KFilePreviewGenerator::updatePreviews()
862 if (!d->m_previewShown) {
863 return;
866 d->killPreviewJobs();
867 d->m_cutItemsCache.clear();
868 d->m_pendingItems.clear();
869 d->m_dispatchedItems.clear();
871 KFileItemList itemList;
872 const int rowCount = d->m_dirModel->rowCount();
873 for (int row = 0; row < rowCount; ++row) {
874 const QModelIndex index = d->m_dirModel->index(row, 0);
875 KFileItem item = d->m_dirModel->itemForIndex(index);
876 itemList.append(item);
879 d->generatePreviews(itemList);
880 d->updateCutItems();
883 void KFilePreviewGenerator::cancelPreviews()
885 d->killPreviewJobs();
886 d->m_cutItemsCache.clear();
887 d->m_pendingItems.clear();
888 d->m_dispatchedItems.clear();
891 void KFilePreviewGenerator::setEnabledPlugins(const QStringList& plugins)
893 d->m_enabledPlugins = plugins;
896 QStringList KFilePreviewGenerator::enabledPlugins() const
898 return d->m_enabledPlugins;
901 #include "kfilepreviewgenerator.moc"