22 #include "private/svg_p.h"
27 #include <QDomDocument>
30 #include <QStringBuilder>
32 #include <kcolorscheme.h>
33 #include <kconfiggroup.h>
35 #include <kfilterdev.h>
36 #include <kiconeffect.h>
37 #include <kglobalsettings.h>
38 #include <ksharedptr.h>
47 SharedSvgRenderer::SharedSvgRenderer(
QObject *parent)
48 : QSvgRenderer(parent)
52 SharedSvgRenderer::SharedSvgRenderer(
53 const QString &filename,
54 const QString &styleSheet,
55 QHash<QString, QRectF> &interestingElements,
57 : QSvgRenderer(parent)
59 QIODevice *file = KFilterDev::deviceForFile(filename,
"application/x-gzip");
60 if (!file->open(QIODevice::ReadOnly)) {
64 load(file->readAll(), styleSheet, interestingElements);
68 SharedSvgRenderer::SharedSvgRenderer(
69 const QByteArray &contents,
70 const QString &styleSheet,
71 QHash<QString, QRectF> &interestingElements,
73 : QSvgRenderer(parent)
75 load(contents, styleSheet, interestingElements);
78 bool SharedSvgRenderer::load(
79 const QByteArray &contents,
80 const QString &styleSheet,
81 QHash<QString, QRectF> &interestingElements)
84 if (!styleSheet.isEmpty() && contents.contains(
"current-color-scheme")) {
86 if (!svg.setContent(contents)) {
90 QDomNode defs = svg.elementsByTagName(
"defs").item(0);
92 for (QDomElement style = defs.firstChildElement(
"style"); !style.isNull();
93 style = style.nextSiblingElement(
"style")) {
94 if (style.attribute(
"id") ==
"current-color-scheme") {
95 QDomElement colorScheme = svg.createElement(
"style");
96 colorScheme.setAttribute(
"type",
"text/css");
97 colorScheme.setAttribute(
"id",
"current-color-scheme");
98 defs.replaceChild(colorScheme, style);
99 colorScheme.appendChild(svg.createCDATASection(styleSheet));
101 interestingElements.insert(
"current-color-scheme", QRect(0,0,1,1));
106 if (!QSvgRenderer::load(svg.toByteArray(-1))) {
109 }
else if (!QSvgRenderer::load(contents)) {
114 const QString contentsAsString(QString::fromLatin1(contents));
115 QRegExp idExpr(
"id\\s*=\\s*(['\"])(\\d+-\\d+-.*)\\1");
116 idExpr.setMinimal(
true);
119 while ((pos = idExpr.indexIn(contentsAsString, pos)) != -1) {
120 QString elementId = idExpr.cap(2);
122 QRectF elementRect = boundsOnElement(elementId);
123 if (elementRect.isValid()) {
124 interestingElements.insert(elementId, elementRect);
127 pos += idExpr.matchedLength();
133 #define QLSEP QLatin1Char('_')
134 #define CACHE_ID_WITH_SIZE(size, id) QString::number(int(size.width())) % QLSEP % QString::number(int(size.height())) % QLSEP % id
135 #define CACHE_ID_NATURAL_SIZE(id) QLatin1Literal("Natural") % QLSEP % id
137 SvgPrivate::SvgPrivate(
Svg *svg)
142 multipleImages(false),
146 cacheRendering(true),
151 SvgPrivate::~SvgPrivate()
157 QString SvgPrivate::cacheId(
const QString &elementId)
159 if (size.isValid() && size != naturalSize) {
167 QString SvgPrivate::cachePath(
const QString &path,
const QSize &size)
172 bool SvgPrivate::setImagePath(
const QString &imagePath)
174 const bool isThemed = !QDir::isAbsolutePath(imagePath);
177 if (isThemed == themed &&
178 ((themed && themePath == imagePath) ||
179 (!themed && path == imagePath))) {
187 bool updateNeeded =
true;
189 QObject::disconnect(actualTheme(), SIGNAL(themeChanged()), q, SLOT(themeChanged()));
190 if (isThemed && !themed && s_systemColorsCache) {
193 QObject::disconnect(s_systemColorsCache.data(), 0, q, 0);
199 localRectCache.clear();
200 elementsWithSizeHints.clear();
203 themePath = imagePath;
205 QObject::connect(actualTheme(), SIGNAL(themeChanged()), q, SLOT(themeChanged()));
206 }
else if (QFile::exists(imagePath)) {
207 QObject::connect(cacheAndColorsTheme(), SIGNAL(themeChanged()), q, SLOT(themeChanged()), Qt::UniqueConnection);
210 kDebug() <<
"file '" << path <<
"' does not exist!";
218 if (themed || QFile::exists(imagePath)) {
220 if (cacheAndColorsTheme()->findInRectsCache(path,
"_Natural", rect)) {
221 naturalSize = rect.size();
224 naturalSize = renderer->defaultSize();
226 cacheAndColorsTheme()->insertIntoRectsCache(path,
"_Natural", QRectF(QPointF(0,0), naturalSize));
234 lastModified = info.lastModified().toTime_t();
240 Theme *SvgPrivate::actualTheme()
249 Theme *SvgPrivate::cacheAndColorsTheme()
252 return actualTheme();
255 if (!s_systemColorsCache) {
257 s_systemColorsCache =
new Plasma::Theme(
"internal-system-colors");
260 return s_systemColorsCache.data();
264 QPixmap SvgPrivate::findInCache(
const QString &elementId,
const QSizeF &s)
267 QString actualElementId;
269 if (elementsWithSizeHints.isEmpty()) {
274 foreach (
const QString &key, cacheAndColorsTheme()->listCachedRectKeys(path)) {
275 if (sizeHintedKeyExpr.exactMatch(key)) {
276 QString baseElementId = sizeHintedKeyExpr.cap(3);
277 QSize sizeHint(sizeHintedKeyExpr.cap(1).toInt(),
278 sizeHintedKeyExpr.cap(2).toInt());
280 if (sizeHint.isValid()) {
281 elementsWithSizeHints.insertMulti(baseElementId, sizeHint);
286 if (elementsWithSizeHints.isEmpty()) {
288 elementsWithSizeHints.insert(QString(), QSize());
294 if (s.isValid() && !elementId.isEmpty()) {
295 QList<QSize> elementSizeHints = elementsWithSizeHints.values(elementId);
297 if (!elementSizeHints.isEmpty()) {
298 QSize bestFit(-1, -1);
300 Q_FOREACH(
const QSize &hint, elementSizeHints) {
302 if (hint.width() >= s.width() && hint.height() >= s.height() &&
303 (!bestFit.isValid() ||
304 (bestFit.width() * bestFit.height()) > (hint.width() * hint.height()))) {
309 if (bestFit.isValid()) {
310 actualElementId = QString::number(bestFit.width()) %
"-" %
311 QString::number(bestFit.height()) %
"-" % elementId;
316 if (elementId.isEmpty() || !q->hasElement(actualElementId)) {
317 actualElementId = elementId;
320 if (elementId.isEmpty() || (multipleImages && s.isValid())) {
323 size = elementRect(actualElementId).size().toSize();
326 if (size.isEmpty()) {
330 QString
id = cachePath(path, size);
332 if (!actualElementId.isEmpty()) {
333 id.append(actualElementId);
339 if (cacheRendering && cacheAndColorsTheme()->findInCache(
id, p, lastModified)) {
351 QRectF finalRect = makeUniform(renderer->boundsOnElement(actualElementId), QRect(QPoint(0,0), size));
357 p.fill(Qt::transparent);
358 QPainter renderPainter(&p);
360 if (actualElementId.isEmpty()) {
361 renderer->render(&renderPainter, finalRect);
363 renderer->render(&renderPainter, actualElementId, finalRect);
370 QImage itmp = p.toImage();
371 KIconEffect::colorize(itmp, cacheAndColorsTheme()->color(Theme::BackgroundColor), 1.0);
372 p = p.fromImage(itmp);
375 if (cacheRendering) {
376 cacheAndColorsTheme()->insertIntoCache(
id, p, QString::number((qint64)q, 16) %
QLSEP % actualElementId);
382 void SvgPrivate::createRenderer()
389 if (themed && path.isEmpty() && !themeFailed) {
390 Applet *applet = qobject_cast<Applet*>(q->parent());
391 if (applet && applet->package()) {
392 path = applet->package()->filePath(
"images", themePath +
".svg");
394 if (path.isEmpty()) {
395 path = applet->package()->filePath(
"images", themePath +
".svgz");
399 if (path.isEmpty()) {
400 path = actualTheme()->imagePath(themePath);
401 themeFailed = path.isEmpty();
403 kWarning() <<
"No image path found for" << themePath;
412 QString styleSheet = cacheAndColorsTheme()->styleSheet(
"SVG");
413 styleCrc = qChecksum(styleSheet.toUtf8(), styleSheet.size());
415 QHash<QString, SharedSvgRenderer::Ptr>::const_iterator it = s_renderers.constFind(styleCrc + path);
417 if (it != s_renderers.constEnd()) {
419 renderer = it.value();
421 if (path.isEmpty()) {
422 renderer =
new SharedSvgRenderer();
424 QHash<QString, QRectF> interestingElements;
425 renderer =
new SharedSvgRenderer(path, styleSheet, interestingElements);
428 QHashIterator<QString, QRectF> i(interestingElements);
430 while (i.hasNext()) {
432 const QString &elementId = i.key();
433 const QRectF &elementRect = i.value();
436 localRectCache.insert(cacheId, elementRect);
437 cacheAndColorsTheme()->insertIntoRectsCache(path, cacheId, elementRect);
441 s_renderers[styleCrc + path] = renderer;
444 if (size == QSizeF()) {
445 size = renderer->defaultSize();
449 void SvgPrivate::eraseRenderer()
451 if (renderer && renderer.count() == 2) {
453 s_renderers.erase(s_renderers.find(styleCrc + path));
456 theme.data()->releaseRectsCache(path);
462 localRectCache.clear();
463 elementsWithSizeHints.clear();
466 QRectF SvgPrivate::elementRect(
const QString &elementId)
468 if (themed && path.isEmpty()) {
473 path = actualTheme()->imagePath(themePath);
474 themeFailed = path.isEmpty();
481 QString
id = cacheId(elementId);
483 if (localRectCache.contains(
id)) {
484 return localRectCache.value(
id);
488 if (cacheAndColorsTheme()->findInRectsCache(path,
id, rect)) {
489 localRectCache.insert(
id, rect);
491 rect = findAndCacheElementRect(elementId);
497 QRectF SvgPrivate::findAndCacheElementRect(
const QString &elementId)
502 const QString
id = cacheId(elementId);
503 if (localRectCache.contains(
id)) {
504 return localRectCache.value(
id);
507 QRectF elementRect = renderer->elementExists(elementId) ?
508 renderer->matrixForElement(elementId).map(renderer->boundsOnElement(elementId)).boundingRect() :
510 naturalSize = renderer->defaultSize();
511 qreal dx = size.width() / naturalSize.width();
512 qreal dy = size.height() / naturalSize.height();
514 elementRect = QRectF(elementRect.x() * dx, elementRect.y() * dy,
515 elementRect.width() * dx, elementRect.height() * dy);
517 cacheAndColorsTheme()->insertIntoRectsCache(path,
id, elementRect);
521 QMatrix SvgPrivate::matrixForElement(
const QString &elementId)
524 return renderer->matrixForElement(elementId);
527 void SvgPrivate::checkColorHints()
529 if (elementRect(
"hint-apply-color-scheme").isValid()) {
532 }
else if (elementRect(
"current-color-scheme").isValid()) {
542 if (usesColors && (!themed || !actualTheme()->colorScheme())) {
543 QObject::connect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()),
544 q, SLOT(colorsChanged()), Qt::UniqueConnection);
546 QObject::disconnect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()),
547 q, SLOT(colorsChanged()));
553 qreal SvgPrivate::closestDistance(qreal to, qreal from)
556 if (qFuzzyCompare(to, from))
558 else if ( to > from ) {
559 qreal b = to - from - 1;
560 return (qAbs(a) > qAbs(b)) ? b : a;
562 qreal b = 1 + to - from;
563 return (qAbs(a) > qAbs(b)) ? b : a;
567 QRectF SvgPrivate::makeUniform(
const QRectF &orig,
const QRectF &dst)
569 if (qFuzzyIsNull(orig.x()) || qFuzzyIsNull(orig.y())) {
574 qreal div_w = dst.width() / orig.width();
575 qreal div_h = dst.height() / orig.height();
577 qreal div_x = dst.x() / orig.x();
578 qreal div_y = dst.y() / orig.y();
581 if (!qFuzzyIsNull(div_x) && !qFuzzyCompare(div_w, div_x)) {
582 qreal rem_orig = orig.x() - (floor(orig.x()));
583 qreal rem_dst = dst.x() - (floor(dst.x()));
584 qreal offset = closestDistance(rem_dst, rem_orig);
585 res.translate(offset + offset*div_w, 0);
586 res.setWidth(res.width() + offset);
589 if (!qFuzzyIsNull(div_y) && !qFuzzyCompare(div_h, div_y)) {
590 qreal rem_orig = orig.y() - (floor(orig.y()));
591 qreal rem_dst = dst.y() - (floor(dst.y()));
592 qreal offset = closestDistance(rem_dst, rem_orig);
593 res.translate(0, offset + offset*div_h);
594 res.setHeight(res.height() + offset);
601 void SvgPrivate::themeChanged()
603 if (q->imagePath().isEmpty()) {
612 QString currentPath = themed ? themePath : path;
615 setImagePath(currentPath);
618 emit q->repaintNeeded();
621 void SvgPrivate::colorsChanged()
629 emit q->repaintNeeded();
632 QHash<QString, SharedSvgRenderer::Ptr> SvgPrivate::s_renderers;
633 QWeakPointer<Theme> SvgPrivate::s_systemColorsCache;
637 d(new SvgPrivate(this))
648 if (elementID.isNull() || d->multipleImages) {
649 return d->findInCache(elementID,
size());
651 return d->findInCache(elementID);
655 void Svg::paint(QPainter *painter,
const QPointF &point,
const QString &elementID)
657 QPixmap pix((elementID.isNull() || d->multipleImages) ? d->findInCache(elementID, size()) :
658 d->findInCache(elementID));
664 painter->drawPixmap(QRectF(point, pix.size()), pix, QRectF(QPointF(0, 0), pix.size()));
667 void Svg::paint(QPainter *painter,
int x,
int y,
const QString &elementID)
669 paint(painter, QPointF(x, y), elementID);
672 void Svg::paint(QPainter *painter,
const QRectF &rect,
const QString &elementID)
674 QPixmap pix(d->findInCache(elementID, rect.size()));
675 painter->drawPixmap(QRectF(rect.topLeft(), pix.size()), pix, QRectF(QPointF(0, 0), pix.size()));
678 void Svg::paint(QPainter *painter,
int x,
int y,
int width,
int height,
const QString &elementID)
680 QPixmap pix(d->findInCache(elementID, QSizeF(width, height)));
681 painter->drawPixmap(x, y, pix, 0, 0, pix.size().width(), pix.size().height());
686 if (d->size.isEmpty()) {
687 d->size = d->naturalSize;
690 return d->size.toSize();
695 resize(QSize(width, height));
700 if (qFuzzyCompare(size.width(), d->size.width()) &&
701 qFuzzyCompare(size.height(), d->size.height())) {
706 d->localRectCache.clear();
712 if (qFuzzyCompare(d->naturalSize.width(), d->size.width()) &&
713 qFuzzyCompare(d->naturalSize.height(), d->size.height())) {
717 d->size = d->naturalSize;
718 d->localRectCache.clear();
724 return d->elementRect(elementId).size().toSize();
729 return d->elementRect(elementId);
734 if (d->path.isNull() && d->themePath.isNull()) {
738 return d->elementRect(elementId).isValid();
761 if (d->path.isNull() && d->themePath.isNull()) {
766 return d->renderer->isValid();
771 d->multipleImages = multiple;
776 return d->multipleImages;
782 if (
FrameSvg *frame = qobject_cast<FrameSvg *>(
this)) {
783 frame->setImagePath(svgFilePath);
787 d->setImagePath(svgFilePath);
794 return d->themed ? d->themePath : d->path;
799 d->cacheRendering = useCache;
804 return d->cacheRendering;
809 if (!theme || theme == d->theme.data()) {
814 disconnect(d->theme.data(), 0,
this, 0);
818 connect(theme, SIGNAL(themeChanged()),
this, SLOT(themeChanged()));
Q_INVOKABLE QRectF elementRect(const QString &elementId) const
The bounding rect of a given element.
Q_INVOKABLE QPixmap pixmap(const QString &elementID=QString())
Returns a pixmap of the SVG represented by this object.
Svg(QObject *parent=0)
Constructs an SVG object that implicitly shares and caches rendering.
void repaintNeeded()
Emitted whenever the SVG data has changed in such a way that a repaint is required.
bool isUsingRenderingCache() const
Whether the rendering cache is being used.
Q_INVOKABLE void resize()
Resizes the rendered image to the natural size of the SVG.
bool containsMultipleImages() const
Whether the SVG contains multiple images.
QSize size() const
The size of the SVG.
void setUsingRenderingCache(bool useCache)
Sets whether or not to cache the results of rendering to pixmaps.
QString imagePath() const
The SVG file to render.
Provides an SVG with borders.
Q_INVOKABLE bool hasElement(const QString &elementId) const
Check whether an element exists in the loaded SVG.
Q_INVOKABLE bool isValid() const
Check whether this object is backed by a valid SVG file.
void sizeChanged()
Emitted whenever the size of the Svg is changed.
void setTheme(Plasma::Theme *theme)
Sets the Plasma::Theme to use with this Svg object.
Q_INVOKABLE void paint(QPainter *painter, const QPointF &point, const QString &elementID=QString())
Paints all or part of the SVG represented by this object.
void setImagePath(const QString &svgFilePath)
Set the SVG file to render.
Q_INVOKABLE QString elementAtPoint(const QPoint &point) const
Returns the element (by id) at the given point.
void setContainsMultipleImages(bool multiple)
Set whether the SVG contains a single image or multiple ones.
Q_INVOKABLE QSize elementSize(const QString &elementId) const
Find the size of a given element.
Theme * theme() const
The Plasma::Theme used by this Svg object.
Interface to the Plasma theme.
static Theme * defaultTheme()
Singleton pattern accessor.
#define CACHE_ID_WITH_SIZE(size, id)
#define CACHE_ID_NATURAL_SIZE(id)