1 | /* |
2 | * Copyright 2006-2007 Aaron Seigo <aseigo@kde.org> |
3 | * Copyright 2008-2010 Marco Martin <notmart@gmail.com> |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU Library General Public License as |
7 | * published by the Free Software Foundation; either version 2, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details |
14 | * |
15 | * You should have received a copy of the GNU Library General Public |
16 | * License along with this program; if not, write to the |
17 | * Free Software Foundation, Inc., |
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
19 | */ |
20 | |
21 | #include "svg.h" |
22 | #include "private/svg_p.h" |
23 | |
24 | #include <cmath> |
25 | |
26 | #include <QDir> |
27 | #include <QDomDocument> |
28 | #include <QMatrix> |
29 | #include <QPainter> |
30 | #include <QStringBuilder> |
31 | |
32 | #include <kcolorscheme.h> |
33 | #include <kconfiggroup.h> |
34 | #include <kdebug.h> |
35 | #include <kfilterdev.h> |
36 | #include <kiconeffect.h> |
37 | #include <kglobalsettings.h> |
38 | #include <ksharedptr.h> |
39 | |
40 | #include "applet.h" |
41 | #include "package.h" |
42 | #include "theme.h" |
43 | |
44 | namespace Plasma |
45 | { |
46 | |
47 | SharedSvgRenderer::SharedSvgRenderer(QObject *parent) |
48 | : QSvgRenderer(parent) |
49 | { |
50 | } |
51 | |
52 | SharedSvgRenderer::SharedSvgRenderer( |
53 | const QString &filename, |
54 | const QString &styleSheet, |
55 | QHash<QString, QRectF> &interestingElements, |
56 | QObject *parent) |
57 | : QSvgRenderer(parent) |
58 | { |
59 | QIODevice *file = KFilterDev::deviceForFile(filename, "application/x-gzip" ); |
60 | if (!file->open(QIODevice::ReadOnly)) { |
61 | delete file; |
62 | return; |
63 | } |
64 | load(file->readAll(), styleSheet, interestingElements); |
65 | delete file; |
66 | } |
67 | |
68 | SharedSvgRenderer::SharedSvgRenderer( |
69 | const QByteArray &contents, |
70 | const QString &styleSheet, |
71 | QHash<QString, QRectF> &interestingElements, |
72 | QObject *parent) |
73 | : QSvgRenderer(parent) |
74 | { |
75 | load(contents, styleSheet, interestingElements); |
76 | } |
77 | |
78 | bool SharedSvgRenderer::load( |
79 | const QByteArray &contents, |
80 | const QString &styleSheet, |
81 | QHash<QString, QRectF> &interestingElements) |
82 | { |
83 | // Apply the style sheet. |
84 | if (!styleSheet.isEmpty() && contents.contains("current-color-scheme" )) { |
85 | QDomDocument svg; |
86 | if (!svg.setContent(contents)) { |
87 | return false; |
88 | } |
89 | |
90 | QDomNode defs = svg.elementsByTagName("defs" ).item(0); |
91 | |
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)); |
100 | |
101 | interestingElements.insert("current-color-scheme" , QRect(0,0,1,1)); |
102 | |
103 | break; |
104 | } |
105 | } |
106 | if (!QSvgRenderer::load(svg.toByteArray(-1))) { |
107 | return false; |
108 | } |
109 | } else if (!QSvgRenderer::load(contents)) { |
110 | return false; |
111 | } |
112 | |
113 | // Search the SVG to find and store all ids that contain size hints. |
114 | const QString contentsAsString(QString::fromLatin1(contents)); |
115 | QRegExp idExpr("id\\s*=\\s*(['\"])(\\d+-\\d+-.*)\\1" ); |
116 | idExpr.setMinimal(true); |
117 | |
118 | int pos = 0; |
119 | while ((pos = idExpr.indexIn(contentsAsString, pos)) != -1) { |
120 | QString elementId = idExpr.cap(2); |
121 | |
122 | QRectF elementRect = boundsOnElement(elementId); |
123 | if (elementRect.isValid()) { |
124 | interestingElements.insert(elementId, elementRect); |
125 | } |
126 | |
127 | pos += idExpr.matchedLength(); |
128 | } |
129 | |
130 | return true; |
131 | } |
132 | |
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 |
136 | |
137 | SvgPrivate::SvgPrivate(Svg *svg) |
138 | : q(svg), |
139 | renderer(0), |
140 | styleCrc(0), |
141 | lastModified(0), |
142 | multipleImages(false), |
143 | themed(false), |
144 | applyColors(false), |
145 | usesColors(false), |
146 | cacheRendering(true), |
147 | themeFailed(false) |
148 | { |
149 | } |
150 | |
151 | SvgPrivate::~SvgPrivate() |
152 | { |
153 | eraseRenderer(); |
154 | } |
155 | |
156 | //This function is meant for the rects cache |
157 | QString SvgPrivate::cacheId(const QString &elementId) |
158 | { |
159 | if (size.isValid() && size != naturalSize) { |
160 | return CACHE_ID_WITH_SIZE(size, elementId); |
161 | } else { |
162 | return CACHE_ID_NATURAL_SIZE(elementId); |
163 | } |
164 | } |
165 | |
166 | //This function is meant for the pixmap cache |
167 | QString SvgPrivate::cachePath(const QString &path, const QSize &size) |
168 | { |
169 | return CACHE_ID_WITH_SIZE(size, path); |
170 | } |
171 | |
172 | bool SvgPrivate::setImagePath(const QString &imagePath) |
173 | { |
174 | const bool isThemed = !QDir::isAbsolutePath(imagePath); |
175 | |
176 | // lets check to see if we're already set to this file |
177 | if (isThemed == themed && |
178 | ((themed && themePath == imagePath) || |
179 | (!themed && path == imagePath))) { |
180 | return false; |
181 | } |
182 | |
183 | eraseRenderer(); |
184 | |
185 | // if we don't have any path right now and are going to set one, |
186 | // then lets not schedule a repaint because we are just initializing! |
187 | bool updateNeeded = true; //!path.isEmpty() || !themePath.isEmpty(); |
188 | |
189 | QObject::disconnect(actualTheme(), SIGNAL(themeChanged()), q, SLOT(themeChanged())); |
190 | if (isThemed && !themed && s_systemColorsCache) { |
191 | // catch the case where we weren't themed, but now we are, and the colors cache was set up |
192 | // ensure we are not connected to that theme previously |
193 | QObject::disconnect(s_systemColorsCache.data(), 0, q, 0); |
194 | } |
195 | |
196 | themed = isThemed; |
197 | path.clear(); |
198 | themePath.clear(); |
199 | localRectCache.clear(); |
200 | elementsWithSizeHints.clear(); |
201 | |
202 | if (themed) { |
203 | themePath = imagePath; |
204 | themeFailed = false; |
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); |
208 | path = imagePath; |
209 | } else { |
210 | kDebug() << "file '" << path << "' does not exist!" ; |
211 | } |
212 | |
213 | // check if svg wants colorscheme applied |
214 | checkColorHints(); |
215 | |
216 | // also images with absolute path needs to have a natural size initialized, |
217 | // even if looks a bit weird using Theme to store non-themed stuff |
218 | if (themed || QFile::exists(imagePath)) { |
219 | QRectF rect; |
220 | if (cacheAndColorsTheme()->findInRectsCache(path, "_Natural" , rect)) { |
221 | naturalSize = rect.size(); |
222 | } else { |
223 | createRenderer(); |
224 | naturalSize = renderer->defaultSize(); |
225 | //kDebug() << "natural size for" << path << "from renderer is" << naturalSize; |
226 | cacheAndColorsTheme()->insertIntoRectsCache(path, "_Natural" , QRectF(QPointF(0,0), naturalSize)); |
227 | //kDebug() << "natural size for" << path << "from cache is" << naturalSize; |
228 | } |
229 | } |
230 | |
231 | if (!themed) { |
232 | QFile f(imagePath); |
233 | QFileInfo info(f); |
234 | lastModified = info.lastModified().toTime_t(); |
235 | } |
236 | |
237 | return updateNeeded; |
238 | } |
239 | |
240 | Theme *SvgPrivate::actualTheme() |
241 | { |
242 | if (!theme) { |
243 | theme = Plasma::Theme::defaultTheme(); |
244 | } |
245 | |
246 | return theme.data(); |
247 | } |
248 | |
249 | Theme *SvgPrivate::cacheAndColorsTheme() |
250 | { |
251 | if (themed) { |
252 | return actualTheme(); |
253 | } else { |
254 | // use a separate cache source for unthemed svg's |
255 | if (!s_systemColorsCache) { |
256 | //FIXME: reference count this, so that it is deleted when no longer in use |
257 | s_systemColorsCache = new Plasma::Theme("internal-system-colors" ); |
258 | } |
259 | |
260 | return s_systemColorsCache.data(); |
261 | } |
262 | } |
263 | |
264 | QPixmap SvgPrivate::findInCache(const QString &elementId, const QSizeF &s) |
265 | { |
266 | QSize size; |
267 | QString actualElementId; |
268 | |
269 | if (elementsWithSizeHints.isEmpty()) { |
270 | // Fetch all size hinted element ids from the theme's rect cache |
271 | // and store them locally. |
272 | QRegExp sizeHintedKeyExpr(CACHE_ID_NATURAL_SIZE("(\\d+)-(\\d+)-(.+)" )); |
273 | |
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()); |
279 | |
280 | if (sizeHint.isValid()) { |
281 | elementsWithSizeHints.insertMulti(baseElementId, sizeHint); |
282 | } |
283 | } |
284 | } |
285 | |
286 | if (elementsWithSizeHints.isEmpty()) { |
287 | // Make sure we won't query the theme unnecessarily. |
288 | elementsWithSizeHints.insert(QString(), QSize()); |
289 | } |
290 | } |
291 | |
292 | // Look at the size hinted elements and try to find the smallest one with an |
293 | // identical aspect ratio. |
294 | if (s.isValid() && !elementId.isEmpty()) { |
295 | QList<QSize> elementSizeHints = elementsWithSizeHints.values(elementId); |
296 | |
297 | if (!elementSizeHints.isEmpty()) { |
298 | QSize bestFit(-1, -1); |
299 | |
300 | Q_FOREACH(const QSize &hint, elementSizeHints) { |
301 | |
302 | if (hint.width() >= s.width() && hint.height() >= s.height() && |
303 | (!bestFit.isValid() || |
304 | (bestFit.width() * bestFit.height()) > (hint.width() * hint.height()))) { |
305 | bestFit = hint; |
306 | } |
307 | } |
308 | |
309 | if (bestFit.isValid()) { |
310 | actualElementId = QString::number(bestFit.width()) % "-" % |
311 | QString::number(bestFit.height()) % "-" % elementId; |
312 | } |
313 | } |
314 | } |
315 | |
316 | if (elementId.isEmpty() || !q->hasElement(actualElementId)) { |
317 | actualElementId = elementId; |
318 | } |
319 | |
320 | if (elementId.isEmpty() || (multipleImages && s.isValid())) { |
321 | size = s.toSize(); |
322 | } else { |
323 | size = elementRect(actualElementId).size().toSize(); |
324 | } |
325 | |
326 | if (size.isEmpty()) { |
327 | return QPixmap(); |
328 | } |
329 | |
330 | QString id = cachePath(path, size); |
331 | |
332 | if (!actualElementId.isEmpty()) { |
333 | id.append(actualElementId); |
334 | } |
335 | |
336 | //kDebug() << "id is " << id; |
337 | |
338 | QPixmap p; |
339 | if (cacheRendering && cacheAndColorsTheme()->findInCache(id, p, lastModified)) { |
340 | //kDebug() << "found cached version of " << id << p.size(); |
341 | return p; |
342 | } |
343 | |
344 | //kDebug() << "didn't find cached version of " << id << ", so re-rendering"; |
345 | |
346 | //kDebug() << "size for " << actualElementId << " is " << s; |
347 | // we have to re-render this puppy |
348 | |
349 | createRenderer(); |
350 | |
351 | QRectF finalRect = makeUniform(renderer->boundsOnElement(actualElementId), QRect(QPoint(0,0), size)); |
352 | |
353 | //don't alter the pixmap size or it won't match up properly to, e.g., FrameSvg elements |
354 | //makeUniform should never change the size so much that it gains or loses a whole pixel |
355 | p = QPixmap(size); |
356 | |
357 | p.fill(Qt::transparent); |
358 | QPainter renderPainter(&p); |
359 | |
360 | if (actualElementId.isEmpty()) { |
361 | renderer->render(&renderPainter, finalRect); |
362 | } else { |
363 | renderer->render(&renderPainter, actualElementId, finalRect); |
364 | } |
365 | |
366 | renderPainter.end(); |
367 | |
368 | // Apply current color scheme if the svg asks for it |
369 | if (applyColors) { |
370 | QImage itmp = p.toImage(); |
371 | KIconEffect::colorize(itmp, cacheAndColorsTheme()->color(Theme::BackgroundColor), 1.0); |
372 | p = p.fromImage(itmp); |
373 | } |
374 | |
375 | if (cacheRendering) { |
376 | cacheAndColorsTheme()->insertIntoCache(id, p, QString::number((qint64)q, 16) % QLSEP % actualElementId); |
377 | } |
378 | |
379 | return p; |
380 | } |
381 | |
382 | void SvgPrivate::createRenderer() |
383 | { |
384 | if (renderer) { |
385 | return; |
386 | } |
387 | |
388 | //kDebug() << kBacktrace(); |
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" ); |
393 | |
394 | if (path.isEmpty()) { |
395 | path = applet->package()->filePath("images" , themePath + ".svgz" ); |
396 | } |
397 | } |
398 | |
399 | if (path.isEmpty()) { |
400 | path = actualTheme()->imagePath(themePath); |
401 | themeFailed = path.isEmpty(); |
402 | if (themeFailed) { |
403 | kWarning() << "No image path found for" << themePath; |
404 | } |
405 | } |
406 | } |
407 | |
408 | //kDebug() << "********************************"; |
409 | //kDebug() << "FAIL! **************************"; |
410 | //kDebug() << path << "**"; |
411 | |
412 | QString styleSheet = cacheAndColorsTheme()->styleSheet("SVG" ); |
413 | styleCrc = qChecksum(styleSheet.toUtf8(), styleSheet.size()); |
414 | |
415 | QHash<QString, SharedSvgRenderer::Ptr>::const_iterator it = s_renderers.constFind(styleCrc + path); |
416 | |
417 | if (it != s_renderers.constEnd()) { |
418 | //kDebug() << "gots us an existing one!"; |
419 | renderer = it.value(); |
420 | } else { |
421 | if (path.isEmpty()) { |
422 | renderer = new SharedSvgRenderer(); |
423 | } else { |
424 | QHash<QString, QRectF> interestingElements; |
425 | renderer = new SharedSvgRenderer(path, styleSheet, interestingElements); |
426 | |
427 | // Add interesting elements to the theme's rect cache. |
428 | QHashIterator<QString, QRectF> i(interestingElements); |
429 | |
430 | while (i.hasNext()) { |
431 | i.next(); |
432 | const QString &elementId = i.key(); |
433 | const QRectF &elementRect = i.value(); |
434 | |
435 | const QString cacheId = CACHE_ID_NATURAL_SIZE(elementId); |
436 | localRectCache.insert(cacheId, elementRect); |
437 | cacheAndColorsTheme()->insertIntoRectsCache(path, cacheId, elementRect); |
438 | } |
439 | } |
440 | |
441 | s_renderers[styleCrc + path] = renderer; |
442 | } |
443 | |
444 | if (size == QSizeF()) { |
445 | size = renderer->defaultSize(); |
446 | } |
447 | } |
448 | |
449 | void SvgPrivate::eraseRenderer() |
450 | { |
451 | if (renderer && renderer.count() == 2) { |
452 | // this and the cache reference it |
453 | s_renderers.erase(s_renderers.find(styleCrc + path)); |
454 | |
455 | if (theme) { |
456 | theme.data()->releaseRectsCache(path); |
457 | } |
458 | } |
459 | |
460 | renderer = 0; |
461 | styleCrc = 0; |
462 | localRectCache.clear(); |
463 | elementsWithSizeHints.clear(); |
464 | } |
465 | |
466 | QRectF SvgPrivate::elementRect(const QString &elementId) |
467 | { |
468 | if (themed && path.isEmpty()) { |
469 | if (themeFailed) { |
470 | return QRectF(); |
471 | } |
472 | |
473 | path = actualTheme()->imagePath(themePath); |
474 | themeFailed = path.isEmpty(); |
475 | |
476 | if (themeFailed) { |
477 | return QRectF(); |
478 | } |
479 | } |
480 | |
481 | QString id = cacheId(elementId); |
482 | |
483 | if (localRectCache.contains(id)) { |
484 | return localRectCache.value(id); |
485 | } |
486 | |
487 | QRectF rect; |
488 | if (cacheAndColorsTheme()->findInRectsCache(path, id, rect)) { |
489 | localRectCache.insert(id, rect); |
490 | } else { |
491 | rect = findAndCacheElementRect(elementId); |
492 | } |
493 | |
494 | return rect; |
495 | } |
496 | |
497 | QRectF SvgPrivate::findAndCacheElementRect(const QString &elementId) |
498 | { |
499 | createRenderer(); |
500 | |
501 | // createRenderer() can insert some interesting rects in the cache, so check it |
502 | const QString id = cacheId(elementId); |
503 | if (localRectCache.contains(id)) { |
504 | return localRectCache.value(id); |
505 | } |
506 | |
507 | QRectF elementRect = renderer->elementExists(elementId) ? |
508 | renderer->matrixForElement(elementId).map(renderer->boundsOnElement(elementId)).boundingRect() : |
509 | QRectF(); |
510 | naturalSize = renderer->defaultSize(); |
511 | qreal dx = size.width() / naturalSize.width(); |
512 | qreal dy = size.height() / naturalSize.height(); |
513 | |
514 | elementRect = QRectF(elementRect.x() * dx, elementRect.y() * dy, |
515 | elementRect.width() * dx, elementRect.height() * dy); |
516 | |
517 | cacheAndColorsTheme()->insertIntoRectsCache(path, id, elementRect); |
518 | return elementRect; |
519 | } |
520 | |
521 | QMatrix SvgPrivate::matrixForElement(const QString &elementId) |
522 | { |
523 | createRenderer(); |
524 | return renderer->matrixForElement(elementId); |
525 | } |
526 | |
527 | void SvgPrivate::checkColorHints() |
528 | { |
529 | if (elementRect("hint-apply-color-scheme" ).isValid()) { |
530 | applyColors = true; |
531 | usesColors = true; |
532 | } else if (elementRect("current-color-scheme" ).isValid()) { |
533 | applyColors = false; |
534 | usesColors = true; |
535 | } else { |
536 | applyColors = false; |
537 | usesColors = false; |
538 | } |
539 | |
540 | // check to see if we are using colors, but the theme isn't being used or isn't providing |
541 | // a colorscheme |
542 | if (usesColors && (!themed || !actualTheme()->colorScheme())) { |
543 | QObject::connect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), |
544 | q, SLOT(colorsChanged()), Qt::UniqueConnection); |
545 | } else { |
546 | QObject::disconnect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), |
547 | q, SLOT(colorsChanged())); |
548 | } |
549 | } |
550 | |
551 | //Following two are utility functions to snap rendered elements to the pixel grid |
552 | //to and from are always 0 <= val <= 1 |
553 | qreal SvgPrivate::closestDistance(qreal to, qreal from) |
554 | { |
555 | qreal a = to - from; |
556 | if (qFuzzyCompare(to, from)) |
557 | return 0; |
558 | else if ( to > from ) { |
559 | qreal b = to - from - 1; |
560 | return (qAbs(a) > qAbs(b)) ? b : a; |
561 | } else { |
562 | qreal b = 1 + to - from; |
563 | return (qAbs(a) > qAbs(b)) ? b : a; |
564 | } |
565 | } |
566 | |
567 | QRectF SvgPrivate::makeUniform(const QRectF &orig, const QRectF &dst) |
568 | { |
569 | if (qFuzzyIsNull(orig.x()) || qFuzzyIsNull(orig.y())) { |
570 | return dst; |
571 | } |
572 | |
573 | QRectF res(dst); |
574 | qreal div_w = dst.width() / orig.width(); |
575 | qreal div_h = dst.height() / orig.height(); |
576 | |
577 | qreal div_x = dst.x() / orig.x(); |
578 | qreal div_y = dst.y() / orig.y(); |
579 | |
580 | //horizontal snap |
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); |
587 | } |
588 | //vertical snap |
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); |
595 | } |
596 | |
597 | //kDebug()<<"Aligning Rects, origin:"<<orig<<"destination:"<<dst<<"result:"<<res; |
598 | return res; |
599 | } |
600 | |
601 | void SvgPrivate::themeChanged() |
602 | { |
603 | if (q->imagePath().isEmpty()) { |
604 | return; |
605 | } |
606 | |
607 | if (themed) { |
608 | // check if new theme svg wants colorscheme applied |
609 | checkColorHints(); |
610 | } |
611 | |
612 | QString currentPath = themed ? themePath : path; |
613 | themePath.clear(); |
614 | eraseRenderer(); |
615 | setImagePath(currentPath); |
616 | |
617 | //kDebug() << themePath << ">>>>>>>>>>>>>>>>>> theme changed"; |
618 | emit q->repaintNeeded(); |
619 | } |
620 | |
621 | void SvgPrivate::colorsChanged() |
622 | { |
623 | if (!usesColors) { |
624 | return; |
625 | } |
626 | |
627 | eraseRenderer(); |
628 | //kDebug() << "repaint needed from colorsChanged"; |
629 | emit q->repaintNeeded(); |
630 | } |
631 | |
632 | QHash<QString, SharedSvgRenderer::Ptr> SvgPrivate::s_renderers; |
633 | QWeakPointer<Theme> SvgPrivate::s_systemColorsCache; |
634 | |
635 | Svg::Svg(QObject *parent) |
636 | : QObject(parent), |
637 | d(new SvgPrivate(this)) |
638 | { |
639 | } |
640 | |
641 | Svg::~Svg() |
642 | { |
643 | delete d; |
644 | } |
645 | |
646 | QPixmap Svg::pixmap(const QString &elementID) |
647 | { |
648 | if (elementID.isNull() || d->multipleImages) { |
649 | return d->findInCache(elementID, size()); |
650 | } else { |
651 | return d->findInCache(elementID); |
652 | } |
653 | } |
654 | |
655 | void Svg::paint(QPainter *painter, const QPointF &point, const QString &elementID) |
656 | { |
657 | QPixmap pix((elementID.isNull() || d->multipleImages) ? d->findInCache(elementID, size()) : |
658 | d->findInCache(elementID)); |
659 | |
660 | if (pix.isNull()) { |
661 | return; |
662 | } |
663 | |
664 | painter->drawPixmap(QRectF(point, pix.size()), pix, QRectF(QPointF(0, 0), pix.size())); |
665 | } |
666 | |
667 | void Svg::paint(QPainter *painter, int x, int y, const QString &elementID) |
668 | { |
669 | paint(painter, QPointF(x, y), elementID); |
670 | } |
671 | |
672 | void Svg::paint(QPainter *painter, const QRectF &rect, const QString &elementID) |
673 | { |
674 | QPixmap pix(d->findInCache(elementID, rect.size())); |
675 | painter->drawPixmap(QRectF(rect.topLeft(), pix.size()), pix, QRectF(QPointF(0, 0), pix.size())); |
676 | } |
677 | |
678 | void Svg::paint(QPainter *painter, int x, int y, int width, int height, const QString &elementID) |
679 | { |
680 | QPixmap pix(d->findInCache(elementID, QSizeF(width, height))); |
681 | painter->drawPixmap(x, y, pix, 0, 0, pix.size().width(), pix.size().height()); |
682 | } |
683 | |
684 | QSize Svg::size() const |
685 | { |
686 | if (d->size.isEmpty()) { |
687 | d->size = d->naturalSize; |
688 | } |
689 | |
690 | return d->size.toSize(); |
691 | } |
692 | |
693 | void Svg::resize(qreal width, qreal height) |
694 | { |
695 | resize(QSize(width, height)); |
696 | } |
697 | |
698 | void Svg::resize(const QSizeF &size) |
699 | { |
700 | if (qFuzzyCompare(size.width(), d->size.width()) && |
701 | qFuzzyCompare(size.height(), d->size.height())) { |
702 | return; |
703 | } |
704 | |
705 | d->size = size; |
706 | d->localRectCache.clear(); |
707 | emit sizeChanged(); |
708 | } |
709 | |
710 | void Svg::resize() |
711 | { |
712 | if (qFuzzyCompare(d->naturalSize.width(), d->size.width()) && |
713 | qFuzzyCompare(d->naturalSize.height(), d->size.height())) { |
714 | return; |
715 | } |
716 | |
717 | d->size = d->naturalSize; |
718 | d->localRectCache.clear(); |
719 | emit sizeChanged(); |
720 | } |
721 | |
722 | QSize Svg::elementSize(const QString &elementId) const |
723 | { |
724 | return d->elementRect(elementId).size().toSize(); |
725 | } |
726 | |
727 | QRectF Svg::elementRect(const QString &elementId) const |
728 | { |
729 | return d->elementRect(elementId); |
730 | } |
731 | |
732 | bool Svg::hasElement(const QString &elementId) const |
733 | { |
734 | if (d->path.isNull() && d->themePath.isNull()) { |
735 | return false; |
736 | } |
737 | |
738 | return d->elementRect(elementId).isValid(); |
739 | } |
740 | |
741 | QString Svg::elementAtPoint(const QPoint &point) const |
742 | { |
743 | Q_UNUSED(point) |
744 | |
745 | return QString(); |
746 | /* |
747 | FIXME: implement when Qt can support us! |
748 | d->createRenderer(); |
749 | QSizeF naturalSize = d->renderer->defaultSize(); |
750 | qreal dx = d->size.width() / naturalSize.width(); |
751 | qreal dy = d->size.height() / naturalSize.height(); |
752 | //kDebug() << point << "is really" |
753 | // << QPoint(point.x() *dx, naturalSize.height() - point.y() * dy); |
754 | |
755 | return QString(); // d->renderer->elementAtPoint(QPoint(point.x() *dx, naturalSize.height() - point.y() * dy)); |
756 | */ |
757 | } |
758 | |
759 | bool Svg::isValid() const |
760 | { |
761 | if (d->path.isNull() && d->themePath.isNull()) { |
762 | return false; |
763 | } |
764 | |
765 | d->createRenderer(); |
766 | return d->renderer->isValid(); |
767 | } |
768 | |
769 | void Svg::setContainsMultipleImages(bool multiple) |
770 | { |
771 | d->multipleImages = multiple; |
772 | } |
773 | |
774 | bool Svg::containsMultipleImages() const |
775 | { |
776 | return d->multipleImages; |
777 | } |
778 | |
779 | void Svg::setImagePath(const QString &svgFilePath) |
780 | { |
781 | //BIC FIXME: setImagePath should be virtual, or call an internal virtual protected method |
782 | if (FrameSvg *frame = qobject_cast<FrameSvg *>(this)) { |
783 | frame->setImagePath(svgFilePath); |
784 | return; |
785 | } |
786 | |
787 | d->setImagePath(svgFilePath); |
788 | //kDebug() << "repaintNeeded"; |
789 | emit repaintNeeded(); |
790 | } |
791 | |
792 | QString Svg::imagePath() const |
793 | { |
794 | return d->themed ? d->themePath : d->path; |
795 | } |
796 | |
797 | void Svg::setUsingRenderingCache(bool useCache) |
798 | { |
799 | d->cacheRendering = useCache; |
800 | } |
801 | |
802 | bool Svg::isUsingRenderingCache() const |
803 | { |
804 | return d->cacheRendering; |
805 | } |
806 | |
807 | void Svg::setTheme(Plasma::Theme *theme) |
808 | { |
809 | if (!theme || theme == d->theme.data()) { |
810 | return; |
811 | } |
812 | |
813 | if (d->theme) { |
814 | disconnect(d->theme.data(), 0, this, 0); |
815 | } |
816 | |
817 | d->theme = theme; |
818 | connect(theme, SIGNAL(themeChanged()), this, SLOT(themeChanged())); |
819 | d->themeChanged(); |
820 | } |
821 | |
822 | Theme *Svg::theme() const |
823 | { |
824 | return d->theme ? d->theme.data() : Theme::defaultTheme(); |
825 | } |
826 | |
827 | } // Plasma namespace |
828 | |
829 | #include "svg.moc" |
830 | |
831 | |