diff --git a/private/svg_p.h b/private/svg_p.h index f6de9ed9a..2eb77506c 100644 --- a/private/svg_p.h +++ b/private/svg_p.h @@ -37,11 +37,23 @@ class SharedSvgRenderer : public QSvgRenderer, public QSharedData typedef KSharedPtr Ptr; SharedSvgRenderer(QObject *parent = 0); - SharedSvgRenderer(const QString &filename, const QString &styleSheet, QObject *parent = 0); - SharedSvgRenderer(const QByteArray &contents, const QString &styleSheet, QObject *parent = 0); + SharedSvgRenderer( + const QString &filename, + const QString &styleSheet, + QHash &elementsWithSizeHints, + QObject *parent = 0); + + SharedSvgRenderer( + const QByteArray &contents, + const QString &styleSheet, + QHash &elementsWithSizeHints, + QObject *parent = 0); private: - bool load(const QByteArray &contents, const QString &styleSheet); + bool load( + const QByteArray &contents, + const QString &styleSheet, + QHash &elementsWithSizeHints); }; class SvgPrivate @@ -86,6 +98,7 @@ public: Svg *q; QWeakPointer theme; QHash localRectCache; + QHash elementsWithSizeHints; SharedSvgRenderer::Ptr renderer; QString themePath; QString path; diff --git a/svg.cpp b/svg.cpp index caf9005f7..ef0d5b51b 100644 --- a/svg.cpp +++ b/svg.cpp @@ -49,7 +49,11 @@ SharedSvgRenderer::SharedSvgRenderer(QObject *parent) { } -SharedSvgRenderer::SharedSvgRenderer(const QString &filename, const QString &styleSheet, QObject *parent) +SharedSvgRenderer::SharedSvgRenderer( + const QString &filename, + const QString &styleSheet, + QHash &elementsWithSizeHints, + QObject *parent) : QSvgRenderer(parent) { QIODevice *file = KFilterDev::deviceForFile(filename, "application/x-gzip"); @@ -57,25 +61,53 @@ SharedSvgRenderer::SharedSvgRenderer(const QString &filename, const QString &sty delete file; return; } - load(file->readAll(), styleSheet); + load(file->readAll(), styleSheet, elementsWithSizeHints); delete file; } -SharedSvgRenderer::SharedSvgRenderer(const QByteArray &contents, const QString &styleSheet, QObject *parent) +SharedSvgRenderer::SharedSvgRenderer( + const QByteArray &contents, + const QString &styleSheet, + QHash &elementsWithSizeHints, + QObject *parent) : QSvgRenderer(parent) { - load(contents, styleSheet); + load(contents, styleSheet, elementsWithSizeHints); } -bool SharedSvgRenderer::load(const QByteArray &contents, const QString &styleSheet) +bool SharedSvgRenderer::load( + const QByteArray &contents, + const QString &styleSheet, + QHash &elementsWithSizeHints) { + { // Search the SVG to find and store all ids that contain size hints. + const QString contentsAsString(QString::fromLatin1(contents)); + QRegExp idExpr("id\\s*=\\s*(['\"])(\\d+)-(\\d+)-(.+)\\1"); + idExpr.setMinimal(true); + + int pos = 0; + while ((pos = idExpr.indexIn(contentsAsString, pos)) != -1) { + QString elementId = idExpr.cap(4); + QSize sizeHint(idExpr.cap(2).toInt(), idExpr.cap(3).toInt()); + + if (sizeHint.isValid()) { + elementsWithSizeHints.insertMulti(elementId, sizeHint); + } + + pos += idExpr.matchedLength(); + } + } + + // Apply the style sheet. if (styleSheet.isEmpty() || ! contents.contains("current-color-scheme")) { return QSvgRenderer::load(contents); } + QDomDocument svg; if (!svg.setContent(contents)) { return false; } + QDomNode defs = svg.elementsByTagName("defs").item(0); for (QDomElement style = defs.firstChildElement("style"); !style.isNull(); @@ -96,6 +128,8 @@ bool SharedSvgRenderer::load(const QByteArray &contents, const QString &styleShe #define QLSEP QLatin1Char('_') #define CACHE_ID_WITH_SIZE(size, id) QString::number(int(size.width())) % QString::number(int(size.height())) % QLSEP % id +#define CACHE_ID_NATURAL_SIZE(id) QLatin1Literal("Natural") % QLSEP % id + SvgPrivate::SvgPrivate(Svg *svg) : q(svg), renderer(0), @@ -121,7 +155,7 @@ QString SvgPrivate::cacheId(const QString &elementId) if (size.isValid() && size != naturalSize) { return CACHE_ID_WITH_SIZE(size, elementId); } else { - return QLatin1Literal("Natural") % QLSEP % elementId; + return CACHE_ID_NATURAL_SIZE(elementId); } } @@ -157,6 +191,7 @@ bool SvgPrivate::setImagePath(const QString &imagePath) path.clear(); themePath.clear(); localRectCache.clear(); + elementsWithSizeHints.clear(); if (themed) { themePath = imagePath; @@ -216,7 +251,60 @@ Theme *SvgPrivate::actualTheme() QPixmap SvgPrivate::findInCache(const QString &elementId, const QSizeF &s) { QSize size; - QString actualElementId(QString::number(int(s.width())) % "-" % QString::number(int(s.height())) % "-" % elementId); + QString actualElementId; + + if (elementsWithSizeHints.isEmpty()) { + // Fetch all size hinted element ids from the theme's rect cache + // and store them locally. + QRegExp sizeHintedKeyExpr(CACHE_ID_NATURAL_SIZE("(\\d+)-(\\d+)-(.+)")); + + Q_FOREACH(const QString &key, actualTheme()->listCachedRectKeys(path)) { + + if (sizeHintedKeyExpr.exactMatch(key)) { + QString baseElementId = sizeHintedKeyExpr.cap(3); + QSize sizeHint( + sizeHintedKeyExpr.cap(1).toInt(), + sizeHintedKeyExpr.cap(2).toInt()); + + if (sizeHint.isValid()) { + elementsWithSizeHints.insertMulti(baseElementId, sizeHint); + } + } + } + + if (elementsWithSizeHints.isEmpty()) { + // Make sure we won't query the theme unnecessarily. + elementsWithSizeHints.insert(QString(), QSize()); + } + } + + // Look at the size hinted elements and try to find the smallest one with an + // identical aspect ratio. + if (s.isValid() && !elementId.isEmpty()) { + + QList elementSizeHints = elementsWithSizeHints.values(elementId); + + if (!elementSizeHints.isEmpty()) { + QSize bestFit(-1, -1); + + Q_FOREACH(const QSize &hint, elementSizeHints) { + + if (hint.width() >= s.width() && hint.height() >= s.height() && + (!bestFit.isValid() || + bestFit.width() * bestFit.height() > + hint.width() * hint.height())) { + + bestFit = hint; + } + } + + if (bestFit.isValid()) { + actualElementId = + QString::number(bestFit.width()) % "-" % + QString::number(bestFit.height()) % "-" % elementId; + } + } + } if (elementId.isEmpty() || !q->hasElement(actualElementId)) { actualElementId = elementId; @@ -329,7 +417,33 @@ void SvgPrivate::createRenderer() if (path.isEmpty()) { renderer = new SharedSvgRenderer(); } else { - renderer = new SharedSvgRenderer(path, actualTheme()->styleSheet("SVG")); + renderer = new SharedSvgRenderer( + path, actualTheme()->styleSheet("SVG"), elementsWithSizeHints); + + // Add size hinted elements to the theme's rect cache. + QHashIterator i(elementsWithSizeHints); + + while (i.hasNext()) { + i.next(); + const QString &baseElementId = i.key(); + const QSize &hintedSize = i.value(); + QString fullElementId = + QString::number(hintedSize.width()) % '-' % + QString::number(hintedSize.height()) % '-' % + baseElementId; + + QString cacheId = CACHE_ID_NATURAL_SIZE(fullElementId); + QRectF elementRect = renderer->boundsOnElement(fullElementId); + if (elementRect.isValid()) { + actualTheme()->insertIntoRectsCache( + path, cacheId, elementRect); + } + } + } + + if (elementsWithSizeHints.isEmpty()) { + // Make sure we won't query the theme unnecessarily. + elementsWithSizeHints.insert(QString(), QSize()); } s_renderers[styleCrc + path] = renderer; @@ -354,6 +468,7 @@ void SvgPrivate::eraseRenderer() renderer = 0; styleCrc = 0; localRectCache.clear(); + elementsWithSizeHints.clear(); } QRectF SvgPrivate::elementRect(const QString &elementId) diff --git a/theme.cpp b/theme.cpp index e8224ea49..354893eb4 100644 --- a/theme.cpp +++ b/theme.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -960,6 +961,24 @@ bool Theme::findInRectsCache(const QString &image, const QString &element, QRect return invalid; } +QStringList Theme::listCachedRectKeys(const QString &image) const +{ + KConfigGroup imageGroup(d->svgElementsCache, image); + QStringList keys = imageGroup.keyList(); + + QMutableListIterator i(keys); + while (i.hasNext()) { + const QString &key = i.next(); + if (key.endsWith("Size")) { + // The actual cache id used from outside doesn't end on "Size". + i.setValue(key.resize(key.size() - 4)); + } else { + i.remove(); + } + } + return keys; +} + void Theme::insertIntoRectsCache(const QString& image, const QString &element, const QRectF &rect) { if (!d->pixmapCache) { diff --git a/theme.h b/theme.h index 7b7ae2623..be9675a97 100644 --- a/theme.h +++ b/theme.h @@ -330,12 +330,23 @@ class PLASMA_EXPORT Theme : public QObject * * @arg image path of the image we want to check * @arg element sub element we want to retrieve - * @arg rect output parameter of the element rect found in cache + * @arg rect output parameter of the element rect found in cache * if not found or if we are sure it doesn't exist it will be QRect() * @return true if the element was found in cache or if we are sure the element doesn't exist **/ bool findInRectsCache(const QString &image, const QString &element, QRectF &rect) const; + /** + * Returns a list of all keys of cached rects for the given image. + * + * @arg image path of the image for which the keys should be returned + * + * @return a QStringList whose elements are the entry keys in the rects cache + * + * @since 4.6 + */ + QStringList listCachedRectKeys(const QString &image) const; + /** * Inserts a rectangle of a sub element of an image into a disk cache *