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)