1 | /******************************************************************************* |
2 | * Copyright (C) 2008-2009 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 | *******************************************************************************/ |
19 | |
20 | #include "kfilepreviewgenerator.h" |
21 | |
22 | #include "../kio/kio/defaultviewadapter_p.h" // KDE5 TODO: move this class here |
23 | #include "../kio/kio/imagefilter_p.h" |
24 | #include <config.h> // for HAVE_XRENDER |
25 | #include <kconfiggroup.h> |
26 | #include <kfileitem.h> |
27 | #include <kiconeffect.h> |
28 | #include <kio/previewjob.h> |
29 | #include <kdirlister.h> |
30 | #include <kdirmodel.h> |
31 | #include <ksharedconfig.h> |
32 | |
33 | #include <QApplication> |
34 | #include <QAbstractItemView> |
35 | #include <QAbstractProxyModel> |
36 | #include <QClipboard> |
37 | #include <QColor> |
38 | #include <QHash> |
39 | #include <QList> |
40 | #include <QListView> |
41 | #include <QPainter> |
42 | #include <QPixmap> |
43 | #include <QScrollBar> |
44 | #include <QIcon> |
45 | |
46 | #if defined(Q_WS_X11) && defined(HAVE_XRENDER) |
47 | # include <QX11Info> |
48 | # include <X11/Xlib.h> |
49 | # include <X11/extensions/Xrender.h> |
50 | #endif |
51 | |
52 | /** |
53 | * If the passed item view is an instance of QListView, expensive |
54 | * layout operations are blocked in the constructor and are unblocked |
55 | * again in the destructor. |
56 | * |
57 | * This helper class is a workaround for the following huge performance |
58 | * problem when having directories with several 1000 items: |
59 | * - each change of an icon emits a dataChanged() signal from the model |
60 | * - QListView iterates through all items on each dataChanged() signal |
61 | * and invokes QItemDelegate::sizeHint() |
62 | * - the sizeHint() implementation of KFileItemDelegate is quite complex, |
63 | * invoking it 1000 times for each icon change might block the UI |
64 | * |
65 | * QListView does not invoke QItemDelegate::sizeHint() when the |
66 | * uniformItemSize property has been set to true, so this property is |
67 | * set before exchanging a block of icons. It is important to reset |
68 | * it again before the event loop is entered, otherwise QListView |
69 | * would not get the correct size hints after dispatching the layoutChanged() |
70 | * signal. |
71 | */ |
72 | class KFilePreviewGenerator::LayoutBlocker |
73 | { |
74 | public: |
75 | LayoutBlocker(QAbstractItemView* view) : |
76 | m_uniformSizes(false), |
77 | m_view(qobject_cast<QListView*>(view)) |
78 | { |
79 | if (m_view != 0) { |
80 | m_uniformSizes = m_view->uniformItemSizes(); |
81 | m_view->setUniformItemSizes(true); |
82 | } |
83 | } |
84 | |
85 | ~LayoutBlocker() |
86 | { |
87 | if (m_view != 0) { |
88 | m_view->setUniformItemSizes(m_uniformSizes); |
89 | } |
90 | } |
91 | |
92 | private: |
93 | bool m_uniformSizes; |
94 | QListView* m_view; |
95 | }; |
96 | |
97 | /** Helper class for drawing frames for image previews. */ |
98 | class KFilePreviewGenerator::TileSet |
99 | { |
100 | public: |
101 | enum { LeftMargin = 3, TopMargin = 2, RightMargin = 3, BottomMargin = 4 }; |
102 | |
103 | enum Tile { TopLeftCorner = 0, TopSide, TopRightCorner, LeftSide, |
104 | RightSide, BottomLeftCorner, BottomSide, BottomRightCorner, |
105 | NumTiles }; |
106 | |
107 | TileSet() |
108 | { |
109 | QImage image(8 * 3, 8 * 3, QImage::Format_ARGB32_Premultiplied); |
110 | |
111 | QPainter p(&image); |
112 | p.setCompositionMode(QPainter::CompositionMode_Source); |
113 | p.fillRect(image.rect(), Qt::transparent); |
114 | p.fillRect(image.rect().adjusted(3, 3, -3, -3), Qt::black); |
115 | p.end(); |
116 | |
117 | KIO::ImageFilter::shadowBlur(image, 3, Qt::black); |
118 | |
119 | QPixmap pixmap = QPixmap::fromImage(image); |
120 | m_tiles[TopLeftCorner] = pixmap.copy(0, 0, 8, 8); |
121 | m_tiles[TopSide] = pixmap.copy(8, 0, 8, 8); |
122 | m_tiles[TopRightCorner] = pixmap.copy(16, 0, 8, 8); |
123 | m_tiles[LeftSide] = pixmap.copy(0, 8, 8, 8); |
124 | m_tiles[RightSide] = pixmap.copy(16, 8, 8, 8); |
125 | m_tiles[BottomLeftCorner] = pixmap.copy(0, 16, 8, 8); |
126 | m_tiles[BottomSide] = pixmap.copy(8, 16, 8, 8); |
127 | m_tiles[BottomRightCorner] = pixmap.copy(16, 16, 8, 8); |
128 | } |
129 | |
130 | void paint(QPainter* p, const QRect& r) |
131 | { |
132 | p->drawPixmap(r.topLeft(), m_tiles[TopLeftCorner]); |
133 | if (r.width() - 16 > 0) { |
134 | p->drawTiledPixmap(r.x() + 8, r.y(), r.width() - 16, 8, m_tiles[TopSide]); |
135 | } |
136 | p->drawPixmap(r.right() - 8 + 1, r.y(), m_tiles[TopRightCorner]); |
137 | if (r.height() - 16 > 0) { |
138 | p->drawTiledPixmap(r.x(), r.y() + 8, 8, r.height() - 16, m_tiles[LeftSide]); |
139 | p->drawTiledPixmap(r.right() - 8 + 1, r.y() + 8, 8, r.height() - 16, m_tiles[RightSide]); |
140 | } |
141 | p->drawPixmap(r.x(), r.bottom() - 8 + 1, m_tiles[BottomLeftCorner]); |
142 | if (r.width() - 16 > 0) { |
143 | p->drawTiledPixmap(r.x() + 8, r.bottom() - 8 + 1, r.width() - 16, 8, m_tiles[BottomSide]); |
144 | } |
145 | p->drawPixmap(r.right() - 8 + 1, r.bottom() - 8 + 1, m_tiles[BottomRightCorner]); |
146 | |
147 | const QRect contentRect = r.adjusted(LeftMargin + 1, TopMargin + 1, |
148 | -(RightMargin + 1), -(BottomMargin + 1)); |
149 | p->fillRect(contentRect, Qt::transparent); |
150 | } |
151 | |
152 | private: |
153 | QPixmap m_tiles[NumTiles]; |
154 | }; |
155 | |
156 | class KFilePreviewGenerator::Private |
157 | { |
158 | public: |
159 | Private(KFilePreviewGenerator* parent, |
160 | KAbstractViewAdapter* viewAdapter, |
161 | QAbstractItemModel* model); |
162 | ~Private(); |
163 | |
164 | /** |
165 | * Requests a new icon for the item \a index. |
166 | * @param sequenceIndex If this is zero, the standard icon is requested, else another one. |
167 | */ |
168 | void requestSequenceIcon(const QModelIndex& index, int sequenceIndex); |
169 | |
170 | /** |
171 | * Generates previews for the items \a items asynchronously. |
172 | */ |
173 | void updateIcons(const KFileItemList& items); |
174 | |
175 | /** |
176 | * Generates previews for the indices within \a topLeft |
177 | * and \a bottomRight asynchronously. |
178 | */ |
179 | void updateIcons(const QModelIndex& topLeft, const QModelIndex& bottomRight); |
180 | |
181 | /** |
182 | * Adds the preview \a pixmap for the item \a item to the preview |
183 | * queue and starts a timer which will dispatch the preview queue |
184 | * later. |
185 | */ |
186 | void addToPreviewQueue(const KFileItem& item, const QPixmap& pixmap); |
187 | |
188 | /** |
189 | * Is invoked when the preview job has been finished and |
190 | * removes the job from the m_previewJobs list. |
191 | */ |
192 | void slotPreviewJobFinished(KJob* job); |
193 | |
194 | /** Synchronizes the icon of all items with the clipboard of cut items. */ |
195 | void updateCutItems(); |
196 | |
197 | /** |
198 | * Reset all icons of the items from m_cutItemsCache and clear |
199 | * the cache. |
200 | */ |
201 | void clearCutItemsCache(); |
202 | |
203 | /** |
204 | * Dispatches the preview queue block by block within |
205 | * time slices. |
206 | */ |
207 | void dispatchIconUpdateQueue(); |
208 | |
209 | /** |
210 | * Pauses all icon updates and invokes KFilePreviewGenerator::resumeIconUpdates() |
211 | * after a short delay. Is invoked as soon as the user has moved |
212 | * a scrollbar. |
213 | */ |
214 | void pauseIconUpdates(); |
215 | |
216 | /** |
217 | * Resumes the icons updates that have been paused after moving the |
218 | * scrollbar. The previews for the current visible area are |
219 | * generated first. |
220 | */ |
221 | void resumeIconUpdates(); |
222 | |
223 | /** |
224 | * Starts the resolving of the MIME types from |
225 | * the m_pendingItems queue. |
226 | */ |
227 | void startMimeTypeResolving(); |
228 | |
229 | /** |
230 | * Resolves the MIME type for exactly one item of the |
231 | * m_pendingItems queue. |
232 | */ |
233 | void resolveMimeType(); |
234 | |
235 | /** |
236 | * Returns true, if the item \a item has been cut into |
237 | * the clipboard. |
238 | */ |
239 | bool isCutItem(const KFileItem& item) const; |
240 | |
241 | /** |
242 | * Applies a cut-item effect to all given \a items, if they |
243 | * are marked as cut in the clipboard. |
244 | */ |
245 | void applyCutItemEffect(const KFileItemList& items); |
246 | |
247 | /** |
248 | * Applies a frame around the icon. False is returned if |
249 | * no frame has been added because the icon is too small. |
250 | */ |
251 | bool applyImageFrame(QPixmap& icon); |
252 | |
253 | /** |
254 | * Resizes the icon to \a maxSize if the icon size does not |
255 | * fit into the maximum size. The aspect ratio of the icon |
256 | * is kept. |
257 | */ |
258 | void limitToSize(QPixmap& icon, const QSize& maxSize); |
259 | |
260 | /** |
261 | * Creates previews by starting new preview jobs for the items |
262 | * and triggers the preview timer. |
263 | */ |
264 | void createPreviews(const KFileItemList& items); |
265 | |
266 | /** |
267 | * Helper method for createPreviews(): Starts a preview job for the given |
268 | * items. For each returned preview addToPreviewQueue() will get invoked. |
269 | */ |
270 | void startPreviewJob(const KFileItemList& items, int width, int height); |
271 | |
272 | /** Kills all ongoing preview jobs. */ |
273 | void killPreviewJobs(); |
274 | |
275 | /** |
276 | * Orders the items \a items in a way that the visible items |
277 | * are moved to the front of the list. When passing this |
278 | * list to a preview job, the visible items will get generated |
279 | * first. |
280 | */ |
281 | void orderItems(KFileItemList& items); |
282 | |
283 | /** |
284 | * Returns true, if \a mimeData represents a selection that has |
285 | * been cut. |
286 | */ |
287 | bool decodeIsCutSelection(const QMimeData* mimeData); |
288 | |
289 | /** |
290 | * Helper method for KFilePreviewGenerator::updateIcons(). Adds |
291 | * recursively all items from the model to the list \a list. |
292 | */ |
293 | void addItemsToList(const QModelIndex& index, KFileItemList& list); |
294 | |
295 | /** |
296 | * Updates the icons of files that are constantly changed due to a copy |
297 | * operation. See m_changedItems and m_changedItemsTimer for details. |
298 | */ |
299 | void delayedIconUpdate(); |
300 | |
301 | /** |
302 | * Any items that are removed from the model are also removed from m_changedItems. |
303 | */ |
304 | void rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end); |
305 | |
306 | /** Remembers the pixmap for an item specified by an URL. */ |
307 | struct ItemInfo |
308 | { |
309 | KUrl url; |
310 | QPixmap pixmap; |
311 | }; |
312 | |
313 | /** |
314 | * During the lifetime of a DataChangeObtainer instance changing |
315 | * the data of the model won't trigger generating a preview. |
316 | */ |
317 | class DataChangeObtainer |
318 | { |
319 | public: |
320 | DataChangeObtainer(KFilePreviewGenerator::Private* generator) : |
321 | m_gen(generator) { ++m_gen->m_internalDataChange; } |
322 | ~DataChangeObtainer() { --m_gen->m_internalDataChange; } |
323 | private: |
324 | KFilePreviewGenerator::Private* m_gen; |
325 | }; |
326 | |
327 | bool m_previewShown; |
328 | |
329 | /** |
330 | * True, if m_pendingItems and m_dispatchedItems should be |
331 | * cleared when the preview jobs have been finished. |
332 | */ |
333 | bool m_clearItemQueues; |
334 | |
335 | /** |
336 | * True if a selection has been done which should cut items. |
337 | */ |
338 | bool m_hasCutSelection; |
339 | |
340 | /** |
341 | * True if the updates of icons has been paused by pauseIconUpdates(). |
342 | * The value is reset by resumeIconUpdates(). |
343 | */ |
344 | bool m_iconUpdatesPaused; |
345 | |
346 | /** |
347 | * If the value is 0, the slot |
348 | * updateIcons(const QModelIndex&, const QModelIndex&) has |
349 | * been triggered by an external data change. |
350 | */ |
351 | int m_internalDataChange; |
352 | |
353 | int m_pendingVisibleIconUpdates; |
354 | |
355 | KAbstractViewAdapter* m_viewAdapter; |
356 | QAbstractItemView* m_itemView; |
357 | QTimer* m_iconUpdateTimer; |
358 | QTimer* m_scrollAreaTimer; |
359 | QList<KJob*> m_previewJobs; |
360 | QWeakPointer<KDirModel> m_dirModel; |
361 | QAbstractProxyModel* m_proxyModel; |
362 | |
363 | /** |
364 | * Set of all items that already have the 'cut' effect applied, together with the pixmap it was applied to |
365 | * This is used to make sure that the 'cut' effect is applied max. once for each pixmap |
366 | * |
367 | * Referencing the pixmaps here imposes no overhead, as they were also given to KDirModel::setData(), |
368 | * and thus are held anyway. |
369 | */ |
370 | QHash<KUrl, QPixmap> m_cutItemsCache; |
371 | QList<ItemInfo> m_previews; |
372 | QMap<KUrl, int> m_sequenceIndices; |
373 | |
374 | /** |
375 | * When huge items are copied, it must be prevented that a preview gets generated |
376 | * for each item size change. m_changedItems keeps track of the changed items and it |
377 | * is assured that a final preview is only done if an item does not change within |
378 | * at least 5 seconds. |
379 | */ |
380 | QHash<KUrl, bool> m_changedItems; |
381 | QTimer* m_changedItemsTimer; |
382 | |
383 | /** |
384 | * Contains all items where a preview must be generated, but |
385 | * where the preview job has not dispatched the items yet. |
386 | */ |
387 | KFileItemList m_pendingItems; |
388 | |
389 | /** |
390 | * Contains all items, where a preview has already been |
391 | * generated by the preview jobs. |
392 | */ |
393 | KFileItemList m_dispatchedItems; |
394 | |
395 | KFileItemList m_resolvedMimeTypes; |
396 | |
397 | QStringList m_enabledPlugins; |
398 | |
399 | TileSet* m_tileSet; |
400 | |
401 | private: |
402 | KFilePreviewGenerator* const q; |
403 | |
404 | }; |
405 | |
406 | KFilePreviewGenerator::Private::Private(KFilePreviewGenerator* parent, |
407 | KAbstractViewAdapter* viewAdapter, |
408 | QAbstractItemModel* model) : |
409 | m_previewShown(true), |
410 | m_clearItemQueues(true), |
411 | m_hasCutSelection(false), |
412 | m_iconUpdatesPaused(false), |
413 | m_internalDataChange(0), |
414 | m_pendingVisibleIconUpdates(0), |
415 | m_viewAdapter(viewAdapter), |
416 | m_itemView(0), |
417 | m_iconUpdateTimer(0), |
418 | m_scrollAreaTimer(0), |
419 | m_previewJobs(), |
420 | m_proxyModel(0), |
421 | m_cutItemsCache(), |
422 | m_previews(), |
423 | m_sequenceIndices(), |
424 | m_changedItems(), |
425 | m_changedItemsTimer(0), |
426 | m_pendingItems(), |
427 | m_dispatchedItems(), |
428 | m_resolvedMimeTypes(), |
429 | m_enabledPlugins(), |
430 | m_tileSet(0), |
431 | q(parent) |
432 | { |
433 | if (!m_viewAdapter->iconSize().isValid()) { |
434 | m_previewShown = false; |
435 | } |
436 | |
437 | m_proxyModel = qobject_cast<QAbstractProxyModel*>(model); |
438 | m_dirModel = (m_proxyModel == 0) ? |
439 | qobject_cast<KDirModel*>(model) : |
440 | qobject_cast<KDirModel*>(m_proxyModel->sourceModel()); |
441 | if (!m_dirModel) { |
442 | // previews can only get generated for directory models |
443 | m_previewShown = false; |
444 | } else { |
445 | KDirModel* dirModel = m_dirModel.data(); |
446 | connect(dirModel->dirLister(), SIGNAL(newItems(KFileItemList)), |
447 | q, SLOT(updateIcons(KFileItemList))); |
448 | connect(dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), |
449 | q, SLOT(updateIcons(QModelIndex,QModelIndex))); |
450 | connect(dirModel, SIGNAL(needSequenceIcon(QModelIndex,int)), |
451 | q, SLOT(requestSequenceIcon(QModelIndex,int))); |
452 | connect(dirModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), |
453 | q, SLOT(rowsAboutToBeRemoved(QModelIndex,int,int))); |
454 | } |
455 | |
456 | QClipboard* clipboard = QApplication::clipboard(); |
457 | connect(clipboard, SIGNAL(dataChanged()), |
458 | q, SLOT(updateCutItems())); |
459 | |
460 | m_iconUpdateTimer = new QTimer(q); |
461 | m_iconUpdateTimer->setSingleShot(true); |
462 | m_iconUpdateTimer->setInterval(200); |
463 | connect(m_iconUpdateTimer, SIGNAL(timeout()), q, SLOT(dispatchIconUpdateQueue())); |
464 | |
465 | // Whenever the scrollbar values have been changed, the pending previews should |
466 | // be reordered in a way that the previews for the visible items are generated |
467 | // first. The reordering is done with a small delay, so that during moving the |
468 | // scrollbars the CPU load is kept low. |
469 | m_scrollAreaTimer = new QTimer(q); |
470 | m_scrollAreaTimer->setSingleShot(true); |
471 | m_scrollAreaTimer->setInterval(200); |
472 | connect(m_scrollAreaTimer, SIGNAL(timeout()), |
473 | q, SLOT(resumeIconUpdates())); |
474 | m_viewAdapter->connect(KAbstractViewAdapter::ScrollBarValueChanged, |
475 | q, SLOT(pauseIconUpdates())); |
476 | |
477 | m_changedItemsTimer = new QTimer(q); |
478 | m_changedItemsTimer->setSingleShot(true); |
479 | m_changedItemsTimer->setInterval(5000); |
480 | connect(m_changedItemsTimer, SIGNAL(timeout()), |
481 | q, SLOT(delayedIconUpdate())); |
482 | |
483 | KConfigGroup globalConfig(KGlobal::config(), "PreviewSettings" ); |
484 | m_enabledPlugins = globalConfig.readEntry("Plugins" , QStringList() |
485 | << "directorythumbnail" |
486 | << "imagethumbnail" |
487 | << "jpegthumbnail" ); |
488 | |
489 | // If the user is upgrading from KDE <= 4.6, we must check if he had the 'jpegrotatedthumbnail' plugin enabled. |
490 | // This plugin does not exist any more in KDE >= 4.7, so we have to replace it with the 'jpegthumbnail' plugin. |
491 | if(m_enabledPlugins.contains(QLatin1String("jpegrotatedthumbnail" ))) { |
492 | m_enabledPlugins.removeAll(QLatin1String("jpegrotatedthumbnail" )); |
493 | m_enabledPlugins.append(QLatin1String("jpegthumbnail" )); |
494 | globalConfig.writeEntry("Plugins" , m_enabledPlugins); |
495 | globalConfig.sync(); |
496 | } |
497 | } |
498 | |
499 | KFilePreviewGenerator::Private::~Private() |
500 | { |
501 | killPreviewJobs(); |
502 | m_pendingItems.clear(); |
503 | m_dispatchedItems.clear(); |
504 | delete m_tileSet; |
505 | } |
506 | |
507 | void KFilePreviewGenerator::Private::requestSequenceIcon(const QModelIndex& index, |
508 | int sequenceIndex) |
509 | { |
510 | if (m_pendingItems.isEmpty() || (sequenceIndex == 0)) { |
511 | KDirModel* dirModel = m_dirModel.data(); |
512 | if (!dirModel) { |
513 | return; |
514 | } |
515 | |
516 | KFileItem item = dirModel->itemForIndex(index); |
517 | if (sequenceIndex == 0) { |
518 | m_sequenceIndices.remove(item.url()); |
519 | } else { |
520 | m_sequenceIndices.insert(item.url(), sequenceIndex); |
521 | } |
522 | |
523 | ///@todo Update directly, without using m_sequenceIndices |
524 | updateIcons(KFileItemList() << item); |
525 | } |
526 | } |
527 | |
528 | void KFilePreviewGenerator::Private::updateIcons(const KFileItemList& items) |
529 | { |
530 | if (items.isEmpty()) { |
531 | return; |
532 | } |
533 | |
534 | applyCutItemEffect(items); |
535 | |
536 | KFileItemList orderedItems = items; |
537 | orderItems(orderedItems); |
538 | |
539 | foreach (const KFileItem& item, orderedItems) { |
540 | m_pendingItems.append(item); |
541 | } |
542 | |
543 | if (m_previewShown) { |
544 | createPreviews(orderedItems); |
545 | } else { |
546 | startMimeTypeResolving(); |
547 | } |
548 | } |
549 | |
550 | void KFilePreviewGenerator::Private::updateIcons(const QModelIndex& topLeft, |
551 | const QModelIndex& bottomRight) |
552 | { |
553 | if (m_internalDataChange > 0) { |
554 | // QAbstractItemModel::setData() has been invoked internally by the KFilePreviewGenerator. |
555 | // The signal dataChanged() is connected with this method, but previews only need |
556 | // to be generated when an external data change has occurred. |
557 | return; |
558 | } |
559 | |
560 | // dataChanged emitted for the root dir (e.g. permission changes) |
561 | if (!topLeft.isValid() || !bottomRight.isValid()) { |
562 | return; |
563 | } |
564 | |
565 | KDirModel* dirModel = m_dirModel.data(); |
566 | if (!dirModel) { |
567 | return; |
568 | } |
569 | |
570 | KFileItemList itemList; |
571 | for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { |
572 | const QModelIndex index = dirModel->index(row, 0); |
573 | if (!index.isValid()) { |
574 | continue; |
575 | } |
576 | const KFileItem item = dirModel->itemForIndex(index); |
577 | Q_ASSERT(!item.isNull()); |
578 | |
579 | if (m_previewShown) { |
580 | const KUrl url = item.url(); |
581 | const bool hasChanged = m_changedItems.contains(url); // O(1) |
582 | m_changedItems.insert(url, hasChanged); |
583 | if (!hasChanged) { |
584 | // only update the icon if it has not been already updated within |
585 | // the last 5 seconds (the other icons will be updated later with |
586 | // the help of m_changedItemsTimer) |
587 | itemList.append(item); |
588 | } |
589 | } else { |
590 | itemList.append(item); |
591 | } |
592 | } |
593 | |
594 | updateIcons(itemList); |
595 | m_changedItemsTimer->start(); |
596 | } |
597 | |
598 | void KFilePreviewGenerator::Private::addToPreviewQueue(const KFileItem& item, const QPixmap& pixmap) |
599 | { |
600 | KIO::PreviewJob* senderJob = qobject_cast<KIO::PreviewJob*>(q->sender()); |
601 | Q_ASSERT(senderJob != 0); |
602 | if (senderJob != 0) { |
603 | QMap<KUrl, int>::iterator it = m_sequenceIndices.find(item.url()); |
604 | if (senderJob->sequenceIndex() && (it == m_sequenceIndices.end() || *it != senderJob->sequenceIndex())) { |
605 | return; // the sequence index does not match the one we want |
606 | } |
607 | if (!senderJob->sequenceIndex() && it != m_sequenceIndices.end()) { |
608 | return; // the sequence index does not match the one we want |
609 | } |
610 | |
611 | m_sequenceIndices.erase(it); |
612 | } |
613 | |
614 | if (!m_previewShown) { |
615 | // the preview has been canceled in the meantime |
616 | return; |
617 | } |
618 | |
619 | KDirModel* dirModel = m_dirModel.data(); |
620 | if (!dirModel) { |
621 | return; |
622 | } |
623 | |
624 | // check whether the item is part of the directory lister (it is possible |
625 | // that a preview from an old directory lister is received) |
626 | bool isOldPreview = true; |
627 | |
628 | KUrl itemParentDir(item.url()); |
629 | itemParentDir.setPath(itemParentDir.directory()); |
630 | |
631 | foreach (const KUrl& dir, dirModel->dirLister()->directories()) { |
632 | if (dir == itemParentDir || !dir.hasPath()) { |
633 | isOldPreview = false; |
634 | break; |
635 | } |
636 | } |
637 | if (isOldPreview) { |
638 | return; |
639 | } |
640 | |
641 | QPixmap icon = pixmap; |
642 | |
643 | const QString mimeType = item.mimetype(); |
644 | const int slashIndex = mimeType.indexOf(QLatin1Char('/')); |
645 | const QString mimeTypeGroup = mimeType.left(slashIndex); |
646 | if ((mimeTypeGroup != QLatin1String("image" )) || !applyImageFrame(icon)) { |
647 | limitToSize(icon, m_viewAdapter->iconSize()); |
648 | } |
649 | |
650 | if (m_hasCutSelection && isCutItem(item)) { |
651 | // apply the disabled effect to the icon for marking it as "cut item" |
652 | // and apply the icon to the item |
653 | KIconEffect *iconEffect = KIconLoader::global()->iconEffect(); |
654 | icon = iconEffect->apply(icon, KIconLoader::Desktop, KIconLoader::DisabledState); |
655 | } |
656 | |
657 | KIconLoader::global()->drawOverlays(item.overlays(), icon, KIconLoader::Desktop); |
658 | |
659 | // remember the preview and URL, so that it can be applied to the model |
660 | // in KFilePreviewGenerator::dispatchIconUpdateQueue() |
661 | ItemInfo preview; |
662 | preview.url = item.url(); |
663 | preview.pixmap = icon; |
664 | m_previews.append(preview); |
665 | |
666 | m_dispatchedItems.append(item); |
667 | } |
668 | |
669 | void KFilePreviewGenerator::Private::slotPreviewJobFinished(KJob* job) |
670 | { |
671 | const int index = m_previewJobs.indexOf(job); |
672 | m_previewJobs.removeAt(index); |
673 | |
674 | if (m_previewJobs.isEmpty()) { |
675 | if (m_clearItemQueues) { |
676 | m_pendingItems.clear(); |
677 | m_dispatchedItems.clear(); |
678 | m_pendingVisibleIconUpdates = 0; |
679 | QMetaObject::invokeMethod(q, "dispatchIconUpdateQueue" , Qt::QueuedConnection); |
680 | } |
681 | m_sequenceIndices.clear(); // just to be sure that we don't leak anything |
682 | } |
683 | } |
684 | |
685 | void KFilePreviewGenerator::Private::updateCutItems() |
686 | { |
687 | KDirModel* dirModel = m_dirModel.data(); |
688 | if (!dirModel) { |
689 | return; |
690 | } |
691 | |
692 | DataChangeObtainer obt(this); |
693 | clearCutItemsCache(); |
694 | |
695 | KFileItemList items; |
696 | KDirLister* dirLister = dirModel->dirLister(); |
697 | const KUrl::List dirs = dirLister->directories(); |
698 | foreach (const KUrl& url, dirs) { |
699 | items << dirLister->itemsForDir(url); |
700 | } |
701 | applyCutItemEffect(items); |
702 | } |
703 | |
704 | void KFilePreviewGenerator::Private::clearCutItemsCache() |
705 | { |
706 | KDirModel* dirModel = m_dirModel.data(); |
707 | if (!dirModel) { |
708 | return; |
709 | } |
710 | |
711 | DataChangeObtainer obt(this); |
712 | KFileItemList previews; |
713 | // Reset the icons of all items that are stored in the cache |
714 | // to use their default MIME type icon. |
715 | foreach (const KUrl& url, m_cutItemsCache.keys()) { |
716 | const QModelIndex index = dirModel->indexForUrl(url); |
717 | if (index.isValid()) { |
718 | dirModel->setData(index, QIcon(), Qt::DecorationRole); |
719 | if (m_previewShown) { |
720 | previews.append(dirModel->itemForIndex(index)); |
721 | } |
722 | } |
723 | } |
724 | m_cutItemsCache.clear(); |
725 | |
726 | if (previews.size() > 0) { |
727 | // assure that the previews gets restored |
728 | Q_ASSERT(m_previewShown); |
729 | orderItems(previews); |
730 | updateIcons(previews); |
731 | } |
732 | } |
733 | |
734 | void KFilePreviewGenerator::Private::dispatchIconUpdateQueue() |
735 | { |
736 | KDirModel* dirModel = m_dirModel.data(); |
737 | if (!dirModel) { |
738 | return; |
739 | } |
740 | |
741 | const int count = m_previewShown ? m_previews.count() |
742 | : m_resolvedMimeTypes.count(); |
743 | if (count > 0) { |
744 | LayoutBlocker blocker(m_itemView); |
745 | DataChangeObtainer obt(this); |
746 | |
747 | if (m_previewShown) { |
748 | // dispatch preview queue |
749 | foreach (const ItemInfo& preview, m_previews) { |
750 | const QModelIndex idx = dirModel->indexForUrl(preview.url); |
751 | if (idx.isValid() && (idx.column() == 0)) { |
752 | dirModel->setData(idx, QIcon(preview.pixmap), Qt::DecorationRole); |
753 | } |
754 | } |
755 | m_previews.clear(); |
756 | } else { |
757 | // dispatch mime type queue |
758 | foreach (const KFileItem& item, m_resolvedMimeTypes) { |
759 | const QModelIndex idx = dirModel->indexForItem(item); |
760 | dirModel->itemChanged(idx); |
761 | } |
762 | m_resolvedMimeTypes.clear(); |
763 | } |
764 | |
765 | m_pendingVisibleIconUpdates -= count; |
766 | if (m_pendingVisibleIconUpdates < 0) { |
767 | m_pendingVisibleIconUpdates = 0; |
768 | } |
769 | } |
770 | |
771 | if (m_pendingVisibleIconUpdates > 0) { |
772 | // As long as there are pending previews for visible items, poll |
773 | // the preview queue periodically. If there are no pending previews, |
774 | // the queue is dispatched in slotPreviewJobFinished(). |
775 | m_iconUpdateTimer->start(); |
776 | } |
777 | } |
778 | |
779 | void KFilePreviewGenerator::Private::pauseIconUpdates() |
780 | { |
781 | m_iconUpdatesPaused = true; |
782 | foreach (KJob* job, m_previewJobs) { |
783 | Q_ASSERT(job != 0); |
784 | job->suspend(); |
785 | } |
786 | m_scrollAreaTimer->start(); |
787 | } |
788 | |
789 | void KFilePreviewGenerator::Private::resumeIconUpdates() |
790 | { |
791 | m_iconUpdatesPaused = false; |
792 | |
793 | // Before creating new preview jobs the m_pendingItems queue must be |
794 | // cleaned up by removing the already dispatched items. Implementation |
795 | // note: The order of the m_dispatchedItems queue and the m_pendingItems |
796 | // queue is usually equal. So even when having a lot of elements the |
797 | // nested loop is no performance bottle neck, as the inner loop is only |
798 | // entered once in most cases. |
799 | foreach (const KFileItem& item, m_dispatchedItems) { |
800 | KFileItemList::iterator begin = m_pendingItems.begin(); |
801 | KFileItemList::iterator end = m_pendingItems.end(); |
802 | for (KFileItemList::iterator it = begin; it != end; ++it) { |
803 | if ((*it).url() == item.url()) { |
804 | m_pendingItems.erase(it); |
805 | break; |
806 | } |
807 | } |
808 | } |
809 | m_dispatchedItems.clear(); |
810 | |
811 | m_pendingVisibleIconUpdates = 0; |
812 | dispatchIconUpdateQueue(); |
813 | |
814 | |
815 | if (m_previewShown) { |
816 | KFileItemList orderedItems = m_pendingItems; |
817 | orderItems(orderedItems); |
818 | |
819 | // Kill all suspended preview jobs. Usually when a preview job |
820 | // has been finished, slotPreviewJobFinished() clears all item queues. |
821 | // This is not wanted in this case, as a new job is created afterwards |
822 | // for m_pendingItems. |
823 | m_clearItemQueues = false; |
824 | killPreviewJobs(); |
825 | m_clearItemQueues = true; |
826 | |
827 | createPreviews(orderedItems); |
828 | } else { |
829 | orderItems(m_pendingItems); |
830 | startMimeTypeResolving(); |
831 | } |
832 | } |
833 | |
834 | void KFilePreviewGenerator::Private::startMimeTypeResolving() |
835 | { |
836 | resolveMimeType(); |
837 | m_iconUpdateTimer->start(); |
838 | } |
839 | |
840 | void KFilePreviewGenerator::Private::resolveMimeType() |
841 | { |
842 | if (m_pendingItems.isEmpty()) { |
843 | return; |
844 | } |
845 | |
846 | // resolve at least one MIME type |
847 | bool resolved = false; |
848 | do { |
849 | KFileItem item = m_pendingItems.takeFirst(); |
850 | if (item.isMimeTypeKnown()) { |
851 | if (m_pendingVisibleIconUpdates > 0) { |
852 | // The item is visible and the MIME type already known. |
853 | // Decrease the update counter for dispatchIconUpdateQueue(): |
854 | --m_pendingVisibleIconUpdates; |
855 | } |
856 | } else { |
857 | // The MIME type is unknown and must get resolved. The |
858 | // directory model is not informed yet, as a single update |
859 | // would be very expensive. Instead the item is remembered in |
860 | // m_resolvedMimeTypes and will be dispatched later |
861 | // by dispatchIconUpdateQueue(). |
862 | item.determineMimeType(); |
863 | m_resolvedMimeTypes.append(item); |
864 | resolved = true; |
865 | } |
866 | } while (!resolved && !m_pendingItems.isEmpty()); |
867 | |
868 | if (m_pendingItems.isEmpty()) { |
869 | // All MIME types have been resolved now. Assure |
870 | // that the directory model gets informed about |
871 | // this, so that an update of the icons is done. |
872 | dispatchIconUpdateQueue(); |
873 | } else if (!m_iconUpdatesPaused) { |
874 | // assure that the MIME type of the next |
875 | // item will be resolved asynchronously |
876 | QMetaObject::invokeMethod(q, "resolveMimeType" , Qt::QueuedConnection); |
877 | } |
878 | } |
879 | |
880 | bool KFilePreviewGenerator::Private::isCutItem(const KFileItem& item) const |
881 | { |
882 | const QMimeData* mimeData = QApplication::clipboard()->mimeData(); |
883 | const KUrl::List cutUrls = KUrl::List::fromMimeData(mimeData); |
884 | return cutUrls.contains(item.url()); |
885 | } |
886 | |
887 | void KFilePreviewGenerator::Private::applyCutItemEffect(const KFileItemList& items) |
888 | { |
889 | const QMimeData* mimeData = QApplication::clipboard()->mimeData(); |
890 | m_hasCutSelection = decodeIsCutSelection(mimeData); |
891 | if (!m_hasCutSelection) { |
892 | return; |
893 | } |
894 | |
895 | KDirModel* dirModel = m_dirModel.data(); |
896 | if (!dirModel) { |
897 | return; |
898 | } |
899 | |
900 | const QSet<KUrl> cutUrls = KUrl::List::fromMimeData(mimeData).toSet(); |
901 | |
902 | DataChangeObtainer obt(this); |
903 | KIconEffect *iconEffect = KIconLoader::global()->iconEffect(); |
904 | foreach (const KFileItem& item, items) { |
905 | if (cutUrls.contains(item.url())) { |
906 | const QModelIndex index = dirModel->indexForItem(item); |
907 | const QVariant value = dirModel->data(index, Qt::DecorationRole); |
908 | if (value.type() == QVariant::Icon) { |
909 | const QIcon icon(qvariant_cast<QIcon>(value)); |
910 | const QSize actualSize = icon.actualSize(m_viewAdapter->iconSize()); |
911 | QPixmap pixmap = icon.pixmap(actualSize); |
912 | |
913 | const QHash<KUrl, QPixmap>::const_iterator cacheIt = m_cutItemsCache.constFind(item.url()); |
914 | if ((cacheIt == m_cutItemsCache.constEnd()) || (cacheIt->cacheKey() != pixmap.cacheKey())) { |
915 | pixmap = iconEffect->apply(pixmap, KIconLoader::Desktop, KIconLoader::DisabledState); |
916 | dirModel->setData(index, QIcon(pixmap), Qt::DecorationRole); |
917 | |
918 | m_cutItemsCache.insert(item.url(), pixmap); |
919 | } |
920 | } |
921 | } |
922 | } |
923 | } |
924 | |
925 | bool KFilePreviewGenerator::Private::applyImageFrame(QPixmap& icon) |
926 | { |
927 | const QSize maxSize = m_viewAdapter->iconSize(); |
928 | |
929 | // The original size of an image is not exported by the thumbnail mechanism. |
930 | // Still it would be helpful to not apply an image frame for e. g. icons that |
931 | // fit into the given boundaries: |
932 | const bool isIconCandidate = (icon.width() == icon.height()) && |
933 | ((icon.width() & 0x7) == 0); |
934 | |
935 | const bool applyFrame = (maxSize.width() > KIconLoader::SizeSmallMedium) && |
936 | (maxSize.height() > KIconLoader::SizeSmallMedium) && |
937 | !isIconCandidate; |
938 | if (!applyFrame) { |
939 | // the maximum size or the image itself is too small for a frame |
940 | return false; |
941 | } |
942 | |
943 | // resize the icon to the maximum size minus the space required for the frame |
944 | const QSize size(maxSize.width() - TileSet::LeftMargin - TileSet::RightMargin, |
945 | maxSize.height() - TileSet::TopMargin - TileSet::BottomMargin); |
946 | limitToSize(icon, size); |
947 | |
948 | if (m_tileSet == 0) { |
949 | m_tileSet = new TileSet(); |
950 | } |
951 | |
952 | QPixmap framedIcon(icon.size().width() + TileSet::LeftMargin + TileSet::RightMargin, |
953 | icon.size().height() + TileSet::TopMargin + TileSet::BottomMargin); |
954 | framedIcon.fill(Qt::transparent); |
955 | |
956 | QPainter painter; |
957 | painter.begin(&framedIcon); |
958 | painter.setCompositionMode(QPainter::CompositionMode_Source); |
959 | m_tileSet->paint(&painter, framedIcon.rect()); |
960 | painter.setCompositionMode(QPainter::CompositionMode_SourceOver); |
961 | painter.drawPixmap(TileSet::LeftMargin, TileSet::TopMargin, icon); |
962 | painter.end(); |
963 | |
964 | icon = framedIcon; |
965 | return true; |
966 | } |
967 | |
968 | void KFilePreviewGenerator::Private::limitToSize(QPixmap& icon, const QSize& maxSize) |
969 | { |
970 | if ((icon.width() > maxSize.width()) || (icon.height() > maxSize.height())) { |
971 | #if defined(Q_WS_X11) && defined(HAVE_XRENDER) |
972 | // Assume that the texture size limit is 2048x2048 |
973 | if ((icon.width() <= 2048) && (icon.height() <= 2048) && icon.x11PictureHandle()) { |
974 | QSize size = icon.size(); |
975 | size.scale(maxSize, Qt::KeepAspectRatio); |
976 | |
977 | const qreal factor = size.width() / qreal(icon.width()); |
978 | |
979 | XTransform xform = {{ |
980 | { XDoubleToFixed(1 / factor), 0, 0 }, |
981 | { 0, XDoubleToFixed(1 / factor), 0 }, |
982 | { 0, 0, XDoubleToFixed(1) } |
983 | }}; |
984 | |
985 | QPixmap pixmap(size); |
986 | pixmap.fill(Qt::transparent); |
987 | |
988 | Display* dpy = QX11Info::display(); |
989 | |
990 | XRenderPictureAttributes attr; |
991 | attr.repeat = RepeatPad; |
992 | XRenderChangePicture(dpy, icon.x11PictureHandle(), CPRepeat, &attr); |
993 | |
994 | XRenderSetPictureFilter(dpy, icon.x11PictureHandle(), FilterBilinear, 0, 0); |
995 | XRenderSetPictureTransform(dpy, icon.x11PictureHandle(), &xform); |
996 | XRenderComposite(dpy, PictOpOver, icon.x11PictureHandle(), None, pixmap.x11PictureHandle(), |
997 | 0, 0, 0, 0, 0, 0, pixmap.width(), pixmap.height()); |
998 | icon = pixmap; |
999 | } else { |
1000 | icon = icon.scaled(maxSize, Qt::KeepAspectRatio, Qt::FastTransformation); |
1001 | } |
1002 | #else |
1003 | icon = icon.scaled(maxSize, Qt::KeepAspectRatio, Qt::FastTransformation); |
1004 | #endif |
1005 | } |
1006 | } |
1007 | |
1008 | void KFilePreviewGenerator::Private::createPreviews(const KFileItemList& items) |
1009 | { |
1010 | if (items.count() == 0) { |
1011 | return; |
1012 | } |
1013 | |
1014 | const QMimeData* mimeData = QApplication::clipboard()->mimeData(); |
1015 | m_hasCutSelection = decodeIsCutSelection(mimeData); |
1016 | |
1017 | // PreviewJob internally caches items always with the size of |
1018 | // 128 x 128 pixels or 256 x 256 pixels. A downscaling is done |
1019 | // by PreviewJob if a smaller size is requested. For images KFilePreviewGenerator must |
1020 | // do a downscaling anyhow because of the frame, so in this case only the provided |
1021 | // cache sizes are requested. |
1022 | KFileItemList imageItems; |
1023 | KFileItemList otherItems; |
1024 | QString mimeType; |
1025 | QString mimeTypeGroup; |
1026 | foreach (const KFileItem& item, items) { |
1027 | mimeType = item.mimetype(); |
1028 | const int slashIndex = mimeType.indexOf(QLatin1Char('/')); |
1029 | mimeTypeGroup = mimeType.left(slashIndex); |
1030 | if (mimeTypeGroup == QLatin1String("image" )) { |
1031 | imageItems.append(item); |
1032 | } else { |
1033 | otherItems.append(item); |
1034 | } |
1035 | } |
1036 | const QSize size = m_viewAdapter->iconSize(); |
1037 | startPreviewJob(otherItems, size.width(), size.height()); |
1038 | |
1039 | const int cacheSize = (size.width() > 128) || (size.height() > 128) ? 256 : 128; |
1040 | startPreviewJob(imageItems, cacheSize, cacheSize); |
1041 | |
1042 | m_iconUpdateTimer->start(); |
1043 | } |
1044 | |
1045 | void KFilePreviewGenerator::Private::startPreviewJob(const KFileItemList& items, int width, int height) |
1046 | { |
1047 | if (items.count() > 0) { |
1048 | KIO::PreviewJob* job = KIO::filePreview(items, QSize(width, height), &m_enabledPlugins); |
1049 | |
1050 | // Set the sequence index to the target. We only need to check if items.count() == 1, |
1051 | // because requestSequenceIcon(..) creates exactly such a request. |
1052 | if (!m_sequenceIndices.isEmpty() && (items.count() == 1)) { |
1053 | QMap<KUrl, int>::iterator it = m_sequenceIndices.find(items[0].url()); |
1054 | if (it != m_sequenceIndices.end()) { |
1055 | job->setSequenceIndex(*it); |
1056 | } |
1057 | } |
1058 | |
1059 | connect(job, SIGNAL(gotPreview(KFileItem,QPixmap)), |
1060 | q, SLOT(addToPreviewQueue(KFileItem,QPixmap))); |
1061 | connect(job, SIGNAL(finished(KJob*)), |
1062 | q, SLOT(slotPreviewJobFinished(KJob*))); |
1063 | m_previewJobs.append(job); |
1064 | } |
1065 | } |
1066 | |
1067 | void KFilePreviewGenerator::Private::killPreviewJobs() |
1068 | { |
1069 | foreach (KJob* job, m_previewJobs) { |
1070 | Q_ASSERT(job != 0); |
1071 | job->kill(); |
1072 | } |
1073 | m_previewJobs.clear(); |
1074 | m_sequenceIndices.clear(); |
1075 | |
1076 | m_iconUpdateTimer->stop(); |
1077 | m_scrollAreaTimer->stop(); |
1078 | m_changedItemsTimer->stop(); |
1079 | } |
1080 | |
1081 | void KFilePreviewGenerator::Private::orderItems(KFileItemList& items) |
1082 | { |
1083 | KDirModel* dirModel = m_dirModel.data(); |
1084 | if (!dirModel) { |
1085 | return; |
1086 | } |
1087 | |
1088 | // Order the items in a way that the preview for the visible items |
1089 | // is generated first, as this improves the feeled performance a lot. |
1090 | const bool hasProxy = (m_proxyModel != 0); |
1091 | const int itemCount = items.count(); |
1092 | const QRect visibleArea = m_viewAdapter->visibleArea(); |
1093 | |
1094 | QModelIndex dirIndex; |
1095 | QRect itemRect; |
1096 | int insertPos = 0; |
1097 | for (int i = 0; i < itemCount; ++i) { |
1098 | dirIndex = dirModel->indexForItem(items.at(i)); // O(n) (n = number of rows) |
1099 | if (hasProxy) { |
1100 | const QModelIndex proxyIndex = m_proxyModel->mapFromSource(dirIndex); |
1101 | itemRect = m_viewAdapter->visualRect(proxyIndex); |
1102 | } else { |
1103 | itemRect = m_viewAdapter->visualRect(dirIndex); |
1104 | } |
1105 | |
1106 | if (itemRect.intersects(visibleArea)) { |
1107 | // The current item is (at least partly) visible. Move it |
1108 | // to the front of the list, so that the preview is |
1109 | // generated earlier. |
1110 | items.insert(insertPos, items.at(i)); |
1111 | items.removeAt(i + 1); |
1112 | ++insertPos; |
1113 | ++m_pendingVisibleIconUpdates; |
1114 | } |
1115 | } |
1116 | } |
1117 | |
1118 | bool KFilePreviewGenerator::Private::decodeIsCutSelection(const QMimeData* mimeData) |
1119 | { |
1120 | const QByteArray data = mimeData->data("application/x-kde-cutselection" ); |
1121 | if (data.isEmpty()) { |
1122 | return false; |
1123 | } else { |
1124 | return data.at(0) == QLatin1Char('1'); |
1125 | } |
1126 | } |
1127 | |
1128 | void KFilePreviewGenerator::Private::addItemsToList(const QModelIndex& index, KFileItemList& list) |
1129 | { |
1130 | KDirModel* dirModel = m_dirModel.data(); |
1131 | if (!dirModel) { |
1132 | return; |
1133 | } |
1134 | |
1135 | const int rowCount = dirModel->rowCount(index); |
1136 | for (int row = 0; row < rowCount; ++row) { |
1137 | const QModelIndex subIndex = dirModel->index(row, 0, index); |
1138 | KFileItem item = dirModel->itemForIndex(subIndex); |
1139 | list.append(item); |
1140 | |
1141 | if (dirModel->rowCount(subIndex) > 0) { |
1142 | // the model is hierarchical (treeview) |
1143 | addItemsToList(subIndex, list); |
1144 | } |
1145 | } |
1146 | } |
1147 | |
1148 | void KFilePreviewGenerator::Private::delayedIconUpdate() |
1149 | { |
1150 | KDirModel* dirModel = m_dirModel.data(); |
1151 | if (!dirModel) { |
1152 | return; |
1153 | } |
1154 | |
1155 | // Precondition: No items have been changed within the last |
1156 | // 5 seconds. This means that items that have been changed constantly |
1157 | // due to a copy operation should be updated now. |
1158 | |
1159 | KFileItemList itemList; |
1160 | |
1161 | QHash<KUrl, bool>::const_iterator it = m_changedItems.constBegin(); |
1162 | while (it != m_changedItems.constEnd()) { |
1163 | const bool hasChanged = it.value(); |
1164 | if (hasChanged) { |
1165 | const QModelIndex index = dirModel->indexForUrl(it.key()); |
1166 | const KFileItem item = dirModel->itemForIndex(index); |
1167 | itemList.append(item); |
1168 | } |
1169 | ++it; |
1170 | } |
1171 | m_changedItems.clear(); |
1172 | |
1173 | updateIcons(itemList); |
1174 | } |
1175 | |
1176 | void KFilePreviewGenerator::Private::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) |
1177 | { |
1178 | if (m_changedItems.isEmpty()) { |
1179 | return; |
1180 | } |
1181 | |
1182 | KDirModel* dirModel = m_dirModel.data(); |
1183 | if (!dirModel) { |
1184 | return; |
1185 | } |
1186 | |
1187 | for (int row = start; row <= end; row++) { |
1188 | const QModelIndex index = dirModel->index(row, 0, parent); |
1189 | |
1190 | const KFileItem item = dirModel->itemForIndex(index); |
1191 | if (!item.isNull()) { |
1192 | m_changedItems.remove(item.url()); |
1193 | } |
1194 | |
1195 | if (dirModel->hasChildren(index)) { |
1196 | rowsAboutToBeRemoved(index, 0, dirModel->rowCount(index) - 1); |
1197 | } |
1198 | } |
1199 | } |
1200 | |
1201 | KFilePreviewGenerator::KFilePreviewGenerator(QAbstractItemView* parent) : |
1202 | QObject(parent), |
1203 | d(new Private(this, new KIO::DefaultViewAdapter(parent, this), parent->model())) |
1204 | { |
1205 | d->m_itemView = parent; |
1206 | } |
1207 | |
1208 | KFilePreviewGenerator::KFilePreviewGenerator(KAbstractViewAdapter* parent, QAbstractProxyModel* model) : |
1209 | QObject(parent), |
1210 | d(new Private(this, parent, model)) |
1211 | { |
1212 | } |
1213 | |
1214 | KFilePreviewGenerator::~KFilePreviewGenerator() |
1215 | { |
1216 | delete d; |
1217 | } |
1218 | |
1219 | void KFilePreviewGenerator::setPreviewShown(bool show) |
1220 | { |
1221 | if (d->m_previewShown == show) { |
1222 | return; |
1223 | } |
1224 | |
1225 | KDirModel* dirModel = d->m_dirModel.data(); |
1226 | if (show && (!d->m_viewAdapter->iconSize().isValid() || !dirModel)) { |
1227 | // The view must provide an icon size and a directory model, |
1228 | // otherwise the showing the previews will get ignored |
1229 | return; |
1230 | } |
1231 | |
1232 | d->m_previewShown = show; |
1233 | if (!show) { |
1234 | // Clear the icon for all items so that the MIME type |
1235 | // gets reloaded |
1236 | KFileItemList itemList; |
1237 | d->addItemsToList(QModelIndex(), itemList); |
1238 | |
1239 | const bool blocked = dirModel->signalsBlocked(); |
1240 | dirModel->blockSignals(true); |
1241 | |
1242 | QList<QModelIndex> indexesWithKnownMimeType; |
1243 | foreach (const KFileItem& item, itemList) { |
1244 | const QModelIndex index = dirModel->indexForItem(item); |
1245 | if (item.isMimeTypeKnown()) { |
1246 | indexesWithKnownMimeType.append(index); |
1247 | } |
1248 | dirModel->setData(index, QIcon(), Qt::DecorationRole); |
1249 | } |
1250 | |
1251 | dirModel->blockSignals(blocked); |
1252 | |
1253 | // Items without a known mimetype will be handled (delayed) by updateIcons. |
1254 | // So we need to update items with a known mimetype ourselves. |
1255 | foreach (const QModelIndex& index, indexesWithKnownMimeType) { |
1256 | dirModel->itemChanged(index); |
1257 | } |
1258 | } |
1259 | updateIcons(); |
1260 | } |
1261 | |
1262 | bool KFilePreviewGenerator::isPreviewShown() const |
1263 | { |
1264 | return d->m_previewShown; |
1265 | } |
1266 | |
1267 | // deprecated (use updateIcons() instead) |
1268 | void KFilePreviewGenerator::updatePreviews() |
1269 | { |
1270 | updateIcons(); |
1271 | } |
1272 | |
1273 | void KFilePreviewGenerator::updateIcons() |
1274 | { |
1275 | d->killPreviewJobs(); |
1276 | |
1277 | d->clearCutItemsCache(); |
1278 | d->m_pendingItems.clear(); |
1279 | d->m_dispatchedItems.clear(); |
1280 | |
1281 | KFileItemList itemList; |
1282 | d->addItemsToList(QModelIndex(), itemList); |
1283 | |
1284 | d->updateIcons(itemList); |
1285 | } |
1286 | |
1287 | void KFilePreviewGenerator::cancelPreviews() |
1288 | { |
1289 | d->killPreviewJobs(); |
1290 | d->m_pendingItems.clear(); |
1291 | d->m_dispatchedItems.clear(); |
1292 | updateIcons(); |
1293 | } |
1294 | |
1295 | void KFilePreviewGenerator::setEnabledPlugins(const QStringList& plugins) |
1296 | { |
1297 | d->m_enabledPlugins = plugins; |
1298 | } |
1299 | |
1300 | QStringList KFilePreviewGenerator::enabledPlugins() const |
1301 | { |
1302 | return d->m_enabledPlugins; |
1303 | } |
1304 | |
1305 | #include "kfilepreviewgenerator.moc" |
1306 | |