blob: 09064e39b68d488007cadd28f73808de064c67d3 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qsvgiconengine.h"
#ifndef QT_NO_SVGRENDERER
#include "qpainter.h"
#include "qpixmap.h"
#include "qsvgrenderer.h"
#include "qpixmapcache.h"
#include "qfileinfo.h"
#include <qmimedatabase.h>
#include <qmimetype.h>
#include <QAtomicInt>
#include "qdebug.h"
#include <private/qguiapplication_p.h>
QT_BEGIN_NAMESPACE
class QSvgIconEnginePrivate : public QSharedData
{
public:
QSvgIconEnginePrivate()
: svgBuffers(0), addedPixmaps(0)
{ stepSerialNum(); }
~QSvgIconEnginePrivate()
{ delete addedPixmaps; delete svgBuffers; }
static int hashKey(QIcon::Mode mode, QIcon::State state)
{ return (((mode)<<4)|state); }
QString pmcKey(const QSize &size, QIcon::Mode mode, QIcon::State state)
{ return QLatin1String("$qt_svgicon_")
+ QString::number(serialNum, 16).append(QLatin1Char('_'))
+ QString::number((((((qint64(size.width()) << 11) | size.height()) << 11) | mode) << 4) | state, 16); }
void stepSerialNum()
{ serialNum = lastSerialNum.fetchAndAddRelaxed(1); }
bool tryLoad(QSvgRenderer *renderer, QIcon::Mode mode, QIcon::State state);
QIcon::Mode loadDataForModeAndState(QSvgRenderer *renderer, QIcon::Mode mode, QIcon::State state);
QHash<int, QString> svgFiles;
QHash<int, QByteArray> *svgBuffers;
QHash<int, QPixmap> *addedPixmaps;
int serialNum;
static QAtomicInt lastSerialNum;
};
QAtomicInt QSvgIconEnginePrivate::lastSerialNum;
QSvgIconEngine::QSvgIconEngine()
: d(new QSvgIconEnginePrivate)
{
}
QSvgIconEngine::QSvgIconEngine(const QSvgIconEngine &other)
: QIconEngine(other), d(new QSvgIconEnginePrivate)
{
d->svgFiles = other.d->svgFiles;
if (other.d->svgBuffers)
d->svgBuffers = new QHash<int, QByteArray>(*other.d->svgBuffers);
if (other.d->addedPixmaps)
d->addedPixmaps = new QHash<int, QPixmap>(*other.d->addedPixmaps);
}
QSvgIconEngine::~QSvgIconEngine()
{
}
QSize QSvgIconEngine::actualSize(const QSize &size, QIcon::Mode mode,
QIcon::State state)
{
if (d->addedPixmaps) {
QPixmap pm = d->addedPixmaps->value(d->hashKey(mode, state));
if (!pm.isNull() && pm.size() == size)
return size;
}
QPixmap pm = pixmap(size, mode, state);
if (pm.isNull())
return QSize();
return pm.size();
}
static QByteArray maybeUncompress(const QByteArray &ba)
{
#ifndef QT_NO_COMPRESS
return qUncompress(ba);
#else
return ba;
#endif
}
bool QSvgIconEnginePrivate::tryLoad(QSvgRenderer *renderer, QIcon::Mode mode, QIcon::State state)
{
if (svgBuffers) {
QByteArray buf = svgBuffers->value(hashKey(mode, state));
if (!buf.isEmpty()) {
buf = maybeUncompress(buf);
renderer->load(buf);
return true;
}
}
QString svgFile = svgFiles.value(hashKey(mode, state));
if (!svgFile.isEmpty()) {
renderer->load(svgFile);
return true;
}
return false;
}
QIcon::Mode QSvgIconEnginePrivate::loadDataForModeAndState(QSvgRenderer *renderer, QIcon::Mode mode, QIcon::State state)
{
if (tryLoad(renderer, mode, state))
return mode;
const QIcon::State oppositeState = (state == QIcon::On) ? QIcon::Off : QIcon::On;
if (mode == QIcon::Disabled || mode == QIcon::Selected) {
const QIcon::Mode oppositeMode = (mode == QIcon::Disabled) ? QIcon::Selected : QIcon::Disabled;
if (tryLoad(renderer, QIcon::Normal, state))
return QIcon::Normal;
if (tryLoad(renderer, QIcon::Active, state))
return QIcon::Active;
if (tryLoad(renderer, mode, oppositeState))
return mode;
if (tryLoad(renderer, QIcon::Normal, oppositeState))
return QIcon::Normal;
if (tryLoad(renderer, QIcon::Active, oppositeState))
return QIcon::Active;
if (tryLoad(renderer, oppositeMode, state))
return oppositeMode;
if (tryLoad(renderer, oppositeMode, oppositeState))
return oppositeMode;
} else {
const QIcon::Mode oppositeMode = (mode == QIcon::Normal) ? QIcon::Active : QIcon::Normal;
if (tryLoad(renderer, oppositeMode, state))
return oppositeMode;
if (tryLoad(renderer, mode, oppositeState))
return mode;
if (tryLoad(renderer, oppositeMode, oppositeState))
return oppositeMode;
if (tryLoad(renderer, QIcon::Disabled, state))
return QIcon::Disabled;
if (tryLoad(renderer, QIcon::Selected, state))
return QIcon::Selected;
if (tryLoad(renderer, QIcon::Disabled, oppositeState))
return QIcon::Disabled;
if (tryLoad(renderer, QIcon::Selected, oppositeState))
return QIcon::Selected;
}
return QIcon::Normal;
}
QPixmap QSvgIconEngine::pixmap(const QSize &size, QIcon::Mode mode,
QIcon::State state)
{
QPixmap pm;
QString pmckey(d->pmcKey(size, mode, state));
if (QPixmapCache::find(pmckey, &pm))
return pm;
if (d->addedPixmaps) {
pm = d->addedPixmaps->value(d->hashKey(mode, state));
if (!pm.isNull() && pm.size() == size)
return pm;
}
QSvgRenderer renderer;
const QIcon::Mode loadmode = d->loadDataForModeAndState(&renderer, mode, state);
if (!renderer.isValid())
return pm;
QSize actualSize = renderer.defaultSize();
if (!actualSize.isNull())
actualSize.scale(size, Qt::KeepAspectRatio);
if (actualSize.isEmpty())
return QPixmap();
QImage img(actualSize, QImage::Format_ARGB32_Premultiplied);
img.fill(0x00000000);
QPainter p(&img);
renderer.render(&p);
p.end();
pm = QPixmap::fromImage(img);
if (qobject_cast<QGuiApplication *>(QCoreApplication::instance())) {
if (loadmode != mode && mode != QIcon::Normal) {
const QPixmap generated = QGuiApplicationPrivate::instance()->applyQIconStyleHelper(mode, pm);
if (!generated.isNull())
pm = generated;
}
}
if (!pm.isNull())
QPixmapCache::insert(pmckey, pm);
return pm;
}
void QSvgIconEngine::addPixmap(const QPixmap &pixmap, QIcon::Mode mode,
QIcon::State state)
{
if (!d->addedPixmaps)
d->addedPixmaps = new QHash<int, QPixmap>;
d->stepSerialNum();
d->addedPixmaps->insert(d->hashKey(mode, state), pixmap);
}
enum FileType { OtherFile, SvgFile, CompressedSvgFile };
static FileType fileType(const QFileInfo &fi)
{
const QString &abs = fi.absoluteFilePath();
if (abs.endsWith(QLatin1String(".svg"), Qt::CaseInsensitive))
return SvgFile;
if (abs.endsWith(QLatin1String(".svgz"), Qt::CaseInsensitive)
|| abs.endsWith(QLatin1String(".svg.gz"), Qt::CaseInsensitive)) {
return CompressedSvgFile;
}
#ifndef QT_NO_MIMETYPE
const QString &mimeTypeName = QMimeDatabase().mimeTypeForFile(fi).name();
if (mimeTypeName == QLatin1String("image/svg+xml"))
return SvgFile;
if (mimeTypeName == QLatin1String("image/svg+xml-compressed"))
return CompressedSvgFile;
#endif // !QT_NO_MIMETYPE
return OtherFile;
}
void QSvgIconEngine::addFile(const QString &fileName, const QSize &,
QIcon::Mode mode, QIcon::State state)
{
if (!fileName.isEmpty()) {
const QFileInfo fi(fileName);
const QString abs = fi.absoluteFilePath();
const FileType type = fileType(fi);
#ifndef QT_NO_COMPRESS
if (type == SvgFile || type == CompressedSvgFile) {
#else
if (type == SvgFile) {
#endif
QSvgRenderer renderer(abs);
if (renderer.isValid()) {
d->stepSerialNum();
d->svgFiles.insert(d->hashKey(mode, state), abs);
}
} else if (type == OtherFile) {
QPixmap pm(abs);
if (!pm.isNull())
addPixmap(pm, mode, state);
}
}
}
void QSvgIconEngine::paint(QPainter *painter, const QRect &rect,
QIcon::Mode mode, QIcon::State state)
{
QSize pixmapSize = rect.size();
if (painter->device())
pixmapSize *= painter->device()->devicePixelRatioF();
painter->drawPixmap(rect, pixmap(pixmapSize, mode, state));
}
QString QSvgIconEngine::key() const
{
return QLatin1String("svg");
}
QIconEngine *QSvgIconEngine::clone() const
{
return new QSvgIconEngine(*this);
}
bool QSvgIconEngine::read(QDataStream &in)
{
d = new QSvgIconEnginePrivate;
d->svgBuffers = new QHash<int, QByteArray>;
if (in.version() >= QDataStream::Qt_4_4) {
int isCompressed;
QHash<int, QString> fileNames; // For memoryoptimization later
in >> fileNames >> isCompressed >> *d->svgBuffers;
#ifndef QT_NO_COMPRESS
if (!isCompressed) {
for (auto it = d->svgBuffers->begin(), end = d->svgBuffers->end(); it != end; ++it)
it.value() = qCompress(it.value());
}
#else
if (isCompressed) {
qWarning("QSvgIconEngine: Can not decompress SVG data");
d->svgBuffers->clear();
}
#endif
int hasAddedPixmaps;
in >> hasAddedPixmaps;
if (hasAddedPixmaps) {
d->addedPixmaps = new QHash<int, QPixmap>;
in >> *d->addedPixmaps;
}
}
else {
QPixmap pixmap;
QByteArray data;
uint mode;
uint state;
int num_entries;
in >> data;
if (!data.isEmpty()) {
#ifndef QT_NO_COMPRESS
data = qUncompress(data);
#endif
if (!data.isEmpty())
d->svgBuffers->insert(d->hashKey(QIcon::Normal, QIcon::Off), data);
}
in >> num_entries;
for (int i=0; i<num_entries; ++i) {
if (in.atEnd())
return false;
in >> pixmap;
in >> mode;
in >> state;
// The pm list written by 4.3 is buggy and/or useless, so ignore.
//addPixmap(pixmap, QIcon::Mode(mode), QIcon::State(state));
}
}
return true;
}
bool QSvgIconEngine::write(QDataStream &out) const
{
if (out.version() >= QDataStream::Qt_4_4) {
int isCompressed = 0;
#ifndef QT_NO_COMPRESS
isCompressed = 1;
#endif
QHash<int, QByteArray> svgBuffers;
if (d->svgBuffers)
svgBuffers = *d->svgBuffers;
for (auto it = d->svgFiles.cbegin(), end = d->svgFiles.cend(); it != end; ++it) {
QByteArray buf;
QFile f(it.value());
if (f.open(QIODevice::ReadOnly))
buf = f.readAll();
#ifndef QT_NO_COMPRESS
buf = qCompress(buf);
#endif
svgBuffers.insert(it.key(), buf);
}
out << d->svgFiles << isCompressed << svgBuffers;
if (d->addedPixmaps)
out << (int)1 << *d->addedPixmaps;
else
out << (int)0;
}
else {
QByteArray buf;
if (d->svgBuffers)
buf = d->svgBuffers->value(d->hashKey(QIcon::Normal, QIcon::Off));
if (buf.isEmpty()) {
QString svgFile = d->svgFiles.value(d->hashKey(QIcon::Normal, QIcon::Off));
if (!svgFile.isEmpty()) {
QFile f(svgFile);
if (f.open(QIODevice::ReadOnly))
buf = f.readAll();
}
}
#ifndef QT_NO_COMPRESS
buf = qCompress(buf);
#endif
out << buf;
// 4.3 has buggy handling of added pixmaps, so don't write any
out << (int)0;
}
return true;
}
void QSvgIconEngine::virtual_hook(int id, void *data)
{
if (id == QIconEngine::IsNullHook) {
*reinterpret_cast<bool*>(data) = d->svgFiles.isEmpty() && !d->addedPixmaps && (!d->svgBuffers || d->svgBuffers->isEmpty());
}
QIconEngine::virtual_hook(id, data);
}
QT_END_NAMESPACE
#endif // QT_NO_SVGRENDERER