1// Copyright (C) 2020 The Qt Company Ltd.
2// Copyright (C) 2015 Olivier Goffart <ogoffart@woboq.com>
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "qicon.h"
6#include "qicon_p.h"
7#include "qiconengine.h"
8#include "qiconengineplugin.h"
9#include "qimagereader.h"
10#include "private/qfactoryloader_p.h"
11#include "private/qiconloader_p.h"
12#include "qpainter.h"
13#include "qfileinfo.h"
14#if QT_CONFIG(mimetype)
15#include <qmimedatabase.h>
16#include <qmimetype.h>
17#endif
18#include "qpixmapcache.h"
19#include "qvariant.h"
20#include "qcache.h"
21#include "qdebug.h"
22#include "qdir.h"
23#include "qpalette.h"
24#include "qmath.h"
25
26#include "private/qhexstring_p.h"
27#include "private/qguiapplication_p.h"
28#include "qpa/qplatformtheme.h"
29
30#ifndef QT_NO_ICON
31QT_BEGIN_NAMESPACE
32
33using namespace Qt::StringLiterals;
34
35/*!
36 \enum QIcon::Mode
37
38 This enum type describes the mode for which a pixmap is intended
39 to be used. The currently defined modes are:
40
41 \value Normal
42 Display the pixmap when the user is
43 not interacting with the icon, but the
44 functionality represented by the icon is available.
45 \value Disabled
46 Display the pixmap when the
47 functionality represented by the icon is not available.
48 \value Active
49 Display the pixmap when the
50 functionality represented by the icon is available and
51 the user is interacting with the icon, for example, moving the
52 mouse over it or clicking it.
53 \value Selected
54 Display the pixmap when the item represented by the icon is
55 selected.
56*/
57
58/*!
59 \enum QIcon::State
60
61 This enum describes the state for which a pixmap is intended to be
62 used. The \e state can be:
63
64 \value Off Display the pixmap when the widget is in an "off" state
65 \value On Display the pixmap when the widget is in an "on" state
66*/
67
68static int nextSerialNumCounter()
69{
70 static QBasicAtomicInt serial = Q_BASIC_ATOMIC_INITIALIZER(0);
71 return 1 + serial.fetchAndAddRelaxed(valueToAdd: 1);
72}
73
74static void qt_cleanup_icon_cache();
75namespace {
76 struct IconCache : public QCache<QString, QIcon>
77 {
78 IconCache()
79 {
80 // ### note: won't readd if QApplication is re-created!
81 qAddPostRoutine(qt_cleanup_icon_cache);
82 }
83 };
84}
85
86Q_GLOBAL_STATIC(IconCache, qtIconCache)
87
88static void qt_cleanup_icon_cache()
89{
90 qtIconCache()->clear();
91}
92
93QIconPrivate::QIconPrivate(QIconEngine *e)
94 : engine(e), ref(1),
95 serialNum(nextSerialNumCounter()),
96 detach_no(0),
97 is_mask(false)
98{
99}
100
101/*! \internal
102 Computes the displayDevicePixelRatio for a pixmap.
103
104 If displayDevicePixelRatio is 1.0 the reurned value is 1.0, always.
105
106 For a displayDevicePixelRatio of 2.0 the returned value will be between
107 1.0 and 2.0, depending on requestedSize and actualsize:
108 * If actualsize < requestedSize : 1.0 (not enough pixels for a normal-dpi pixmap)
109 * If actualsize == requestedSize * 2.0 : 2.0 (enough pixels for a high-dpi pixmap)
110 * else : a scaled value between 1.0 and 2.0. (pixel count is between normal-dpi and high-dpi)
111*/
112qreal QIconPrivate::pixmapDevicePixelRatio(qreal displayDevicePixelRatio, const QSize &requestedSize, const QSize &actualSize)
113{
114 QSize targetSize = requestedSize * displayDevicePixelRatio;
115 if ((actualSize.width() == targetSize.width() && actualSize.height() <= targetSize.height()) ||
116 (actualSize.width() <= targetSize.width() && actualSize.height() == targetSize.height())) {
117 // Correctly scaled for dpr, just having different aspect ratio
118 return displayDevicePixelRatio;
119 }
120 qreal scale = 0.5 * (qreal(actualSize.width()) / qreal(targetSize.width()) +
121 qreal(actualSize.height() / qreal(targetSize.height())));
122 return qMax(a: qreal(1.0), b: displayDevicePixelRatio *scale);
123}
124
125QPixmapIconEngine::QPixmapIconEngine()
126{
127}
128
129QPixmapIconEngine::QPixmapIconEngine(const QPixmapIconEngine &other)
130 : QIconEngine(other), pixmaps(other.pixmaps)
131{
132}
133
134QPixmapIconEngine::~QPixmapIconEngine()
135{
136}
137
138void QPixmapIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state)
139{
140 auto paintDevice = painter->device();
141 qreal dpr = paintDevice ? paintDevice->devicePixelRatio() : qApp->devicePixelRatio();
142 const QSize pixmapSize = rect.size() * dpr;
143 QPixmap px = scaledPixmap(size: pixmapSize, mode, state, scale: dpr);
144 painter->drawPixmap(r: rect, pm: px);
145}
146
147static inline int area(const QSize &s) { return s.width() * s.height(); }
148
149// Returns the smallest of the two that is still larger than or equal to size.
150// Pixmaps at the correct scale are preferred, pixmaps at lower scale are
151// used as fallbacks. We assume that the pixmap set is complete, in the sense
152// that no 2x pixmap is going to be a better match than a 3x pixmap for the the
153// target scale of 3 (It's OK if 3x pixmaps are missing - we'll fall back to
154// the 2x pixmaps then.)
155static QPixmapIconEngineEntry *bestSizeScaleMatch(const QSize &size, qreal scale, QPixmapIconEngineEntry *pa, QPixmapIconEngineEntry *pb)
156{
157
158 // scale: we can only differentiate on scale if the scale differs
159 if (pa->scale != pb->scale) {
160
161 // Score the pixmaps: 0 is an exact scale match, positive
162 // scores have more detail than requested, negative scores
163 // have less detail than requested.
164 qreal ascore = pa->scale - scale;
165 qreal bscore = pb->scale - scale;
166
167 // Take the one closest to 0
168 return (qAbs(t: ascore) < qAbs(t: bscore)) ? pa : pb;
169 }
170
171 int s = area(s: size);
172 if (pa->size == QSize() && pa->pixmap.isNull()) {
173 pa->pixmap = QPixmap(pa->fileName);
174 pa->size = pa->pixmap.size();
175 }
176 int a = area(s: pa->size);
177 if (pb->size == QSize() && pb->pixmap.isNull()) {
178 pb->pixmap = QPixmap(pb->fileName);
179 pb->size = pb->pixmap.size();
180 }
181 int b = area(s: pb->size);
182 int res = a;
183 if (qMin(a,b) >= s)
184 res = qMin(a,b);
185 else
186 res = qMax(a,b);
187 if (res == a)
188 return pa;
189 return pb;
190}
191
192QPixmapIconEngineEntry *QPixmapIconEngine::tryMatch(const QSize &size, qreal scale, QIcon::Mode mode, QIcon::State state)
193{
194 QPixmapIconEngineEntry *pe = nullptr;
195 for (int i = 0; i < pixmaps.size(); ++i)
196 if (pixmaps.at(i).mode == mode && pixmaps.at(i).state == state) {
197 if (pe)
198 pe = bestSizeScaleMatch(size, scale, pa: &pixmaps[i], pb: pe);
199 else
200 pe = &pixmaps[i];
201 }
202 return pe;
203}
204
205
206QPixmapIconEngineEntry *QPixmapIconEngine::bestMatch(const QSize &size, qreal scale, QIcon::Mode mode, QIcon::State state, bool sizeOnly)
207{
208 QPixmapIconEngineEntry *pe = tryMatch(size, scale, mode, state);
209 while (!pe){
210 QIcon::State oppositeState = (state == QIcon::On) ? QIcon::Off : QIcon::On;
211 if (mode == QIcon::Disabled || mode == QIcon::Selected) {
212 QIcon::Mode oppositeMode = (mode == QIcon::Disabled) ? QIcon::Selected : QIcon::Disabled;
213 if ((pe = tryMatch(size, scale, mode: QIcon::Normal, state)))
214 break;
215 if ((pe = tryMatch(size, scale, mode: QIcon::Active, state)))
216 break;
217 if ((pe = tryMatch(size, scale, mode, state: oppositeState)))
218 break;
219 if ((pe = tryMatch(size, scale, mode: QIcon::Normal, state: oppositeState)))
220 break;
221 if ((pe = tryMatch(size, scale, mode: QIcon::Active, state: oppositeState)))
222 break;
223 if ((pe = tryMatch(size, scale, mode: oppositeMode, state)))
224 break;
225 if ((pe = tryMatch(size, scale, mode: oppositeMode, state: oppositeState)))
226 break;
227 } else {
228 QIcon::Mode oppositeMode = (mode == QIcon::Normal) ? QIcon::Active : QIcon::Normal;
229 if ((pe = tryMatch(size, scale, mode: oppositeMode, state)))
230 break;
231 if ((pe = tryMatch(size, scale, mode, state: oppositeState)))
232 break;
233 if ((pe = tryMatch(size, scale, mode: oppositeMode, state: oppositeState)))
234 break;
235 if ((pe = tryMatch(size, scale, mode: QIcon::Disabled, state)))
236 break;
237 if ((pe = tryMatch(size, scale, mode: QIcon::Selected, state)))
238 break;
239 if ((pe = tryMatch(size, scale, mode: QIcon::Disabled, state: oppositeState)))
240 break;
241 if ((pe = tryMatch(size, scale, mode: QIcon::Selected, state: oppositeState)))
242 break;
243 }
244
245 if (!pe)
246 return pe;
247 }
248
249 if (sizeOnly ? (pe->size.isNull() || !pe->size.isValid()) : pe->pixmap.isNull()) {
250 pe->pixmap = QPixmap(pe->fileName);
251 if (!pe->pixmap.isNull())
252 pe->size = pe->pixmap.size();
253 }
254
255 return pe;
256}
257
258QPixmap QPixmapIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
259{
260 return scaledPixmap(size, mode, state, scale: 1.0);
261}
262
263QPixmap QPixmapIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale)
264{
265
266 QPixmap pm;
267 QPixmapIconEngineEntry *pe = bestMatch(size, scale, mode, state, sizeOnly: false);
268 if (pe)
269 pm = pe->pixmap;
270
271 if (pm.isNull()) {
272 int idx = pixmaps.size();
273 while (--idx >= 0) {
274 if (pe == &pixmaps.at(i: idx)) {
275 pixmaps.remove(i: idx);
276 break;
277 }
278 }
279 if (pixmaps.isEmpty())
280 return pm;
281 else
282 return pixmap(size, mode, state);
283 }
284
285 QSize actualSize = pm.size();
286 if (!actualSize.isNull() && (actualSize.width() > size.width() || actualSize.height() > size.height()))
287 actualSize.scale(s: size, mode: Qt::KeepAspectRatio);
288
289 QString key = "qt_"_L1
290 % HexString<quint64>(pm.cacheKey())
291 % HexString<uint>(pe ? pe->mode : QIcon::Normal)
292 % HexString<quint64>(QGuiApplication::palette().cacheKey())
293 % HexString<uint>(actualSize.width())
294 % HexString<uint>(actualSize.height());
295
296 if (mode == QIcon::Active) {
297 if (QPixmapCache::find(key: key % HexString<uint>(mode), pixmap: &pm))
298 return pm; // horray
299 if (QPixmapCache::find(key: key % HexString<uint>(QIcon::Normal), pixmap: &pm)) {
300 QPixmap active = pm;
301 if (QGuiApplication *guiApp = qobject_cast<QGuiApplication *>(qApp))
302 active = static_cast<QGuiApplicationPrivate*>(QObjectPrivate::get(o: guiApp))->applyQIconStyleHelper(QIcon::Active, basePixmap: pm);
303 if (pm.cacheKey() == active.cacheKey())
304 return pm;
305 }
306 }
307
308 if (!QPixmapCache::find(key: key % HexString<uint>(mode), pixmap: &pm)) {
309 if (pm.size() != actualSize)
310 pm = pm.scaled(s: actualSize, aspectMode: Qt::IgnoreAspectRatio, mode: Qt::SmoothTransformation);
311 if (pe->mode != mode && mode != QIcon::Normal) {
312 QPixmap generated = pm;
313 if (QGuiApplication *guiApp = qobject_cast<QGuiApplication *>(qApp))
314 generated = static_cast<QGuiApplicationPrivate*>(QObjectPrivate::get(o: guiApp))->applyQIconStyleHelper(mode, basePixmap: pm);
315 if (!generated.isNull())
316 pm = generated;
317 }
318 QPixmapCache::insert(key: key % HexString<uint>(mode), pixmap: pm);
319 }
320 return pm;
321}
322
323QSize QPixmapIconEngine::actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state)
324{
325 QSize actualSize;
326
327 // The returned actual size is the size in device independent pixels,
328 // so we limit the search to scale 1 and assume that e.g. @2x versions
329 // does not proviode extra actual sizes not also provided by the 1x versions.
330 qreal scale = 1;
331
332 if (QPixmapIconEngineEntry *pe = bestMatch(size, scale, mode, state, sizeOnly: true))
333 actualSize = pe->size;
334
335 if (actualSize.isNull())
336 return actualSize;
337
338 if (!actualSize.isNull() && (actualSize.width() > size.width() || actualSize.height() > size.height()))
339 actualSize.scale(s: size, mode: Qt::KeepAspectRatio);
340 return actualSize;
341}
342
343QList<QSize> QPixmapIconEngine::availableSizes(QIcon::Mode mode, QIcon::State state)
344{
345 QList<QSize> sizes;
346 for (int i = 0; i < pixmaps.size(); ++i) {
347 QPixmapIconEngineEntry &pe = pixmaps[i];
348 if (pe.size == QSize() && pe.pixmap.isNull()) {
349 pe.pixmap = QPixmap(pe.fileName);
350 pe.size = pe.pixmap.size();
351 }
352 if (pe.mode == mode && pe.state == state && !pe.size.isEmpty())
353 sizes.push_back(t: pe.size);
354 }
355 return sizes;
356}
357
358void QPixmapIconEngine::addPixmap(const QPixmap &pixmap, QIcon::Mode mode, QIcon::State state)
359{
360 if (!pixmap.isNull()) {
361 QPixmapIconEngineEntry *pe = tryMatch(size: pixmap.size(), scale: pixmap.devicePixelRatio(), mode, state);
362 if (pe && pe->size == pixmap.size() && pe->scale == pixmap.devicePixelRatio()) {
363 pe->pixmap = pixmap;
364 pe->fileName.clear();
365 } else {
366 pixmaps += QPixmapIconEngineEntry(pixmap, mode, state);
367 }
368 }
369}
370
371// Read out original image depth as set by ICOReader
372static inline int origIcoDepth(const QImage &image)
373{
374 const QString s = image.text(QStringLiteral("_q_icoOrigDepth"));
375 return s.isEmpty() ? 32 : s.toInt();
376}
377
378static inline int findBySize(const QList<QImage> &images, const QSize &size)
379{
380 for (int i = 0; i < images.size(); ++i) {
381 if (images.at(i).size() == size)
382 return i;
383 }
384 return -1;
385}
386
387// Convenience class providing a bool read() function.
388namespace {
389class ImageReader
390{
391public:
392 ImageReader(const QString &fileName) : m_reader(fileName), m_atEnd(false) {}
393
394 QByteArray format() const { return m_reader.format(); }
395
396 bool read(QImage *image)
397 {
398 if (m_atEnd)
399 return false;
400 *image = m_reader.read();
401 if (!image->size().isValid()) {
402 m_atEnd = true;
403 return false;
404 }
405 m_atEnd = !m_reader.jumpToNextImage();
406 return true;
407 }
408
409private:
410 QImageReader m_reader;
411 bool m_atEnd;
412};
413} // namespace
414
415void QPixmapIconEngine::addFile(const QString &fileName, const QSize &size, QIcon::Mode mode, QIcon::State state)
416{
417 if (fileName.isEmpty())
418 return;
419 const QString abs = fileName.startsWith(c: u':') ? fileName : QFileInfo(fileName).absoluteFilePath();
420 const bool ignoreSize = !size.isValid();
421 ImageReader imageReader(abs);
422 const QByteArray format = imageReader.format();
423 if (format.isEmpty()) // Device failed to open or unsupported format.
424 return;
425 QImage image;
426 if (format != "ico") {
427 if (ignoreSize) { // No size specified: Add all images.
428 while (imageReader.read(image: &image))
429 pixmaps += QPixmapIconEngineEntry(abs, image, mode, state);
430 } else {
431 // Try to match size. If that fails, add a placeholder with the filename and empty pixmap for the size.
432 while (imageReader.read(image: &image) && image.size() != size) {}
433 pixmaps += image.size() == size ?
434 QPixmapIconEngineEntry(abs, image, mode, state) : QPixmapIconEngineEntry(abs, size, mode, state);
435 }
436 return;
437 }
438 // Special case for reading Windows ".ico" files. Historically (QTBUG-39287),
439 // these files may contain low-resolution images. As this information is lost,
440 // ICOReader sets the original format as an image text key value. Read all matching
441 // images into a list trying to find the highest quality per size.
442 QList<QImage> icoImages;
443 while (imageReader.read(image: &image)) {
444 if (ignoreSize || image.size() == size) {
445 const int position = findBySize(images: icoImages, size: image.size());
446 if (position >= 0) { // Higher quality available? -> replace.
447 if (origIcoDepth(image) > origIcoDepth(image: icoImages.at(i: position)))
448 icoImages[position] = image;
449 } else {
450 icoImages.append(t: image);
451 }
452 }
453 }
454 for (const QImage &i : std::as_const(t&: icoImages))
455 pixmaps += QPixmapIconEngineEntry(abs, i, mode, state);
456 if (icoImages.isEmpty() && !ignoreSize) // Add placeholder with the filename and empty pixmap for the size.
457 pixmaps += QPixmapIconEngineEntry(abs, size, mode, state);
458}
459
460QString QPixmapIconEngine::key() const
461{
462 return "QPixmapIconEngine"_L1;
463}
464
465QIconEngine *QPixmapIconEngine::clone() const
466{
467 return new QPixmapIconEngine(*this);
468}
469
470bool QPixmapIconEngine::read(QDataStream &in)
471{
472 int num_entries;
473 QPixmap pm;
474 QString fileName;
475 QSize sz;
476 uint mode;
477 uint state;
478
479 in >> num_entries;
480 for (int i=0; i < num_entries; ++i) {
481 if (in.atEnd()) {
482 pixmaps.clear();
483 return false;
484 }
485 in >> pm;
486 in >> fileName;
487 in >> sz;
488 in >> mode;
489 in >> state;
490 if (pm.isNull()) {
491 addFile(fileName, size: sz, mode: QIcon::Mode(mode), state: QIcon::State(state));
492 } else {
493 QPixmapIconEngineEntry pe(fileName, sz, QIcon::Mode(mode), QIcon::State(state));
494 pe.pixmap = pm;
495 pixmaps += pe;
496 }
497 }
498 return true;
499}
500
501bool QPixmapIconEngine::write(QDataStream &out) const
502{
503 int num_entries = pixmaps.size();
504 out << num_entries;
505 for (int i=0; i < num_entries; ++i) {
506 if (pixmaps.at(i).pixmap.isNull())
507 out << QPixmap(pixmaps.at(i).fileName);
508 else
509 out << pixmaps.at(i).pixmap;
510 out << pixmaps.at(i).fileName;
511 out << pixmaps.at(i).size;
512 out << (uint) pixmaps.at(i).mode;
513 out << (uint) pixmaps.at(i).state;
514 }
515 return true;
516}
517
518Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader,
519 (QIconEngineFactoryInterface_iid, "/iconengines"_L1, Qt::CaseInsensitive))
520
521QFactoryLoader *qt_iconEngineFactoryLoader()
522{
523 return loader();
524}
525
526
527/*!
528 \class QIcon
529
530 \brief The QIcon class provides scalable icons in different modes
531 and states.
532
533 \ingroup painting
534 \ingroup shared
535 \inmodule QtGui
536
537 A QIcon can generate smaller, larger, active, and disabled pixmaps
538 from the set of pixmaps it is given. Such pixmaps are used by Qt
539 widgets to show an icon representing a particular action.
540
541 The simplest use of QIcon is to create one from a QPixmap file or
542 resource, and then use it, allowing Qt to work out all the required
543 icon styles and sizes. For example:
544
545 \snippet code/src_gui_image_qicon.cpp 0
546
547 To undo a QIcon, simply set a null icon in its place:
548
549 \snippet code/src_gui_image_qicon.cpp 1
550
551 Use the QImageReader::supportedImageFormats() and
552 QImageWriter::supportedImageFormats() functions to retrieve a
553 complete list of the supported file formats.
554
555 When you retrieve a pixmap using pixmap(QSize, Mode, State), and no
556 pixmap for this given size, mode and state has been added with
557 addFile() or addPixmap(), then QIcon will generate one on the
558 fly. This pixmap generation happens in a QIconEngine. The default
559 engine scales pixmaps down if required, but never up, and it uses
560 the current style to calculate a disabled appearance. By using
561 custom icon engines, you can customize every aspect of generated
562 icons. With QIconEnginePlugin it is possible to register different
563 icon engines for different file suffixes, making it possible for
564 third parties to provide additional icon engines to those included
565 with Qt.
566
567 \note Since Qt 4.2, an icon engine that supports SVG is included.
568
569 \section1 Making Classes that Use QIcon
570
571 If you write your own widgets that have an option to set a small
572 pixmap, consider allowing a QIcon to be set for that pixmap. The
573 Qt class QToolButton is an example of such a widget.
574
575 Provide a method to set a QIcon, and when you draw the icon, choose
576 whichever pixmap is appropriate for the current state of your widget.
577 For example:
578 \snippet code/src_gui_image_qicon.cpp 2
579
580 You might also make use of the \c Active mode, perhaps making your
581 widget \c Active when the mouse is over the widget (see \l
582 QWidget::enterEvent()), while the mouse is pressed pending the
583 release that will activate the function, or when it is the currently
584 selected item. If the widget can be toggled, the "On" mode might be
585 used to draw a different icon.
586
587 \image icon.png QIcon
588
589 \note QIcon needs a QGuiApplication instance before the icon is created.
590
591 \section1 High DPI Icons
592
593 There are two ways that QIcon supports \l {High DPI}{high DPI}
594 icons: via \l addFile() and \l fromTheme().
595
596 \l addFile() is useful if you have your own custom directory structure and do
597 not need to use the \l {Icon Theme Specification}{freedesktop.org Icon Theme
598 Specification}. Icons created via this approach use Qt's \l {High Resolution
599 Versions of Images}{"@nx" high DPI syntax}.
600
601 Using \l fromTheme() is necessary if you plan on following the Icon Theme
602 Specification. To make QIcon use the high DPI version of an image, add an
603 additional entry to the appropriate \c index.theme file:
604
605 \badcode
606 [Icon Theme]
607 Name=Test
608 Comment=Test Theme
609
610 Directories=32x32/actions,32x32@2/actions
611
612 [32x32/actions]
613 Size=32
614 Context=Actions
615 Type=Fixed
616
617 # High DPI version of the entry above.
618 [32x32@2/actions]
619 Size=32
620 Scale=2
621 Type=Fixed
622 \endcode
623
624 Your icon theme directory would then look something like this:
625
626 \badcode
627 ├── 32x32
628 │ └── actions
629 │ └── appointment-new.png
630 ├── 32x32@2
631 │ └── actions
632 │ └── appointment-new.png
633 └── index.theme
634 \endcode
635
636 \sa {Icons Example}
637*/
638
639
640/*!
641 Constructs a null icon.
642*/
643QIcon::QIcon() noexcept
644 : d(nullptr)
645{
646}
647
648/*!
649 Constructs an icon from a \a pixmap.
650 */
651QIcon::QIcon(const QPixmap &pixmap)
652 :d(nullptr)
653{
654 addPixmap(pixmap);
655}
656
657/*!
658 Constructs a copy of \a other. This is very fast.
659*/
660QIcon::QIcon(const QIcon &other)
661 :d(other.d)
662{
663 if (d)
664 d->ref.ref();
665}
666
667/*!
668 \fn QIcon::QIcon(QIcon &&other)
669
670 Move-constructs a QIcon instance, making it point to the same object
671 that \a other was pointing to.
672*/
673
674/*!
675 Constructs an icon from the file with the given \a fileName. The
676 file will be loaded on demand.
677
678 If \a fileName contains a relative path (e.g. the filename only)
679 the relevant file must be found relative to the runtime working
680 directory.
681
682 The file name can refer to an actual file on disk or to
683 one of the application's embedded resources. See the
684 \l{resources.html}{Resource System} overview for details on how to
685 embed images and other resource files in the application's
686 executable.
687
688 Use the QImageReader::supportedImageFormats() and
689 QImageWriter::supportedImageFormats() functions to retrieve a
690 complete list of the supported file formats.
691*/
692QIcon::QIcon(const QString &fileName)
693 : d(nullptr)
694{
695 addFile(fileName);
696}
697
698
699/*!
700 Creates an icon with a specific icon \a engine. The icon takes
701 ownership of the engine.
702*/
703QIcon::QIcon(QIconEngine *engine)
704 :d(new QIconPrivate(engine))
705{
706}
707
708/*!
709 Destroys the icon.
710*/
711QIcon::~QIcon()
712{
713 if (d && !d->ref.deref())
714 delete d;
715}
716
717/*!
718 Assigns the \a other icon to this icon and returns a reference to
719 this icon.
720*/
721QIcon &QIcon::operator=(const QIcon &other)
722{
723 if (other.d)
724 other.d->ref.ref();
725 if (d && !d->ref.deref())
726 delete d;
727 d = other.d;
728 return *this;
729}
730
731/*!
732 \fn QIcon &QIcon::operator=(QIcon &&other)
733
734 Move-assigns \a other to this QIcon instance.
735
736 \since 5.2
737*/
738
739/*!
740 \fn void QIcon::swap(QIcon &other)
741 \since 4.8
742
743 Swaps icon \a other with this icon. This operation is very
744 fast and never fails.
745*/
746
747/*!
748 Returns the icon as a QVariant.
749*/
750QIcon::operator QVariant() const
751{
752 return QVariant::fromValue(value: *this);
753}
754
755/*!
756 Returns a number that identifies the contents of this QIcon
757 object. Distinct QIcon objects can have the same key if
758 they refer to the same contents.
759 \since 4.3
760
761 The cacheKey() will change when the icon is altered via
762 addPixmap() or addFile().
763
764 Cache keys are mostly useful in conjunction with caching.
765
766 \sa QPixmap::cacheKey()
767*/
768qint64 QIcon::cacheKey() const
769{
770 if (!d)
771 return 0;
772 return (((qint64) d->serialNum) << 32) | ((qint64) (d->detach_no));
773}
774
775/*!
776 Returns a pixmap with the requested \a size, \a mode, and \a
777 state, generating one if necessary. The pixmap might be smaller than
778 requested, but never larger, unless the device-pixel ratio of the returned
779 pixmap is larger than 1.
780
781 \sa actualSize(), paint()
782*/
783QPixmap QIcon::pixmap(const QSize &size, Mode mode, State state) const
784{
785 if (!d)
786 return QPixmap();
787 const qreal dpr = -1; // don't know target dpr
788 return pixmap(size, devicePixelRatio: dpr, mode, state);
789}
790
791/*!
792 \fn QPixmap QIcon::pixmap(int w, int h, Mode mode = Normal, State state = Off) const
793
794 \overload
795
796 Returns a pixmap of size QSize(\a w, \a h). The pixmap might be smaller than
797 requested, but never larger, unless the device-pixel ratio of the returned
798 pixmap is larger than 1.
799*/
800
801/*!
802 \fn QPixmap QIcon::pixmap(int extent, Mode mode = Normal, State state = Off) const
803
804 \overload
805
806 Returns a pixmap of size QSize(\a extent, \a extent). The pixmap might be smaller
807 than requested, but never larger, unless the device-pixel ratio of the returned
808 pixmap is larger than 1.
809*/
810
811/*!
812 \overload
813 \since 6.0
814
815 Returns a pixmap with the requested \a size, \a devicePixelRatio, \a mode, and \a
816 state, generating one if necessary.
817
818 \sa actualSize(), paint()
819*/
820QPixmap QIcon::pixmap(const QSize &size, qreal devicePixelRatio, Mode mode, State state) const
821{
822 if (!d)
823 return QPixmap();
824
825 // Use the global devicePixelRatio if the caller does not know the target dpr
826 if (devicePixelRatio == -1)
827 devicePixelRatio = qApp->devicePixelRatio();
828
829 // Handle the simple normal-dpi case
830 if (!(devicePixelRatio > 1.0)) {
831 QPixmap pixmap = d->engine->pixmap(size, mode, state);
832 pixmap.setDevicePixelRatio(1.0);
833 return pixmap;
834 }
835
836 // Try get a pixmap that is big enough to be displayed at device pixel resolution.
837 QPixmap pixmap = d->engine->scaledPixmap(size: size * devicePixelRatio, mode, state, scale: devicePixelRatio);
838 pixmap.setDevicePixelRatio(d->pixmapDevicePixelRatio(displayDevicePixelRatio: devicePixelRatio, requestedSize: size, actualSize: pixmap.size()));
839 return pixmap;
840}
841
842#if QT_DEPRECATED_SINCE(6, 0)
843/*!
844 \since 5.1
845 \deprecated [6.0] Use pixmap(size, devicePixelRatio) instead.
846
847 Returns a pixmap with the requested \a window \a size, \a mode, and \a
848 state, generating one if necessary.
849
850 The pixmap can be smaller than the requested size. If \a window is on
851 a high-dpi display the pixmap can be larger. In that case it will have
852 a devicePixelRatio larger than 1.
853
854 \sa actualSize(), paint()
855*/
856
857QPixmap QIcon::pixmap(QWindow *window, const QSize &size, Mode mode, State state) const
858{
859 if (!d)
860 return QPixmap();
861
862 qreal devicePixelRatio = window ? window->devicePixelRatio() : qApp->devicePixelRatio();
863 return pixmap(size, devicePixelRatio, mode, state);
864}
865#endif
866
867
868/*! Returns the actual size of the icon for the requested \a size, \a
869 mode, and \a state. The result might be smaller than requested, but
870 never larger. The returned size is in device-independent pixels (This
871 is relevant for high-dpi pixmaps.)
872
873 \sa pixmap(), paint()
874*/
875QSize QIcon::actualSize(const QSize &size, Mode mode, State state) const
876{
877 if (!d)
878 return QSize();
879
880 const qreal devicePixelRatio = qApp->devicePixelRatio();
881
882 // Handle the simple normal-dpi case:
883 if (!(devicePixelRatio > 1.0))
884 return d->engine->actualSize(size, mode, state);
885
886 const QSize actualSize = d->engine->actualSize(size: size * devicePixelRatio, mode, state);
887 return actualSize / d->pixmapDevicePixelRatio(displayDevicePixelRatio: devicePixelRatio, requestedSize: size, actualSize);
888}
889
890#if QT_DEPRECATED_SINCE(6, 0)
891/*!
892 \since 5.1
893 \deprecated [6.0] Use actualSize(size) instead.
894
895 Returns the actual size of the icon for the requested \a window \a size, \a
896 mode, and \a state.
897
898 The pixmap can be smaller than the requested size. The returned size
899 is in device-independent pixels (This is relevant for high-dpi pixmaps.)
900
901 \sa actualSize(), pixmap(), paint()
902*/
903
904QSize QIcon::actualSize(QWindow *window, const QSize &size, Mode mode, State state) const
905{
906 if (!d)
907 return QSize();
908
909 qreal devicePixelRatio = window ? window->devicePixelRatio() : qApp->devicePixelRatio();
910
911 // Handle the simple normal-dpi case:
912 if (!(devicePixelRatio > 1.0))
913 return d->engine->actualSize(size, mode, state);
914
915 QSize actualSize = d->engine->actualSize(size: size * devicePixelRatio, mode, state);
916 return actualSize / d->pixmapDevicePixelRatio(displayDevicePixelRatio: devicePixelRatio, requestedSize: size, actualSize);
917}
918#endif
919
920/*!
921 Uses the \a painter to paint the icon with specified \a alignment,
922 required \a mode, and \a state into the rectangle \a rect.
923
924 \sa actualSize(), pixmap()
925*/
926void QIcon::paint(QPainter *painter, const QRect &rect, Qt::Alignment alignment, Mode mode, State state) const
927{
928 if (!d || !painter)
929 return;
930
931 // Copy of QStyle::alignedRect
932 const QSize size = d->engine->actualSize(size: rect.size(), mode, state);
933 alignment = QGuiApplicationPrivate::visualAlignment(direction: painter->layoutDirection(), alignment);
934 int x = rect.x();
935 int y = rect.y();
936 int w = size.width();
937 int h = size.height();
938 if ((alignment & Qt::AlignVCenter) == Qt::AlignVCenter)
939 y += rect.size().height()/2 - h/2;
940 else if ((alignment & Qt::AlignBottom) == Qt::AlignBottom)
941 y += rect.size().height() - h;
942 if ((alignment & Qt::AlignRight) == Qt::AlignRight)
943 x += rect.size().width() - w;
944 else if ((alignment & Qt::AlignHCenter) == Qt::AlignHCenter)
945 x += rect.size().width()/2 - w/2;
946 QRect alignedRect(x, y, w, h);
947
948 d->engine->paint(painter, rect: alignedRect, mode, state);
949}
950
951/*!
952 \fn void QIcon::paint(QPainter *painter, int x, int y, int w, int h, Qt::Alignment alignment,
953 Mode mode, State state) const
954
955 \overload
956
957 Paints the icon into the rectangle QRect(\a x, \a y, \a w, \a h).
958*/
959
960/*!
961 Returns \c true if the icon is empty; otherwise returns \c false.
962
963 An icon is empty if it has neither a pixmap nor a filename.
964
965 Note: Even a non-null icon might not be able to create valid
966 pixmaps, eg. if the file does not exist or cannot be read.
967*/
968bool QIcon::isNull() const
969{
970 return !d || d->engine->isNull();
971}
972
973/*!\internal
974 */
975bool QIcon::isDetached() const
976{
977 return !d || d->ref.loadRelaxed() == 1;
978}
979
980/*! \internal
981 */
982void QIcon::detach()
983{
984 if (d) {
985 if (d->engine->isNull()) {
986 if (!d->ref.deref())
987 delete d;
988 d = nullptr;
989 return;
990 } else if (d->ref.loadRelaxed() != 1) {
991 QIconPrivate *x = new QIconPrivate(d->engine->clone());
992 if (!d->ref.deref())
993 delete d;
994 d = x;
995 }
996 ++d->detach_no;
997 }
998}
999
1000/*!
1001 Adds \a pixmap to the icon, as a specialization for \a mode and
1002 \a state.
1003
1004 Custom icon engines are free to ignore additionally added
1005 pixmaps.
1006
1007 \sa addFile()
1008*/
1009void QIcon::addPixmap(const QPixmap &pixmap, Mode mode, State state)
1010{
1011 if (pixmap.isNull())
1012 return;
1013 detach();
1014 if (!d)
1015 d = new QIconPrivate(new QPixmapIconEngine);
1016 d->engine->addPixmap(pixmap, mode, state);
1017}
1018
1019static QIconEngine *iconEngineFromSuffix(const QString &fileName, const QString &suffix)
1020{
1021 if (!suffix.isEmpty()) {
1022 const int index = loader()->indexOf(needle: suffix);
1023 if (index != -1) {
1024 if (QIconEnginePlugin *factory = qobject_cast<QIconEnginePlugin*>(object: loader()->instance(index))) {
1025 return factory->create(filename: fileName);
1026 }
1027 }
1028 }
1029 return nullptr;
1030}
1031
1032/*! Adds an image from the file with the given \a fileName to the
1033 icon, as a specialization for \a size, \a mode and \a state. The
1034 file will be loaded on demand. Note: custom icon engines are free
1035 to ignore additionally added pixmaps.
1036
1037 If \a fileName contains a relative path (e.g. the filename only)
1038 the relevant file must be found relative to the runtime working
1039 directory.
1040
1041 The file name can refer to an actual file on disk or to
1042 one of the application's embedded resources. See the
1043 \l{resources.html}{Resource System} overview for details on how to
1044 embed images and other resource files in the application's
1045 executable.
1046
1047 Use the QImageReader::supportedImageFormats() and
1048 QImageWriter::supportedImageFormats() functions to retrieve a
1049 complete list of the supported file formats.
1050
1051 If a high resolution version of the image exists (identified by
1052 the suffix \c @2x on the base name), it is automatically loaded
1053 and added with the \e{device pixel ratio} set to a value of 2.
1054 This can be disabled by setting the environment variable
1055 \c QT_HIGHDPI_DISABLE_2X_IMAGE_LOADING (see QImageReader).
1056
1057 \note When you add a non-empty filename to a QIcon, the icon becomes
1058 non-null, even if the file doesn't exist or points to a corrupt file.
1059
1060 \sa addPixmap(), QPixmap::devicePixelRatio()
1061 */
1062void QIcon::addFile(const QString &fileName, const QSize &size, Mode mode, State state)
1063{
1064 if (fileName.isEmpty())
1065 return;
1066 detach();
1067 if (!d) {
1068
1069 QFileInfo info(fileName);
1070 QString suffix = info.suffix();
1071#if QT_CONFIG(mimetype)
1072 if (suffix.isEmpty())
1073 suffix = QMimeDatabase().mimeTypeForFile(fileInfo: info).preferredSuffix(); // determination from contents
1074#endif // mimetype
1075 QIconEngine *engine = iconEngineFromSuffix(fileName, suffix);
1076 d = new QIconPrivate(engine ? engine : new QPixmapIconEngine);
1077 }
1078
1079 d->engine->addFile(fileName, size, mode, state);
1080
1081 // Check if a "@Nx" file exists and add it.
1082 QString atNxFileName = qt_findAtNxFile(baseFileName: fileName, qApp->devicePixelRatio());
1083 if (atNxFileName != fileName)
1084 d->engine->addFile(fileName: atNxFileName, size, mode, state);
1085}
1086
1087/*!
1088 \since 4.5
1089
1090 Returns a list of available icon sizes for the specified \a mode and
1091 \a state.
1092*/
1093QList<QSize> QIcon::availableSizes(Mode mode, State state) const
1094{
1095 if (!d || !d->engine)
1096 return QList<QSize>();
1097 return d->engine->availableSizes(mode, state);
1098}
1099
1100/*!
1101 \since 4.7
1102
1103 Returns the name used to create the icon, if available.
1104
1105 Depending on the way the icon was created, it may have an associated
1106 name. This is the case for icons created with fromTheme().
1107
1108 \sa fromTheme(), QIconEngine::iconName()
1109*/
1110QString QIcon::name() const
1111{
1112 if (!d || !d->engine)
1113 return QString();
1114 return d->engine->iconName();
1115}
1116
1117/*!
1118 \since 4.6
1119
1120 Sets the search paths for icon themes to \a paths.
1121 \sa themeSearchPaths(), fromTheme(), setThemeName()
1122*/
1123void QIcon::setThemeSearchPaths(const QStringList &paths)
1124{
1125 QIconLoader::instance()->setThemeSearchPath(paths);
1126}
1127
1128/*!
1129 \since 4.6
1130
1131 Returns the search paths for icon themes.
1132
1133 The default value will depend on the platform:
1134
1135 On X11, the search path will use the XDG_DATA_DIRS environment
1136 variable if available.
1137
1138 By default all platforms will have the resource directory
1139 \c{:\icons} as a fallback. You can use "rcc -project" to generate a
1140 resource file from your icon theme.
1141
1142 \sa setThemeSearchPaths(), fromTheme(), setThemeName()
1143*/
1144QStringList QIcon::themeSearchPaths()
1145{
1146 return QIconLoader::instance()->themeSearchPaths();
1147}
1148
1149/*!
1150 \since 5.11
1151
1152 Returns the fallback search paths for icons.
1153
1154 The default value will depend on the platform.
1155
1156 \sa setFallbackSearchPaths(), themeSearchPaths()
1157*/
1158QStringList QIcon::fallbackSearchPaths()
1159{
1160 return QIconLoader::instance()->fallbackSearchPaths();
1161}
1162
1163/*!
1164 \since 5.11
1165
1166 Sets the fallback search paths for icons to \a paths.
1167
1168 \note To add some path without replacing existing ones:
1169
1170 \snippet code/src_gui_image_qicon.cpp 5
1171
1172 \sa fallbackSearchPaths(), setThemeSearchPaths()
1173*/
1174void QIcon::setFallbackSearchPaths(const QStringList &paths)
1175{
1176 QIconLoader::instance()->setFallbackSearchPaths(paths);
1177}
1178
1179/*!
1180 \since 4.6
1181
1182 Sets the current icon theme to \a name.
1183
1184 The \a name should correspond to a directory name in the
1185 themeSearchPath() containing an index.theme
1186 file describing its contents.
1187
1188 \sa themeSearchPaths(), themeName()
1189*/
1190void QIcon::setThemeName(const QString &name)
1191{
1192 QIconLoader::instance()->setThemeName(name);
1193}
1194
1195/*!
1196 \since 4.6
1197
1198 Returns the name of the current icon theme.
1199
1200 On X11, the current icon theme depends on your desktop
1201 settings. On other platforms it is not set by default.
1202
1203 \sa setThemeName(), themeSearchPaths(), fromTheme(),
1204 hasThemeIcon()
1205*/
1206QString QIcon::themeName()
1207{
1208 return QIconLoader::instance()->themeName();
1209}
1210
1211/*!
1212 \since 5.12
1213
1214 Returns the name of the fallback icon theme.
1215
1216 On X11, if not set, the fallback icon theme depends on your desktop
1217 settings. On other platforms it is not set by default.
1218
1219 \sa setFallbackThemeName(), themeName()
1220*/
1221QString QIcon::fallbackThemeName()
1222{
1223 return QIconLoader::instance()->fallbackThemeName();
1224}
1225
1226/*!
1227 \since 5.12
1228
1229 Sets the fallback icon theme to \a name.
1230
1231 The \a name should correspond to a directory name in the
1232 themeSearchPath() containing an index.theme
1233 file describing its contents.
1234
1235 \note This should be done before creating \l QGuiApplication, to ensure
1236 correct initialization.
1237
1238 \sa fallbackThemeName(), themeSearchPaths(), themeName()
1239*/
1240void QIcon::setFallbackThemeName(const QString &name)
1241{
1242 QIconLoader::instance()->setFallbackThemeName(name);
1243}
1244
1245/*!
1246 \since 4.6
1247
1248 Returns the QIcon corresponding to \a name in the current
1249 icon theme.
1250
1251 The latest version of the freedesktop icon specification and naming
1252 specification can be obtained here:
1253
1254 \list
1255 \li \l{http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html}
1256 \li \l{http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html}
1257 \endlist
1258
1259 To fetch an icon from the current icon theme:
1260
1261 \snippet code/src_gui_image_qicon.cpp 3
1262
1263 \note By default, only X11 will support themed icons. In order to
1264 use themed icons on Mac and Windows, you will have to bundle a
1265 compliant theme in one of your themeSearchPaths() and set the
1266 appropriate themeName().
1267
1268 \note Qt will make use of GTK's icon-theme.cache if present to speed up
1269 the lookup. These caches can be generated using gtk-update-icon-cache:
1270 \l{https://developer.gnome.org/gtk3/stable/gtk-update-icon-cache.html}.
1271
1272 \note If an icon can't be found in the current theme, then it will be
1273 searched in fallbackSearchPaths() as an unthemed icon.
1274
1275 \sa themeName(), setThemeName(), themeSearchPaths(), fallbackSearchPaths()
1276*/
1277QIcon QIcon::fromTheme(const QString &name)
1278{
1279
1280 if (QIcon *cachedIcon = qtIconCache()->object(key: name)) {
1281 if (!cachedIcon->isNull())
1282 return *cachedIcon;
1283 qtIconCache()->remove(key: name);
1284 }
1285
1286 QIcon icon;
1287 if (QDir::isAbsolutePath(path: name)) {
1288 return QIcon(name);
1289 } else {
1290 QPlatformTheme * const platformTheme = QGuiApplicationPrivate::platformTheme();
1291 bool hasUserTheme = QIconLoader::instance()->hasUserTheme();
1292 QIconEngine * const engine = (platformTheme && !hasUserTheme) ? platformTheme->createIconEngine(iconName: name)
1293 : new QIconLoaderEngine(name);
1294 icon = QIcon(engine);
1295 if (!icon.isNull())
1296 qtIconCache()->insert(key: name, object: new QIcon(icon));
1297 }
1298
1299 return icon;
1300}
1301
1302/*!
1303 \overload
1304
1305 Returns the QIcon corresponding to \a name in the current
1306 icon theme. If no such icon is found in the current theme
1307 \a fallback is returned instead.
1308
1309 If you want to provide a guaranteed fallback for platforms that
1310 do not support theme icons, you can use the second argument:
1311
1312 \snippet code/src_gui_image_qicon.cpp 4
1313*/
1314QIcon QIcon::fromTheme(const QString &name, const QIcon &fallback)
1315{
1316 QIcon icon = fromTheme(name);
1317
1318 if (icon.isNull() || icon.availableSizes().isEmpty())
1319 return fallback;
1320
1321 return icon;
1322}
1323
1324/*!
1325 \since 4.6
1326
1327 Returns \c true if there is an icon available for \a name in the
1328 current icon theme, otherwise returns \c false.
1329
1330 \sa themeSearchPaths(), fromTheme(), setThemeName()
1331*/
1332bool QIcon::hasThemeIcon(const QString &name)
1333{
1334 QIcon icon = fromTheme(name);
1335
1336 return icon.name() == name;
1337}
1338
1339/*!
1340 \since 5.6
1341
1342 Indicate that this icon is a mask image(boolean \a isMask), and hence can
1343 potentially be modified based on where it's displayed.
1344 \sa isMask()
1345*/
1346void QIcon::setIsMask(bool isMask)
1347{
1348 detach();
1349 if (!d)
1350 d = new QIconPrivate(new QPixmapIconEngine);
1351 d->is_mask = isMask;
1352}
1353
1354/*!
1355 \since 5.6
1356
1357 Returns \c true if this icon has been marked as a mask image.
1358 Certain platforms render mask icons differently (for example,
1359 menu icons on \macos).
1360
1361 \sa setIsMask()
1362*/
1363bool QIcon::isMask() const
1364{
1365 if (!d)
1366 return false;
1367 return d->is_mask;
1368}
1369
1370/*****************************************************************************
1371 QIcon stream functions
1372 *****************************************************************************/
1373#if !defined(QT_NO_DATASTREAM)
1374/*!
1375 \fn QDataStream &operator<<(QDataStream &stream, const QIcon &icon)
1376 \relates QIcon
1377 \since 4.2
1378
1379 Writes the given \a icon to the given \a stream as a PNG
1380 image. If the icon contains more than one image, all images will
1381 be written to the stream. Note that writing the stream to a file
1382 will not produce a valid image file.
1383*/
1384
1385QDataStream &operator<<(QDataStream &s, const QIcon &icon)
1386{
1387 if (s.version() >= QDataStream::Qt_4_3) {
1388 if (icon.isNull()) {
1389 s << QString();
1390 } else {
1391 s << icon.d->engine->key();
1392 icon.d->engine->write(out&: s);
1393 }
1394 } else if (s.version() == QDataStream::Qt_4_2) {
1395 if (icon.isNull()) {
1396 s << 0;
1397 } else {
1398 QPixmapIconEngine *engine = static_cast<QPixmapIconEngine *>(icon.d->engine);
1399 int num_entries = engine->pixmaps.size();
1400 s << num_entries;
1401 for (int i=0; i < num_entries; ++i) {
1402 s << engine->pixmaps.at(i).pixmap;
1403 s << engine->pixmaps.at(i).fileName;
1404 s << engine->pixmaps.at(i).size;
1405 s << (uint) engine->pixmaps.at(i).mode;
1406 s << (uint) engine->pixmaps.at(i).state;
1407 }
1408 }
1409 } else {
1410 s << QPixmap(icon.pixmap(w: 22,h: 22));
1411 }
1412 return s;
1413}
1414
1415/*!
1416 \fn QDataStream &operator>>(QDataStream &stream, QIcon &icon)
1417 \relates QIcon
1418 \since 4.2
1419
1420 Reads an image, or a set of images, from the given \a stream into
1421 the given \a icon.
1422*/
1423
1424QDataStream &operator>>(QDataStream &s, QIcon &icon)
1425{
1426 if (s.version() >= QDataStream::Qt_4_3) {
1427 icon = QIcon();
1428 QString key;
1429 s >> key;
1430 if (key == "QPixmapIconEngine"_L1) {
1431 icon.d = new QIconPrivate(new QPixmapIconEngine);
1432 icon.d->engine->read(in&: s);
1433 } else if (key == "QIconLoaderEngine"_L1) {
1434 icon.d = new QIconPrivate(new QIconLoaderEngine());
1435 icon.d->engine->read(in&: s);
1436 } else {
1437 const int index = loader()->indexOf(needle: key);
1438 if (index != -1) {
1439 if (QIconEnginePlugin *factory = qobject_cast<QIconEnginePlugin*>(object: loader()->instance(index))) {
1440 if (QIconEngine *engine= factory->create()) {
1441 icon.d = new QIconPrivate(engine);
1442 engine->read(in&: s);
1443 } // factory
1444 } // instance
1445 } // index
1446 }
1447 } else if (s.version() == QDataStream::Qt_4_2) {
1448 icon = QIcon();
1449 int num_entries;
1450 QPixmap pm;
1451 QString fileName;
1452 QSize sz;
1453 uint mode;
1454 uint state;
1455
1456 s >> num_entries;
1457 for (int i=0; i < num_entries; ++i) {
1458 s >> pm;
1459 s >> fileName;
1460 s >> sz;
1461 s >> mode;
1462 s >> state;
1463 if (pm.isNull())
1464 icon.addFile(fileName, size: sz, mode: QIcon::Mode(mode), state: QIcon::State(state));
1465 else
1466 icon.addPixmap(pixmap: pm, mode: QIcon::Mode(mode), state: QIcon::State(state));
1467 }
1468 } else {
1469 QPixmap pm;
1470 s >> pm;
1471 icon.addPixmap(pixmap: pm);
1472 }
1473 return s;
1474}
1475
1476#endif //QT_NO_DATASTREAM
1477
1478#ifndef QT_NO_DEBUG_STREAM
1479QDebug operator<<(QDebug dbg, const QIcon &i)
1480{
1481 QDebugStateSaver saver(dbg);
1482 dbg.resetFormat();
1483 dbg.nospace();
1484 dbg << "QIcon(";
1485 if (i.isNull()) {
1486 dbg << "null";
1487 } else {
1488 if (!i.name().isEmpty())
1489 dbg << i.name() << ',';
1490 dbg << "availableSizes[normal,Off]=" << i.availableSizes()
1491 << ",cacheKey=" << Qt::showbase << Qt::hex << i.cacheKey() << Qt::dec << Qt::noshowbase;
1492 }
1493 dbg << ')';
1494 return dbg;
1495}
1496#endif
1497
1498/*!
1499 \fn DataPtr &QIcon::data_ptr()
1500 \internal
1501*/
1502
1503/*!
1504 \typedef QIcon::DataPtr
1505 \internal
1506*/
1507
1508/*!
1509 \internal
1510 \since 5.6
1511 Attempts to find a suitable @Nx file for the given \a targetDevicePixelRatio
1512 Returns the \a baseFileName if no such file was found.
1513
1514 Given base foo.png and a target dpr of 2.5, this function will look for
1515 foo@3x.png, then foo@2x, then fall back to foo.png if not found.
1516
1517 \a sourceDevicePixelRatio will be set to the value of N if the argument is
1518 not \nullptr
1519*/
1520QString qt_findAtNxFile(const QString &baseFileName, qreal targetDevicePixelRatio,
1521 qreal *sourceDevicePixelRatio)
1522{
1523 if (targetDevicePixelRatio <= 1.0)
1524 return baseFileName;
1525
1526 static bool disableNxImageLoading = !qEnvironmentVariableIsEmpty(varName: "QT_HIGHDPI_DISABLE_2X_IMAGE_LOADING");
1527 if (disableNxImageLoading)
1528 return baseFileName;
1529
1530 int dotIndex = baseFileName.lastIndexOf(c: u'.');
1531 if (dotIndex == -1) { /* no dot */
1532 dotIndex = baseFileName.size(); /* append */
1533 } else if (dotIndex >= 2 && baseFileName[dotIndex - 1] == u'9'
1534 && baseFileName[dotIndex - 2] == u'.') {
1535 // If the file has a .9.* (9-patch image) extension, we must ensure that the @nx goes before it.
1536 dotIndex -= 2;
1537 }
1538
1539 QString atNxfileName = baseFileName;
1540 atNxfileName.insert(i: dotIndex, s: "@2x"_L1);
1541 // Check for @Nx, ..., @3x, @2x file versions,
1542 for (int n = qMin(a: qCeil(v: targetDevicePixelRatio), b: 9); n > 1; --n) {
1543 atNxfileName[dotIndex + 1] = QLatin1Char('0' + n);
1544 if (QFile::exists(fileName: atNxfileName)) {
1545 if (sourceDevicePixelRatio)
1546 *sourceDevicePixelRatio = n;
1547 return atNxfileName;
1548 }
1549 }
1550
1551 return baseFileName;
1552}
1553
1554QT_END_NAMESPACE
1555#endif //QT_NO_ICON
1556

source code of qtbase/src/gui/image/qicon.cpp