1 /*******************************************************************************
2 * Copyright (C) 2008 by Peter Penz <peter.penz@gmx.at> *
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. *
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. *
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>
45 # include <X11/Xlib.h>
46 # include <X11/extensions/Xrender.h>
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()
71 LayoutBlocker(QAbstractItemView
* view
) :
72 m_uniformSizes(false),
73 m_view(qobject_cast
<QListView
*>(view
))
76 m_uniformSizes
= m_view
->uniformItemSizes();
77 m_view
->setUniformItemSizes(true);
84 m_view
->setUniformItemSizes(m_uniformSizes
);
96 enum Tile
{ TopLeftCorner
= 0, TopSide
, TopRightCorner
, LeftSide
, Center
,
97 RightSide
, BottomLeftCorner
, BottomSide
, BottomRightCorner
,
102 QImage
image(8 * 3, 8 * 3, QImage::Format_ARGB32_Premultiplied
);
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
);
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
]);
144 QPixmap m_tiles
[NumTiles
];
147 class KFilePreviewGenerator::Private
150 Private(KFilePreviewGenerator
* parent
,
151 KAbstractViewAdapter
* viewAdapter
,
152 QAbstractItemModel
* model
);
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
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
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
187 void pausePreviews();
190 * Resumes the previews that have been paused after moving the
191 * scrollbar. The previews for the current visible area are
194 void resumePreviews();
197 * Returns true, if the item \a item has been cut into
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
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
233 void orderItems(KFileItemList
& items
);
236 * Returns true, if \a mimeData represents a selection that has
239 bool decodeIsCutSelection(const QMimeData
* mimeData
);
241 /** Remembers the pixmap for an item specified by an URL. */
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
;
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
),
307 m_scrollAreaTimer(0),
311 m_mimeTypeResolver(0),
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;
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()
359 m_pendingItems
.clear();
360 m_dispatchedItems
.clear();
361 if (m_mimeTypeResolver
!= 0) {
362 m_mimeTypeResolver
->deleteLater();
363 m_mimeTypeResolver
= 0;
368 void KFilePreviewGenerator::Private::generatePreviews(const KFileItemList
& items
)
370 applyCutItemEffect();
372 if (!m_previewShown
) {
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
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;
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()) {
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()
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
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
) {
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
);
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;
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
) {
569 void KFilePreviewGenerator::Private::applyCutItemEffect()
571 const QMimeData
* mimeData
= QApplication::clipboard()->mimeData();
572 m_hasCutSelection
= decodeIsCutSelection(mimeData
);
573 if (!m_hasCutSelection
) {
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
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
));
617 // the maximum size or the image itself is too small for a frame
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
));
629 m_tileSet
= new TileSet
;
632 QPixmap
framedIcon(icon
.size().width() + doubleFrame
, icon
.size().height() + doubleFrame
);
633 framedIcon
.fill(Qt::transparent
);
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
);
647 void KFilePreviewGenerator::Private::limitToSize(QPixmap
& icon
, const QSize
& maxSize
)
649 if ((icon
.width() > maxSize
.width()) || (icon
.height() > maxSize
.height())) {
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());
674 icon
= icon
.scaled(maxSize
, Qt::KeepAspectRatio
, Qt::FastTransformation
);
677 icon
= icon
.scaled(maxSize
, Qt::KeepAspectRatio
, Qt::FastTransformation
);
682 void KFilePreviewGenerator::Private::startPreviewJob(const KFileItemList
& items
)
684 if (items
.count() == 0) {
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
) {
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
;
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
) {
744 const QModelIndex proxyIndex
= m_proxyModel
->index(row
, 0);
745 itemRect
= m_viewAdapter
->visualRect(proxyIndex
);
746 dirIndex
= m_proxyModel
->mapToSource(proxyIndex
);
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'
757 for (int i
= 0; i
< itemCount
; ++i
) {
758 if (items
.at(i
).url() == url
) {
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
);
771 ++m_pendingVisiblePreviews
;
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)
780 const QModelIndex proxyIndex
= m_proxyModel
->mapFromSource(dirIndex
);
781 itemRect
= m_viewAdapter
->visualRect(proxyIndex
);
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);
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()) {
805 return data
.at(0) == '1';
809 KFilePreviewGenerator::KFilePreviewGenerator(QAbstractItemView
* 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
) :
818 d(new Private(this, parent
, model
))
822 KFilePreviewGenerator::~KFilePreviewGenerator()
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
835 if (d
->m_previewShown
!= show
) {
836 d
->m_previewShown
= show
;
837 d
->m_cutItemsCache
.clear();
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
) {
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
);
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"