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 plugins of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
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 Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | #include "qsvgiconengine.h" |
40 | |
41 | #ifndef QT_NO_SVGRENDERER |
42 | |
43 | #include "qpainter.h" |
44 | #include "qpixmap.h" |
45 | #include "qsvgrenderer.h" |
46 | #include "qpixmapcache.h" |
47 | #include "qfileinfo.h" |
48 | #include <qmimedatabase.h> |
49 | #include <qmimetype.h> |
50 | #include <QAtomicInt> |
51 | #include "qdebug.h" |
52 | #include <private/qguiapplication_p.h> |
53 | |
54 | QT_BEGIN_NAMESPACE |
55 | |
56 | class QSvgIconEnginePrivate : public QSharedData |
57 | { |
58 | public: |
59 | QSvgIconEnginePrivate() |
60 | : svgBuffers(0), addedPixmaps(0) |
61 | { stepSerialNum(); } |
62 | |
63 | ~QSvgIconEnginePrivate() |
64 | { delete addedPixmaps; delete svgBuffers; } |
65 | |
66 | static int hashKey(QIcon::Mode mode, QIcon::State state) |
67 | { return (((mode)<<4)|state); } |
68 | |
69 | QString pmcKey(const QSize &size, QIcon::Mode mode, QIcon::State state) |
70 | { return QLatin1String("$qt_svgicon_" ) |
71 | + QString::number(serialNum, base: 16).append(c: QLatin1Char('_')) |
72 | + QString::number((((((qint64(size.width()) << 11) | size.height()) << 11) | mode) << 4) | state, base: 16); } |
73 | |
74 | void stepSerialNum() |
75 | { serialNum = lastSerialNum.fetchAndAddRelaxed(valueToAdd: 1); } |
76 | |
77 | bool tryLoad(QSvgRenderer *renderer, QIcon::Mode mode, QIcon::State state); |
78 | QIcon::Mode loadDataForModeAndState(QSvgRenderer *renderer, QIcon::Mode mode, QIcon::State state); |
79 | |
80 | QHash<int, QString> svgFiles; |
81 | QHash<int, QByteArray> *svgBuffers; |
82 | QHash<int, QPixmap> *addedPixmaps; |
83 | int serialNum; |
84 | static QAtomicInt lastSerialNum; |
85 | }; |
86 | |
87 | QAtomicInt QSvgIconEnginePrivate::lastSerialNum; |
88 | |
89 | QSvgIconEngine::QSvgIconEngine() |
90 | : d(new QSvgIconEnginePrivate) |
91 | { |
92 | } |
93 | |
94 | QSvgIconEngine::QSvgIconEngine(const QSvgIconEngine &other) |
95 | : QIconEngine(other), d(new QSvgIconEnginePrivate) |
96 | { |
97 | d->svgFiles = other.d->svgFiles; |
98 | if (other.d->svgBuffers) |
99 | d->svgBuffers = new QHash<int, QByteArray>(*other.d->svgBuffers); |
100 | if (other.d->addedPixmaps) |
101 | d->addedPixmaps = new QHash<int, QPixmap>(*other.d->addedPixmaps); |
102 | } |
103 | |
104 | |
105 | QSvgIconEngine::~QSvgIconEngine() |
106 | { |
107 | } |
108 | |
109 | |
110 | QSize QSvgIconEngine::actualSize(const QSize &size, QIcon::Mode mode, |
111 | QIcon::State state) |
112 | { |
113 | if (d->addedPixmaps) { |
114 | QPixmap pm = d->addedPixmaps->value(akey: d->hashKey(mode, state)); |
115 | if (!pm.isNull() && pm.size() == size) |
116 | return size; |
117 | } |
118 | |
119 | QPixmap pm = pixmap(size, mode, state); |
120 | if (pm.isNull()) |
121 | return QSize(); |
122 | return pm.size(); |
123 | } |
124 | |
125 | static QByteArray maybeUncompress(const QByteArray &ba) |
126 | { |
127 | #ifndef QT_NO_COMPRESS |
128 | return qUncompress(data: ba); |
129 | #else |
130 | return ba; |
131 | #endif |
132 | } |
133 | |
134 | bool QSvgIconEnginePrivate::tryLoad(QSvgRenderer *renderer, QIcon::Mode mode, QIcon::State state) |
135 | { |
136 | if (svgBuffers) { |
137 | QByteArray buf = svgBuffers->value(akey: hashKey(mode, state)); |
138 | if (!buf.isEmpty()) { |
139 | buf = maybeUncompress(ba: buf); |
140 | renderer->load(contents: buf); |
141 | return true; |
142 | } |
143 | } |
144 | QString svgFile = svgFiles.value(akey: hashKey(mode, state)); |
145 | if (!svgFile.isEmpty()) { |
146 | renderer->load(filename: svgFile); |
147 | return true; |
148 | } |
149 | return false; |
150 | } |
151 | |
152 | QIcon::Mode QSvgIconEnginePrivate::loadDataForModeAndState(QSvgRenderer *renderer, QIcon::Mode mode, QIcon::State state) |
153 | { |
154 | if (tryLoad(renderer, mode, state)) |
155 | return mode; |
156 | |
157 | const QIcon::State oppositeState = (state == QIcon::On) ? QIcon::Off : QIcon::On; |
158 | if (mode == QIcon::Disabled || mode == QIcon::Selected) { |
159 | const QIcon::Mode oppositeMode = (mode == QIcon::Disabled) ? QIcon::Selected : QIcon::Disabled; |
160 | if (tryLoad(renderer, mode: QIcon::Normal, state)) |
161 | return QIcon::Normal; |
162 | if (tryLoad(renderer, mode: QIcon::Active, state)) |
163 | return QIcon::Active; |
164 | if (tryLoad(renderer, mode, state: oppositeState)) |
165 | return mode; |
166 | if (tryLoad(renderer, mode: QIcon::Normal, state: oppositeState)) |
167 | return QIcon::Normal; |
168 | if (tryLoad(renderer, mode: QIcon::Active, state: oppositeState)) |
169 | return QIcon::Active; |
170 | if (tryLoad(renderer, mode: oppositeMode, state)) |
171 | return oppositeMode; |
172 | if (tryLoad(renderer, mode: oppositeMode, state: oppositeState)) |
173 | return oppositeMode; |
174 | } else { |
175 | const QIcon::Mode oppositeMode = (mode == QIcon::Normal) ? QIcon::Active : QIcon::Normal; |
176 | if (tryLoad(renderer, mode: oppositeMode, state)) |
177 | return oppositeMode; |
178 | if (tryLoad(renderer, mode, state: oppositeState)) |
179 | return mode; |
180 | if (tryLoad(renderer, mode: oppositeMode, state: oppositeState)) |
181 | return oppositeMode; |
182 | if (tryLoad(renderer, mode: QIcon::Disabled, state)) |
183 | return QIcon::Disabled; |
184 | if (tryLoad(renderer, mode: QIcon::Selected, state)) |
185 | return QIcon::Selected; |
186 | if (tryLoad(renderer, mode: QIcon::Disabled, state: oppositeState)) |
187 | return QIcon::Disabled; |
188 | if (tryLoad(renderer, mode: QIcon::Selected, state: oppositeState)) |
189 | return QIcon::Selected; |
190 | } |
191 | return QIcon::Normal; |
192 | } |
193 | |
194 | QPixmap QSvgIconEngine::pixmap(const QSize &size, QIcon::Mode mode, |
195 | QIcon::State state) |
196 | { |
197 | QPixmap pm; |
198 | |
199 | QString pmckey(d->pmcKey(size, mode, state)); |
200 | if (QPixmapCache::find(key: pmckey, pixmap: &pm)) |
201 | return pm; |
202 | |
203 | if (d->addedPixmaps) { |
204 | pm = d->addedPixmaps->value(akey: d->hashKey(mode, state)); |
205 | if (!pm.isNull() && pm.size() == size) |
206 | return pm; |
207 | } |
208 | |
209 | QSvgRenderer renderer; |
210 | const QIcon::Mode loadmode = d->loadDataForModeAndState(renderer: &renderer, mode, state); |
211 | if (!renderer.isValid()) |
212 | return pm; |
213 | |
214 | QSize actualSize = renderer.defaultSize(); |
215 | if (!actualSize.isNull()) |
216 | actualSize.scale(s: size, mode: Qt::KeepAspectRatio); |
217 | |
218 | if (actualSize.isEmpty()) |
219 | return QPixmap(); |
220 | |
221 | QImage img(actualSize, QImage::Format_ARGB32_Premultiplied); |
222 | img.fill(pixel: 0x00000000); |
223 | QPainter p(&img); |
224 | renderer.render(p: &p); |
225 | p.end(); |
226 | pm = QPixmap::fromImage(image: img); |
227 | if (qobject_cast<QGuiApplication *>(object: QCoreApplication::instance())) { |
228 | if (loadmode != mode && mode != QIcon::Normal) { |
229 | const QPixmap generated = QGuiApplicationPrivate::instance()->applyQIconStyleHelper(mode, basePixmap: pm); |
230 | if (!generated.isNull()) |
231 | pm = generated; |
232 | } |
233 | } |
234 | |
235 | if (!pm.isNull()) |
236 | QPixmapCache::insert(key: pmckey, pixmap: pm); |
237 | |
238 | return pm; |
239 | } |
240 | |
241 | |
242 | void QSvgIconEngine::addPixmap(const QPixmap &pixmap, QIcon::Mode mode, |
243 | QIcon::State state) |
244 | { |
245 | if (!d->addedPixmaps) |
246 | d->addedPixmaps = new QHash<int, QPixmap>; |
247 | d->stepSerialNum(); |
248 | d->addedPixmaps->insert(akey: d->hashKey(mode, state), avalue: pixmap); |
249 | } |
250 | |
251 | enum FileType { OtherFile, SvgFile, CompressedSvgFile }; |
252 | |
253 | static FileType fileType(const QFileInfo &fi) |
254 | { |
255 | const QString &abs = fi.absoluteFilePath(); |
256 | if (abs.endsWith(s: QLatin1String(".svg" ), cs: Qt::CaseInsensitive)) |
257 | return SvgFile; |
258 | if (abs.endsWith(s: QLatin1String(".svgz" ), cs: Qt::CaseInsensitive) |
259 | || abs.endsWith(s: QLatin1String(".svg.gz" ), cs: Qt::CaseInsensitive)) { |
260 | return CompressedSvgFile; |
261 | } |
262 | #ifndef QT_NO_MIMETYPE |
263 | const QString &mimeTypeName = QMimeDatabase().mimeTypeForFile(fileInfo: fi).name(); |
264 | if (mimeTypeName == QLatin1String("image/svg+xml" )) |
265 | return SvgFile; |
266 | if (mimeTypeName == QLatin1String("image/svg+xml-compressed" )) |
267 | return CompressedSvgFile; |
268 | #endif // !QT_NO_MIMETYPE |
269 | return OtherFile; |
270 | } |
271 | |
272 | void QSvgIconEngine::addFile(const QString &fileName, const QSize &, |
273 | QIcon::Mode mode, QIcon::State state) |
274 | { |
275 | if (!fileName.isEmpty()) { |
276 | const QFileInfo fi(fileName); |
277 | const QString abs = fi.absoluteFilePath(); |
278 | const FileType type = fileType(fi); |
279 | #ifndef QT_NO_COMPRESS |
280 | if (type == SvgFile || type == CompressedSvgFile) { |
281 | #else |
282 | if (type == SvgFile) { |
283 | #endif |
284 | QSvgRenderer renderer(abs); |
285 | if (renderer.isValid()) { |
286 | d->stepSerialNum(); |
287 | d->svgFiles.insert(akey: d->hashKey(mode, state), avalue: abs); |
288 | } |
289 | } else if (type == OtherFile) { |
290 | QPixmap pm(abs); |
291 | if (!pm.isNull()) |
292 | addPixmap(pixmap: pm, mode, state); |
293 | } |
294 | } |
295 | } |
296 | |
297 | void QSvgIconEngine::paint(QPainter *painter, const QRect &rect, |
298 | QIcon::Mode mode, QIcon::State state) |
299 | { |
300 | QSize pixmapSize = rect.size(); |
301 | if (painter->device()) |
302 | pixmapSize *= painter->device()->devicePixelRatioF(); |
303 | painter->drawPixmap(r: rect, pm: pixmap(size: pixmapSize, mode, state)); |
304 | } |
305 | |
306 | QString QSvgIconEngine::key() const |
307 | { |
308 | return QLatin1String("svg" ); |
309 | } |
310 | |
311 | QIconEngine *QSvgIconEngine::clone() const |
312 | { |
313 | return new QSvgIconEngine(*this); |
314 | } |
315 | |
316 | |
317 | bool QSvgIconEngine::read(QDataStream &in) |
318 | { |
319 | d = new QSvgIconEnginePrivate; |
320 | d->svgBuffers = new QHash<int, QByteArray>; |
321 | |
322 | if (in.version() >= QDataStream::Qt_4_4) { |
323 | int isCompressed; |
324 | QHash<int, QString> fileNames; // For memoryoptimization later |
325 | in >> fileNames >> isCompressed >> *d->svgBuffers; |
326 | #ifndef QT_NO_COMPRESS |
327 | if (!isCompressed) { |
328 | for (auto it = d->svgBuffers->begin(), end = d->svgBuffers->end(); it != end; ++it) |
329 | it.value() = qCompress(data: it.value()); |
330 | } |
331 | #else |
332 | if (isCompressed) { |
333 | qWarning("QSvgIconEngine: Can not decompress SVG data" ); |
334 | d->svgBuffers->clear(); |
335 | } |
336 | #endif |
337 | int hasAddedPixmaps; |
338 | in >> hasAddedPixmaps; |
339 | if (hasAddedPixmaps) { |
340 | d->addedPixmaps = new QHash<int, QPixmap>; |
341 | in >> *d->addedPixmaps; |
342 | } |
343 | } |
344 | else { |
345 | QPixmap pixmap; |
346 | QByteArray data; |
347 | uint mode; |
348 | uint state; |
349 | int num_entries; |
350 | |
351 | in >> data; |
352 | if (!data.isEmpty()) { |
353 | #ifndef QT_NO_COMPRESS |
354 | data = qUncompress(data); |
355 | #endif |
356 | if (!data.isEmpty()) |
357 | d->svgBuffers->insert(akey: d->hashKey(mode: QIcon::Normal, state: QIcon::Off), avalue: data); |
358 | } |
359 | in >> num_entries; |
360 | for (int i=0; i<num_entries; ++i) { |
361 | if (in.atEnd()) |
362 | return false; |
363 | in >> pixmap; |
364 | in >> mode; |
365 | in >> state; |
366 | // The pm list written by 4.3 is buggy and/or useless, so ignore. |
367 | //addPixmap(pixmap, QIcon::Mode(mode), QIcon::State(state)); |
368 | } |
369 | } |
370 | |
371 | return true; |
372 | } |
373 | |
374 | |
375 | bool QSvgIconEngine::write(QDataStream &out) const |
376 | { |
377 | if (out.version() >= QDataStream::Qt_4_4) { |
378 | int isCompressed = 0; |
379 | #ifndef QT_NO_COMPRESS |
380 | isCompressed = 1; |
381 | #endif |
382 | QHash<int, QByteArray> svgBuffers; |
383 | if (d->svgBuffers) |
384 | svgBuffers = *d->svgBuffers; |
385 | for (auto it = d->svgFiles.cbegin(), end = d->svgFiles.cend(); it != end; ++it) { |
386 | QByteArray buf; |
387 | QFile f(it.value()); |
388 | if (f.open(flags: QIODevice::ReadOnly)) |
389 | buf = f.readAll(); |
390 | #ifndef QT_NO_COMPRESS |
391 | buf = qCompress(data: buf); |
392 | #endif |
393 | svgBuffers.insert(akey: it.key(), avalue: buf); |
394 | } |
395 | out << d->svgFiles << isCompressed << svgBuffers; |
396 | if (d->addedPixmaps) |
397 | out << (int)1 << *d->addedPixmaps; |
398 | else |
399 | out << (int)0; |
400 | } |
401 | else { |
402 | QByteArray buf; |
403 | if (d->svgBuffers) |
404 | buf = d->svgBuffers->value(akey: d->hashKey(mode: QIcon::Normal, state: QIcon::Off)); |
405 | if (buf.isEmpty()) { |
406 | QString svgFile = d->svgFiles.value(akey: d->hashKey(mode: QIcon::Normal, state: QIcon::Off)); |
407 | if (!svgFile.isEmpty()) { |
408 | QFile f(svgFile); |
409 | if (f.open(flags: QIODevice::ReadOnly)) |
410 | buf = f.readAll(); |
411 | } |
412 | } |
413 | #ifndef QT_NO_COMPRESS |
414 | buf = qCompress(data: buf); |
415 | #endif |
416 | out << buf; |
417 | // 4.3 has buggy handling of added pixmaps, so don't write any |
418 | out << (int)0; |
419 | } |
420 | return true; |
421 | } |
422 | |
423 | void QSvgIconEngine::virtual_hook(int id, void *data) |
424 | { |
425 | if (id == QIconEngine::IsNullHook) { |
426 | *reinterpret_cast<bool*>(data) = d->svgFiles.isEmpty() && !d->addedPixmaps && (!d->svgBuffers || d->svgBuffers->isEmpty()); |
427 | } |
428 | QIconEngine::virtual_hook(id, data); |
429 | } |
430 | |
431 | QT_END_NAMESPACE |
432 | |
433 | #endif // QT_NO_SVGRENDERER |
434 | |