some more win32'fication to fix non-ascii filename handling
[kdelibs.git] / plasma / framesvg.cpp
blob3d4d62412679d6a311bb4b19f88703bfc3c06dd4
1 /*
2 * Copyright 2008 by Aaron Seigo <aseigo@kde.org>
3 * Copyright 2008 Marco Martin <notmart@gmail.com>
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.
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
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.
21 #include "framesvg.h"
22 #include "private/framesvg_p.h"
24 #include <QPainter>
25 #include <QSize>
26 #include <QBitmap>
27 #include <QRegion>
28 #include <QTimer>
29 #include <QCryptographicHash>
31 #include <kdebug.h>
33 #include <plasma/theme.h>
34 #include <plasma/applet.h>
36 namespace Plasma
39 FrameSvg::FrameSvg(QObject *parent)
40 : Svg(parent),
41 d(new FrameSvgPrivate(this))
43 connect(this, SIGNAL(repaintNeeded()), this, SLOT(updateNeeded()));
44 d->frames.insert(QString(), new FrameData());
46 d->saveTimer = new QTimer(this);
47 d->saveTimer->setSingleShot(true);
48 connect(d->saveTimer, SIGNAL(timeout()), this, SLOT(scheduledCacheUpdate()));
51 FrameSvg::~FrameSvg()
53 delete d;
56 void FrameSvg::setImagePath(const QString &path)
58 if (path == imagePath()) {
59 return;
62 Svg::setImagePath(path);
63 setContainsMultipleImages(true);
65 clearCache();
66 d->updateAndSignalSizes();
69 void FrameSvg::setEnabledBorders(const EnabledBorders borders)
71 if (borders == d->frames[d->prefix]->enabledBorders) {
72 return;
75 d->frames[d->prefix]->enabledBorders = borders;
76 d->updateAndSignalSizes();
79 FrameSvg::EnabledBorders FrameSvg::enabledBorders() const
81 QHash<QString, FrameData*>::const_iterator it = d->frames.constFind(d->prefix);
83 if (it != d->frames.constEnd()) {
84 return it.value()->enabledBorders;
85 } else {
86 return NoBorder;
90 void FrameSvg::setElementPrefix(Plasma::Location location)
92 switch (location) {
93 case TopEdge:
94 setElementPrefix("north");
95 break;
96 case BottomEdge:
97 setElementPrefix("south");
98 break;
99 case LeftEdge:
100 setElementPrefix("west");
101 break;
102 case RightEdge:
103 setElementPrefix("east");
104 break;
105 default:
106 setElementPrefix(QString());
107 break;
109 d->location = location;
112 void FrameSvg::setElementPrefix(const QString & prefix)
114 const QString oldPrefix(d->prefix);
116 if (!hasElement(prefix + "-center")) {
117 d->prefix.clear();
118 } else {
119 d->prefix = prefix;
120 if (!d->prefix.isEmpty()) {
121 d->prefix += '-';
126 if (oldPrefix == d->prefix && d->frames[oldPrefix]) {
127 return;
130 if (!d->frames.contains(d->prefix)) {
131 d->frames.insert(d->prefix, new FrameData(*(d->frames[oldPrefix])));
132 d->updateSizes();
135 if (!d->cacheAll) {
136 delete d->frames[oldPrefix];
137 d->framesToSave.removeAll(oldPrefix);
138 d->frames.remove(oldPrefix);
141 d->location = Floating;
144 bool FrameSvg::hasElementPrefix(const QString & prefix) const
146 //for now it simply checks if a center element exists,
147 //because it could make sense for certain themes to not have all the elements
148 if (prefix.isEmpty()) {
149 return hasElement("center");
150 } else {
151 return hasElement(prefix + "-center");
155 bool FrameSvg::hasElementPrefix(Plasma::Location location) const
157 switch (location) {
158 case TopEdge:
159 return hasElementPrefix("north");
160 break;
161 case BottomEdge:
162 return hasElementPrefix("south");
163 break;
164 case LeftEdge:
165 return hasElementPrefix("west");
166 break;
167 case RightEdge:
168 return hasElementPrefix("east");
169 break;
170 default:
171 return hasElementPrefix(QString());
172 break;
176 QString FrameSvg::prefix()
178 if (d->prefix.isEmpty()) {
179 return d->prefix;
182 return d->prefix.left(d->prefix.size() - 1);
185 void FrameSvg::resizeFrame(const QSizeF &size)
187 if (size.isEmpty()) {
188 kWarning() << "Invalid size" << size;
189 return;
192 if (size == d->frames[d->prefix]->frameSize) {
193 return;
196 d->updateSizes();
197 d->frames[d->prefix]->frameSize = size.toSize();
200 QSizeF FrameSvg::frameSize() const
202 QHash<QString, FrameData*>::const_iterator it = d->frames.constFind(d->prefix);
204 if (it != d->frames.constEnd()) {
205 return it.value()->frameSize;
206 } else {
207 return QSize(-1, -1);
211 qreal FrameSvg::marginSize(const Plasma::MarginEdge edge) const
213 if (d->frames[d->prefix]->noBorderPadding) {
214 return .0;
217 switch (edge) {
218 case Plasma::TopMargin:
219 return d->frames[d->prefix]->topMargin;
220 break;
222 case Plasma::LeftMargin:
223 return d->frames[d->prefix]->leftMargin;
224 break;
226 case Plasma::RightMargin:
227 return d->frames[d->prefix]->rightMargin;
228 break;
230 //Plasma::BottomMargin
231 default:
232 return d->frames[d->prefix]->bottomMargin;
233 break;
237 void FrameSvg::getMargins(qreal &left, qreal &top, qreal &right, qreal &bottom) const
239 FrameData *frame = d->frames[d->prefix];
241 if (!frame || frame->noBorderPadding) {
242 left = top = right = bottom = 0;
243 return;
246 top = frame->topMargin;
247 left = frame->leftMargin;
248 right = frame->rightMargin;
249 bottom = frame->bottomMargin;
252 QRectF FrameSvg::contentsRect() const
254 QSizeF size(frameSize());
256 if (size.isValid()) {
257 QRectF rect(QPointF(0, 0), size);
258 FrameData *frame = d->frames[d->prefix];
260 return rect.adjusted(frame->leftMargin, frame->topMargin,
261 -frame->rightMargin, -frame->bottomMargin);
262 } else {
263 return QRectF();
267 QPixmap FrameSvg::alphaMask() const
269 FrameData *frame = d->frames[d->prefix];
271 if (hasElement("mask-" + d->prefix + "center")) {
272 QString oldPrefix = d->prefix;
274 // We are setting the prefix only temporary to generate
275 // the needed mask image
276 d->prefix = "mask-" + oldPrefix;
278 if (!d->frames.contains(d->prefix)) {
279 d->frames.insert(d->prefix, new FrameData(*(d->frames[oldPrefix])));
280 d->updateSizes();
283 FrameData *maskFrame = d->frames[d->prefix];
284 if (maskFrame->cachedBackground.isNull() || maskFrame->frameSize != frame->frameSize ) {
285 maskFrame->frameSize = frame->frameSize;
286 maskFrame->cachedBackground = QPixmap();
288 d->generateBackground(maskFrame);
289 if (maskFrame->cachedBackground.isNull()) {
290 return QPixmap();
294 d->prefix = oldPrefix;
295 return maskFrame->cachedBackground;
296 } else {
297 if (frame->cachedBackground.isNull()) {
298 d->generateBackground(frame);
299 if (frame->cachedBackground.isNull()) {
300 return QPixmap();
303 return frame->cachedBackground;
307 QRegion FrameSvg::mask() const
309 FrameData *frame = d->frames[d->prefix];
310 frame->cachedMask = QRegion(QBitmap(alphaMask().alphaChannel().createMaskFromColor(Qt::black)));
311 return frame->cachedMask;
314 void FrameSvg::setCacheAllRenderedFrames(bool cache)
316 if (d->cacheAll && !cache) {
317 clearCache();
320 d->cacheAll = cache;
323 bool FrameSvg::cacheAllRenderedFrames() const
325 return d->cacheAll;
328 void FrameSvg::clearCache()
330 FrameData *frame = d->frames[d->prefix];
332 d->saveTimer->stop();
333 d->framesToSave.clear();
335 // delete all the frames that aren't this one
336 QMutableHashIterator<QString, FrameData*> it(d->frames);
337 while (it.hasNext()) {
338 FrameData *p = it.next().value();
340 if (frame != p) {
341 delete p;
342 it.remove();
347 QPixmap FrameSvg::framePixmap()
349 FrameData *frame = d->frames[d->prefix];
350 if (frame->cachedBackground.isNull()) {
351 d->generateBackground(frame);
352 if (frame->cachedBackground.isNull()) {
353 return QPixmap();
358 return frame->cachedBackground;
361 void FrameSvg::paintFrame(QPainter *painter, const QRectF &target, const QRectF &source)
363 FrameData *frame = d->frames[d->prefix];
364 if (frame->cachedBackground.isNull()) {
365 d->generateBackground(frame);
366 if (frame->cachedBackground.isNull()) {
367 return;
371 painter->drawPixmap(target, frame->cachedBackground, source.isValid() ? source : target);
374 void FrameSvg::paintFrame(QPainter *painter, const QPointF &pos)
376 FrameData *frame = d->frames[d->prefix];
377 if (frame->cachedBackground.isNull()) {
378 d->generateBackground(frame);
379 if (frame->cachedBackground.isNull()) {
380 return;
384 painter->drawPixmap(pos, frame->cachedBackground);
387 void FrameSvgPrivate::generateBackground(FrameData *frame)
389 if (!frame->cachedBackground.isNull()) {
390 return;
394 QString id = QString::fromLatin1("%5_%4_%3_%2_%1_").
395 arg(frame->enabledBorders).arg(frame->frameSize.width()).arg(frame->frameSize.height()).arg(prefix).arg(q->imagePath());
397 Theme *theme = Theme::defaultTheme();
398 if (theme->findInCache(id, frame->cachedBackground) && !frame->cachedBackground.isNull()) {
399 return;
402 //kDebug() << "generating background";
403 const int topWidth = q->elementSize(prefix + "top").width();
404 const int leftHeight = q->elementSize(prefix + "left").height();
405 const int topOffset = 0;
406 const int leftOffset = 0;
409 if (!frame->frameSize.isValid()) {
410 kWarning() << "Invalid frame size" << frame->frameSize;
411 return;
414 const int contentWidth = frame->frameSize.width() - frame->leftWidth - frame->rightWidth;
415 const int contentHeight = frame->frameSize.height() - frame->topHeight - frame->bottomHeight;
416 int contentTop = 0;
417 int contentLeft = 0;
418 int rightOffset = contentWidth;
419 int bottomOffset = contentHeight;
421 frame->cachedBackground = QPixmap(frame->leftWidth + contentWidth + frame->rightWidth,
422 frame->topHeight + contentHeight + frame->bottomHeight);
423 frame->cachedBackground.fill(Qt::transparent);
424 QPainter p(&frame->cachedBackground);
425 p.setCompositionMode(QPainter::CompositionMode_Source);
426 p.setRenderHint(QPainter::SmoothPixmapTransform);
428 //CENTER
429 if (frame->tileCenter) {
430 if (contentHeight > 0 && contentWidth > 0) {
431 const int centerTileHeight = q->elementSize(prefix + "center").height();
432 const int centerTileWidth = q->elementSize(prefix + "center").width();
433 QPixmap center(centerTileWidth, centerTileHeight);
434 center.fill(Qt::transparent);
437 QPainter centerPainter(&center);
438 centerPainter.setCompositionMode(QPainter::CompositionMode_Source);
439 q->paint(&centerPainter, QRect(QPoint(0, 0), q->elementSize(prefix + "center")), prefix + "center");
442 p.drawTiledPixmap(QRect(frame->leftWidth, frame->topHeight,
443 contentWidth, contentHeight), center);
445 } else {
446 if (contentHeight > 0 && contentWidth > 0) {
447 q->paint(&p, QRect(frame->leftWidth, frame->topHeight,
448 contentWidth, contentHeight),
449 prefix + "center");
453 if (frame->enabledBorders & FrameSvg::LeftBorder && q->hasElement(prefix + "left")) {
454 rightOffset += frame->leftWidth;
457 // Corners
458 if (frame->enabledBorders & FrameSvg::TopBorder && q->hasElement(prefix + "top")) {
459 contentTop = frame->topHeight;
460 bottomOffset += frame->topHeight;
462 if (q->hasElement(prefix + "topleft") && frame->enabledBorders & FrameSvg::LeftBorder) {
463 q->paint(&p, QRect(leftOffset, topOffset, frame->leftWidth, frame->topHeight), prefix + "topleft");
465 contentLeft = frame->leftWidth;
468 if (q->hasElement(prefix + "topright") && frame->enabledBorders & FrameSvg::RightBorder) {
469 q->paint(&p, QRect(rightOffset, topOffset, frame->rightWidth, frame->topHeight), prefix + "topright");
473 if (frame->enabledBorders & FrameSvg::BottomBorder && q->hasElement(prefix + "bottom")) {
474 if (q->hasElement(prefix + "bottomleft") && frame->enabledBorders & FrameSvg::LeftBorder) {
475 q->paint(&p, QRect(leftOffset, bottomOffset, frame->leftWidth, frame->bottomHeight), prefix + "bottomleft");
477 contentLeft = frame->leftWidth;
480 if (frame->enabledBorders & FrameSvg::RightBorder && q->hasElement(prefix + "bottomright")) {
481 q->paint(&p, QRect(rightOffset, bottomOffset, frame->rightWidth, frame->bottomHeight), prefix + "bottomright");
485 // Sides
486 if (frame->stretchBorders) {
487 if (frame->enabledBorders & FrameSvg::LeftBorder || frame->enabledBorders & FrameSvg::RightBorder) {
488 if (q->hasElement(prefix + "left") &&
489 frame->enabledBorders & FrameSvg::LeftBorder) {
490 q->paint(&p, QRect(leftOffset, contentTop, frame->leftWidth, contentHeight), prefix + "left");
493 if (q->hasElement(prefix + "right") &&
494 frame->enabledBorders & FrameSvg::RightBorder) {
495 q->paint(&p, QRect(rightOffset, contentTop, frame->rightWidth, contentHeight), prefix + "right");
499 if (frame->enabledBorders & FrameSvg::TopBorder || frame->enabledBorders & FrameSvg::BottomBorder) {
500 if (frame->enabledBorders & FrameSvg::TopBorder && q->hasElement(prefix + "top")) {
501 q->paint(&p, QRect(contentLeft, topOffset, contentWidth, frame->topHeight), prefix + "top");
504 if (frame->enabledBorders & FrameSvg::BottomBorder && q->hasElement(prefix + "bottom")) {
505 q->paint(&p, QRect(contentLeft, bottomOffset, contentWidth, frame->bottomHeight), prefix + "bottom");
508 } else {
509 if (frame->enabledBorders & FrameSvg::LeftBorder && q->hasElement(prefix + "left")
510 && leftHeight > 0 && frame->leftWidth > 0) {
511 QPixmap left(frame->leftWidth, leftHeight);
512 left.fill(Qt::transparent);
514 QPainter sidePainter(&left);
515 sidePainter.setCompositionMode(QPainter::CompositionMode_Source);
516 q->paint(&sidePainter, QRect(QPoint(0, 0), left.size()), prefix + "left");
518 p.drawTiledPixmap(QRect(leftOffset, contentTop, frame->leftWidth, contentHeight), left);
521 if (frame->enabledBorders & FrameSvg::RightBorder && q->hasElement(prefix + "right") &&
522 leftHeight > 0 && frame->rightWidth > 0) {
523 QPixmap right(frame->rightWidth, leftHeight);
524 right.fill(Qt::transparent);
526 QPainter sidePainter(&right);
527 sidePainter.setCompositionMode(QPainter::CompositionMode_Source);
528 q->paint(&sidePainter, QRect(QPoint(0, 0), right.size()), prefix + "right");
530 p.drawTiledPixmap(QRect(rightOffset, contentTop, frame->rightWidth, contentHeight), right);
533 if (frame->enabledBorders & FrameSvg::TopBorder && q->hasElement(prefix + "top")
534 && topWidth > 0 && frame->topHeight > 0) {
535 QPixmap top(topWidth, frame->topHeight);
536 top.fill(Qt::transparent);
538 QPainter sidePainter(&top);
539 sidePainter.setCompositionMode(QPainter::CompositionMode_Source);
540 q->paint(&sidePainter, QRect(QPoint(0, 0), top.size()), prefix + "top");
542 p.drawTiledPixmap(QRect(contentLeft, topOffset, contentWidth, frame->topHeight), top);
545 if (frame->enabledBorders & FrameSvg::BottomBorder && q->hasElement(prefix + "bottom")
546 && topWidth > 0 && frame->bottomHeight > 0) {
547 QPixmap bottom(topWidth, frame->bottomHeight);
548 bottom.fill(Qt::transparent);
550 QPainter sidePainter(&bottom);
551 sidePainter.setCompositionMode(QPainter::CompositionMode_Source);
552 q->paint(&sidePainter, QRect(QPoint(0, 0), bottom.size()), prefix + "bottom");
554 p.drawTiledPixmap(QRect(contentLeft, bottomOffset, contentWidth, frame->bottomHeight), bottom);
558 //Overlays
559 if (!prefix.startsWith("mask-") && q->hasElement(prefix+"overlay")) {
560 QPoint pos = QPoint(0, 0);
561 QSize overlaySize = q->elementSize(prefix+"overlay");
563 //Random pos, stretched and tiled are mutually exclusive
564 if (q->hasElement(prefix+"hint-overlay-random-pos")) {
565 pos = overlayPos;
566 //Stretched or Tiled?
567 } else if (q->hasElement(prefix+"hint-overlay-stretch")) {
568 overlaySize = frame->frameSize;
569 } else {
570 if (q->hasElement(prefix+"hint-overlay-tile-horizontal")) {
571 overlaySize.setWidth(frame->frameSize.width());
573 if (q->hasElement(prefix+"hint-overlay-tile-vertical")) {
574 overlaySize.setHeight(frame->frameSize.height());
578 QString id = QString::fromLatin1("overlay_%7_%6_%5_%4_%3_%2_%1_").
579 arg(overlayPos.y()).arg(overlayPos.x()).arg(frame->enabledBorders).arg(frame->frameSize.width()).arg(frame->frameSize.height()).arg(prefix).arg(q->imagePath());
581 QPixmap overlay = q->alphaMask();
583 QPainter overlayPainter(&overlay);
584 overlayPainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
586 //Tiling?
587 if (q->hasElement(prefix+"hint-overlay-tile-horizontal") ||
588 q->hasElement(prefix+"hint-overlay-tile-vertical")) {
590 QSize s = q->size();
591 q->resize(q->elementSize(prefix+"overlay"));
593 overlayPainter.drawTiledPixmap(QRect(QPoint(0,0), overlaySize), q->pixmap(prefix+"overlay"));
594 q->resize(s);
595 } else {
596 q->paint(&overlayPainter, QRect(overlayPos, overlaySize), prefix+"overlay");
598 overlayPainter.end();
600 p.setCompositionMode(QPainter::CompositionMode_SourceOver);
602 p.drawPixmap(overlayPos, overlay, QRect(overlayPos, overlaySize));
605 if (!framesToSave.contains(prefix)) {
606 framesToSave.append(prefix);
609 saveTimer->start(300);
613 void FrameSvgPrivate::scheduledCacheUpdate()
615 foreach ( QString prefixToSave, framesToSave) {
616 //insert background
617 FrameData *frame = frames[prefix];
618 framesToSave.removeAll(prefixToSave);
620 QString id = QString::fromLatin1("%5_%4_%3_%2_%1_").
621 arg(frame->enabledBorders).arg(frame->frameSize.width()).arg(frame->frameSize.height()).arg(prefix).arg(q->imagePath());
623 //kDebug()<<"Saving to cache frame"<<id;
625 Theme::defaultTheme()->insertIntoCache(id, frame->cachedBackground);
627 //insert overlay
628 id = QString::fromLatin1("overlay_%7_%6_%5_%4_%3_%2_%1_").
629 arg(overlayPos.y()).arg(overlayPos.x()).arg(frame->enabledBorders).arg(frame->frameSize.width()).arg(frame->frameSize.height()).arg(prefix).arg(q->imagePath());
631 Theme::defaultTheme()->insertIntoCache(id, frame->cachedBackground);
635 void FrameSvgPrivate::updateSizes()
637 //kDebug() << "!!!!!!!!!!!!!!!!!!!!!! updating sizes" << prefix;
638 FrameData *frame = frames[prefix];
639 Q_ASSERT(frame);
641 QSize s = q->size();
642 q->resize();
643 frame->cachedBackground = QPixmap();
644 frame->cachedMask = QRegion();
646 if (frame->enabledBorders & FrameSvg::TopBorder) {
647 frame->topHeight = q->elementSize(prefix + "top").height();
649 if (q->hasElement(prefix + "hint-top-margin")) {
650 frame->topMargin = q->elementSize(prefix + "hint-top-margin").height();
651 } else {
652 frame->topMargin = frame->topHeight;
654 } else {
655 frame->topMargin = frame->topHeight = 0;
658 if (frame->enabledBorders & FrameSvg::LeftBorder) {
659 frame->leftWidth = q->elementSize(prefix + "left").width();
661 if (q->hasElement(prefix + "hint-left-margin")) {
662 frame->leftMargin = q->elementSize(prefix + "hint-left-margin").width();
663 } else {
664 frame->leftMargin = frame->leftWidth;
666 } else {
667 frame->leftMargin = frame->leftWidth = 0;
670 if (frame->enabledBorders & FrameSvg::RightBorder) {
671 frame->rightWidth = q->elementSize(prefix + "right").width();
673 if (q->hasElement(prefix + "hint-right-margin")) {
674 frame->rightMargin = q->elementSize(prefix + "hint-right-margin").width();
675 } else {
676 frame->rightMargin = frame->rightWidth;
678 } else {
679 frame->rightMargin = frame->rightWidth = 0;
682 if (frame->enabledBorders & FrameSvg::BottomBorder) {
683 frame->bottomHeight = q->elementSize(prefix + "bottom").height();
685 if (q->hasElement(prefix + "hint-bottom-margin")) {
686 frame->bottomMargin = q->elementSize(prefix + "hint-bottom-margin").height();
687 } else {
688 frame->bottomMargin = frame->bottomHeight;
690 } else {
691 frame->bottomMargin = frame->bottomHeight = 0;
694 //since it's rectangular, topWidth and bottomWidth must be the same
695 frame->tileCenter = q->hasElement("hint-tile-center");
696 frame->noBorderPadding = q->hasElement("hint-no-border-padding");
697 frame->stretchBorders = q->hasElement("hint-stretch-borders");
698 q->resize(s);
701 void FrameSvgPrivate::updateNeeded()
703 q->clearCache();
704 updateSizes();
707 void FrameSvgPrivate::updateAndSignalSizes()
709 updateSizes();
710 emit q->repaintNeeded();
713 } // Plasma namespace
715 #include "framesvg.moc"