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>
47SharedSvgRenderer::SharedSvgRenderer(
QObject *parent)
48 : QSvgRenderer(parent)
52SharedSvgRenderer::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);
68SharedSvgRenderer::SharedSvgRenderer(
69 const QByteArray &contents,
70 const QString &styleSheet,
71 QHash<QString, QRectF> &interestingElements,
73 : QSvgRenderer(parent)
75 load(contents, styleSheet, interestingElements);
78bool 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
137SvgPrivate::SvgPrivate(Svg *svg)
142 multipleImages(false),
146 cacheRendering(true),
151SvgPrivate::~SvgPrivate()
157QString SvgPrivate::cacheId(
const QString &elementId)
159 if (size.isValid() && size != naturalSize) {
167QString SvgPrivate::cachePath(
const QString &path,
const QSize &size)
172bool 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();
240Theme *SvgPrivate::actualTheme()
249Theme *SvgPrivate::cacheAndColorsTheme()
252 return actualTheme();
255 if (!s_systemColorsCache) {
257 s_systemColorsCache =
new Plasma::Theme(
"internal-system-colors");
260 return s_systemColorsCache.data();
264QPixmap 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);
382void 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();
449void 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();
466QRectF 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);
497QRectF 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);
521QMatrix SvgPrivate::matrixForElement(
const QString &elementId)
524 return renderer->matrixForElement(elementId);
527void 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()));
553qreal 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;
567QRectF 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);
601void SvgPrivate::themeChanged()
603 if (q->imagePath().isEmpty()) {
612 QString currentPath = themed ? themePath : path;
615 setImagePath(currentPath);
618 emit q->repaintNeeded();
621void SvgPrivate::colorsChanged()
629 emit q->repaintNeeded();
632QHash<QString, SharedSvgRenderer::Ptr> SvgPrivate::s_renderers;
633QWeakPointer<Theme> SvgPrivate::s_systemColorsCache;
637 d(new SvgPrivate(this))
646QPixmap Svg::pixmap(
const QString &elementID)
648 if (elementID.isNull() || d->multipleImages) {
649 return d->findInCache(elementID,
size());
651 return d->findInCache(elementID);
655void 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()));
667void Svg::paint(QPainter *painter,
int x,
int y,
const QString &elementID)
669 paint(painter, QPointF(x, y), elementID);
672void 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()));
678void 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());
684QSize Svg::size()
const
686 if (d->size.isEmpty()) {
687 d->size = d->naturalSize;
690 return d->size.toSize();
693void Svg::resize(qreal width, qreal height)
695 resize(QSize(width, height));
698void Svg::resize(
const QSizeF &size)
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();
722QSize Svg::elementSize(
const QString &elementId)
const
724 return d->elementRect(elementId).size().toSize();
727QRectF Svg::elementRect(
const QString &elementId)
const
729 return d->elementRect(elementId);
732bool Svg::hasElement(
const QString &elementId)
const
734 if (d->path.isNull() && d->themePath.isNull()) {
738 return d->elementRect(elementId).isValid();
741QString Svg::elementAtPoint(
const QPoint &point)
const
759bool Svg::isValid()
const
761 if (d->path.isNull() && d->themePath.isNull()) {
766 return d->renderer->isValid();
769void Svg::setContainsMultipleImages(
bool multiple)
771 d->multipleImages = multiple;
774bool Svg::containsMultipleImages()
const
776 return d->multipleImages;
779void Svg::setImagePath(
const QString &svgFilePath)
782 if (
FrameSvg *frame = qobject_cast<FrameSvg *>(
this)) {
783 frame->setImagePath(svgFilePath);
787 d->setImagePath(svgFilePath);
792QString Svg::imagePath()
const
794 return d->themed ? d->themePath : d->path;
797void Svg::setUsingRenderingCache(
bool useCache)
799 d->cacheRendering = useCache;
802bool Svg::isUsingRenderingCache()
const
804 return d->cacheRendering;
814 disconnect(d->theme.data(), 0,
this, 0);
818 connect(
theme, SIGNAL(themeChanged()),
this, SLOT(themeChanged()));
Provides an SVG with borders.
Q_INVOKABLE void paint(QPainter *painter, const QPointF &point, const QString &elementID=QString())
Paints all or part of the SVG represented by this object.
Theme * theme() const
The Plasma::Theme used by this Svg object.
Q_INVOKABLE void resize()
Resizes the rendered image to the natural size of the SVG.
void repaintNeeded()
Emitted whenever the SVG data has changed in such a way that a repaint is required.
void sizeChanged()
Emitted whenever the size of the Svg is changed.
Interface to the Plasma theme.
static Theme * defaultTheme()
Singleton pattern accessor.
Namespace for everything in libplasma.
#define CACHE_ID_NATURAL_SIZE(id)
#define CACHE_ID_WITH_SIZE(size, id)