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)
52 SharedSvgRenderer::SharedSvgRenderer(
59 QIODevice *file = KFilterDev::deviceForFile(filename,
"application/x-gzip");
60 if (!file->
open(QIODevice::ReadOnly)) {
64 load(file->
readAll(), styleSheet, interestingElements);
68 SharedSvgRenderer::SharedSvgRenderer(
75 load(contents, styleSheet, interestingElements);
78 bool SharedSvgRenderer::load(
84 if (!styleSheet.
isEmpty() && contents.
contains(
"current-color-scheme")) {
93 style = style.nextSiblingElement(
"style")) {
94 if (style.attribute(
"id") ==
"current-color-scheme") {
101 interestingElements.
insert(
"current-color-scheme",
QRect(0,0,1,1));
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);
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()
159 if (size.isValid() && size != naturalSize) {
172 bool SvgPrivate::setImagePath(
const QString &imagePath)
177 if (isThemed == themed &&
178 ((themed && themePath == imagePath) ||
179 (!themed && path == imagePath))) {
187 bool updateNeeded =
true;
190 if (isThemed && !themed && s_systemColorsCache) {
199 localRectCache.clear();
200 elementsWithSizeHints.clear();
203 themePath = imagePath;
205 QObject::connect(actualTheme(), SIGNAL(themeChanged()), q, SLOT(themeChanged()));
207 QObject::connect(cacheAndColorsTheme(), SIGNAL(themeChanged()), q, SLOT(themeChanged()), Qt::UniqueConnection);
210 kDebug() <<
"file '" << path <<
"' does not exist!";
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();
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()) {
295 QList<QSize> elementSizeHints = elementsWithSizeHints.values(elementId);
297 if (!elementSizeHints.
isEmpty()) {
298 QSize bestFit(-1, -1);
300 Q_FOREACH(
const QSize &hint, elementSizeHints) {
303 (!bestFit.isValid() ||
304 (bestFit.width() * bestFit.height()) > (hint.
width() * hint.
height()))) {
309 if (bestFit.isValid()) {
316 if (elementId.
isEmpty() || !q->hasElement(actualElementId)) {
317 actualElementId = elementId;
323 size = elementRect(actualElementId).
size().
toSize();
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);
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());
417 if (it != s_renderers.constEnd()) {
419 renderer = it.value();
421 if (path.isEmpty()) {
422 renderer =
new SharedSvgRenderer();
425 renderer =
new SharedSvgRenderer(path, styleSheet, 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;
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();
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);
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())) {
544 q, SLOT(colorsChanged()), Qt::UniqueConnection);
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;
569 if (qFuzzyIsNull(orig.
x()) || qFuzzyIsNull(orig.
y())) {
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();
637 d(new SvgPrivate(this))
648 if (elementID.
isNull() || d->multipleImages) {
649 return d->findInCache(elementID,
size());
651 return d->findInCache(elementID);
657 QPixmap pix((elementID.
isNull() || d->multipleImages) ? d->findInCache(elementID, size()) :
658 d->findInCache(elementID));
674 QPixmap pix(d->findInCache(elementID, rect.
size()));
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();
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()) {
818 connect(theme, SIGNAL(themeChanged()),
this, SLOT(themeChanged()));
Q_INVOKABLE QRectF elementRect(const QString &elementId) const
The bounding rect of a given element.
iterator insert(const Key &key, const T &value)
QDomNode item(int index) const
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.
QDomNode appendChild(const QDomNode &newChild)
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.
virtual bool open(QFlags< QIODevice::OpenModeFlag > mode)
Provides an SVG with borders.
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
Q_INVOKABLE bool hasElement(const QString &elementId) const
Check whether an element exists in the loaded SVG.
QString number(int n, int base)
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.
bool load(const QString &filename)
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 setAttribute(const QString &name, const QString &value)
void drawPixmap(const QRectF &target, const QPixmap &pixmap, const QRectF &source)
QDomNodeList elementsByTagName(const QString &tagname) const
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.
QDomCDATASection createCDATASection(const QString &value)
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.
bool isAbsolutePath(const QString &path)
Interface to the Plasma theme.
QDomNode replaceChild(const QDomNode &newChild, const QDomNode &oldChild)
static Theme * defaultTheme()
Singleton pattern accessor.
QDomElement firstChildElement(const QString &tagName) const
bool contains(char ch) const
#define CACHE_ID_WITH_SIZE(size, id)
QString fromLatin1(const char *str, int size)
QDomElement createElement(const QString &tagName)
#define CACHE_ID_NATURAL_SIZE(id)
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QByteArray toByteArray(int indent) const
bool setContent(const QByteArray &data, bool namespaceProcessing, QString *errorMsg, int *errorLine, int *errorColumn)
QByteArray toUtf8() const