1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29
30#include <QtTest/QtTest>
31#include <QImageReader>
32#include <qicon.h>
33#include <qiconengine.h>
34#include <QtCore/QStandardPaths>
35
36#include <algorithm>
37
38
39class tst_QIcon : public QObject
40{
41 Q_OBJECT
42public:
43 tst_QIcon();
44
45private slots:
46 void initTestCase();
47 void actualSize_data(); // test with 1 pixmap
48 void actualSize();
49 void actualSize2_data(); // test with 2 pixmaps with different aspect ratio
50 void actualSize2();
51 void isNull();
52 void isMask();
53 void swap();
54 void bestMatch();
55 void cacheKey();
56 void detach();
57 void addFile();
58 void availableSizes();
59 void name();
60 void streamAvailableSizes_data();
61 void streamAvailableSizes();
62 void fromTheme();
63 void fromThemeCache();
64
65#ifndef QT_NO_WIDGETS
66 void task184901_badCache();
67#endif
68 void task223279_inconsistentAddFile();
69
70private:
71 bool haveImageFormat(QByteArray const&);
72
73 const QString m_pngImageFileName;
74 const QString m_pngRectFileName;
75 const QString m_sourceFileName;
76};
77
78bool tst_QIcon::haveImageFormat(QByteArray const& desiredFormat)
79{
80 return QImageReader::supportedImageFormats().contains(t: desiredFormat);
81}
82
83tst_QIcon::tst_QIcon()
84 : m_pngImageFileName(QFINDTESTDATA("image.png"))
85 , m_pngRectFileName(QFINDTESTDATA("rect.png"))
86 , m_sourceFileName(":/tst_qicon.cpp")
87{
88}
89
90void tst_QIcon::initTestCase()
91{
92 QVERIFY(!m_pngImageFileName.isEmpty());
93 QVERIFY(!m_pngRectFileName.isEmpty());
94 QVERIFY(!m_sourceFileName.isEmpty());
95}
96
97void tst_QIcon::actualSize_data()
98{
99 QTest::addColumn<QString>(name: "source");
100 QTest::addColumn<QSize>(name: "argument");
101 QTest::addColumn<QSize>(name: "result");
102
103 // square image
104 QTest::newRow(dataTag: "resource0") << ":/image.png" << QSize(128, 128) << QSize(128, 128);
105 QTest::newRow(dataTag: "resource1") << ":/image.png" << QSize( 64, 64) << QSize( 64, 64);
106 QTest::newRow(dataTag: "resource2") << ":/image.png" << QSize( 32, 64) << QSize( 32, 32);
107 QTest::newRow(dataTag: "resource3") << ":/image.png" << QSize( 16, 64) << QSize( 16, 16);
108 QTest::newRow(dataTag: "resource4") << ":/image.png" << QSize( 16, 128) << QSize( 16, 16);
109 QTest::newRow(dataTag: "resource5") << ":/image.png" << QSize( 128, 16) << QSize( 16, 16);
110 QTest::newRow(dataTag: "resource6") << ":/image.png" << QSize( 150, 150) << QSize( 128, 128);
111 // rect image
112 QTest::newRow(dataTag: "resource7") << ":/rect.png" << QSize( 20, 40) << QSize( 20, 40);
113 QTest::newRow(dataTag: "resource8") << ":/rect.png" << QSize( 10, 20) << QSize( 10, 20);
114 QTest::newRow(dataTag: "resource9") << ":/rect.png" << QSize( 15, 50) << QSize( 15, 30);
115 QTest::newRow(dataTag: "resource10") << ":/rect.png" << QSize( 25, 50) << QSize( 20, 40);
116
117 QTest::newRow(dataTag: "external0") << m_pngImageFileName << QSize(128, 128) << QSize(128, 128);
118 QTest::newRow(dataTag: "external1") << m_pngImageFileName << QSize( 64, 64) << QSize( 64, 64);
119 QTest::newRow(dataTag: "external2") << m_pngImageFileName << QSize( 32, 64) << QSize( 32, 32);
120 QTest::newRow(dataTag: "external3") << m_pngImageFileName << QSize( 16, 64) << QSize( 16, 16);
121 QTest::newRow(dataTag: "external4") << m_pngImageFileName << QSize( 16, 128) << QSize( 16, 16);
122 QTest::newRow(dataTag: "external5") << m_pngImageFileName << QSize(128, 16) << QSize( 16, 16);
123 QTest::newRow(dataTag: "external6") << m_pngImageFileName << QSize(150, 150) << QSize(128, 128);
124 // rect image
125 QTest::newRow(dataTag: "external7") << ":/rect.png" << QSize( 20, 40) << QSize( 20, 40);
126 QTest::newRow(dataTag: "external8") << ":/rect.png" << QSize( 10, 20) << QSize( 10, 20);
127 QTest::newRow(dataTag: "external9") << ":/rect.png" << QSize( 15, 50) << QSize( 15, 30);
128 QTest::newRow(dataTag: "external10") << ":/rect.png" << QSize( 25, 50) << QSize( 20, 40);
129}
130
131void tst_QIcon::actualSize()
132{
133 QFETCH(QString, source);
134 QFETCH(QSize, argument);
135 QFETCH(QSize, result);
136
137 {
138 QPixmap pixmap(source);
139 QIcon icon(pixmap);
140 QCOMPARE(icon.actualSize(argument), result);
141 QCOMPARE(icon.pixmap(argument).size(), result);
142 }
143
144 {
145 QIcon icon(source);
146 QCOMPARE(icon.actualSize(argument), result);
147 QCOMPARE(icon.pixmap(argument).size(), result);
148 }
149}
150
151void tst_QIcon::actualSize2_data()
152{
153 QTest::addColumn<QSize>(name: "argument");
154 QTest::addColumn<QSize>(name: "result");
155
156 // two images - 128x128 and 20x40. Let the games begin
157 QTest::newRow(dataTag: "trivial1") << QSize( 128, 128) << QSize( 128, 128);
158 QTest::newRow(dataTag: "trivial2") << QSize( 20, 40) << QSize( 20, 40);
159
160 // QIcon chooses the one with the smallest area to choose the pixmap
161 QTest::newRow(dataTag: "best1") << QSize( 100, 100) << QSize( 100, 100);
162 QTest::newRow(dataTag: "best2") << QSize( 20, 20) << QSize( 10, 20);
163 QTest::newRow(dataTag: "best3") << QSize( 15, 30) << QSize( 15, 30);
164 QTest::newRow(dataTag: "best4") << QSize( 5, 5) << QSize( 2, 5);
165 QTest::newRow(dataTag: "best5") << QSize( 10, 15) << QSize( 7, 15);
166}
167
168void tst_QIcon::actualSize2()
169{
170 QIcon icon;
171 icon.addPixmap(pixmap: m_pngImageFileName);
172 icon.addPixmap(pixmap: m_pngRectFileName);
173
174 QFETCH(QSize, argument);
175 QFETCH(QSize, result);
176
177 QCOMPARE(icon.actualSize(argument), result);
178 QCOMPARE(icon.pixmap(argument).size(), result);
179}
180
181void tst_QIcon::isNull() {
182 // test default constructor
183 QIcon defaultConstructor;
184 QVERIFY(defaultConstructor.isNull());
185
186 // test copy constructor
187 QVERIFY(QIcon(defaultConstructor).isNull());
188
189 // test pixmap constructor
190 QPixmap nullPixmap;
191 QVERIFY(QIcon(nullPixmap).isNull());
192
193 // test string constructor with empty string
194 QIcon iconEmptyString = QIcon(QString());
195 QVERIFY(iconEmptyString.isNull());
196 QVERIFY(!iconEmptyString.actualSize(QSize(32, 32)).isValid());;
197
198 // test string constructor with non-existing file
199 QIcon iconNoFile = QIcon("imagedoesnotexist");
200 QVERIFY(!iconNoFile.isNull());
201 QVERIFY(!iconNoFile.actualSize(QSize(32, 32)).isValid());
202
203 // test string constructor with non-existing file with suffix
204 QIcon iconNoFileSuffix = QIcon("imagedoesnotexist.png");
205 QVERIFY(!iconNoFileSuffix.isNull());
206 QVERIFY(!iconNoFileSuffix.actualSize(QSize(32, 32)).isValid());
207
208 // test string constructor with existing file but unsupported format
209 QIcon iconUnsupportedFormat = QIcon(m_sourceFileName);
210 QVERIFY(!iconUnsupportedFormat.isNull());
211 QVERIFY(!iconUnsupportedFormat.actualSize(QSize(32, 32)).isValid());
212
213 // test string constructor with existing file and supported format
214 QIcon iconSupportedFormat = QIcon(m_pngImageFileName);
215 QVERIFY(!iconSupportedFormat.isNull());
216 QVERIFY(iconSupportedFormat.actualSize(QSize(32, 32)).isValid());
217}
218
219void tst_QIcon::isMask()
220{
221 QIcon icon;
222 icon.setIsMask(true);
223 icon.addPixmap(pixmap: QPixmap());
224 QVERIFY(icon.isMask());
225
226 QIcon icon2;
227 icon2.setIsMask(true);
228 QVERIFY(icon2.isMask());
229 icon2.setIsMask(false);
230 QVERIFY(!icon2.isMask());
231}
232
233void tst_QIcon::swap()
234{
235 QPixmap p1(1, 1), p2(2, 2);
236 p1.fill(fillColor: Qt::black);
237 p2.fill(fillColor: Qt::black);
238
239 QIcon i1(p1), i2(p2);
240 const qint64 i1k = i1.cacheKey();
241 const qint64 i2k = i2.cacheKey();
242 QVERIFY(i1k != i2k);
243 i1.swap(other&: i2);
244 QCOMPARE(i1.cacheKey(), i2k);
245 QCOMPARE(i2.cacheKey(), i1k);
246}
247
248void tst_QIcon::bestMatch()
249{
250 QPixmap p1(1, 1);
251 QPixmap p2(2, 2);
252 QPixmap p3(3, 3);
253 QPixmap p4(4, 4);
254 QPixmap p5(5, 5);
255 QPixmap p6(6, 6);
256 QPixmap p7(7, 7);
257 QPixmap p8(8, 8);
258
259 p1.fill(fillColor: Qt::black);
260 p2.fill(fillColor: Qt::black);
261 p3.fill(fillColor: Qt::black);
262 p4.fill(fillColor: Qt::black);
263 p5.fill(fillColor: Qt::black);
264 p6.fill(fillColor: Qt::black);
265 p7.fill(fillColor: Qt::black);
266 p8.fill(fillColor: Qt::black);
267
268 for (int i = 0; i < 4; ++i) {
269 for (int j = 0; j < 2; ++j) {
270 QIcon::State state = (j == 0) ? QIcon::On : QIcon::Off;
271 QIcon::State oppositeState = (state == QIcon::On) ? QIcon::Off
272 : QIcon::On;
273 QIcon::Mode mode;
274 QIcon::Mode oppositeMode;
275
276 QIcon icon;
277
278 switch (i) {
279 case 0:
280 default:
281 mode = QIcon::Normal;
282 oppositeMode = QIcon::Active;
283 break;
284 case 1:
285 mode = QIcon::Active;
286 oppositeMode = QIcon::Normal;
287 break;
288 case 2:
289 mode = QIcon::Disabled;
290 oppositeMode = QIcon::Selected;
291 break;
292 case 3:
293 mode = QIcon::Selected;
294 oppositeMode = QIcon::Disabled;
295 }
296
297 /*
298 The test mirrors the code in
299 QPixmapIconEngine::bestMatch(), to make sure that
300 nobody breaks QPixmapIconEngine by mistake. Before
301 you change this test or the code that it tests,
302 please talk to the maintainer if possible.
303 */
304 if (mode == QIcon::Disabled || mode == QIcon::Selected) {
305 icon.addPixmap(pixmap: p1, mode: oppositeMode, state: oppositeState);
306 QVERIFY(icon.pixmap(100, mode, state).size() == p1.size());
307
308 icon.addPixmap(pixmap: p2, mode: oppositeMode, state);
309 QVERIFY(icon.pixmap(100, mode, state).size() == p2.size());
310
311 icon.addPixmap(pixmap: p3, mode: QIcon::Active, state: oppositeState);
312 QVERIFY(icon.pixmap(100, mode, state).size() == p3.size());
313
314 icon.addPixmap(pixmap: p4, mode: QIcon::Normal, state: oppositeState);
315 QVERIFY(icon.pixmap(100, mode, state).size() == p4.size());
316
317 icon.addPixmap(pixmap: p5, mode, state: oppositeState);
318 QVERIFY(icon.pixmap(100, mode, state).size() == p5.size());
319
320 icon.addPixmap(pixmap: p6, mode: QIcon::Active, state);
321 QVERIFY(icon.pixmap(100, mode, state).size() == p6.size());
322
323 icon.addPixmap(pixmap: p7, mode: QIcon::Normal, state);
324 QVERIFY(icon.pixmap(100, mode, state).size() == p7.size());
325
326 icon.addPixmap(pixmap: p8, mode, state);
327 QVERIFY(icon.pixmap(100, mode, state).size() == p8.size());
328 } else {
329 icon.addPixmap(pixmap: p1, mode: QIcon::Selected, state: oppositeState);
330 QVERIFY(icon.pixmap(100, mode, state).size() == p1.size());
331
332 icon.addPixmap(pixmap: p2, mode: QIcon::Disabled, state: oppositeState);
333 QVERIFY(icon.pixmap(100, mode, state).size() == p2.size());
334
335 icon.addPixmap(pixmap: p3, mode: QIcon::Selected, state);
336 QVERIFY(icon.pixmap(100, mode, state).size() == p3.size());
337
338 icon.addPixmap(pixmap: p4, mode: QIcon::Disabled, state);
339 QVERIFY(icon.pixmap(100, mode, state).size() == p4.size());
340
341 icon.addPixmap(pixmap: p5, mode: oppositeMode, state: oppositeState);
342 QVERIFY(icon.pixmap(100, mode, state).size() == p5.size());
343
344 icon.addPixmap(pixmap: p6, mode, state: oppositeState);
345 QVERIFY(icon.pixmap(100, mode, state).size() == p6.size());
346
347 icon.addPixmap(pixmap: p7, mode: oppositeMode, state);
348 QVERIFY(icon.pixmap(100, mode, state).size() == p7.size());
349
350 icon.addPixmap(pixmap: p8, mode, state);
351 QVERIFY(icon.pixmap(100, mode, state).size() == p8.size());
352 }
353 }
354 }
355}
356
357void tst_QIcon::cacheKey()
358{
359 QIcon icon1(m_pngImageFileName);
360 qint64 icon1_key = icon1.cacheKey();
361 QIcon icon2 = icon1;
362
363 QCOMPARE(icon2.cacheKey(), icon1.cacheKey());
364 icon2.detach();
365 QVERIFY(icon2.cacheKey() != icon1.cacheKey());
366 QCOMPARE(icon1.cacheKey(), icon1_key);
367}
368
369void tst_QIcon::detach()
370{
371 QImage img(32, 32, QImage::Format_ARGB32_Premultiplied);
372 img.fill(pixel: 0xffff0000);
373 QIcon icon1(QPixmap::fromImage(image: img));
374 QIcon icon2 = icon1;
375 icon2.addFile(fileName: m_pngImageFileName, size: QSize(64, 64));
376
377 QImage img1 = icon1.pixmap(w: 64, h: 64).toImage();
378 QImage img2 = icon2.pixmap(w: 64, h: 64).toImage();
379 QVERIFY(img1 != img2);
380
381 img1 = icon1.pixmap(w: 32, h: 32).toImage();
382 img2 = icon2.pixmap(w: 32, h: 32).toImage();
383 QCOMPARE(img1, img2);
384}
385
386void tst_QIcon::addFile()
387{
388 QIcon icon;
389 icon.addFile(fileName: QLatin1String(":/styles/commonstyle/images/standardbutton-open-16.png"));
390 icon.addFile(fileName: QLatin1String(":/styles/commonstyle/images/standardbutton-open-32.png"));
391 icon.addFile(fileName: QLatin1String(":/styles/commonstyle/images/standardbutton-open-128.png"));
392 icon.addFile(fileName: QLatin1String(":/styles/commonstyle/images/standardbutton-save-16.png"), size: QSize(), mode: QIcon::Selected);
393 icon.addFile(fileName: QLatin1String(":/styles/commonstyle/images/standardbutton-save-32.png"), size: QSize(), mode: QIcon::Selected);
394 icon.addFile(fileName: QLatin1String(":/styles/commonstyle/images/standardbutton-save-128.png"), size: QSize(), mode: QIcon::Selected);
395
396 QVERIFY(icon.pixmap(16, QIcon::Normal).toImage() ==
397 QPixmap(QLatin1String(":/styles/commonstyle/images/standardbutton-open-16.png")).toImage());
398 QVERIFY(icon.pixmap(32, QIcon::Normal).toImage() ==
399 QPixmap(QLatin1String(":/styles/commonstyle/images/standardbutton-open-32.png")).toImage());
400 QVERIFY(icon.pixmap(128, QIcon::Normal).toImage() ==
401 QPixmap(QLatin1String(":/styles/commonstyle/images/standardbutton-open-128.png")).toImage());
402 QVERIFY(icon.pixmap(16, QIcon::Selected).toImage() ==
403 QPixmap(QLatin1String(":/styles/commonstyle/images/standardbutton-save-16.png")).toImage());
404 QVERIFY(icon.pixmap(32, QIcon::Selected).toImage() ==
405 QPixmap(QLatin1String(":/styles/commonstyle/images/standardbutton-save-32.png")).toImage());
406 QVERIFY(icon.pixmap(128, QIcon::Selected).toImage() ==
407 QPixmap(QLatin1String(":/styles/commonstyle/images/standardbutton-save-128.png")).toImage());
408}
409
410static bool sizeLess(const QSize &a, const QSize &b)
411{
412 return a.width() < b.width();
413}
414
415void tst_QIcon::availableSizes()
416{
417 {
418 QIcon icon;
419 icon.addFile(fileName: m_pngImageFileName, size: QSize(32,32));
420 icon.addFile(fileName: m_pngImageFileName, size: QSize(64,64));
421 icon.addFile(fileName: m_pngImageFileName, size: QSize(128,128));
422 icon.addFile(fileName: m_pngImageFileName, size: QSize(256,256), mode: QIcon::Disabled);
423 icon.addFile(fileName: m_pngImageFileName, size: QSize(16,16), mode: QIcon::Normal, state: QIcon::On);
424
425 QList<QSize> availableSizes = icon.availableSizes();
426 QCOMPARE(availableSizes.size(), 3);
427 std::sort(first: availableSizes.begin(), last: availableSizes.end(), comp: sizeLess);
428 QCOMPARE(availableSizes.at(0), QSize(32,32));
429 QCOMPARE(availableSizes.at(1), QSize(64,64));
430 QCOMPARE(availableSizes.at(2), QSize(128,128));
431
432 availableSizes = icon.availableSizes(mode: QIcon::Disabled);
433 QCOMPARE(availableSizes.size(), 1);
434 QCOMPARE(availableSizes.at(0), QSize(256,256));
435
436 availableSizes = icon.availableSizes(mode: QIcon::Normal, state: QIcon::On);
437 QCOMPARE(availableSizes.size(), 1);
438 QCOMPARE(availableSizes.at(0), QSize(16,16));
439 }
440
441 {
442 // we try to load an icon from resources
443 QIcon icon(QLatin1String(":/styles/commonstyle/images/standardbutton-open-16.png"));
444 QList<QSize> availableSizes = icon.availableSizes();
445 QCOMPARE(availableSizes.size(), 1);
446 QCOMPARE(availableSizes.at(0), QSize(16, 16));
447 }
448
449 {
450 // load an icon from binary data.
451 QPixmap pix;
452 QFile file(QLatin1String(":/styles/commonstyle/images/standardbutton-open-16.png"));
453 QVERIFY(file.open(QIODevice::ReadOnly));
454 uchar *data = file.map(offset: 0, size: file.size());
455 QVERIFY(data != 0);
456 pix.loadFromData(buf: data, len: file.size());
457 QIcon icon(pix);
458
459 QList<QSize> availableSizes = icon.availableSizes();
460 QCOMPARE(availableSizes.size(), 1);
461 QCOMPARE(availableSizes.at(0), QSize(16,16));
462 }
463
464 {
465 // there shouldn't be available sizes for invalid images!
466 QVERIFY(QIcon(QLatin1String("")).availableSizes().isEmpty());
467 QVERIFY(QIcon(QLatin1String("non-existing.png")).availableSizes().isEmpty());
468 }
469}
470
471void tst_QIcon::name()
472{
473 {
474 // No name if icon does not come from a theme
475 QIcon icon(":/image.png");
476 QString name = icon.name();
477 QVERIFY(name.isEmpty());
478 }
479
480 {
481 // Getting the name of an icon coming from a theme should work
482 QString searchPath = QLatin1String(":/icons");
483 QIcon::setThemeSearchPaths(QStringList() << searchPath);
484 QString themeName("testtheme");
485 QIcon::setThemeName(themeName);
486
487 QIcon icon = QIcon::fromTheme(name: "appointment-new");
488 QString name = icon.name();
489 QCOMPARE(name, QLatin1String("appointment-new"));
490 }
491}
492
493void tst_QIcon::streamAvailableSizes_data()
494{
495 QTest::addColumn<QIcon>(name: "icon");
496
497 QIcon icon;
498 icon.addFile(fileName: ":/image.png", size: QSize(32,32));
499 QTest::newRow( dataTag: "32x32" ) << icon;
500 icon.addFile(fileName: ":/image.png", size: QSize(64,64));
501 QTest::newRow( dataTag: "64x64" ) << icon;
502 icon.addFile(fileName: ":/image.png", size: QSize(128,128));
503 QTest::newRow( dataTag: "128x128" ) << icon;
504 icon.addFile(fileName: ":/image.png", size: QSize(256,256));
505 QTest::newRow( dataTag: "256x256" ) << icon;
506}
507
508void tst_QIcon::streamAvailableSizes()
509{
510 QFETCH(QIcon, icon);
511
512 QByteArray ba;
513 // write to QByteArray
514 {
515 QBuffer buffer(&ba);
516 buffer.open(openMode: QIODevice::WriteOnly);
517 QDataStream stream(&buffer);
518 stream << icon;
519 }
520
521 // read from QByteArray
522 {
523 QBuffer buffer(&ba);
524 buffer.open(openMode: QIODevice::ReadOnly);
525 QDataStream stream(&buffer);
526 QIcon i;
527 stream >> i;
528 QCOMPARE(i.isNull(), icon.isNull());
529 QCOMPARE(i.availableSizes(), icon.availableSizes());
530 }
531}
532
533#ifndef QT_NO_WIDGETS
534void tst_QIcon::task184901_badCache()
535{
536 QPixmap pm(m_pngImageFileName);
537 QIcon icon(pm);
538
539 //the disabled icon must have an effect (grayed)
540 QVERIFY(icon.pixmap(32, QIcon::Normal).toImage() != icon.pixmap(32, QIcon::Disabled).toImage());
541
542 icon.addPixmap(pixmap: pm, mode: QIcon::Disabled);
543 //the disabled icon must now be the same as the normal one.
544 QVERIFY( icon.pixmap(32, QIcon::Normal).toImage() == icon.pixmap(32, QIcon::Disabled).toImage() );
545}
546#endif
547
548void tst_QIcon::fromTheme()
549{
550 QString firstSearchPath = QLatin1String(":/icons");
551 QString secondSearchPath = QLatin1String(":/second_icons");
552 QIcon::setThemeSearchPaths(QStringList() << firstSearchPath << secondSearchPath);
553 QCOMPARE(QIcon::themeSearchPaths().size(), 2);
554 QCOMPARE(firstSearchPath, QIcon::themeSearchPaths()[0]);
555 QCOMPARE(secondSearchPath, QIcon::themeSearchPaths()[1]);
556
557 QString fallbackSearchPath = QStringLiteral(":/fallback_icons");
558 QIcon::setFallbackSearchPaths(QStringList() << fallbackSearchPath);
559 QCOMPARE(QIcon::fallbackSearchPaths().size(), 1);
560 QCOMPARE(fallbackSearchPath, QIcon::fallbackSearchPaths().at(0));
561
562 QString themeName("testtheme");
563 QIcon::setThemeName(themeName);
564 QCOMPARE(QIcon::themeName(), themeName);
565
566 // Test normal icon
567 QIcon appointmentIcon = QIcon::fromTheme(name: "appointment-new");
568 QVERIFY(!appointmentIcon.isNull());
569 QVERIFY(!appointmentIcon.availableSizes(QIcon::Normal, QIcon::Off).isEmpty());
570 QVERIFY(appointmentIcon.availableSizes().contains(QSize(16, 16)));
571 QVERIFY(appointmentIcon.availableSizes().contains(QSize(32, 32)));
572 QVERIFY(appointmentIcon.availableSizes().contains(QSize(22, 22)));
573
574 // Test fallback to less specific icon
575 QIcon specificAppointmentIcon = QIcon::fromTheme(name: "appointment-new-specific");
576 QVERIFY(!QIcon::hasThemeIcon("appointment-new-specific"));
577 QVERIFY(QIcon::hasThemeIcon("appointment-new"));
578 QCOMPARE(specificAppointmentIcon.name(), QString::fromLatin1("appointment-new"));
579 QCOMPARE(specificAppointmentIcon.availableSizes(), appointmentIcon.availableSizes());
580 QCOMPARE(specificAppointmentIcon.pixmap(32).cacheKey(), appointmentIcon.pixmap(32).cacheKey());
581
582 // Test icon from parent theme
583 QIcon abIcon = QIcon::fromTheme(name: "address-book-new");
584 QVERIFY(!abIcon.isNull());
585 QVERIFY(QIcon::hasThemeIcon("address-book-new"));
586 QVERIFY(!abIcon.availableSizes().isEmpty());
587
588 // Test icon from fallback path
589 QIcon fallbackIcon = QIcon::fromTheme(name: "red");
590 QVERIFY(!fallbackIcon.isNull());
591 QVERIFY(QIcon::hasThemeIcon("red"));
592 QCOMPARE(fallbackIcon.availableSizes().size(), 1);
593
594 // Test non existing icon
595 QIcon noIcon = QIcon::fromTheme(name: "broken-icon");
596 QVERIFY(noIcon.isNull());
597 QVERIFY(!QIcon::hasThemeIcon("broken-icon"));
598 QCOMPARE(noIcon.actualSize(QSize(32, 32), QIcon::Normal, QIcon::On), QSize(0, 0));
599
600 // Test non existing icon with fallback
601 noIcon = QIcon::fromTheme(name: "broken-icon", fallback: abIcon);
602 QCOMPARE(noIcon.cacheKey(), abIcon.cacheKey());
603
604 // Test svg-only icon
605 noIcon = QIcon::fromTheme(name: "svg-icon", fallback: abIcon);
606 QVERIFY(!noIcon.availableSizes().isEmpty());
607
608 // Pixmaps should be no larger than the requested size (QTBUG-17953)
609 QCOMPARE(appointmentIcon.pixmap(22).size(), QSize(22, 22)); // exact
610 QCOMPARE(appointmentIcon.pixmap(32).size(), QSize(32, 32)); // exact
611 QCOMPARE(appointmentIcon.pixmap(48).size(), QSize(32, 32)); // smaller
612 QCOMPARE(appointmentIcon.pixmap(16).size(), QSize(16, 16)); // scaled down
613 QCOMPARE(appointmentIcon.pixmap(8).size(), QSize(8, 8)); // scaled down
614 QCOMPARE(appointmentIcon.pixmap(16).size(), QSize(16, 16)); // scaled down
615
616 QByteArray ba;
617 // write to QByteArray
618 {
619 QBuffer buffer(&ba);
620 buffer.open(openMode: QIODevice::WriteOnly);
621 QDataStream stream(&buffer);
622 stream << abIcon;
623 }
624
625 // read from QByteArray
626 {
627 QBuffer buffer(&ba);
628 buffer.open(openMode: QIODevice::ReadOnly);
629 QDataStream stream(&buffer);
630 QIcon i;
631 stream >> i;
632 QCOMPARE(i.isNull(), abIcon.isNull());
633 QCOMPARE(i.availableSizes(), abIcon.availableSizes());
634 }
635
636 // Make sure setting the theme name clears the state
637 QIcon::setThemeName("");
638 abIcon = QIcon::fromTheme(name: "address-book-new");
639 QVERIFY(abIcon.isNull());
640
641 // Passing a full path to fromTheme is not very useful, but should work anyway
642 QIcon fullPathIcon = QIcon::fromTheme(name: m_pngImageFileName);
643 QVERIFY(!fullPathIcon.isNull());
644}
645
646static inline QString findGtkUpdateIconCache()
647{
648 QString binary = QLatin1String("gtk-update-icon-cache");
649#ifdef Q_OS_WIN
650 binary += QLatin1String(".exe");
651#endif
652 return QStandardPaths::findExecutable(executableName: binary);
653}
654
655void tst_QIcon::fromThemeCache()
656{
657 QTemporaryDir dir;
658 QVERIFY2(dir.isValid(), qPrintable(dir.errorString()));
659
660 QVERIFY(QDir().mkpath(dir.path() + QLatin1String("/testcache/16x16/actions")));
661 QVERIFY(QFile(QStringLiteral(":/styles/commonstyle/images/standardbutton-open-16.png"))
662 .copy( dir.path() + QLatin1String("/testcache/16x16/actions/button-open.png")));
663
664 {
665 QFile index(dir.path() + QLatin1String("/testcache/index.theme"));
666 QVERIFY(index.open(QFile::WriteOnly));
667 index.write(data: "[Icon Theme]\nDirectories=16x16/actions\n[16x16/actions]\nSize=16\nContext=Actions\nType=Fixed\n");
668 }
669 QIcon::setThemeSearchPaths(QStringList() << dir.path());
670 QIcon::setThemeName("testcache");
671
672 // We just created a theme with that icon, it must exist
673 QVERIFY(!QIcon::fromTheme("button-open").isNull());
674
675 QString cacheName = dir.path() + QLatin1String("/testcache/icon-theme.cache");
676
677 // An invalid cache should not prevent lookup
678 {
679 QFile cacheFile(cacheName);
680 QVERIFY(cacheFile.open(QFile::WriteOnly));
681 QDataStream(&cacheFile) << quint16(1) << quint16(0) << "invalid corrupted stuff in there\n";
682 }
683 QIcon::setThemeSearchPaths(QStringList() << dir.path()); // reload themes
684 QVERIFY(!QIcon::fromTheme("button-open").isNull());
685
686 // An empty cache should prevent the lookup
687 {
688 QFile cacheFile(cacheName);
689 QVERIFY(cacheFile.open(QFile::WriteOnly));
690 QDataStream ds(&cacheFile);
691 ds << quint16(1) << quint16(0); // 0: version
692 ds << quint32(12) << quint32(20); // 4: hash offset / dir list offset
693 ds << quint32(1) << quint32(0xffffffff); // 12: one empty bucket
694 ds << quint32(1) << quint32(28); // 20: list with one element
695 ds.writeRawData("16x16/actions", len: sizeof("16x16/actions")); // 28
696 }
697 QIcon::setThemeSearchPaths(QStringList() << dir.path()); // reload themes
698 QVERIFY(QIcon::fromTheme("button-open").isNull()); // The icon was not in the cache, it should not be found
699
700 // Adding an icon should be changing the modification date of one sub directory which should make the cache ignored
701 QTest::qWait(ms: 1000); // wait enough to have a different modification time in seconds
702 QVERIFY(QFile(QStringLiteral(":/styles/commonstyle/images/standardbutton-save-16.png"))
703 .copy(dir.path() + QLatin1String("/testcache/16x16/actions/button-save.png")));
704 QVERIFY(QFileInfo(cacheName).lastModified() < QFileInfo(dir.path() + QLatin1String("/testcache/16x16/actions")).lastModified());
705 QIcon::setThemeSearchPaths(QStringList() << dir.path()); // reload themes
706 QVERIFY(!QIcon::fromTheme("button-open").isNull());
707
708 // Try to run the actual gtk-update-icon-cache and make sure that icons are still found
709 const QString gtkUpdateIconCache = findGtkUpdateIconCache();
710 if (gtkUpdateIconCache.isEmpty()) {
711 QIcon::setThemeSearchPaths(QStringList());
712 QSKIP("gtk-update-icon-cache not run (binary not found)");
713 }
714#if QT_CONFIG(process)
715 QProcess process;
716 process.start(program: gtkUpdateIconCache,
717 arguments: QStringList() << QStringLiteral("-f") << QStringLiteral("-t") << (dir.path() + QLatin1String("/testcache")));
718 QVERIFY2(process.waitForStarted(), qPrintable(QLatin1String("Unable to start: ")
719 + gtkUpdateIconCache + QLatin1String(": ")
720 + process.errorString()));
721 QVERIFY(process.waitForFinished());
722 QCOMPARE(process.exitStatus(), QProcess::NormalExit);
723 QCOMPARE(process.exitCode(), 0);
724#endif // QT_CONFIG(process)
725 QVERIFY(QFileInfo(cacheName).lastModified() >= QFileInfo(dir.path() + QLatin1String("/testcache/16x16/actions")).lastModified());
726 QIcon::setThemeSearchPaths(QStringList() << dir.path()); // reload themes
727 QVERIFY(!QIcon::fromTheme("button-open").isNull());
728 QVERIFY(!QIcon::fromTheme("button-open-fallback").isNull());
729 QVERIFY(QIcon::fromTheme("notexist-fallback").isNull());
730}
731
732void tst_QIcon::task223279_inconsistentAddFile()
733{
734 QIcon icon1;
735 icon1.addFile(fileName: QLatin1String(":/styles/commonstyle/images/standardbutton-open-16.png"));
736 icon1.addFile(fileName: QLatin1String("IconThatDoesntExist"), size: QSize(32, 32));
737 QPixmap pm1 = icon1.pixmap(w: 32, h: 32);
738
739 QIcon icon2;
740 icon2.addFile(fileName: QLatin1String(":/styles/commonstyle/images/standardbutton-open-16.png"));
741 icon2.addFile(fileName: QLatin1String("IconThatDoesntExist"));
742 QPixmap pm2 = icon1.pixmap(w: 32, h: 32);
743
744 QCOMPARE(pm1.isNull(), false);
745 QCOMPARE(pm1.size(), QSize(16,16));
746 QCOMPARE(pm1.isNull(), pm2.isNull());
747 QCOMPARE(pm1.size(), pm2.size());
748}
749
750
751QTEST_MAIN(tst_QIcon)
752#include "tst_qicon.moc"
753

source code of qtbase/tests/auto/gui/image/qicon/tst_qicon.cpp