KIO

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

KDE's Doxygen guidelines are available online.