source: trunk/src/gui/image/qicon.cpp

Last change on this file was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 36.1 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the QtGui module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qicon.h"
43#include "qicon_p.h"
44#include "qiconengine.h"
45#include "qiconengineplugin.h"
46#include "private/qfactoryloader_p.h"
47#include "private/qiconloader_p.h"
48#include "qapplication.h"
49#include "qstyleoption.h"
50#include "qpainter.h"
51#include "qfileinfo.h"
52#include "qstyle.h"
53#include "qpixmapcache.h"
54#include "qvariant.h"
55#include "qcache.h"
56#include "qdebug.h"
57#include "private/qguiplatformplugin_p.h"
58
59#ifdef Q_WS_MAC
60#include <private/qt_mac_p.h>
61#include <private/qt_cocoa_helpers_mac_p.h>
62#endif
63
64#ifdef Q_WS_X11
65#include "private/qt_x11_p.h"
66#include "private/qkde_p.h"
67#endif
68
69#include "private/qstylehelper_p.h"
70
71#ifndef QT_NO_ICON
72QT_BEGIN_NAMESPACE
73
74/*!
75 \enum QIcon::Mode
76
77 This enum type describes the mode for which a pixmap is intended
78 to be used. The currently defined modes are:
79
80 \value Normal
81 Display the pixmap when the user is
82 not interacting with the icon, but the
83 functionality represented by the icon is available.
84 \value Disabled
85 Display the pixmap when the
86 functionality represented by the icon is not available.
87 \value Active
88 Display the pixmap when the
89 functionality represented by the icon is available and
90 the user is interacting with the icon, for example, moving the
91 mouse over it or clicking it.
92 \value Selected
93 Display the pixmap when the item represented by the icon is
94 selected.
95*/
96
97/*!
98 \enum QIcon::State
99
100 This enum describes the state for which a pixmap is intended to be
101 used. The \e state can be:
102
103 \value Off Display the pixmap when the widget is in an "off" state
104 \value On Display the pixmap when the widget is in an "on" state
105*/
106
107static QBasicAtomicInt serialNumCounter = Q_BASIC_ATOMIC_INITIALIZER(1);
108
109static void qt_cleanup_icon_cache();
110typedef QCache<QString, QIcon> IconCache;
111Q_GLOBAL_STATIC_WITH_INITIALIZER(IconCache, qtIconCache, qAddPostRoutine(qt_cleanup_icon_cache))
112
113static void qt_cleanup_icon_cache()
114{
115 qtIconCache()->clear();
116}
117
118QIconPrivate::QIconPrivate()
119 : engine(0), ref(1),
120 serialNum(serialNumCounter.fetchAndAddRelaxed(1)),
121 detach_no(0),
122 engine_version(2),
123 v1RefCount(0)
124{
125}
126
127QPixmapIconEngine::QPixmapIconEngine()
128{
129}
130
131QPixmapIconEngine::QPixmapIconEngine(const QPixmapIconEngine &other)
132 : QIconEngineV2(other), pixmaps(other.pixmaps)
133{
134}
135
136QPixmapIconEngine::~QPixmapIconEngine()
137{
138}
139
140void QPixmapIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state)
141{
142 QSize pixmapSize = rect.size();
143#if defined(Q_WS_MAC)
144 pixmapSize *= qt_mac_get_scalefactor();
145#endif
146 painter->drawPixmap(rect, pixmap(pixmapSize, mode, state));
147}
148
149static inline int area(const QSize &s) { return s.width() * s.height(); }
150
151// returns the smallest of the two that is still larger than or equal to size.
152static QPixmapIconEngineEntry *bestSizeMatch( const QSize &size, QPixmapIconEngineEntry *pa, QPixmapIconEngineEntry *pb)
153{
154 int s = area(size);
155 if (pa->size == QSize() && pa->pixmap.isNull()) {
156 pa->pixmap = QPixmap(pa->fileName);
157 pa->size = pa->pixmap.size();
158 }
159 int a = area(pa->size);
160 if (pb->size == QSize() && pb->pixmap.isNull()) {
161 pb->pixmap = QPixmap(pb->fileName);
162 pb->size = pb->pixmap.size();
163 }
164 int b = area(pb->size);
165 int res = a;
166 if (qMin(a,b) >= s)
167 res = qMin(a,b);
168 else
169 res = qMax(a,b);
170 if (res == a)
171 return pa;
172 return pb;
173}
174
175QPixmapIconEngineEntry *QPixmapIconEngine::tryMatch(const QSize &size, QIcon::Mode mode, QIcon::State state)
176{
177 QPixmapIconEngineEntry *pe = 0;
178 for (int i = 0; i < pixmaps.count(); ++i)
179 if (pixmaps.at(i).mode == mode && pixmaps.at(i).state == state) {
180 if (pe)
181 pe = bestSizeMatch(size, &pixmaps[i], pe);
182 else
183 pe = &pixmaps[i];
184 }
185 return pe;
186}
187
188
189QPixmapIconEngineEntry *QPixmapIconEngine::bestMatch(const QSize &size, QIcon::Mode mode, QIcon::State state, bool sizeOnly)
190{
191 QPixmapIconEngineEntry *pe = tryMatch(size, mode, state);
192 while (!pe){
193 QIcon::State oppositeState = (state == QIcon::On) ? QIcon::Off : QIcon::On;
194 if (mode == QIcon::Disabled || mode == QIcon::Selected) {
195 QIcon::Mode oppositeMode = (mode == QIcon::Disabled) ? QIcon::Selected : QIcon::Disabled;
196 if ((pe = tryMatch(size, QIcon::Normal, state)))
197 break;
198 if ((pe = tryMatch(size, QIcon::Active, state)))
199 break;
200 if ((pe = tryMatch(size, mode, oppositeState)))
201 break;
202 if ((pe = tryMatch(size, QIcon::Normal, oppositeState)))
203 break;
204 if ((pe = tryMatch(size, QIcon::Active, oppositeState)))
205 break;
206 if ((pe = tryMatch(size, oppositeMode, state)))
207 break;
208 if ((pe = tryMatch(size, oppositeMode, oppositeState)))
209 break;
210 } else {
211 QIcon::Mode oppositeMode = (mode == QIcon::Normal) ? QIcon::Active : QIcon::Normal;
212 if ((pe = tryMatch(size, oppositeMode, state)))
213 break;
214 if ((pe = tryMatch(size, mode, oppositeState)))
215 break;
216 if ((pe = tryMatch(size, oppositeMode, oppositeState)))
217 break;
218 if ((pe = tryMatch(size, QIcon::Disabled, state)))
219 break;
220 if ((pe = tryMatch(size, QIcon::Selected, state)))
221 break;
222 if ((pe = tryMatch(size, QIcon::Disabled, oppositeState)))
223 break;
224 if ((pe = tryMatch(size, QIcon::Selected, oppositeState)))
225 break;
226 }
227
228 if (!pe)
229 return pe;
230 }
231
232 if (sizeOnly ? (pe->size.isNull() || !pe->size.isValid()) : pe->pixmap.isNull()) {
233 pe->pixmap = QPixmap(pe->fileName);
234 if (!pe->pixmap.isNull())
235 pe->size = pe->pixmap.size();
236 }
237
238 return pe;
239}
240
241QPixmap QPixmapIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
242{
243 QPixmap pm;
244 QPixmapIconEngineEntry *pe = bestMatch(size, mode, state, false);
245 if (pe)
246 pm = pe->pixmap;
247
248 if (pm.isNull()) {
249 int idx = pixmaps.count();
250 while (--idx >= 0) {
251 if (pe == &pixmaps[idx]) {
252 pixmaps.remove(idx);
253 break;
254 }
255 }
256 if (pixmaps.isEmpty())
257 return pm;
258 else
259 return pixmap(size, mode, state);
260 }
261
262 QSize actualSize = pm.size();
263 if (!actualSize.isNull() && (actualSize.width() > size.width() || actualSize.height() > size.height()))
264 actualSize.scale(size, Qt::KeepAspectRatio);
265
266 QString key = QLatin1Literal("qt_")
267 % HexString<quint64>(pm.cacheKey())
268 % HexString<uint>(pe->mode)
269 % HexString<quint64>(QApplication::palette().cacheKey())
270 % HexString<uint>(actualSize.width())
271 % HexString<uint>(actualSize.height());
272
273 if (mode == QIcon::Active) {
274 if (QPixmapCache::find(key % HexString<uint>(mode), pm))
275 return pm; // horray
276 if (QPixmapCache::find(key % HexString<uint>(QIcon::Normal), pm)) {
277 QStyleOption opt(0);
278 opt.palette = QApplication::palette();
279 QPixmap active = QApplication::style()->generatedIconPixmap(QIcon::Active, pm, &opt);
280 if (pm.cacheKey() == active.cacheKey())
281 return pm;
282 }
283 }
284
285 if (!QPixmapCache::find(key % HexString<uint>(mode), pm)) {
286 if (pm.size() != actualSize)
287 pm = pm.scaled(actualSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
288 if (pe->mode != mode && mode != QIcon::Normal) {
289 QStyleOption opt(0);
290 opt.palette = QApplication::palette();
291 QPixmap generated = QApplication::style()->generatedIconPixmap(mode, pm, &opt);
292 if (!generated.isNull())
293 pm = generated;
294 }
295 QPixmapCache::insert(key % HexString<uint>(mode), pm);
296 }
297 return pm;
298}
299
300QSize QPixmapIconEngine::actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state)
301{
302 QSize actualSize;
303 if (QPixmapIconEngineEntry *pe = bestMatch(size, mode, state, true))
304 actualSize = pe->size;
305
306 if (actualSize.isNull())
307 return actualSize;
308
309 if (!actualSize.isNull() && (actualSize.width() > size.width() || actualSize.height() > size.height()))
310 actualSize.scale(size, Qt::KeepAspectRatio);
311 return actualSize;
312}
313
314void QPixmapIconEngine::addPixmap(const QPixmap &pixmap, QIcon::Mode mode, QIcon::State state)
315{
316 if (!pixmap.isNull()) {
317 QPixmapIconEngineEntry *pe = tryMatch(pixmap.size(), mode, state);
318 if(pe && pe->size == pixmap.size()) {
319 pe->pixmap = pixmap;
320 pe->fileName.clear();
321 } else {
322 pixmaps += QPixmapIconEngineEntry(pixmap, mode, state);
323 }
324 }
325}
326
327void QPixmapIconEngine::addFile(const QString &fileName, const QSize &_size, QIcon::Mode mode, QIcon::State state)
328{
329 if (!fileName.isEmpty()) {
330 QSize size = _size;
331 QPixmap pixmap;
332
333 QString abs = fileName;
334 if (fileName.at(0) != QLatin1Char(':'))
335 abs = QFileInfo(fileName).absoluteFilePath();
336
337 for (int i = 0; i < pixmaps.count(); ++i) {
338 if (pixmaps.at(i).mode == mode && pixmaps.at(i).state == state) {
339 QPixmapIconEngineEntry *pe = &pixmaps[i];
340 if(size == QSize()) {
341 pixmap = QPixmap(abs);
342 size = pixmap.size();
343 }
344 if (pe->size == QSize() && pe->pixmap.isNull()) {
345 pe->pixmap = QPixmap(pe->fileName);
346 pe->size = pe->pixmap.size();
347 }
348 if(pe->size == size) {
349 pe->pixmap = pixmap;
350 pe->fileName = abs;
351 return;
352 }
353 }
354 }
355 QPixmapIconEngineEntry e(abs, size, mode, state);
356 e.pixmap = pixmap;
357 pixmaps += e;
358 }
359}
360
361QString QPixmapIconEngine::key() const
362{
363 return QLatin1String("QPixmapIconEngine");
364}
365
366QIconEngineV2 *QPixmapIconEngine::clone() const
367{
368 return new QPixmapIconEngine(*this);
369}
370
371bool QPixmapIconEngine::read(QDataStream &in)
372{
373 int num_entries;
374 QPixmap pm;
375 QString fileName;
376 QSize sz;
377 uint mode;
378 uint state;
379
380 in >> num_entries;
381 for (int i=0; i < num_entries; ++i) {
382 if (in.atEnd()) {
383 pixmaps.clear();
384 return false;
385 }
386 in >> pm;
387 in >> fileName;
388 in >> sz;
389 in >> mode;
390 in >> state;
391 if (pm.isNull()) {
392 addFile(fileName, sz, QIcon::Mode(mode), QIcon::State(state));
393 } else {
394 QPixmapIconEngineEntry pe(fileName, sz, QIcon::Mode(mode), QIcon::State(state));
395 pe.pixmap = pm;
396 pixmaps += pe;
397 }
398 }
399 return true;
400}
401
402bool QPixmapIconEngine::write(QDataStream &out) const
403{
404 int num_entries = pixmaps.size();
405 out << num_entries;
406 for (int i=0; i < num_entries; ++i) {
407 if (pixmaps.at(i).pixmap.isNull())
408 out << QPixmap(pixmaps.at(i).fileName);
409 else
410 out << pixmaps.at(i).pixmap;
411 out << pixmaps.at(i).fileName;
412 out << pixmaps.at(i).size;
413 out << (uint) pixmaps.at(i).mode;
414 out << (uint) pixmaps.at(i).state;
415 }
416 return true;
417}
418
419void QPixmapIconEngine::virtual_hook(int id, void *data)
420{
421 switch (id) {
422 case QIconEngineV2::AvailableSizesHook: {
423 QIconEngineV2::AvailableSizesArgument &arg =
424 *reinterpret_cast<QIconEngineV2::AvailableSizesArgument*>(data);
425 arg.sizes.clear();
426 for (int i = 0; i < pixmaps.size(); ++i) {
427 QPixmapIconEngineEntry &pe = pixmaps[i];
428 if (pe.size == QSize() && pe.pixmap.isNull()) {
429 pe.pixmap = QPixmap(pe.fileName);
430 pe.size = pe.pixmap.size();
431 }
432 if (pe.mode == arg.mode && pe.state == arg.state && !pe.size.isEmpty())
433 arg.sizes.push_back(pe.size);
434 }
435 break;
436 }
437 default:
438 QIconEngineV2::virtual_hook(id, data);
439 }
440}
441
442#ifndef QT_NO_LIBRARY
443Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader,
444 (QIconEngineFactoryInterface_iid, QLatin1String("/iconengines"), Qt::CaseInsensitive))
445Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loaderV2,
446 (QIconEngineFactoryInterfaceV2_iid, QLatin1String("/iconengines"), Qt::CaseInsensitive))
447#endif
448
449
450
451/*!
452 \class QIcon
453
454 \brief The QIcon class provides scalable icons in different modes
455 and states.
456
457 \ingroup painting
458 \ingroup shared
459
460
461 A QIcon can generate smaller, larger, active, and disabled pixmaps
462 from the set of pixmaps it is given. Such pixmaps are used by Qt
463 widgets to show an icon representing a particular action.
464
465 The simplest use of QIcon is to create one from a QPixmap file or
466 resource, and then use it, allowing Qt to work out all the required
467 icon styles and sizes. For example:
468
469 \snippet doc/src/snippets/code/src_gui_image_qicon.cpp 0
470
471 To undo a QIcon, simply set a null icon in its place:
472
473 \snippet doc/src/snippets/code/src_gui_image_qicon.cpp 1
474
475 Use the QImageReader::supportedImageFormats() and
476 QImageWriter::supportedImageFormats() functions to retrieve a
477 complete list of the supported file formats.
478
479 When you retrieve a pixmap using pixmap(QSize, Mode, State), and no
480 pixmap for this given size, mode and state has been added with
481 addFile() or addPixmap(), then QIcon will generate one on the
482 fly. This pixmap generation happens in a QIconEngineV2. The default
483 engine scales pixmaps down if required, but never up, and it uses
484 the current style to calculate a disabled appearance. By using
485 custom icon engines, you can customize every aspect of generated
486 icons. With QIconEnginePluginV2 it is possible to register different
487 icon engines for different file suffixes, making it possible for
488 third parties to provide additional icon engines to those included
489 with Qt.
490
491 \note Since Qt 4.2, an icon engine that supports SVG is included.
492
493 \section1 Making Classes that Use QIcon
494
495 If you write your own widgets that have an option to set a small
496 pixmap, consider allowing a QIcon to be set for that pixmap. The
497 Qt class QToolButton is an example of such a widget.
498
499 Provide a method to set a QIcon, and when you draw the icon, choose
500 whichever pixmap is appropriate for the current state of your widget.
501 For example:
502 \snippet doc/src/snippets/code/src_gui_image_qicon.cpp 2
503
504 You might also make use of the \c Active mode, perhaps making your
505 widget \c Active when the mouse is over the widget (see \l
506 QWidget::enterEvent()), while the mouse is pressed pending the
507 release that will activate the function, or when it is the currently
508 selected item. If the widget can be toggled, the "On" mode might be
509 used to draw a different icon.
510
511 \img icon.png QIcon
512
513 \sa {fowler}{GUI Design Handbook: Iconic Label}, {Icons Example}
514*/
515
516
517/*!
518 Constructs a null icon.
519*/
520QIcon::QIcon()
521 : d(0)
522{
523}
524
525/*!
526 Constructs an icon from a \a pixmap.
527 */
528QIcon::QIcon(const QPixmap &pixmap)
529 :d(0)
530{
531 addPixmap(pixmap);
532}
533
534/*!
535 Constructs a copy of \a other. This is very fast.
536*/
537QIcon::QIcon(const QIcon &other)
538 :d(other.d)
539{
540 if (d)
541 d->ref.ref();
542}
543
544/*!
545 Constructs an icon from the file with the given \a fileName. The
546 file will be loaded on demand.
547
548 If \a fileName contains a relative path (e.g. the filename only)
549 the relevant file must be found relative to the runtime working
550 directory.
551
552 The file name can be either refer to an actual file on disk or to
553 one of the application's embedded resources. See the
554 \l{resources.html}{Resource System} overview for details on how to
555 embed images and other resource files in the application's
556 executable.
557
558 Use the QImageReader::supportedImageFormats() and
559 QImageWriter::supportedImageFormats() functions to retrieve a
560 complete list of the supported file formats.
561*/
562QIcon::QIcon(const QString &fileName)
563 : d(0)
564{
565 addFile(fileName);
566}
567
568
569/*!
570 Creates an icon with a specific icon \a engine. The icon takes
571 ownership of the engine.
572*/
573QIcon::QIcon(QIconEngine *engine)
574 :d(new QIconPrivate)
575{
576 d->engine_version = 1;
577 d->engine = engine;
578 d->v1RefCount = new QAtomicInt(1);
579}
580
581/*!
582 Creates an icon with a specific icon \a engine. The icon takes
583 ownership of the engine.
584*/
585QIcon::QIcon(QIconEngineV2 *engine)
586 :d(new QIconPrivate)
587{
588 d->engine_version = 2;
589 d->engine = engine;
590}
591
592/*!
593 Destroys the icon.
594*/
595QIcon::~QIcon()
596{
597 if (d && !d->ref.deref())
598 delete d;
599}
600
601/*!
602 Assigns the \a other icon to this icon and returns a reference to
603 this icon.
604*/
605QIcon &QIcon::operator=(const QIcon &other)
606{
607 if (other.d)
608 other.d->ref.ref();
609 if (d && !d->ref.deref())
610 delete d;
611 d = other.d;
612 return *this;
613}
614
615/*!
616 Returns the icon as a QVariant.
617*/
618QIcon::operator QVariant() const
619{
620 return QVariant(QVariant::Icon, this);
621}
622
623/*! \obsolete
624
625 Returns a number that identifies the contents of this
626 QIcon object. Distinct QIcon objects can have
627 the same serial number if they refer to the same contents
628 (but they don't have to). Also, the serial number of
629 a QIcon object may change during its lifetime.
630
631 Use cacheKey() instead.
632
633 A null icon always has a serial number of 0.
634
635 Serial numbers are mostly useful in conjunction with caching.
636
637 \sa QPixmap::serialNumber()
638*/
639
640int QIcon::serialNumber() const
641{
642 return d ? d->serialNum : 0;
643}
644
645/*!
646 Returns a number that identifies the contents of this QIcon
647 object. Distinct QIcon objects can have the same key if
648 they refer to the same contents.
649 \since 4.3
650
651 The cacheKey() will change when the icon is altered via
652 addPixmap() or addFile().
653
654 Cache keys are mostly useful in conjunction with caching.
655
656 \sa QPixmap::cacheKey()
657*/
658qint64 QIcon::cacheKey() const
659{
660 if (!d)
661 return 0;
662 return (((qint64) d->serialNum) << 32) | ((qint64) (d->detach_no));
663}
664
665/*!
666 Returns a pixmap with the requested \a size, \a mode, and \a
667 state, generating one if necessary. The pixmap might be smaller than
668 requested, but never larger.
669
670 \sa actualSize(), paint()
671*/
672QPixmap QIcon::pixmap(const QSize &size, Mode mode, State state) const
673{
674 if (!d)