1/*
2 * Copyright 2008-2010 by 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 "framesvg.h"
22#include "private/framesvg_p.h"
23
24#include <QAtomicInt>
25#include <QBitmap>
26#include <QCryptographicHash>
27#include <QPainter>
28#include <QRegion>
29#include <QSize>
30#include <QStringBuilder>
31#include <QTimer>
32
33#include <kdebug.h>
34
35#include <applet.h>
36#include <theme.h>
37#include <private/svg_p.h>
38
39namespace Plasma
40{
41
42
43QHash<QString, FrameData *> FrameSvgPrivate::s_sharedFrames;
44
45// Any attempt to generate a frame whose width or height is larger than this
46// will be rejected
47static const int MAX_FRAME_SIZE = 100000;
48
49FrameSvg::FrameSvg(QObject *parent)
50 : Svg(parent),
51 d(new FrameSvgPrivate(this))
52{
53 connect(this, SIGNAL(repaintNeeded()), this, SLOT(updateNeeded()));
54 d->frames.insert(QString(), new FrameData(this));
55}
56
57FrameSvg::~FrameSvg()
58{
59 delete d;
60}
61
62void FrameSvg::setImagePath(const QString &path)
63{
64 if (path == imagePath()) {
65 return;
66 }
67
68 bool updateNeeded = true;
69 clearCache();
70
71 FrameData *fd = d->frames[d->prefix];
72 if (fd->refcount() == 1) {
73 // we're the only user of it, let's remove it from the shared keys
74 // we don't want to deref it, however, as we'll still be using it
75 const QString oldKey = d->cacheId(fd, d->prefix);
76 FrameSvgPrivate::s_sharedFrames.remove(oldKey);
77 } else {
78 // others are using this frame, so deref it for ourselves
79 fd->deref(this);
80 fd = 0;
81 }
82
83 Svg::d->setImagePath(path);
84
85 if (!fd) {
86 // we need to replace our frame, start by looking in the frame cache
87 FrameData *oldFd = d->frames[d->prefix];
88 const QString key = d->cacheId(oldFd, d->prefix);
89 fd = FrameSvgPrivate::s_sharedFrames.value(key);
90
91 if (fd) {
92 // we found one, so ref it and use it; we also don't need to (or want to!)
93 // trigger a full update of the frame since it is already the one we want
94 // and likely already rendered just fine
95 fd->ref(this);
96 updateNeeded = false;
97 } else {
98 // nothing exists for us in the cache, so create a new FrameData based
99 // on the old one
100 fd = new FrameData(*oldFd, this);
101 }
102
103 d->frames.insert(d->prefix, fd);
104 }
105
106 setContainsMultipleImages(true);
107 if (updateNeeded) {
108 // ensure our frame is in the cache
109 const QString key = d->cacheId(fd, d->prefix);
110 FrameSvgPrivate::s_sharedFrames.insert(key, fd);
111
112 // this will emit repaintNeeded() as well when it is done
113 d->updateAndSignalSizes();
114 } else {
115 emit repaintNeeded();
116 }
117}
118
119void FrameSvg::setEnabledBorders(const EnabledBorders borders)
120{
121 if (borders == d->frames[d->prefix]->enabledBorders) {
122 return;
123 }
124
125 FrameData *fd = d->frames[d->prefix];
126
127 const QString oldKey = d->cacheId(fd, d->prefix);
128 const EnabledBorders oldBorders = fd->enabledBorders;
129 fd->enabledBorders = borders;
130 const QString newKey = d->cacheId(fd, d->prefix);
131 fd->enabledBorders = oldBorders;
132
133 //kDebug() << "looking for" << newKey;
134 FrameData *newFd = FrameSvgPrivate::s_sharedFrames.value(newKey);
135 if (newFd) {
136 //kDebug() << "FOUND IT!" << newFd->refcount;
137 // we've found a math, so insert that new one and ref it ..
138 newFd->ref(this);
139 d->frames.insert(d->prefix, newFd);
140
141 //.. then deref the old one and if it's no longer used, get rid of it
142 if (fd->deref(this)) {
143 //const QString oldKey = d->cacheId(fd, d->prefix);
144 //kDebug() << "1. Removing it" << oldKey << fd->refcount;
145 FrameSvgPrivate::s_sharedFrames.remove(oldKey);
146 delete fd;
147 }
148
149 return;
150 }
151
152 if (fd->refcount() == 1) {
153 // we're the only user of it, let's remove it from the shared keys
154 // we don't want to deref it, however, as we'll still be using it
155 FrameSvgPrivate::s_sharedFrames.remove(oldKey);
156 } else {
157 // others are using it, but we wish to change its size. so deref it,
158 // then create a copy of it (we're automatically ref'd via the ctor),
159 // then insert it into our frames.
160 fd->deref(this);
161 fd = new FrameData(*fd, this);
162 d->frames.insert(d->prefix, fd);
163 }
164
165 fd->enabledBorders = borders;
166 d->updateAndSignalSizes();
167}
168
169FrameSvg::EnabledBorders FrameSvg::enabledBorders() const
170{
171 if (d->frames.isEmpty()) {
172 return NoBorder;
173 }
174
175 QHash<QString, FrameData*>::const_iterator it = d->frames.constFind(d->prefix);
176
177 if (it != d->frames.constEnd()) {
178 return it.value()->enabledBorders;
179 } else {
180 return NoBorder;
181 }
182}
183
184void FrameSvg::setElementPrefix(Plasma::Location location)
185{
186 switch (location) {
187 case TopEdge:
188 setElementPrefix("north");
189 break;
190 case BottomEdge:
191 setElementPrefix("south");
192 break;
193 case LeftEdge:
194 setElementPrefix("west");
195 break;
196 case RightEdge:
197 setElementPrefix("east");
198 break;
199 default:
200 setElementPrefix(QString());
201 break;
202 }
203
204 d->location = location;
205}
206
207void FrameSvg::setElementPrefix(const QString &prefix)
208{
209 const QString oldPrefix(d->prefix);
210
211 if (!hasElement(prefix % "-center")) {
212 d->prefix.clear();
213 } else {
214 d->prefix = prefix;
215 if (!d->prefix.isEmpty()) {
216 d->prefix += '-';
217 }
218 }
219
220 FrameData *oldFrameData = d->frames.value(oldPrefix);
221 if (oldPrefix == d->prefix && oldFrameData) {
222 return;
223 }
224
225 if (!d->frames.contains(d->prefix)) {
226 if (oldFrameData) {
227 FrameData *newFd = 0;
228 if (!oldFrameData->frameSize.isEmpty()) {
229 const QString key = d->cacheId(oldFrameData, d->prefix);
230 newFd = FrameSvgPrivate::s_sharedFrames.value(key);
231 }
232
233 // we need to put this in the cache if we didn't find it in the shared frames
234 // and we have a size; if we don't have a size, we'll catch it later
235 const bool cache = !newFd && !oldFrameData->frameSize.isEmpty();
236 if (newFd) {
237 newFd->ref(this);
238 } else {
239 newFd = new FrameData(*oldFrameData, this);
240 }
241
242 d->frames.insert(d->prefix, newFd);
243
244 if (cache) {
245 // we have to cache after inserting the frame since the cacheId requires the
246 // frame to be in the frames collection already
247 const QString key = d->cacheId(oldFrameData, d->prefix);
248 //kDebug() << this << " 1. inserting as" << key;
249
250 FrameSvgPrivate::s_sharedFrames.insert(key, newFd);
251 }
252 } else {
253 // couldn't find anything useful, so we just create something here
254 // we don't have a size for it yet, so don't bother trying to share it just yet
255 FrameData *newFd = new FrameData(this);
256 d->frames.insert(d->prefix, newFd);
257 }
258
259 d->updateSizes();
260 }
261
262 if (!d->cacheAll) {
263 d->frames.remove(oldPrefix);
264 if (oldFrameData) {
265 if (oldFrameData->deref(this)) {
266 const QString oldKey = d->cacheId(oldFrameData, oldPrefix);
267 FrameSvgPrivate::s_sharedFrames.remove(oldKey);
268 delete oldFrameData;
269 }
270 }
271 }
272
273 d->location = Floating;
274}
275
276bool FrameSvg::hasElementPrefix(const QString & prefix) const
277{
278 //for now it simply checks if a center element exists,
279 //because it could make sense for certain themes to not have all the elements
280 if (prefix.isEmpty()) {
281 return hasElement("center");
282 } else {
283 return hasElement(prefix % "-center");
284 }
285}
286
287bool FrameSvg::hasElementPrefix(Plasma::Location location) const
288{
289 switch (location) {
290 case TopEdge:
291 return hasElementPrefix("north");
292 break;
293 case BottomEdge:
294 return hasElementPrefix("south");
295 break;
296 case LeftEdge:
297 return hasElementPrefix("west");
298 break;
299 case RightEdge:
300 return hasElementPrefix("east");
301 break;
302 default:
303 return hasElementPrefix(QString());
304 break;
305 }
306}
307
308QString FrameSvg::prefix()
309{
310 if (d->prefix.isEmpty()) {
311 return d->prefix;
312 }
313
314 return d->prefix.left(d->prefix.size() - 1);
315}
316
317void FrameSvg::resizeFrame(const QSizeF &size)
318{
319 if (imagePath().isEmpty()) {
320 return;
321 }
322
323 if (size.isEmpty()) {
324 //kDebug() << "Invalid size" << size;
325 return;
326 }
327
328 FrameData *fd = d->frames[d->prefix];
329 if (size == fd->frameSize) {
330 return;
331 }
332
333 const QString oldKey = d->cacheId(fd, d->prefix);
334 const QSize currentSize = fd->frameSize;
335 fd->frameSize = size.toSize();
336 const QString newKey = d->cacheId(fd, d->prefix);
337 fd->frameSize = currentSize;
338
339 //kDebug() << "looking for" << newKey;
340 FrameData *newFd = FrameSvgPrivate::s_sharedFrames.value(newKey);
341 if (newFd) {
342 //kDebug() << "FOUND IT!" << newFd->refcount;
343 // we've found a math, so insert that new one and ref it ..
344 newFd->ref(this);
345 d->frames.insert(d->prefix, newFd);
346
347 //.. then deref the old one and if it's no longer used, get rid of it
348 if (fd->deref(this)) {
349 //const QString oldKey = d->cacheId(fd, d->prefix);
350 //kDebug() << "1. Removing it" << oldKey << fd->refcount;
351 FrameSvgPrivate::s_sharedFrames.remove(oldKey);
352 delete fd;
353 }
354
355 return;
356 }
357
358 if (fd->refcount() == 1) {
359 // we're the only user of it, let's remove it from the shared keys
360 // we don't want to deref it, however, as we'll still be using it
361 FrameSvgPrivate::s_sharedFrames.remove(oldKey);
362 } else {
363 // others are using it, but we wish to change its size. so deref it,
364 // then create a copy of it (we're automatically ref'd via the ctor),
365 // then insert it into our frames.
366 fd->deref(this);
367 fd = new FrameData(*fd, this);
368 d->frames.insert(d->prefix, fd);
369 }
370
371 d->updateSizes();
372 fd->frameSize = size.toSize();
373 // we know it isn't in s_sharedFrames due to the check above, so insert it now
374 FrameSvgPrivate::s_sharedFrames.insert(newKey, fd);
375}
376
377QSizeF FrameSvg::frameSize() const
378{
379 QHash<QString, FrameData*>::const_iterator it = d->frames.constFind(d->prefix);
380
381 if (it == d->frames.constEnd()) {
382 return QSize(-1, -1);
383 } else {
384 return d->frameSize(it.value());
385 }
386}
387
388qreal FrameSvg::marginSize(const Plasma::MarginEdge edge) const
389{
390 if (d->frames[d->prefix]->noBorderPadding) {
391 return .0;
392 }
393
394 switch (edge) {
395 case Plasma::TopMargin:
396 return d->frames[d->prefix]->topMargin;
397 break;
398
399 case Plasma::LeftMargin:
400 return d->frames[d->prefix]->leftMargin;
401 break;
402
403 case Plasma::RightMargin:
404 return d->frames[d->prefix]->rightMargin;
405 break;
406
407 //Plasma::BottomMargin
408 default:
409 return d->frames[d->prefix]->bottomMargin;
410 break;
411 }
412}
413
414void FrameSvg::getMargins(qreal &left, qreal &top, qreal &right, qreal &bottom) const
415{
416 FrameData *frame = d->frames[d->prefix];
417
418 if (frame->noBorderPadding) {
419 left = top = right = bottom = 0;
420 return;
421 }
422
423 top = frame->topMargin;
424 left = frame->leftMargin;
425 right = frame->rightMargin;
426 bottom = frame->bottomMargin;
427}
428
429QRectF FrameSvg::contentsRect() const
430{
431 QSizeF size(frameSize());
432
433 if (size.isValid()) {
434 QRectF rect(QPointF(0, 0), size);
435 FrameData *frame = d->frames[d->prefix];
436
437 return rect.adjusted(frame->leftMargin, frame->topMargin,
438 -frame->rightMargin, -frame->bottomMargin);
439 } else {
440 return QRectF();
441 }
442}
443
444QPixmap FrameSvg::alphaMask() const
445{
446 //FIXME: the distinction between overlay and
447 return d->alphaMask();
448}
449
450QRegion FrameSvg::mask() const
451{
452 FrameData *frame = d->frames[d->prefix];
453 QString id = d->cacheId(frame, QString());
454 if (!frame->cachedMasks.contains(id)) {
455 //TODO: Implement a better way to cap the number of cached masks
456 if (frame->cachedMasks.count() > frame->MAX_CACHED_MASKS) {
457 frame->cachedMasks.clear();
458 }
459 frame->cachedMasks.insert(id, QRegion(QBitmap(d->alphaMask().alphaChannel().createMaskFromColor(Qt::black))));
460 }
461 return frame->cachedMasks[id];
462}
463
464void FrameSvg::setCacheAllRenderedFrames(bool cache)
465{
466 if (d->cacheAll && !cache) {
467 clearCache();
468 }
469
470 d->cacheAll = cache;
471}
472
473bool FrameSvg::cacheAllRenderedFrames() const
474{
475 return d->cacheAll;
476}
477
478void FrameSvg::clearCache()
479{
480 FrameData *frame = d->frames[d->prefix];
481
482 // delete all the frames that aren't this one
483 QMutableHashIterator<QString, FrameData*> it(d->frames);
484 while (it.hasNext()) {
485 FrameData *p = it.next().value();
486 if (frame != p) {
487 //TODO: should we clear from the Theme pixmap cache as well?
488 if (p->deref(this)) {
489 const QString key = d->cacheId(p, it.key());
490 FrameSvgPrivate::s_sharedFrames.remove(key);
491 p->cachedBackground = QPixmap();
492 }
493
494 it.remove();
495 }
496 }
497}
498
499QPixmap FrameSvg::framePixmap()
500{
501 FrameData *frame = d->frames[d->prefix];
502 if (frame->cachedBackground.isNull()) {
503 d->generateBackground(frame);
504 if (frame->cachedBackground.isNull()) {
505 return QPixmap();
506 }
507 }
508
509 return frame->cachedBackground;
510}
511
512void FrameSvg::paintFrame(QPainter *painter, const QRectF &target, const QRectF &source)
513{
514 FrameData *frame = d->frames[d->prefix];
515 if (frame->cachedBackground.isNull()) {
516 d->generateBackground(frame);
517 if (frame->cachedBackground.isNull()) {
518 return;
519 }
520 }
521
522 painter->drawPixmap(target, frame->cachedBackground, source.isValid() ? source : target);
523}
524
525void FrameSvg::paintFrame(QPainter *painter, const QPointF &pos)
526{
527 FrameData *frame = d->frames[d->prefix];
528 if (frame->cachedBackground.isNull()) {
529 d->generateBackground(frame);
530 if (frame->cachedBackground.isNull()) {
531 return;
532 }
533 }
534
535 painter->drawPixmap(pos, frame->cachedBackground);
536}
537
538//#define DEBUG_FRAMESVG_CACHE
539FrameSvgPrivate::~FrameSvgPrivate()
540{
541#ifdef DEBUG_FRAMESVG_CACHE
542 kDebug() << "*************" << q << q->imagePath() << "****************";
543#endif
544
545 QHashIterator<QString, FrameData *> it(frames);
546 while (it.hasNext()) {
547 it.next();
548 if (it.value()) {
549 // we remove all references from this widget to the frame, and delete it if we're the
550 // last user
551 if (it.value()->removeRefs(q)) {
552 const QString key = cacheId(it.value(), it.key());
553#ifdef DEBUG_FRAMESVG_CACHE
554 kDebug() << "2. Removing it" << key << it.value() << it.value()->refcount() << s_sharedFrames.contains(key);
555#endif
556 s_sharedFrames.remove(key);
557 delete it.value();
558 }
559#ifdef DEBUG_FRAMESVG_CACHE
560 else {
561 kDebug() << "still shared:" << cacheId(it.value(), it.key()) << it.value() << it.value()->refcount() << it.value()->isUsed();
562 }
563 } else {
564 kDebug() << "lost our value for" << it.key();
565#endif
566 }
567 }
568
569#ifdef DEBUG_FRAMESVG_CACHE
570 QHashIterator<QString, FrameData *> it2(s_sharedFrames);
571 int shares = 0;
572 while (it2.hasNext()) {
573 it2.next();
574 const int rc = it2.value()->refcount();
575 if (rc == 0) {
576 kDebug() << " LOST!" << it2.key() << rc << it2.value();// << it2.value()->references;
577 } else {
578 kDebug() << " " << it2.key() << rc << it2.value();
579 foreach (FrameSvg *data, it2.value()->references.keys()) {
580 kDebug( )<< " " << (void*)data << it2.value()->references[data];
581 }
582 shares += rc - 1;
583 }
584 }
585 kDebug() << "#####################################" << s_sharedFrames.count() << ", pixmaps saved:" << shares;
586#endif
587
588 frames.clear();
589}
590
591QPixmap FrameSvgPrivate::alphaMask()
592{
593 FrameData *frame = frames[prefix];
594 QString maskPrefix;
595
596 if (q->hasElement("mask-" % prefix % "center")) {
597 maskPrefix = "mask-";
598 }
599
600 if (maskPrefix.isNull()) {
601 if (frame->cachedBackground.isNull()) {
602 generateBackground(frame);
603 if (frame->cachedBackground.isNull()) {
604 return QPixmap();
605 }
606 }
607
608 return frame->cachedBackground;
609 } else {
610 QString oldPrefix = prefix;
611
612 // We are setting the prefix only temporary to generate
613 // the needed mask image
614 prefix = maskPrefix % oldPrefix;
615
616 if (!frames.contains(prefix)) {
617 const QString key = cacheId(frame, prefix);
618 // see if we can find a suitable candidate in the shared frames
619 // if successful, ref and insert, otherwise create a new one
620 // and insert that into both the shared frames and our frames.
621 FrameData *maskFrame = s_sharedFrames.value(key);
622
623 if (maskFrame) {
624 maskFrame->ref(q);
625 } else {
626 maskFrame = new FrameData(*frame, q);
627 s_sharedFrames.insert(key, maskFrame);
628 }
629
630 frames.insert(prefix, maskFrame);
631 updateSizes();
632 }
633
634 FrameData *maskFrame = frames[prefix];
635 if (maskFrame->cachedBackground.isNull() || maskFrame->frameSize != frameSize(frame)) {
636 const QString oldKey = cacheId(maskFrame, prefix);
637 maskFrame->frameSize = frameSize(frame).toSize();
638 const QString newKey = cacheId(maskFrame, prefix);
639 if (s_sharedFrames.contains(oldKey)) {
640 s_sharedFrames.remove(oldKey);
641 s_sharedFrames.insert(newKey, maskFrame);
642 }
643
644 maskFrame->cachedBackground = QPixmap();
645
646 generateBackground(maskFrame);
647 if (maskFrame->cachedBackground.isNull()) {
648 return QPixmap();
649 }
650 }
651
652 prefix = oldPrefix;
653 return maskFrame->cachedBackground;
654 }
655}
656
657void FrameSvgPrivate::generateBackground(FrameData *frame)
658{
659 if (!frame->cachedBackground.isNull() || !q->hasElementPrefix(q->prefix())) {
660 return;
661 }
662
663 const QString id = cacheId(frame, prefix);
664
665 Theme *theme = Theme::defaultTheme();
666 bool frameCached = !frame->cachedBackground.isNull();
667 bool overlayCached = false;
668 const bool overlayAvailable = !prefix.startsWith(QLatin1String("mask-")) && q->hasElement(prefix % "overlay");
669 QPixmap overlay;
670 if (q->isUsingRenderingCache()) {
671 frameCached = theme->findInCache(id, frame->cachedBackground) && !frame->cachedBackground.isNull();
672
673 if (overlayAvailable) {
674 overlayCached = theme->findInCache("overlay_" % id, overlay) && !overlay.isNull();
675 }
676 }
677
678 if (!frameCached) {
679 generateFrameBackground(frame);
680 }
681
682 //Overlays
683 QSize overlaySize;
684 QPoint actualOverlayPos = QPoint(0, 0);
685 if (overlayAvailable && !overlayCached) {
686 QPoint pos = QPoint(0, 0);
687 overlaySize = q->elementSize(prefix % "overlay");
688
689 //Random pos, stretched and tiled are mutually exclusive
690 if (q->hasElement(prefix % "hint-overlay-random-pos")) {
691 actualOverlayPos = overlayPos;
692 } else if (q->hasElement(prefix % "hint-overlay-pos-right")) {
693 actualOverlayPos.setX(frame->frameSize.width() - overlaySize.width());
694 } else if (q->hasElement(prefix % "hint-overlay-pos-bottom")) {
695 actualOverlayPos.setY(frame->frameSize.height() - overlaySize.height());
696 //Stretched or Tiled?
697 } else if (q->hasElement(prefix % "hint-overlay-stretch")) {
698 overlaySize = frameSize(frame).toSize();
699 } else {
700 if (q->hasElement(prefix % "hint-overlay-tile-horizontal")) {
701 overlaySize.setWidth(frameSize(frame).width());
702 }
703 if (q->hasElement(prefix % "hint-overlay-tile-vertical")) {
704 overlaySize.setHeight(frameSize(frame).height());
705 }
706 }
707
708 overlay = alphaMask();
709 QPainter overlayPainter(&overlay);
710 overlayPainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
711 //Tiling?
712 if (q->hasElement(prefix % "hint-overlay-tile-horizontal") ||
713 q->hasElement(prefix % "hint-overlay-tile-vertical")) {
714
715 QSize s = q->size();
716 q->resize(q->elementSize(prefix % "overlay"));
717
718 overlayPainter.drawTiledPixmap(QRect(QPoint(0,0), overlaySize), q->pixmap(prefix % "overlay"));
719 q->resize(s);
720 } else {
721 q->paint(&overlayPainter, QRect(actualOverlayPos, overlaySize), prefix % "overlay");
722 }
723
724 overlayPainter.end();
725 }
726
727 if (!frameCached) {
728 cacheFrame(prefix, frame->cachedBackground, overlayCached ? overlay : QPixmap());
729 }
730
731 if (!overlay.isNull()) {
732 QPainter p(&frame->cachedBackground);
733 p.setCompositionMode(QPainter::CompositionMode_SourceOver);
734 p.drawPixmap(actualOverlayPos, overlay, QRect(actualOverlayPos, overlaySize));
735 }
736}
737
738void FrameSvgPrivate::generateFrameBackground(FrameData *frame)
739{
740 //kDebug() << "generating background";
741 const QSizeF size = frameSize(frame);
742 const int topWidth = q->elementSize(prefix % "top").width();
743 const int leftHeight = q->elementSize(prefix % "left").height();
744 const int topOffset = 0;
745 const int leftOffset = 0;
746
747
748 if (!size.isValid()) {
749 kDebug() << "Invalid frame size" << size;
750 return;
751 }
752 if (size.width() >= MAX_FRAME_SIZE || size.height() >= MAX_FRAME_SIZE) {
753 kWarning() << "Not generating frame background for a size whose width or height is more than" << MAX_FRAME_SIZE << size;
754 return;
755 }
756
757 const int contentWidth = size.width() - frame->leftWidth - frame->rightWidth;
758 const int contentHeight = size.height() - frame->topHeight - frame->bottomHeight;
759 int contentTop = 0;
760 int contentLeft = 0;
761 int rightOffset = contentWidth;
762 int bottomOffset = contentHeight;
763
764 frame->cachedBackground = QPixmap(frame->leftWidth + contentWidth + frame->rightWidth,
765 frame->topHeight + contentHeight + frame->bottomHeight);
766 frame->cachedBackground.fill(Qt::transparent);
767 QPainter p(&frame->cachedBackground);
768 p.setCompositionMode(QPainter::CompositionMode_Source);
769 p.setRenderHint(QPainter::SmoothPixmapTransform);
770
771 //CENTER
772 if (frame->tileCenter) {
773 if (contentHeight > 0 && contentWidth > 0) {
774 const int centerTileHeight = q->elementSize(prefix % "center").height();
775 const int centerTileWidth = q->elementSize(prefix % "center").width();
776 QPixmap center(centerTileWidth, centerTileHeight);
777 center.fill(Qt::transparent);
778
779 {
780 QPainter centerPainter(&center);
781 centerPainter.setCompositionMode(QPainter::CompositionMode_Source);
782 q->paint(&centerPainter, QRect(QPoint(0, 0), q->elementSize(prefix % "center")), prefix % "center");
783 }
784
785 if (frame->composeOverBorder) {
786 p.drawTiledPixmap(QRect(QPoint(0, 0), size.toSize()), center);
787 } else {
788 p.drawTiledPixmap(QRect(frame->leftWidth, frame->topHeight,
789 contentWidth, contentHeight), center);
790 }
791 }
792 } else {
793 if (contentHeight > 0 && contentWidth > 0) {
794 if (frame->composeOverBorder) {
795 q->paint(&p, QRect(QPoint(0, 0), size.toSize()),
796 prefix % "center");
797 } else {
798 q->paint(&p, QRect(frame->leftWidth, frame->topHeight,
799 contentWidth, contentHeight),
800 prefix % "center");
801 }
802 }
803 }
804
805 if (frame->composeOverBorder) {
806 p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
807 p.drawPixmap(QRect(QPoint(0, 0), size.toSize()), alphaMask());
808 p.setCompositionMode(QPainter::CompositionMode_SourceOver);
809 }
810
811 if (frame->enabledBorders & FrameSvg::LeftBorder && q->hasElement(prefix % "left")) {
812 rightOffset += frame->leftWidth;
813 }
814
815 // Corners
816 if (frame->enabledBorders & FrameSvg::TopBorder && q->hasElement(prefix % "top")) {
817 contentTop = frame->topHeight;
818 bottomOffset += frame->topHeight;
819
820 if (q->hasElement(prefix % "topleft") && frame->enabledBorders & FrameSvg::LeftBorder) {
821 q->paint(&p, QRect(leftOffset, topOffset, frame->leftWidth, frame->topHeight), prefix % "topleft");
822
823 contentLeft = frame->leftWidth;
824 }
825
826 if (q->hasElement(prefix % "topright") && frame->enabledBorders & FrameSvg::RightBorder) {
827 q->paint(&p, QRect(rightOffset, topOffset, frame->rightWidth, frame->topHeight), prefix % "topright");
828 }
829 }
830
831 if (frame->enabledBorders & FrameSvg::BottomBorder && q->hasElement(prefix % "bottom")) {
832 if (q->hasElement(prefix % "bottomleft") && frame->enabledBorders & FrameSvg::LeftBorder) {
833 q->paint(&p, QRect(leftOffset, bottomOffset, frame->leftWidth, frame->bottomHeight), prefix % "bottomleft");
834
835 contentLeft = frame->leftWidth;
836 }
837
838 if (frame->enabledBorders & FrameSvg::RightBorder && q->hasElement(prefix % "bottomright")) {
839 q->paint(&p, QRect(rightOffset, bottomOffset, frame->rightWidth, frame->bottomHeight), prefix % "bottomright");
840 }
841 }
842
843 // Sides
844 if (frame->stretchBorders) {
845 if (frame->enabledBorders & FrameSvg::LeftBorder || frame->enabledBorders & FrameSvg::RightBorder) {
846 if (q->hasElement(prefix % "left") &&
847 frame->enabledBorders & FrameSvg::LeftBorder &&
848 contentHeight > 0) {
849 q->paint(&p, QRect(leftOffset, contentTop, frame->leftWidth, contentHeight), prefix % "left");
850 }
851
852 if (q->hasElement(prefix % "right") &&
853 frame->enabledBorders & FrameSvg::RightBorder &&
854 contentHeight > 0) {
855 q->paint(&p, QRect(rightOffset, contentTop, frame->rightWidth, contentHeight), prefix % "right");
856 }
857 }
858
859 if (frame->enabledBorders & FrameSvg::TopBorder || frame->enabledBorders & FrameSvg::BottomBorder) {
860 if (frame->enabledBorders & FrameSvg::TopBorder && q->hasElement(prefix % "top") &&
861 contentWidth > 0) {
862 q->paint(&p, QRect(contentLeft, topOffset, contentWidth, frame->topHeight), prefix % "top");
863 }
864
865 if (frame->enabledBorders & FrameSvg::BottomBorder && q->hasElement(prefix % "bottom") &&
866 contentWidth > 0) {
867 q->paint(&p, QRect(contentLeft, bottomOffset, contentWidth, frame->bottomHeight), prefix % "bottom");
868 }
869 }
870 } else {
871 if (frame->enabledBorders & FrameSvg::LeftBorder && q->hasElement(prefix % "left")
872 && leftHeight > 0 && frame->leftWidth > 0) {
873 QPixmap left(frame->leftWidth, leftHeight);
874 left.fill(Qt::transparent);
875
876 QPainter sidePainter(&left);
877 sidePainter.setCompositionMode(QPainter::CompositionMode_Source);
878 q->paint(&sidePainter, QRect(QPoint(0, 0), left.size()), prefix % "left");
879
880 p.drawTiledPixmap(QRect(leftOffset, contentTop, frame->leftWidth, contentHeight), left);
881 }
882
883 if (frame->enabledBorders & FrameSvg::RightBorder && q->hasElement(prefix % "right") &&
884 leftHeight > 0 && frame->rightWidth > 0) {
885 QPixmap right(frame->rightWidth, leftHeight);
886 right.fill(Qt::transparent);
887
888 QPainter sidePainter(&right);
889 sidePainter.setCompositionMode(QPainter::CompositionMode_Source);
890 q->paint(&sidePainter, QRect(QPoint(0, 0), right.size()), prefix % "right");
891
892 p.drawTiledPixmap(QRect(rightOffset, contentTop, frame->rightWidth, contentHeight), right);
893 }
894
895 if (frame->enabledBorders & FrameSvg::TopBorder && q->hasElement(prefix % "top")
896 && topWidth > 0 && frame->topHeight > 0) {
897 QPixmap top(topWidth, frame->topHeight);
898 top.fill(Qt::transparent);
899
900 QPainter sidePainter(&top);
901 sidePainter.setCompositionMode(QPainter::CompositionMode_Source);
902 q->paint(&sidePainter, QRect(QPoint(0, 0), top.size()), prefix % "top");
903
904 p.drawTiledPixmap(QRect(contentLeft, topOffset, contentWidth, frame->topHeight), top);
905 }
906
907 if (frame->enabledBorders & FrameSvg::BottomBorder && q->hasElement(prefix % "bottom")
908 && topWidth > 0 && frame->bottomHeight > 0) {
909 QPixmap bottom(topWidth, frame->bottomHeight);
910 bottom.fill(Qt::transparent);
911
912 QPainter sidePainter(&bottom);
913 sidePainter.setCompositionMode(QPainter::CompositionMode_Source);
914 q->paint(&sidePainter, QRect(QPoint(0, 0), bottom.size()), prefix % "bottom");
915
916 p.drawTiledPixmap(QRect(contentLeft, bottomOffset, contentWidth, frame->bottomHeight), bottom);
917 }
918 }
919
920}
921
922QString FrameSvgPrivate::cacheId(FrameData *frame, const QString &prefixToSave) const
923{
924 const QSize size = frameSize(frame).toSize();
925 const QLatin1Char s('_');
926 return QString::number(frame->enabledBorders) % s % QString::number(size.width()) % s % QString::number(size.height()) % s % prefixToSave % s % q->imagePath();
927}
928
929void FrameSvgPrivate::cacheFrame(const QString &prefixToSave, const QPixmap &background, const QPixmap &overlay)
930{
931 if (!q->isUsingRenderingCache()) {
932 return;
933 }
934
935 //insert background
936 FrameData *frame = frames.value(prefixToSave);
937
938 if (!frame) {
939 return;
940 }
941
942 const QString id = cacheId(frame, prefixToSave);
943
944 //kDebug()<<"Saving to cache frame"<<id;
945
946 Theme::defaultTheme()->insertIntoCache(id, background, QString::number((qint64)q, 16) % prefixToSave);
947
948 if (!overlay.isNull()) {
949 //insert overlay
950 Theme::defaultTheme()->insertIntoCache("overlay_" % id, overlay, QString::number((qint64)q, 16) % prefixToSave % "overlay");
951 }
952}
953
954void FrameSvgPrivate::updateSizes() const
955{
956 //kDebug() << "!!!!!!!!!!!!!!!!!!!!!! updating sizes" << prefix;
957 FrameData *frame = frames[prefix];
958 Q_ASSERT(frame);
959
960 QSize s = q->size();
961 q->resize();
962 frame->cachedBackground = QPixmap();
963
964 if (frame->enabledBorders & FrameSvg::TopBorder) {
965 frame->topHeight = q->elementSize(prefix % "top").height();
966
967 if (q->hasElement(prefix % "hint-top-margin")) {
968 frame->topMargin = q->elementSize(prefix % "hint-top-margin").height();
969 } else {
970 frame->topMargin = frame->topHeight;
971 }
972 } else {
973 frame->topMargin = frame->topHeight = 0;
974 }
975
976 if (frame->enabledBorders & FrameSvg::LeftBorder) {
977 frame->leftWidth = q->elementSize(prefix % "left").width();
978
979 if (q->hasElement(prefix % "hint-left-margin")) {
980 frame->leftMargin = q->elementSize(prefix % "hint-left-margin").width();
981 } else {
982 frame->leftMargin = frame->leftWidth;
983 }
984 } else {
985 frame->leftMargin = frame->leftWidth = 0;
986 }
987
988 if (frame->enabledBorders & FrameSvg::RightBorder) {
989 frame->rightWidth = q->elementSize(prefix % "right").width();
990
991 if (q->hasElement(prefix % "hint-right-margin")) {
992 frame->rightMargin = q->elementSize(prefix % "hint-right-margin").width();
993 } else {
994 frame->rightMargin = frame->rightWidth;
995 }
996 } else {
997 frame->rightMargin = frame->rightWidth = 0;
998 }
999
1000 if (frame->enabledBorders & FrameSvg::BottomBorder) {
1001 frame->bottomHeight = q->elementSize(prefix % "bottom").height();
1002
1003 if (q->hasElement(prefix % "hint-bottom-margin")) {
1004 frame->bottomMargin = q->elementSize(prefix % "hint-bottom-margin").height();
1005 } else {
1006 frame->bottomMargin = frame->bottomHeight;
1007 }
1008 } else {
1009 frame->bottomMargin = frame->bottomHeight = 0;
1010 }
1011
1012 frame->composeOverBorder = (q->hasElement(prefix % "hint-compose-over-border") &&
1013 q->hasElement("mask-" % prefix % "center"));
1014
1015 //since it's rectangular, topWidth and bottomWidth must be the same
1016 //the ones that don't have a prefix is for retrocompatibility
1017 frame->tileCenter = (q->hasElement("hint-tile-center") || q->hasElement(prefix % "hint-tile-center"));
1018 frame->noBorderPadding = (q->hasElement("hint-no-border-padding") || q->hasElement(prefix % "hint-no-border-padding"));
1019 frame->stretchBorders = (q->hasElement("hint-stretch-borders") || q->hasElement(prefix % "hint-stretch-borders"));
1020 q->resize(s);
1021}
1022
1023void FrameSvgPrivate::updateNeeded()
1024{
1025 q->clearCache();
1026 updateSizes();
1027}
1028
1029void FrameSvgPrivate::updateAndSignalSizes()
1030{
1031 updateSizes();
1032 emit q->repaintNeeded();
1033}
1034
1035QSizeF FrameSvgPrivate::frameSize(FrameData *frame) const
1036{
1037 if (!frame->frameSize.isValid()) {
1038 updateSizes();
1039 frame->frameSize = q->size();
1040 }
1041
1042 return frame->frameSize;
1043}
1044
1045void FrameData::ref(FrameSvg *svg)
1046{
1047 references[svg] = references[svg] + 1;
1048 //kDebug() << this << svg << references[svg];
1049}
1050
1051bool FrameData::deref(FrameSvg *svg)
1052{
1053 references[svg] = references[svg] - 1;
1054 //kDebug() << this << svg << references[svg];
1055 if (references[svg] < 1) {
1056 references.remove(svg);
1057 }
1058
1059 return references.isEmpty();
1060}
1061
1062bool FrameData::removeRefs(FrameSvg *svg)
1063{
1064 references.remove(svg);
1065 return references.isEmpty();
1066}
1067
1068bool FrameData::isUsed() const
1069{
1070 return !references.isEmpty();
1071}
1072
1073int FrameData::refcount() const
1074{
1075 return references.count();
1076}
1077
1078} // Plasma namespace
1079
1080#include "framesvg.moc"
1081