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