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 <KSharedConfig>
14#include <KUrlMimeData>
15#include <imagefilter_p.h> // from kiowidgets
16#include <kdirlister.h>
17#include <kdirmodel.h>
18#include <kfileitem.h>
19#include <kio/paste.h>
20#include <kio/previewjob.h>
21
22#include <QAbstractItemView>
23#include <QAbstractProxyModel>
24#include <QApplication>
25#include <QClipboard>
26#include <QHash>
27#include <QIcon>
28#include <QList>
29#include <QListView>
30#include <QMimeData>
31#include <QPainter>
32#include <QPixmap>
33#include <QPointer>
34#include <QTimer>
35
36class KFilePreviewGeneratorPrivate
37{
38 class TileSet;
39 class LayoutBlocker;
40
41public:
42 KFilePreviewGeneratorPrivate(KFilePreviewGenerator *qq, KAbstractViewAdapter *viewAdapter, QAbstractItemModel *model);
43
44 ~KFilePreviewGeneratorPrivate();
45 /**
46 * Requests a new icon for the item \a index.
47 * @param sequenceIndex If this is zero, the standard icon is requested, else another one.
48 */
49 void requestSequenceIcon(const QModelIndex &index, int sequenceIndex);
50
51 /**
52 * Generates previews for the items \a items asynchronously.
53 */
54 void updateIcons(const KFileItemList &items);
55
56 /**
57 * Generates previews for the indices within \a topLeft
58 * and \a bottomRight asynchronously.
59 */
60 void updateIcons(const QModelIndex &topLeft, const QModelIndex &bottomRight);
61
62 /**
63 * Adds the preview \a pixmap for the item \a item to the preview
64 * queue and starts a timer which will dispatch the preview queue
65 * later.
66 */
67 void addToPreviewQueue(const KFileItem &item, const QPixmap &pixmap, KIO::PreviewJob *job);
68
69 /**
70 * Is invoked when the preview job has been finished and
71 * removes the job from the m_previewJobs list.
72 */
73 void slotPreviewJobFinished(KJob *job);
74
75 /** Synchronizes the icon of all items with the clipboard of cut items. */
76 void updateCutItems();
77
78 /**
79 * Reset all icons of the items from m_cutItemsCache and clear
80 * the cache.
81 */
82 void clearCutItemsCache();
83
84 /**
85 * Dispatches the preview queue block by block within
86 * time slices.
87 */
88 void dispatchIconUpdateQueue();
89
90 /**
91 * Pauses all icon updates and invokes KFilePreviewGenerator::resumeIconUpdates()
92 * after a short delay. Is invoked as soon as the user has moved
93 * a scrollbar.
94 */
95 void pauseIconUpdates();
96
97 /**
98 * Resumes the icons updates that have been paused after moving the
99 * scrollbar. The previews for the current visible area are
100 * generated first.
101 */
102 void resumeIconUpdates();
103
104 /**
105 * Starts the resolving of the MIME types from
106 * the m_pendingItems queue.
107 */
108 void startMimeTypeResolving();
109
110 /**
111 * Resolves the MIME type for exactly one item of the
112 * m_pendingItems queue.
113 */
114 void resolveMimeType();
115
116 /**
117 * Returns true, if the item \a item has been cut into
118 * the clipboard.
119 */
120 bool isCutItem(const KFileItem &item) const;
121
122 /**
123 * Applies a cut-item effect to all given \a items, if they
124 * are marked as cut in the clipboard.
125 */
126 void applyCutItemEffect(const KFileItemList &items);
127
128 /**
129 * Applies a frame around the icon. False is returned if
130 * no frame has been added because the icon is too small.
131 */
132 bool applyImageFrame(QPixmap &icon);
133
134 /**
135 * Resizes the icon to \a maxSize if the icon size does not
136 * fit into the maximum size. The aspect ratio of the icon
137 * is kept.
138 */
139 void limitToSize(QPixmap &icon, const QSize &maxSize);
140
141 /**
142 * Creates previews by starting new preview jobs for the items
143 * and triggers the preview timer.
144 */
145 void createPreviews(const KFileItemList &items);
146
147 /**
148 * Helper method for createPreviews(): Starts a preview job for the given
149 * items. For each returned preview addToPreviewQueue() will get invoked.
150 */
151 void startPreviewJob(const KFileItemList &items, int width, int height);
152
153 /** Kills all ongoing preview jobs. */
154 void killPreviewJobs();
155
156 /**
157 * Orders the items \a items in a way that the visible items
158 * are moved to the front of the list. When passing this
159 * list to a preview job, the visible items will get generated
160 * first.
161 */
162 void orderItems(KFileItemList &items);
163
164 /**
165 * Helper method for KFilePreviewGenerator::updateIcons(). Adds
166 * recursively all items from the model to the list \a list.
167 */
168 void addItemsToList(const QModelIndex &index, KFileItemList &list);
169
170 /**
171 * Updates the icons of files that are constantly changed due to a copy
172 * operation. See m_changedItems and m_changedItemsTimer for details.
173 */
174 void delayedIconUpdate();
175
176 /**
177 * Any items that are removed from the model are also removed from m_changedItems.
178 */
179 void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end);
180
181 /** Remembers the pixmap for an item specified by an URL. */
182 struct ItemInfo {
183 QUrl url;
184 QPixmap pixmap;
185 };
186
187 /**
188 * During the lifetime of a DataChangeObtainer instance changing
189 * the data of the model won't trigger generating a preview.
190 */
191 class DataChangeObtainer
192 {
193 public:
194 explicit DataChangeObtainer(KFilePreviewGeneratorPrivate *generator)
195 : m_gen(generator)
196 {
197 ++m_gen->m_internalDataChange;
198 }
199
200 ~DataChangeObtainer()
201 {
202 --m_gen->m_internalDataChange;
203 }
204
205 private:
206 KFilePreviewGeneratorPrivate *m_gen;
207 };
208
209 KFilePreviewGenerator *const q;
210
211 bool m_previewShown = true;
212
213 /**
214 * True, if m_pendingItems and m_dispatchedItems should be
215 * cleared when the preview jobs have been finished.
216 */
217 bool m_clearItemQueues = true;
218
219 /**
220 * True if a selection has been done which should cut items.
221 */
222 bool m_hasCutSelection = false;
223
224 /**
225 * True if the updates of icons has been paused by pauseIconUpdates().
226 * The value is reset by resumeIconUpdates().
227 */
228 bool m_iconUpdatesPaused = false;
229
230 /**
231 * If the value is 0, the slot
232 * updateIcons(const QModelIndex&, const QModelIndex&) has
233 * been triggered by an external data change.
234 */
235 int m_internalDataChange = 0;
236
237 int m_pendingVisibleIconUpdates = 0;
238
239 KAbstractViewAdapter *m_viewAdapter = nullptr;
240 QAbstractItemView *m_itemView = nullptr;
241 QTimer *m_iconUpdateTimer = nullptr;
242 QTimer *m_scrollAreaTimer = nullptr;
243 QList<KJob *> m_previewJobs;
244 QPointer<KDirModel> m_dirModel;
245 QAbstractProxyModel *m_proxyModel = nullptr;
246
247 /**
248 * Set of all items that already have the 'cut' effect applied, together with the pixmap it was applied to
249 * This is used to make sure that the 'cut' effect is applied max. once for each pixmap
250 *
251 * Referencing the pixmaps here imposes no overhead, as they were also given to KDirModel::setData(),
252 * and thus are held anyway.
253 */
254 QHash<QUrl, QPixmap> m_cutItemsCache;
255 QList<ItemInfo> m_previews;
256 QMap<QUrl, int> m_sequenceIndices;
257
258 /**
259 * When huge items are copied, it must be prevented that a preview gets generated
260 * for each item size change. m_changedItems keeps track of the changed items and it
261 * is assured that a final preview is only done if an item does not change within
262 * at least 5 seconds.
263 */
264 QHash<QUrl, bool> m_changedItems;
265 QTimer *m_changedItemsTimer = nullptr;
266
267 /**
268 * Contains all items where a preview must be generated, but
269 * where the preview job has not dispatched the items yet.
270 */
271 KFileItemList m_pendingItems;
272
273 /**
274 * Contains all items, where a preview has already been
275 * generated by the preview jobs.
276 */
277 KFileItemList m_dispatchedItems;
278
279 KFileItemList m_resolvedMimeTypes;
280
281 QStringList m_enabledPlugins;
282
283 std::unique_ptr<TileSet> m_tileSet;
284};
285
286/**
287 * If the passed item view is an instance of QListView, expensive
288 * layout operations are blocked in the constructor and are unblocked
289 * again in the destructor.
290 *
291 * This helper class is a workaround for the following huge performance
292 * problem when having directories with several 1000 items:
293 * - each change of an icon emits a dataChanged() signal from the model
294 * - QListView iterates through all items on each dataChanged() signal
295 * and invokes QItemDelegate::sizeHint()
296 * - the sizeHint() implementation of KFileItemDelegate is quite complex,
297 * invoking it 1000 times for each icon change might block the UI
298 *
299 * QListView does not invoke QItemDelegate::sizeHint() when the
300 * uniformItemSize property has been set to true, so this property is
301 * set before exchanging a block of icons.
302 */
303class KFilePreviewGeneratorPrivate::LayoutBlocker
304{
305public:
306 explicit LayoutBlocker(QAbstractItemView *view)
307 : m_uniformSizes(false)
308 , m_view(qobject_cast<QListView *>(view))
309 {
310 if (m_view) {
311 m_uniformSizes = m_view->uniformItemSizes();
312 m_view->setUniformItemSizes(true);
313 }
314 }
315
316 ~LayoutBlocker()
317 {
318 if (m_view) {
319 m_view->setUniformItemSizes(m_uniformSizes);
320 /* The QListView did the layout with uniform item
321 * sizes, so trigger a relayout with the expected sizes. */
322 if (!m_uniformSizes) {
323 m_view->setGridSize(m_view->gridSize());
324 }
325 }
326 }
327
328private:
329 bool m_uniformSizes = false;
330 QListView *m_view = nullptr;
331};
332
333/** Helper class for drawing frames for image previews. */
334class KFilePreviewGeneratorPrivate::TileSet
335{
336public:
337 enum { LeftMargin = 3, TopMargin = 2, RightMargin = 3, BottomMargin = 4 };
338
339 enum Tile {
340 TopLeftCorner = 0,
341 TopSide,
342 TopRightCorner,
343 LeftSide,
344 RightSide,
345 BottomLeftCorner,
346 BottomSide,
347 BottomRightCorner,
348 NumTiles,
349 };
350
351 explicit TileSet()
352 {
353 QImage image(8 * 3, 8 * 3, QImage::Format_ARGB32_Premultiplied);
354
355 QPainter p(&image);
356 p.setCompositionMode(QPainter::CompositionMode_Source);
357 p.fillRect(image.rect(), Qt::transparent);
358 p.fillRect(image.rect().adjusted(3, 3, -3, -3), Qt::black);
359 p.end();
360
361 KIO::ImageFilter::shadowBlur(image, 3, Qt::black);
362
363 QPixmap pixmap = QPixmap::fromImage(image);
364 m_tiles[TopLeftCorner] = pixmap.copy(0, 0, 8, 8);
365 m_tiles[TopSide] = pixmap.copy(8, 0, 8, 8);
366 m_tiles[TopRightCorner] = pixmap.copy(16, 0, 8, 8);
367 m_tiles[LeftSide] = pixmap.copy(0, 8, 8, 8);
368 m_tiles[RightSide] = pixmap.copy(16, 8, 8, 8);
369 m_tiles[BottomLeftCorner] = pixmap.copy(0, 16, 8, 8);
370 m_tiles[BottomSide] = pixmap.copy(8, 16, 8, 8);
371 m_tiles[BottomRightCorner] = pixmap.copy(16, 16, 8, 8);
372 }
373
374 void paint(QPainter *p, const QRect &r)
375 {
376 p->drawPixmap(r.topLeft(), m_tiles[TopLeftCorner]);
377 if (r.width() - 16 > 0) {
378 p->drawTiledPixmap(r.x() + 8, r.y(), r.width() - 16, 8, m_tiles[TopSide]);
379 }
380 p->drawPixmap(r.right() - 8 + 1, r.y(), m_tiles[TopRightCorner]);
381 if (r.height() - 16 > 0) {
382 p->drawTiledPixmap(r.x(), r.y() + 8, 8, r.height() - 16, m_tiles[LeftSide]);
383 p->drawTiledPixmap(r.right() - 8 + 1, r.y() + 8, 8, r.height() - 16, m_tiles[RightSide]);
384 }
385 p->drawPixmap(r.x(), r.bottom() - 8 + 1, m_tiles[BottomLeftCorner]);
386 if (r.width() - 16 > 0) {
387 p->drawTiledPixmap(r.x() + 8, r.bottom() - 8 + 1, r.width() - 16, 8, m_tiles[BottomSide]);
388 }
389 p->drawPixmap(r.right() - 8 + 1, r.bottom() - 8 + 1, m_tiles[BottomRightCorner]);
390
391 const QRect contentRect = r.adjusted(LeftMargin + 1, TopMargin + 1, -(RightMargin + 1), -(BottomMargin + 1));
392 p->fillRect(contentRect, Qt::transparent);
393 }
394
395private:
396 QPixmap m_tiles[NumTiles];
397};
398
399KFilePreviewGeneratorPrivate::KFilePreviewGeneratorPrivate(KFilePreviewGenerator *qq, KAbstractViewAdapter *viewAdapter, QAbstractItemModel *model)
400 : q(qq)
401 , m_viewAdapter(viewAdapter)
402{
403 if (!m_viewAdapter->iconSize().isValid()) {
404 m_previewShown = false;
405 }
406
407 m_proxyModel = qobject_cast<QAbstractProxyModel *>(model);
408 m_dirModel = (m_proxyModel == nullptr) ? qobject_cast<KDirModel *>(model) : qobject_cast<KDirModel *>(m_proxyModel->sourceModel());
409 if (!m_dirModel) {
410 // previews can only get generated for directory models
411 m_previewShown = false;
412 } else {
413 KDirModel *dirModel = m_dirModel.data();
414 q->connect(dirModel->dirLister(), &KCoreDirLister::newItems, q, [this](const KFileItemList &items) {
415 updateIcons(items);
416 });
417
418 q->connect(dirModel, &KDirModel::dataChanged, q, [this](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
419 updateIcons(topLeft, bottomRight);
420 });
421
422 q->connect(dirModel, &KDirModel::needSequenceIcon, q, [this](const QModelIndex &index, int sequenceIndex) {
423 requestSequenceIcon(index, sequenceIndex);
424 });
425
426 q->connect(dirModel, &KDirModel::rowsAboutToBeRemoved, q, [this](const QModelIndex &parent, int first, int last) {
427 rowsAboutToBeRemoved(parent, first, last);
428 });
429 }
430
431 QClipboard *clipboard = QApplication::clipboard();
432 q->connect(clipboard, &QClipboard::dataChanged, q, [this]() {
433 updateCutItems();
434 });
435
436 m_iconUpdateTimer = new QTimer(q);
437 m_iconUpdateTimer->setSingleShot(true);
438 m_iconUpdateTimer->setInterval(200);
439 q->connect(m_iconUpdateTimer, &QTimer::timeout, q, [this]() {
440 dispatchIconUpdateQueue();
441 });
442
443 // Whenever the scrollbar values have been changed, the pending previews should
444 // be reordered in a way that the previews for the visible items are generated
445 // first. The reordering is done with a small delay, so that during moving the
446 // scrollbars the CPU load is kept low.
447 m_scrollAreaTimer = new QTimer(q);
448 m_scrollAreaTimer->setSingleShot(true);
449 m_scrollAreaTimer->setInterval(200);
450 q->connect(m_scrollAreaTimer, &QTimer::timeout, q, [this]() {
451 resumeIconUpdates();
452 });
453 m_viewAdapter->connect(KAbstractViewAdapter::IconSizeChanged, q, SLOT(updateIcons()));
454 m_viewAdapter->connect(KAbstractViewAdapter::ScrollBarValueChanged, q, SLOT(pauseIconUpdates()));
455
456 m_changedItemsTimer = new QTimer(q);
457 m_changedItemsTimer->setSingleShot(true);
458 m_changedItemsTimer->setInterval(5000);
459 q->connect(m_changedItemsTimer, &QTimer::timeout, q, [this]() {
460 delayedIconUpdate();
461 });
462
463 KConfigGroup globalConfig(KSharedConfig::openConfig(QStringLiteral("dolphinrc")), QStringLiteral("PreviewSettings"));
464 m_enabledPlugins =
465 globalConfig.readEntry("Plugins", QStringList{QStringLiteral("directorythumbnail"), QStringLiteral("imagethumbnail"), QStringLiteral("jpegthumbnail")});
466
467 // Compatibility update: in 4.7, jpegrotatedthumbnail was merged into (or
468 // replaced with?) jpegthumbnail
469 if (m_enabledPlugins.contains(QLatin1String("jpegrotatedthumbnail"))) {
470 m_enabledPlugins.removeAll(QStringLiteral("jpegrotatedthumbnail"));
471 m_enabledPlugins.append(QStringLiteral("jpegthumbnail"));
472 globalConfig.writeEntry("Plugins", m_enabledPlugins);
473 globalConfig.sync();
474 }
475}
476
477KFilePreviewGeneratorPrivate::~KFilePreviewGeneratorPrivate()
478{
479 killPreviewJobs();
480 m_pendingItems.clear();
481 m_dispatchedItems.clear();
482}
483
484void KFilePreviewGeneratorPrivate::requestSequenceIcon(const QModelIndex &index, int sequenceIndex)
485{
486 if (m_pendingItems.isEmpty() || (sequenceIndex == 0)) {
487 KDirModel *dirModel = m_dirModel.data();
488 if (!dirModel) {
489 return;
490 }
491
492 KFileItem item = dirModel->itemForIndex(index);
493 if (sequenceIndex == 0) {
494 m_sequenceIndices.remove(item.url());
495 } else {
496 m_sequenceIndices.insert(item.url(), sequenceIndex);
497 }
498
499 ///@todo Update directly, without using m_sequenceIndices
500 updateIcons(KFileItemList{item});
501 }
502}
503
504void KFilePreviewGeneratorPrivate::updateIcons(const KFileItemList &items)
505{
506 if (items.isEmpty()) {
507 return;
508 }
509
510 applyCutItemEffect(items);
511
512 KFileItemList orderedItems = items;
513 orderItems(orderedItems);
514
515 m_pendingItems.reserve(m_pendingItems.size() + orderedItems.size());
516 for (const KFileItem &item : std::as_const(orderedItems)) {
517 m_pendingItems.append(item);
518 }
519
520 if (m_previewShown) {
521 createPreviews(orderedItems);
522 } else {
523 startMimeTypeResolving();
524 }
525}
526
527void KFilePreviewGeneratorPrivate::updateIcons(const QModelIndex &topLeft, const QModelIndex &bottomRight)
528{
529 if (m_internalDataChange > 0) {
530 // QAbstractItemModel::setData() has been invoked internally by the KFilePreviewGenerator.
531 // The signal dataChanged() is connected with this method, but previews only need
532 // to be generated when an external data change has occurred.
533 return;
534 }
535
536 // dataChanged emitted for the root dir (e.g. permission changes)
537 if (!topLeft.isValid() || !bottomRight.isValid()) {
538 return;
539 }
540
541 KDirModel *dirModel = m_dirModel.data();
542 if (!dirModel) {
543 return;
544 }
545
546 KFileItemList itemList;
547 for (int row = topLeft.row(); row <= bottomRight.row(); ++row) {
548 const QModelIndex index = dirModel->index(row, 0);
549 if (!index.isValid()) {
550 continue;
551 }
552 const KFileItem item = dirModel->itemForIndex(index);
553 Q_ASSERT(!item.isNull());
554
555 if (m_previewShown) {
556 const QUrl url = item.url();
557 const bool hasChanged = m_changedItems.contains(url); // O(1)
558 m_changedItems.insert(url, hasChanged);
559 if (!hasChanged) {
560 // only update the icon if it has not been already updated within
561 // the last 5 seconds (the other icons will be updated later with
562 // the help of m_changedItemsTimer)
563 itemList.append(item);
564 }
565 } else {
566 itemList.append(item);
567 }
568 }
569
570 updateIcons(itemList);
571 m_changedItemsTimer->start();
572}
573
574void KFilePreviewGeneratorPrivate::addToPreviewQueue(const KFileItem &item, const QPixmap &pixmap, KIO::PreviewJob *job)
575{
576 Q_ASSERT(job);
577 if (job) {
578 QMap<QUrl, int>::iterator it = m_sequenceIndices.find(item.url());
579 if (job->sequenceIndex() && (it == m_sequenceIndices.end() || *it != job->sequenceIndex())) {
580 return; // the sequence index does not match the one we want
581 }
582 if (!job->sequenceIndex() && it != m_sequenceIndices.end()) {
583 return; // the sequence index does not match the one we want
584 }
585
586 if (it != m_sequenceIndices.end()) {
587 m_sequenceIndices.erase(it);
588 }
589 }
590
591 if (!m_previewShown) {
592 // the preview has been canceled in the meantime
593 return;
594 }
595
596 KDirModel *dirModel = m_dirModel.data();
597 if (!dirModel) {
598 return;
599 }
600
601 const QUrl itemParentDir = item.url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
602
603 const QList<QUrl> dirs = dirModel->dirLister()->directories();
604
605 // check whether the item is part of the directory lister (it is possible
606 // that a preview from an old directory lister is received)
607 const bool isOldPreview = std::none_of(dirs.cbegin(), dirs.cend(), [&itemParentDir](const QUrl &dir) {
608 return dir == itemParentDir || dir.path().isEmpty();
609 });
610 if (isOldPreview) {
611 return;
612 }
613
614 QPixmap icon = pixmap;
615
616 const QString mimeType = item.mimetype();
617 const int slashIndex = mimeType.indexOf(QLatin1Char('/'));
618 const auto mimeTypeGroup = QStringView(mimeType).left(slashIndex);
619 if (mimeTypeGroup != QLatin1String("image") || !applyImageFrame(icon)) {
620 limitToSize(icon, m_viewAdapter->iconSize());
621 }
622
623 if (m_hasCutSelection && isCutItem(item)) {
624 // apply the disabled effect to the icon for marking it as "cut item"
625 // and apply the icon to the item
626 KIconEffect *iconEffect = KIconLoader::global()->iconEffect();
627 icon = iconEffect->apply(icon, KIconLoader::Desktop, KIconLoader::DisabledState);
628 }
629
630 KIconLoader::global()->drawOverlays(item.overlays(), icon, KIconLoader::Desktop);
631
632 // remember the preview and URL, so that it can be applied to the model
633 // in KFilePreviewGenerator::dispatchIconUpdateQueue()
634 ItemInfo preview;
635 preview.url = item.url();
636 preview.pixmap = icon;
637 m_previews.append(preview);
638
639 m_pendingItems.removeOne(item);
640
641 m_dispatchedItems.append(item);
642}
643
644void KFilePreviewGeneratorPrivate::slotPreviewJobFinished(KJob *job)
645{
646 const int index = m_previewJobs.indexOf(job);
647 m_previewJobs.removeAt(index);
648
649 if (m_previewJobs.isEmpty()) {
650 for (const KFileItem &item : std::as_const(m_pendingItems)) {
651 if (item.isMimeTypeKnown()) {
652 m_resolvedMimeTypes.append(item);
653 }
654 }
655
656 if (m_clearItemQueues) {
657 m_pendingItems.clear();
658 m_dispatchedItems.clear();
659 m_pendingVisibleIconUpdates = 0;
660 auto dispatchFunc = [this]() {
661 dispatchIconUpdateQueue();
662 };
664 }
665 m_sequenceIndices.clear(); // just to be sure that we don't leak anything
666 }
667}
668
669void KFilePreviewGeneratorPrivate::updateCutItems()
670{
671 KDirModel *dirModel = m_dirModel.data();
672 if (!dirModel) {
673 return;
674 }
675
676 DataChangeObtainer obt(this);
677 clearCutItemsCache();
678
679 KFileItemList items;
680 KDirLister *dirLister = dirModel->dirLister();
681 const QList<QUrl> dirs = dirLister->directories();
682 items.reserve(dirs.size());
683 for (const QUrl &url : dirs) {
684 items << dirLister->itemsForDir(url);
685 }
686 applyCutItemEffect(items);
687}
688
689void KFilePreviewGeneratorPrivate::clearCutItemsCache()
690{
691 KDirModel *dirModel = m_dirModel.data();
692 if (!dirModel) {
693 return;
694 }
695
696 DataChangeObtainer obt(this);
697 KFileItemList previews;
698 // Reset the icons of all items that are stored in the cache
699 // to use their default MIME type icon.
700 for (auto it = m_cutItemsCache.cbegin(); it != m_cutItemsCache.cend(); ++it) {
701 const QModelIndex index = dirModel->indexForUrl(it.key());
702 if (index.isValid()) {
703 dirModel->setData(index, QIcon(), Qt::DecorationRole);
704 if (m_previewShown) {
705 previews.append(dirModel->itemForIndex(index));
706 }
707 }
708 }
709 m_cutItemsCache.clear();
710
711 if (!previews.isEmpty()) {
712 // assure that the previews gets restored
713 Q_ASSERT(m_previewShown);
714 orderItems(previews);
715 updateIcons(previews);
716 }
717}
718
719void KFilePreviewGeneratorPrivate::dispatchIconUpdateQueue()
720{
721 KDirModel *dirModel = m_dirModel.data();
722 if (!dirModel) {
723 return;
724 }
725
726 const int count = m_previews.count() + m_resolvedMimeTypes.count();
727 if (count > 0) {
728 LayoutBlocker blocker(m_itemView);
729 DataChangeObtainer obt(this);
730
731 if (m_previewShown) {
732 // dispatch preview queue
733 for (const ItemInfo &preview : std::as_const(m_previews)) {
734 const QModelIndex idx = dirModel->indexForUrl(preview.url);
735 if (idx.isValid() && (idx.column() == 0)) {
736 dirModel->setData(idx, QIcon(preview.pixmap), Qt::DecorationRole);
737 }
738 }
739 m_previews.clear();
740 }
741
742 // dispatch MIME type queue
743 for (const KFileItem &item : std::as_const(m_resolvedMimeTypes)) {
744 const QModelIndex idx = dirModel->indexForItem(item);
745 dirModel->itemChanged(idx);
746 }
747 m_resolvedMimeTypes.clear();
748
749 m_pendingVisibleIconUpdates -= count;
750 if (m_pendingVisibleIconUpdates < 0) {
751 m_pendingVisibleIconUpdates = 0;
752 }
753 }
754
755 if (m_pendingVisibleIconUpdates > 0) {
756 // As long as there are pending previews for visible items, poll
757 // the preview queue periodically. If there are no pending previews,
758 // the queue is dispatched in slotPreviewJobFinished().
759 m_iconUpdateTimer->start();
760 }
761}
762
763void KFilePreviewGeneratorPrivate::pauseIconUpdates()
764{
765 m_iconUpdatesPaused = true;
766 for (KJob *job : std::as_const(m_previewJobs)) {
767 Q_ASSERT(job);
768 job->suspend();
769 }
770 m_scrollAreaTimer->start();
771}
772
773void KFilePreviewGeneratorPrivate::resumeIconUpdates()
774{
775 m_iconUpdatesPaused = false;
776
777 // Before creating new preview jobs the m_pendingItems queue must be
778 // cleaned up by removing the already dispatched items. Implementation
779 // note: The order of the m_dispatchedItems queue and the m_pendingItems
780 // queue is usually equal. So even when having a lot of elements the
781 // nested loop is no performance bottle neck, as the inner loop is only
782 // entered once in most cases.
783 for (const KFileItem &item : std::as_const(m_dispatchedItems)) {
784 auto it = std::remove_if(m_pendingItems.begin(), m_pendingItems.end(), [&item](const KFileItem &pending) {
785 return pending.url() == item.url();
786 });
787 m_pendingItems.erase(it, m_pendingItems.end());
788 }
789
790 m_dispatchedItems.clear();
791
792 m_pendingVisibleIconUpdates = 0;
793 dispatchIconUpdateQueue();
794
795 if (m_previewShown) {
796 KFileItemList orderedItems = m_pendingItems;
797 orderItems(orderedItems);
798
799 // Kill all suspended preview jobs. Usually when a preview job
800 // has been finished, slotPreviewJobFinished() clears all item queues.
801 // This is not wanted in this case, as a new job is created afterwards
802 // for m_pendingItems.
803 m_clearItemQueues = false;
804 killPreviewJobs();
805 m_clearItemQueues = true;
806
807 createPreviews(orderedItems);
808 } else {
809 orderItems(m_pendingItems);
810 startMimeTypeResolving();
811 }
812}
813
814void KFilePreviewGeneratorPrivate::startMimeTypeResolving()
815{
816 resolveMimeType();
817 m_iconUpdateTimer->start();
818}
819
820void KFilePreviewGeneratorPrivate::resolveMimeType()
821{
822 if (m_pendingItems.isEmpty()) {
823 return;
824 }
825
826 // resolve at least one MIME type
827 bool resolved = false;
828 do {
829 KFileItem item = m_pendingItems.takeFirst();
830 if (item.isMimeTypeKnown()) {
831 if (m_pendingVisibleIconUpdates > 0) {
832 // The item is visible and the MIME type already known.
833 // Decrease the update counter for dispatchIconUpdateQueue():
834 --m_pendingVisibleIconUpdates;
835 }
836 } else {
837 // The MIME type is unknown and must get resolved. The
838 // directory model is not informed yet, as a single update
839 // would be very expensive. Instead the item is remembered in
840 // m_resolvedMimeTypes and will be dispatched later
841 // by dispatchIconUpdateQueue().
842 item.determineMimeType();
843 m_resolvedMimeTypes.append(item);
844 resolved = true;
845 }
846 } while (!resolved && !m_pendingItems.isEmpty());
847
848 if (m_pendingItems.isEmpty()) {
849 // All MIME types have been resolved now. Assure
850 // that the directory model gets informed about
851 // this, so that an update of the icons is done.
852 dispatchIconUpdateQueue();
853 } else if (!m_iconUpdatesPaused) {
854 // assure that the MIME type of the next
855 // item will be resolved asynchronously
856 auto mimeFunc = [this]() {
857 resolveMimeType();
858 };
860 }
861}
862
863bool KFilePreviewGeneratorPrivate::isCutItem(const KFileItem &item) const
864{
865 const QMimeData *mimeData = QApplication::clipboard()->mimeData();
866 const QList<QUrl> cutUrls = KUrlMimeData::urlsFromMimeData(mimeData);
867 return cutUrls.contains(item.url());
868}
869
870void KFilePreviewGeneratorPrivate::applyCutItemEffect(const KFileItemList &items)
871{
872 const QMimeData *mimeData = QApplication::clipboard()->mimeData();
873 m_hasCutSelection = mimeData && KIO::isClipboardDataCut(mimeData);
874 if (!m_hasCutSelection) {
875 return;
876 }
877
878 KDirModel *dirModel = m_dirModel.data();
879 if (!dirModel) {
880 return;
881 }
882
883 const QList<QUrl> urlsList = KUrlMimeData::urlsFromMimeData(mimeData);
884 const QSet<QUrl> cutUrls(urlsList.begin(), urlsList.end());
885
886 DataChangeObtainer obt(this);
887 KIconEffect *iconEffect = KIconLoader::global()->iconEffect();
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())) {
899 pixmap = iconEffect->apply(pixmap, KIconLoader::Desktop, KIconLoader::DisabledState);
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.
Definition previewjob.h:31
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.
QImage apply(const QImage &src, int effect, float value, const QColor &rgb, bool trans) const
static KIconLoader * global()
void drawOverlays(const QStringList &overlays, QPixmap &pixmap, KIconLoader::Group group, int state=KIconLoader::DefaultState) const
KIconEffect * iconEffect() const
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:271
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)
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 May 3 2024 11:49:40 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.