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 {
339 LeftMargin = 3,
340 TopMargin = 2,
341 RightMargin = 3,
342 BottomMargin = 4
343 };
344
345 enum Tile {
346 TopLeftCorner = 0,
347 TopSide,
348 TopRightCorner,
349 LeftSide,
350 RightSide,
351 BottomLeftCorner,
352 BottomSide,
353 BottomRightCorner,
354 NumTiles,
355 };
356
357 explicit TileSet()
358 {
359 QImage image(8 * 3, 8 * 3, QImage::Format_ARGB32_Premultiplied);
360
361 QPainter p(&image);
362 p.setCompositionMode(QPainter::CompositionMode_Source);
363 p.fillRect(image.rect(), Qt::transparent);
364 p.fillRect(image.rect().adjusted(3, 3, -3, -3), Qt::black);
365 p.end();
366
367 KIO::ImageFilter::shadowBlur(image, 3, Qt::black);
368
369 QPixmap pixmap = QPixmap::fromImage(image);
370 m_tiles[TopLeftCorner] = pixmap.copy(0, 0, 8, 8);
371 m_tiles[TopSide] = pixmap.copy(8, 0, 8, 8);
372 m_tiles[TopRightCorner] = pixmap.copy(16, 0, 8, 8);
373 m_tiles[LeftSide] = pixmap.copy(0, 8, 8, 8);
374 m_tiles[RightSide] = pixmap.copy(16, 8, 8, 8);
375 m_tiles[BottomLeftCorner] = pixmap.copy(0, 16, 8, 8);
376 m_tiles[BottomSide] = pixmap.copy(8, 16, 8, 8);
377 m_tiles[BottomRightCorner] = pixmap.copy(16, 16, 8, 8);
378 }
379
380 void paint(QPainter *p, const QRect &r)
381 {
382 p->drawPixmap(r.topLeft(), m_tiles[TopLeftCorner]);
383 if (r.width() - 16 > 0) {
384 p->drawTiledPixmap(r.x() + 8, r.y(), r.width() - 16, 8, m_tiles[TopSide]);
385 }
386 p->drawPixmap(r.right() - 8 + 1, r.y(), m_tiles[TopRightCorner]);
387 if (r.height() - 16 > 0) {
388 p->drawTiledPixmap(r.x(), r.y() + 8, 8, r.height() - 16, m_tiles[LeftSide]);
389 p->drawTiledPixmap(r.right() - 8 + 1, r.y() + 8, 8, r.height() - 16, m_tiles[RightSide]);
390 }
391 p->drawPixmap(r.x(), r.bottom() - 8 + 1, m_tiles[BottomLeftCorner]);
392 if (r.width() - 16 > 0) {
393 p->drawTiledPixmap(r.x() + 8, r.bottom() - 8 + 1, r.width() - 16, 8, m_tiles[BottomSide]);
394 }
395 p->drawPixmap(r.right() - 8 + 1, r.bottom() - 8 + 1, m_tiles[BottomRightCorner]);
396
397 const QRect contentRect = r.adjusted(LeftMargin + 1, TopMargin + 1, -(RightMargin + 1), -(BottomMargin + 1));
398 p->fillRect(contentRect, Qt::transparent);
399 }
400
401private:
402 QPixmap m_tiles[NumTiles];
403};
404
405KFilePreviewGeneratorPrivate::KFilePreviewGeneratorPrivate(KFilePreviewGenerator *qq, KAbstractViewAdapter *viewAdapter, QAbstractItemModel *model)
406 : q(qq)
407 , m_viewAdapter(viewAdapter)
408{
409 if (!m_viewAdapter->iconSize().isValid()) {
410 m_previewShown = false;
411 }
412
413 m_proxyModel = qobject_cast<QAbstractProxyModel *>(model);
414 m_dirModel = (m_proxyModel == nullptr) ? qobject_cast<KDirModel *>(model) : qobject_cast<KDirModel *>(m_proxyModel->sourceModel());
415 if (!m_dirModel) {
416 // previews can only get generated for directory models
417 m_previewShown = false;
418 } else {
419 KDirModel *dirModel = m_dirModel.data();
420 q->connect(dirModel->dirLister(), &KCoreDirLister::newItems, q, [this](const KFileItemList &items) {
421 updateIcons(items);
422 });
423
424 q->connect(dirModel, &KDirModel::dataChanged, q, [this](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
425 updateIcons(topLeft, bottomRight);
426 });
427
428 q->connect(dirModel, &KDirModel::needSequenceIcon, q, [this](const QModelIndex &index, int sequenceIndex) {
429 requestSequenceIcon(index, sequenceIndex);
430 });
431
432 q->connect(dirModel, &KDirModel::rowsAboutToBeRemoved, q, [this](const QModelIndex &parent, int first, int last) {
433 rowsAboutToBeRemoved(parent, first, last);
434 });
435 }
436
437 QClipboard *clipboard = QApplication::clipboard();
438 q->connect(clipboard, &QClipboard::dataChanged, q, [this]() {
439 updateCutItems();
440 });
441
442 m_iconUpdateTimer = new QTimer(q);
443 m_iconUpdateTimer->setSingleShot(true);
444 m_iconUpdateTimer->setInterval(200);
445 q->connect(m_iconUpdateTimer, &QTimer::timeout, q, [this]() {
446 dispatchIconUpdateQueue();
447 });
448
449 // Whenever the scrollbar values have been changed, the pending previews should
450 // be reordered in a way that the previews for the visible items are generated
451 // first. The reordering is done with a small delay, so that during moving the
452 // scrollbars the CPU load is kept low.
453 m_scrollAreaTimer = new QTimer(q);
454 m_scrollAreaTimer->setSingleShot(true);
455 m_scrollAreaTimer->setInterval(200);
456 q->connect(m_scrollAreaTimer, &QTimer::timeout, q, [this]() {
457 resumeIconUpdates();
458 });
459 m_viewAdapter->connect(KAbstractViewAdapter::IconSizeChanged, q, SLOT(updateIcons()));
460 m_viewAdapter->connect(KAbstractViewAdapter::ScrollBarValueChanged, q, SLOT(pauseIconUpdates()));
461
462 m_changedItemsTimer = new QTimer(q);
463 m_changedItemsTimer->setSingleShot(true);
464 m_changedItemsTimer->setInterval(5000);
465 q->connect(m_changedItemsTimer, &QTimer::timeout, q, [this]() {
466 delayedIconUpdate();
467 });
468
469 KConfigGroup globalConfig(KSharedConfig::openConfig(QStringLiteral("dolphinrc")), QStringLiteral("PreviewSettings"));
470 m_enabledPlugins =
471 globalConfig.readEntry("Plugins", QStringList{QStringLiteral("directorythumbnail"), QStringLiteral("imagethumbnail"), QStringLiteral("jpegthumbnail")});
472
473 // Compatibility update: in 4.7, jpegrotatedthumbnail was merged into (or
474 // replaced with?) jpegthumbnail
475 if (m_enabledPlugins.contains(QLatin1String("jpegrotatedthumbnail"))) {
476 m_enabledPlugins.removeAll(QStringLiteral("jpegrotatedthumbnail"));
477 m_enabledPlugins.append(QStringLiteral("jpegthumbnail"));
478 globalConfig.writeEntry("Plugins", m_enabledPlugins);
479 globalConfig.sync();
480 }
481}
482
483KFilePreviewGeneratorPrivate::~KFilePreviewGeneratorPrivate()
484{
485 killPreviewJobs();
486 m_pendingItems.clear();
487 m_dispatchedItems.clear();
488}
489
490void KFilePreviewGeneratorPrivate::requestSequenceIcon(const QModelIndex &index, int sequenceIndex)
491{
492 if (m_pendingItems.isEmpty() || (sequenceIndex == 0)) {
493 KDirModel *dirModel = m_dirModel.data();
494 if (!dirModel) {
495 return;
496 }
497
498 KFileItem item = dirModel->itemForIndex(index);
499 if (sequenceIndex == 0) {
500 m_sequenceIndices.remove(item.url());
501 } else {
502 m_sequenceIndices.insert(item.url(), sequenceIndex);
503 }
504
505 ///@todo Update directly, without using m_sequenceIndices
506 updateIcons(KFileItemList{item});
507 }
508}
509
510void KFilePreviewGeneratorPrivate::updateIcons(const KFileItemList &items)
511{
512 if (items.isEmpty()) {
513 return;
514 }
515
516 applyCutItemEffect(items);
517
518 KFileItemList orderedItems = items;
519 orderItems(orderedItems);
520
521 m_pendingItems.reserve(m_pendingItems.size() + orderedItems.size());
522 for (const KFileItem &item : std::as_const(orderedItems)) {
523 m_pendingItems.append(item);
524 }
525
526 if (m_previewShown) {
527 createPreviews(orderedItems);
528 } else {
529 startMimeTypeResolving();
530 }
531}
532
533void KFilePreviewGeneratorPrivate::updateIcons(const QModelIndex &topLeft, const QModelIndex &bottomRight)
534{
535 if (m_internalDataChange > 0) {
536 // QAbstractItemModel::setData() has been invoked internally by the KFilePreviewGenerator.
537 // The signal dataChanged() is connected with this method, but previews only need
538 // to be generated when an external data change has occurred.
539 return;
540 }
541
542 // dataChanged emitted for the root dir (e.g. permission changes)
543 if (!topLeft.isValid() || !bottomRight.isValid()) {
544 return;
545 }
546
547 KDirModel *dirModel = m_dirModel.data();
548 if (!dirModel) {
549 return;
550 }
551
552 KFileItemList itemList;
553 for (int row = topLeft.row(); row <= bottomRight.row(); ++row) {
554 const QModelIndex index = dirModel->index(row, 0);
555 if (!index.isValid()) {
556 continue;
557 }
558 const KFileItem item = dirModel->itemForIndex(index);
559 Q_ASSERT(!item.isNull());
560
561 if (m_previewShown) {
562 const QUrl url = item.url();
563 const bool hasChanged = m_changedItems.contains(url); // O(1)
564 m_changedItems.insert(url, hasChanged);
565 if (!hasChanged) {
566 // only update the icon if it has not been already updated within
567 // the last 5 seconds (the other icons will be updated later with
568 // the help of m_changedItemsTimer)
569 itemList.append(item);
570 }
571 } else {
572 itemList.append(item);
573 }
574 }
575
576 updateIcons(itemList);
577 m_changedItemsTimer->start();
578}
579
580void KFilePreviewGeneratorPrivate::addToPreviewQueue(const KFileItem &item, const QPixmap &pixmap, KIO::PreviewJob *job)
581{
582 Q_ASSERT(job);
583 if (job) {
584 QMap<QUrl, int>::iterator it = m_sequenceIndices.find(item.url());
585 if (job->sequenceIndex() && (it == m_sequenceIndices.end() || *it != job->sequenceIndex())) {
586 return; // the sequence index does not match the one we want
587 }
588 if (!job->sequenceIndex() && it != m_sequenceIndices.end()) {
589 return; // the sequence index does not match the one we want
590 }
591
592 if (it != m_sequenceIndices.end()) {
593 m_sequenceIndices.erase(it);
594 }
595 }
596
597 if (!m_previewShown) {
598 // the preview has been canceled in the meantime
599 return;
600 }
601
602 KDirModel *dirModel = m_dirModel.data();
603 if (!dirModel) {
604 return;
605 }
606
607 const QUrl itemParentDir = item.url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
608
609 const QList<QUrl> dirs = dirModel->dirLister()->directories();
610
611 // check whether the item is part of the directory lister (it is possible
612 // that a preview from an old directory lister is received)
613 const bool isOldPreview = std::none_of(dirs.cbegin(), dirs.cend(), [&itemParentDir](const QUrl &dir) {
614 return dir == itemParentDir || dir.path().isEmpty();
615 });
616 if (isOldPreview) {
617 return;
618 }
619
620 QPixmap icon = pixmap;
621
622 const QString mimeType = item.mimetype();
623 const int slashIndex = mimeType.indexOf(QLatin1Char('/'));
624 const auto mimeTypeGroup = QStringView(mimeType).left(slashIndex);
625 if (mimeTypeGroup != QLatin1String("image") || !applyImageFrame(icon)) {
626 limitToSize(icon, m_viewAdapter->iconSize());
627 }
628
629 if (m_hasCutSelection && isCutItem(item)) {
630 // apply the disabled effect to the icon for marking it as "cut item"
631 // and apply the icon to the item
633 }
634
635 const QSize size = icon.size();
636 icon = KIconUtils::addOverlays(icon, item.overlays()).pixmap(size);
637
638 // remember the preview and URL, so that it can be applied to the model
639 // in KFilePreviewGenerator::dispatchIconUpdateQueue()
640 ItemInfo preview;
641 preview.url = item.url();
642 preview.pixmap = icon;
643 m_previews.append(preview);
644
645 m_pendingItems.removeOne(item);
646
647 m_dispatchedItems.append(item);
648}
649
650void KFilePreviewGeneratorPrivate::slotPreviewJobFinished(KJob *job)
651{
652 const int index = m_previewJobs.indexOf(job);
653 m_previewJobs.removeAt(index);
654
655 if (m_previewJobs.isEmpty()) {
656 for (const KFileItem &item : std::as_const(m_pendingItems)) {
657 if (item.isMimeTypeKnown()) {
658 m_resolvedMimeTypes.append(item);
659 }
660 }
661
662 if (m_clearItemQueues) {
663 m_pendingItems.clear();
664 m_dispatchedItems.clear();
665 m_pendingVisibleIconUpdates = 0;
666 auto dispatchFunc = [this]() {
667 dispatchIconUpdateQueue();
668 };
670 }
671 m_sequenceIndices.clear(); // just to be sure that we don't leak anything
672 }
673}
674
675void KFilePreviewGeneratorPrivate::updateCutItems()
676{
677 KDirModel *dirModel = m_dirModel.data();
678 if (!dirModel) {
679 return;
680 }
681
682 DataChangeObtainer obt(this);
683 clearCutItemsCache();
684
685 KFileItemList items;
686 KDirLister *dirLister = dirModel->dirLister();
687 const QList<QUrl> dirs = dirLister->directories();
688 items.reserve(dirs.size());
689 for (const QUrl &url : dirs) {
690 items << dirLister->itemsForDir(url);
691 }
692 applyCutItemEffect(items);
693}
694
695void KFilePreviewGeneratorPrivate::clearCutItemsCache()
696{
697 KDirModel *dirModel = m_dirModel.data();
698 if (!dirModel) {
699 return;
700 }
701
702 DataChangeObtainer obt(this);
703 KFileItemList previews;
704 // Reset the icons of all items that are stored in the cache
705 // to use their default MIME type icon.
706 for (auto it = m_cutItemsCache.cbegin(); it != m_cutItemsCache.cend(); ++it) {
707 const QModelIndex index = dirModel->indexForUrl(it.key());
708 if (index.isValid()) {
709 dirModel->setData(index, QIcon(), Qt::DecorationRole);
710 if (m_previewShown) {
711 previews.append(dirModel->itemForIndex(index));
712 }
713 }
714 }
715 m_cutItemsCache.clear();
716
717 if (!previews.isEmpty()) {
718 // assure that the previews gets restored
719 Q_ASSERT(m_previewShown);
720 orderItems(previews);
721 updateIcons(previews);
722 }
723}
724
725void KFilePreviewGeneratorPrivate::dispatchIconUpdateQueue()
726{
727 KDirModel *dirModel = m_dirModel.data();
728 if (!dirModel) {
729 return;
730 }
731
732 const int count = m_previews.count() + m_resolvedMimeTypes.count();
733 if (count > 0) {
734 LayoutBlocker blocker(m_itemView);
735 DataChangeObtainer obt(this);
736
737 if (m_previewShown) {
738 // dispatch preview queue
739 for (const ItemInfo &preview : std::as_const(m_previews)) {
740 const QModelIndex idx = dirModel->indexForUrl(preview.url);
741 if (idx.isValid() && (idx.column() == 0)) {
742 dirModel->setData(idx, QIcon(preview.pixmap), Qt::DecorationRole);
743 }
744 }
745 m_previews.clear();
746 }
747
748 // dispatch MIME type queue
749 for (const KFileItem &item : std::as_const(m_resolvedMimeTypes)) {
750 const QModelIndex idx = dirModel->indexForItem(item);
751 dirModel->itemChanged(idx);
752 }
753 m_resolvedMimeTypes.clear();
754
755 m_pendingVisibleIconUpdates -= count;
756 if (m_pendingVisibleIconUpdates < 0) {
757 m_pendingVisibleIconUpdates = 0;
758 }
759 }
760
761 if (m_pendingVisibleIconUpdates > 0) {
762 // As long as there are pending previews for visible items, poll
763 // the preview queue periodically. If there are no pending previews,
764 // the queue is dispatched in slotPreviewJobFinished().
765 m_iconUpdateTimer->start();
766 }
767}
768
769void KFilePreviewGeneratorPrivate::pauseIconUpdates()
770{
771 m_iconUpdatesPaused = true;
772 for (KJob *job : std::as_const(m_previewJobs)) {
773 Q_ASSERT(job);
774 job->suspend();
775 }
776 m_scrollAreaTimer->start();
777}
778
779void KFilePreviewGeneratorPrivate::resumeIconUpdates()
780{
781 m_iconUpdatesPaused = false;
782
783 // Before creating new preview jobs the m_pendingItems queue must be
784 // cleaned up by removing the already dispatched items. Implementation
785 // note: The order of the m_dispatchedItems queue and the m_pendingItems
786 // queue is usually equal. So even when having a lot of elements the
787 // nested loop is no performance bottle neck, as the inner loop is only
788 // entered once in most cases.
789 for (const KFileItem &item : std::as_const(m_dispatchedItems)) {
790 auto it = std::remove_if(m_pendingItems.begin(), m_pendingItems.end(), [&item](const KFileItem &pending) {
791 return pending.url() == item.url();
792 });
793 m_pendingItems.erase(it, m_pendingItems.end());
794 }
795
796 m_dispatchedItems.clear();
797
798 m_pendingVisibleIconUpdates = 0;
799 dispatchIconUpdateQueue();
800
801 if (m_previewShown) {
802 KFileItemList orderedItems = m_pendingItems;
803 orderItems(orderedItems);
804
805 // Kill all suspended preview jobs. Usually when a preview job
806 // has been finished, slotPreviewJobFinished() clears all item queues.
807 // This is not wanted in this case, as a new job is created afterwards
808 // for m_pendingItems.
809 m_clearItemQueues = false;
810 killPreviewJobs();
811 m_clearItemQueues = true;
812
813 createPreviews(orderedItems);
814 } else {
815 orderItems(m_pendingItems);
816 startMimeTypeResolving();
817 }
818}
819
820void KFilePreviewGeneratorPrivate::startMimeTypeResolving()
821{
822 resolveMimeType();
823 m_iconUpdateTimer->start();
824}
825
826void KFilePreviewGeneratorPrivate::resolveMimeType()
827{
828 if (m_pendingItems.isEmpty()) {
829 return;
830 }
831
832 // resolve at least one MIME type
833 bool resolved = false;
834 do {
835 KFileItem item = m_pendingItems.takeFirst();
836 if (item.isMimeTypeKnown()) {
837 if (m_pendingVisibleIconUpdates > 0) {
838 // The item is visible and the MIME type already known.
839 // Decrease the update counter for dispatchIconUpdateQueue():
840 --m_pendingVisibleIconUpdates;
841 }
842 } else {
843 // The MIME type is unknown and must get resolved. The
844 // directory model is not informed yet, as a single update
845 // would be very expensive. Instead the item is remembered in
846 // m_resolvedMimeTypes and will be dispatched later
847 // by dispatchIconUpdateQueue().
848 item.determineMimeType();
849 m_resolvedMimeTypes.append(item);
850 resolved = true;
851 }
852 } while (!resolved && !m_pendingItems.isEmpty());
853
854 if (m_pendingItems.isEmpty()) {
855 // All MIME types have been resolved now. Assure
856 // that the directory model gets informed about
857 // this, so that an update of the icons is done.
858 dispatchIconUpdateQueue();
859 } else if (!m_iconUpdatesPaused) {
860 // assure that the MIME type of the next
861 // item will be resolved asynchronously
862 auto mimeFunc = [this]() {
863 resolveMimeType();
864 };
866 }
867}
868
869bool KFilePreviewGeneratorPrivate::isCutItem(const KFileItem &item) const
870{
871 const QMimeData *mimeData = QApplication::clipboard()->mimeData();
872 const QList<QUrl> cutUrls = KUrlMimeData::urlsFromMimeData(mimeData);
873 return cutUrls.contains(item.url());
874}
875
876void KFilePreviewGeneratorPrivate::applyCutItemEffect(const KFileItemList &items)
877{
878 const QMimeData *mimeData = QApplication::clipboard()->mimeData();
879 m_hasCutSelection = mimeData && KIO::isClipboardDataCut(mimeData);
880 if (!m_hasCutSelection) {
881 return;
882 }
883
884 KDirModel *dirModel = m_dirModel.data();
885 if (!dirModel) {
886 return;
887 }
888
889 const QList<QUrl> urlsList = KUrlMimeData::urlsFromMimeData(mimeData);
890 const QSet<QUrl> cutUrls(urlsList.begin(), urlsList.end());
891
892 DataChangeObtainer obt(this);
893 for (const KFileItem &item : items) {
894 if (cutUrls.contains(item.url())) {
895 const QModelIndex index = dirModel->indexForItem(item);
896 const QVariant value = dirModel->data(index, Qt::DecorationRole);
897 if (value.typeId() == QMetaType::QIcon) {
898 const QIcon icon(qvariant_cast<QIcon>(value));
899 const QSize actualSize = icon.actualSize(m_viewAdapter->iconSize());
900 QPixmap pixmap = icon.pixmap(actualSize);
901
902 const auto cacheIt = m_cutItemsCache.constFind(item.url());
903 if ((cacheIt == m_cutItemsCache.constEnd()) || (cacheIt->cacheKey() != pixmap.cacheKey())) {
905 dirModel->setData(index, QIcon(pixmap), Qt::DecorationRole);
906
907 m_cutItemsCache.insert(item.url(), pixmap);
908 }
909 }
910 }
911 }
912}
913
914bool KFilePreviewGeneratorPrivate::applyImageFrame(QPixmap &icon)
915{
916 const QSize maxSize = m_viewAdapter->iconSize();
917 const bool applyFrame = (maxSize.width() > KIconLoader::SizeSmallMedium) && (maxSize.height() > KIconLoader::SizeSmallMedium) && !icon.hasAlpha();
918 if (!applyFrame) {
919 // the maximum size or the image itself is too small for a frame
920 return false;
921 }
922
923 // resize the icon to the maximum size minus the space required for the frame
924 const QSize size(maxSize.width() - TileSet::LeftMargin - TileSet::RightMargin, maxSize.height() - TileSet::TopMargin - TileSet::BottomMargin);
925 limitToSize(icon, size);
926
927 if (!m_tileSet) {
928 m_tileSet.reset(new TileSet{});
929 }
930
931 QPixmap framedIcon(icon.size().width() + TileSet::LeftMargin + TileSet::RightMargin, icon.size().height() + TileSet::TopMargin + TileSet::BottomMargin);
932 framedIcon.fill(Qt::transparent);
933
934 QPainter painter;
935 painter.begin(&framedIcon);
937 m_tileSet->paint(&painter, framedIcon.rect());
939 painter.drawPixmap(TileSet::LeftMargin, TileSet::TopMargin, icon);
940 painter.end();
941
942 icon = framedIcon;
943 return true;
944}
945
946void KFilePreviewGeneratorPrivate::limitToSize(QPixmap &icon, const QSize &maxSize)
947{
948 if ((icon.width() > maxSize.width()) || (icon.height() > maxSize.height())) {
950 }
951}
952
953void KFilePreviewGeneratorPrivate::createPreviews(const KFileItemList &items)
954{
955 if (items.isEmpty()) {
956 return;
957 }
958
959 const QMimeData *mimeData = QApplication::clipboard()->mimeData();
960 m_hasCutSelection = mimeData && KIO::isClipboardDataCut(mimeData);
961
962 // PreviewJob internally caches items always with the size of
963 // 128 x 128 pixels or 256 x 256 pixels. A downscaling is done
964 // by PreviewJob if a smaller size is requested. For images KFilePreviewGenerator must
965 // do a downscaling anyhow because of the frame, so in this case only the provided
966 // cache sizes are requested.
967 KFileItemList imageItems;
968 KFileItemList otherItems;
970 for (const KFileItem &item : items) {
971 mimeType = item.mimetype();
972 const int slashIndex = mimeType.indexOf(QLatin1Char('/'));
973 const auto mimeTypeGroup = QStringView(mimeType).left(slashIndex);
974 if (mimeTypeGroup == QLatin1String("image")) {
975 imageItems.append(item);
976 } else {
977 otherItems.append(item);
978 }
979 }
980 const QSize size = m_viewAdapter->iconSize();
981 const int width = size.width();
982 const int height = size.height();
983 startPreviewJob(otherItems, width, height);
984
985 const int longer = std::max(width, height);
986 int cacheSize = 128;
987 if (longer > 512) {
988 cacheSize = 1024;
989 } else if (longer > 256) {
990 cacheSize = 512;
991 } else if (longer > 128) {
992 cacheSize = 256;
993 }
994 startPreviewJob(imageItems, cacheSize, cacheSize);
995
996 m_iconUpdateTimer->start();
997}
998
999void KFilePreviewGeneratorPrivate::startPreviewJob(const KFileItemList &items, int width, int height)
1000{
1001 if (items.isEmpty()) {
1002 return;
1003 }
1004
1005 KIO::PreviewJob *job = KIO::filePreview(items, QSize(width, height), &m_enabledPlugins);
1006
1007 // Set the sequence index to the target. We only need to check if items.count() == 1,
1008 // because requestSequenceIcon(..) creates exactly such a request.
1009 if (!m_sequenceIndices.isEmpty() && (items.count() == 1)) {
1010 const auto it = m_sequenceIndices.constFind(items[0].url());
1011 if (it != m_sequenceIndices.cend()) {
1012 job->setSequenceIndex(*it);
1013 }
1014 }
1015
1016 q->connect(job, &KIO::PreviewJob::gotPreview, q, [this, job](const KFileItem &item, const QPixmap &pixmap) {
1017 addToPreviewQueue(item, pixmap, job);
1018 });
1019
1020 q->connect(job, &KIO::PreviewJob::finished, q, [this, job]() {
1021 slotPreviewJobFinished(job);
1022 });
1023 m_previewJobs.append(job);
1024}
1025
1026void KFilePreviewGeneratorPrivate::killPreviewJobs()
1027{
1028 for (KJob *job : std::as_const(m_previewJobs)) {
1029 Q_ASSERT(job);
1030 job->kill();
1031 }
1032 m_previewJobs.clear();
1033 m_sequenceIndices.clear();
1034
1035 m_iconUpdateTimer->stop();
1036 m_scrollAreaTimer->stop();
1037 m_changedItemsTimer->stop();
1038}
1039
1040void KFilePreviewGeneratorPrivate::orderItems(KFileItemList &items)
1041{
1042 KDirModel *dirModel = m_dirModel.data();
1043 if (!dirModel) {
1044 return;
1045 }
1046
1047 // Order the items in a way that the preview for the visible items
1048 // is generated first, as this improves the felt performance a lot.
1049 const bool hasProxy = m_proxyModel != nullptr;
1050 const int itemCount = items.count();
1051 const QRect visibleArea = m_viewAdapter->visibleArea();
1052
1053 QModelIndex dirIndex;
1054 QRect itemRect;
1055 int insertPos = 0;
1056 for (int i = 0; i < itemCount; ++i) {
1057 dirIndex = dirModel->indexForItem(items.at(i)); // O(n) (n = number of rows)
1058 if (hasProxy) {
1059 const QModelIndex proxyIndex = m_proxyModel->mapFromSource(dirIndex);
1060 itemRect = m_viewAdapter->visualRect(proxyIndex);
1061 } else {
1062 itemRect = m_viewAdapter->visualRect(dirIndex);
1063 }
1064
1065 if (itemRect.intersects(visibleArea)) {
1066 // The current item is (at least partly) visible. Move it
1067 // to the front of the list, so that the preview is
1068 // generated earlier.
1069 items.insert(insertPos, items.at(i));
1070 items.removeAt(i + 1);
1071 ++insertPos;
1072 ++m_pendingVisibleIconUpdates;
1073 }
1074 }
1075}
1076
1077void KFilePreviewGeneratorPrivate::addItemsToList(const QModelIndex &index, KFileItemList &list)
1078{
1079 KDirModel *dirModel = m_dirModel.data();
1080 if (!dirModel) {
1081 return;
1082 }
1083
1084 const int rowCount = dirModel->rowCount(index);
1085 for (int row = 0; row < rowCount; ++row) {
1086 const QModelIndex subIndex = dirModel->index(row, 0, index);
1087 KFileItem item = dirModel->itemForIndex(subIndex);
1088 list.append(item);
1089
1090 if (dirModel->rowCount(subIndex) > 0) {
1091 // the model is hierarchical (treeview)
1092 addItemsToList(subIndex, list);
1093 }
1094 }
1095}
1096
1097void KFilePreviewGeneratorPrivate::delayedIconUpdate()
1098{
1099 KDirModel *dirModel = m_dirModel.data();
1100 if (!dirModel) {
1101 return;
1102 }
1103
1104 // Precondition: No items have been changed within the last
1105 // 5 seconds. This means that items that have been changed constantly
1106 // due to a copy operation should be updated now.
1107
1108 KFileItemList itemList;
1109
1110 for (auto it = m_changedItems.cbegin(); it != m_changedItems.cend(); ++it) {
1111 const bool hasChanged = it.value();
1112 if (hasChanged) {
1113 const QModelIndex index = dirModel->indexForUrl(it.key());
1114 const KFileItem item = dirModel->itemForIndex(index);
1115 itemList.append(item);
1116 }
1117 }
1118 m_changedItems.clear();
1119
1120 updateIcons(itemList);
1121}
1122
1123void KFilePreviewGeneratorPrivate::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
1124{
1125 if (m_changedItems.isEmpty()) {
1126 return;
1127 }
1128
1129 KDirModel *dirModel = m_dirModel.data();
1130 if (!dirModel) {
1131 return;
1132 }
1133
1134 for (int row = start; row <= end; row++) {
1135 const QModelIndex index = dirModel->index(row, 0, parent);
1136
1137 const KFileItem item = dirModel->itemForIndex(index);
1138 if (!item.isNull()) {
1139 m_changedItems.remove(item.url());
1140 }
1141
1142 if (dirModel->hasChildren(index)) {
1143 rowsAboutToBeRemoved(index, 0, dirModel->rowCount(index) - 1);
1144 }
1145 }
1146}
1147
1149 : QObject(parent)
1150 , d(new KFilePreviewGeneratorPrivate(this, new KIO::DefaultViewAdapter(parent, this), parent->model()))
1151{
1152 d->m_itemView = parent;
1153}
1154
1156 : QObject(parent)
1157 , d(new KFilePreviewGeneratorPrivate(this, parent, model))
1158{
1159}
1160
1161KFilePreviewGenerator::~KFilePreviewGenerator() = default;
1162
1164{
1165 if (d->m_previewShown == show) {
1166 return;
1167 }
1168
1169 KDirModel *dirModel = d->m_dirModel.data();
1170 if (show && (!d->m_viewAdapter->iconSize().isValid() || !dirModel)) {
1171 // The view must provide an icon size and a directory model,
1172 // otherwise the showing the previews will get ignored
1173 return;
1174 }
1175
1176 d->m_previewShown = show;
1177 if (!show) {
1178 dirModel->clearAllPreviews();
1179 }
1180 updateIcons();
1181}
1182
1183bool KFilePreviewGenerator::isPreviewShown() const
1184{
1185 return d->m_previewShown;
1186}
1187
1189{
1190 d->killPreviewJobs();
1191
1192 d->clearCutItemsCache();
1193 d->m_pendingItems.clear();
1194 d->m_dispatchedItems.clear();
1195
1196 KFileItemList itemList;
1197 d->addItemsToList(QModelIndex(), itemList);
1198
1199 d->updateIcons(itemList);
1200}
1201
1203{
1204 d->killPreviewJobs();
1205 d->m_pendingItems.clear();
1206 d->m_dispatchedItems.clear();
1207 updateIcons();
1208}
1209
1211{
1212 d->m_enabledPlugins = plugins;
1213}
1214
1216{
1217 return d->m_enabledPlugins;
1218}
1219
1220#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:632
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()
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
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)
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 Nov 29 2024 11:50:33 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.