/* * Copyright 2008 by Aaron Seigo * Copyright 2008 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "framesvg.h" #include #include #include #include #include #include #include namespace Plasma { class FrameData { public: FrameData() : enabledBorders(FrameSvg::AllBorders), frameSize(-1,-1) { } FrameData(const FrameData &other) : enabledBorders(other.enabledBorders), frameSize(other.frameSize) { } ~FrameData() { } FrameSvg::EnabledBorders enabledBorders; QPixmap cachedBackground; QRegion cachedMask; QSizeF frameSize; //measures int topHeight; int leftWidth; int rightWidth; int bottomHeight; //margins, are equal to the measures by default int topMargin; int leftMargin; int rightMargin; int bottomMargin; //size of the svg where the size of the "center" //element is contentWidth x contentHeight bool noBorderPadding : 1; bool stretchBorders : 1; bool tileCenter : 1; }; class FrameSvgPrivate { public: FrameSvgPrivate(FrameSvg *psvg) : q(psvg), cacheAll(false), saveTimer(0) { } ~FrameSvgPrivate() { qDeleteAll(frames); frames.clear(); } void generateBackground(FrameData *frame); void scheduledCacheUpdate(); void updateSizes(); void updateNeeded(); void updateAndSignalSizes(); Location location; QString prefix; FrameSvg *q; bool cacheAll : 1; QStringList framesToSave; QTimer *saveTimer; QHash frames; }; FrameSvg::FrameSvg(QObject *parent) : Svg(parent), d(new FrameSvgPrivate(this)) { connect(this, SIGNAL(repaintNeeded()), this, SLOT(updateNeeded())); d->frames.insert(QString(), new FrameData()); d->saveTimer = new QTimer(this); d->saveTimer->setSingleShot(true); connect(d->saveTimer, SIGNAL(timeout()), this, SLOT(scheduledCacheUpdate())); } FrameSvg::~FrameSvg() { delete d; } void FrameSvg::setImagePath(const QString &path) { if (path == imagePath()) { return; } Svg::setImagePath(path); setContainsMultipleImages(true); clearCache(); d->updateAndSignalSizes(); } void FrameSvg::setEnabledBorders(const EnabledBorders borders) { if (borders == d->frames[d->prefix]->enabledBorders) { return; } d->frames[d->prefix]->enabledBorders = borders; d->updateAndSignalSizes(); } FrameSvg::EnabledBorders FrameSvg::enabledBorders() const { QHash::const_iterator it = d->frames.constFind(d->prefix); if (it != d->frames.constEnd()) { return it.value()->enabledBorders; } else { return NoBorder; } } void FrameSvg::setElementPrefix(Plasma::Location location) { switch (location) { case TopEdge: setElementPrefix("north"); break; case BottomEdge: setElementPrefix("south"); break; case LeftEdge: setElementPrefix("west"); break; case RightEdge: setElementPrefix("east"); break; default: setElementPrefix(QString()); break; } d->location = location; } void FrameSvg::setElementPrefix(const QString & prefix) { const QString oldPrefix(d->prefix); if (!hasElement(prefix + "-center")) { d->prefix.clear(); } else { d->prefix = prefix; if (!d->prefix.isEmpty()) { d->prefix += '-'; } } if (oldPrefix == d->prefix && d->frames[oldPrefix]) { return; } if (!d->frames.contains(d->prefix)) { d->frames.insert(d->prefix, new FrameData(*(d->frames[oldPrefix]))); d->updateSizes(); } if (!d->cacheAll) { delete d->frames[oldPrefix]; d->framesToSave.removeAll(oldPrefix); d->frames.remove(oldPrefix); } d->location = Floating; } bool FrameSvg::hasElementPrefix(const QString & prefix) const { //for now it simply checks if a center element exists, //because it could make sense for certain themes to not have all the elements if (prefix.isEmpty()) { return hasElement("center"); } else { return hasElement(prefix + "-center"); } } bool FrameSvg::hasElementPrefix(Plasma::Location location) const { switch (location) { case TopEdge: return hasElementPrefix("north"); break; case BottomEdge: return hasElementPrefix("south"); break; case LeftEdge: return hasElementPrefix("west"); break; case RightEdge: return hasElementPrefix("east"); break; default: return hasElementPrefix(QString()); break; } } QString FrameSvg::prefix() { if (d->prefix.isEmpty()) { return d->prefix; } return d->prefix.left(d->prefix.size() - 1); } void FrameSvg::resizeFrame(const QSizeF &size) { if (size.isEmpty()) { kWarning() << "Invalid size" << size; return; } if (size == d->frames[d->prefix]->frameSize) { return; } d->updateSizes(); d->frames[d->prefix]->frameSize = size; } QSizeF FrameSvg::frameSize() const { QHash::const_iterator it = d->frames.constFind(d->prefix); if (it != d->frames.constEnd()) { return it.value()->frameSize; } else { return QSize(-1, -1); } } qreal FrameSvg::marginSize(const Plasma::MarginEdge edge) const { if (d->frames[d->prefix]->noBorderPadding) { return .0; } switch (edge) { case Plasma::TopMargin: return d->frames[d->prefix]->topMargin; break; case Plasma::LeftMargin: return d->frames[d->prefix]->leftMargin; break; case Plasma::RightMargin: return d->frames[d->prefix]->rightMargin; break; //Plasma::BottomMargin default: return d->frames[d->prefix]->bottomMargin; break; } } void FrameSvg::getMargins(qreal &left, qreal &top, qreal &right, qreal &bottom) const { FrameData *frame = d->frames[d->prefix]; if (!frame || frame->noBorderPadding) { left = top = right = bottom = 0; return; } top = frame->topMargin; left = frame->leftMargin; right = frame->rightMargin; bottom = frame->bottomMargin; } QRectF FrameSvg::contentsRect() const { QSizeF size(frameSize()); if (size.isValid()) { QRectF rect(QPointF(0, 0), size); FrameData *frame = d->frames[d->prefix]; return rect.adjusted(frame->leftMargin, frame->topMargin, -frame->rightMargin, -frame->bottomMargin); } else { return QRectF(); } } QRegion FrameSvg::mask() const { FrameData *frame = d->frames[d->prefix]; if (frame->cachedMask.isEmpty()) { // ivan: we are testing whether we have the mask prefixed // elements to use for creating the mask. if (hasElement("mask-" + d->prefix + "center")) { QString oldPrefix = d->prefix; // We are setting the prefix only temporary to generate // the needed mask image d->prefix = "mask-" + oldPrefix; if (!d->frames.contains(d->prefix)) { d->frames.insert(d->prefix, new FrameData(*(d->frames[oldPrefix]))); d->updateSizes(); } FrameData *maskFrame = d->frames[d->prefix]; if (maskFrame->cachedBackground.isNull()) { d->generateBackground(maskFrame); if (maskFrame->cachedBackground.isNull()) { return QRegion(); } } frame->cachedMask = QBitmap(maskFrame->cachedBackground.alphaChannel().createMaskFromColor(Qt::black)); d->prefix = oldPrefix; } else { if (frame->cachedBackground.isNull()) { d->generateBackground(frame); if (frame->cachedBackground.isNull()) { return QRegion(); } } frame->cachedMask = QRegion(QBitmap(frame->cachedBackground.alphaChannel().createMaskFromColor(Qt::black))); } } return frame->cachedMask; } void FrameSvg::setCacheAllRenderedFrames(bool cache) { if (d->cacheAll && !cache) { clearCache(); } d->cacheAll = cache; } bool FrameSvg::cacheAllRenderedFrames() const { return d->cacheAll; } void FrameSvg::clearCache() { FrameData *frame = d->frames[d->prefix]; d->saveTimer->stop(); d->framesToSave.clear(); // delete all the frames that aren't this one QMutableHashIterator it(d->frames); while (it.hasNext()) { FrameData *p = it.next().value(); if (frame != p) { delete p; it.remove(); } } } QPixmap FrameSvg::framePixmap() { FrameData *frame = d->frames[d->prefix]; if (frame->cachedBackground.isNull()) { d->generateBackground(frame); if (frame->cachedBackground.isNull()) { return QPixmap(); } } return frame->cachedBackground; } void FrameSvg::paintFrame(QPainter *painter, const QRectF &target, const QRectF &source) { FrameData *frame = d->frames[d->prefix]; if (frame->cachedBackground.isNull()) { d->generateBackground(frame); if (frame->cachedBackground.isNull()) { return; } } painter->drawPixmap(target, frame->cachedBackground, source.isValid() ? source : target); } void FrameSvg::paintFrame(QPainter *painter, const QPointF &pos) { FrameData *frame = d->frames[d->prefix]; if (frame->cachedBackground.isNull()) { d->generateBackground(frame); if (frame->cachedBackground.isNull()) { return; } } painter->drawPixmap(pos, frame->cachedBackground); } void FrameSvgPrivate::generateBackground(FrameData *frame) { if (!frame->cachedBackground.isNull()) { return; } QString id = QString::fromLatin1("%5_%4_%3_%2_%1_"). arg(frame->enabledBorders).arg(frame->frameSize.width()).arg(frame->frameSize.height()).arg(prefix).arg(q->imagePath()); Theme *theme = Theme::defaultTheme(); if (theme->findInCache(id, frame->cachedBackground) && !frame->cachedBackground.isNull()) { return; } //kDebug() << "generating background"; const int topWidth = q->elementSize(prefix + "top").width(); const int leftHeight = q->elementSize(prefix + "left").height(); const int topOffset = 0; const int leftOffset = 0; if (!frame->frameSize.isValid()) { kWarning() << "Invalid frame size" << frame->frameSize; return; } const int contentWidth = frame->frameSize.width() - frame->leftWidth - frame->rightWidth; const int contentHeight = frame->frameSize.height() - frame->topHeight - frame->bottomHeight; int contentTop = 0; int contentLeft = 0; int rightOffset = contentWidth; int bottomOffset = contentHeight; frame->cachedBackground = QPixmap(frame->leftWidth + contentWidth + frame->rightWidth, frame->topHeight + contentHeight + frame->bottomHeight); frame->cachedBackground.fill(Qt::transparent); QPainter p(&frame->cachedBackground); p.setCompositionMode(QPainter::CompositionMode_Source); p.setRenderHint(QPainter::SmoothPixmapTransform); //if we must stretch the center or the borders we compute how much we will have to stretch //the svg to get the desired element sizes QSizeF scaledContentSize(0, 0); if (q->elementSize(prefix + "center").width() > 0 && q->elementSize(prefix + "center").height() > 0 && (!frame->tileCenter || frame->stretchBorders)) { scaledContentSize = QSizeF(contentWidth * ((qreal)q->size().width() / (qreal)q->elementSize(prefix + "center").width()), contentHeight * ((qreal)q->size().height() / (qreal)q->elementSize(prefix + "center").height())); } //CENTER if (frame->tileCenter) { if (contentHeight > 0 && contentWidth > 0) { int centerTileHeight; int centerTileWidth; centerTileHeight = q->elementSize(prefix + "center").height(); centerTileWidth = q->elementSize(prefix + "center").width(); QPixmap center(centerTileWidth, centerTileHeight); center.fill(Qt::transparent); { QPainter centerPainter(¢er); centerPainter.setCompositionMode(QPainter::CompositionMode_Source); q->paint(¢erPainter, QRect(QPoint(0, 0), q->elementSize(prefix + "center")), prefix + "center"); } p.drawTiledPixmap(QRect(frame->leftWidth, frame->topHeight, contentWidth, contentHeight), center); } } else { if (contentHeight > 0 && contentWidth > 0) { q->paint(&p, QRect(frame->leftWidth, frame->topHeight, contentWidth, contentHeight), prefix + "center"); } } // Corners if (frame->enabledBorders & FrameSvg::TopBorder && q->hasElement(prefix + "top")) { contentTop = frame->topHeight; bottomOffset += frame->topHeight; if (q->hasElement(prefix + "topleft") && frame->enabledBorders & FrameSvg::LeftBorder) { q->paint(&p, QRect(leftOffset, topOffset, frame->leftWidth, frame->topHeight), prefix + "topleft"); contentLeft = frame->leftWidth; rightOffset = contentWidth + frame->leftWidth; } if (q->hasElement(prefix + "topright") && frame->enabledBorders & FrameSvg::RightBorder) { q->paint(&p, QRect(rightOffset, topOffset, frame->rightWidth, frame->topHeight), prefix + "topright"); } } if (frame->enabledBorders & FrameSvg::BottomBorder && q->hasElement(prefix + "bottom")) { if (q->hasElement(prefix + "bottomleft") && frame->enabledBorders & FrameSvg::LeftBorder) { q->paint(&p, QRect(leftOffset, bottomOffset, frame->leftWidth, frame->bottomHeight), prefix + "bottomleft"); contentLeft = frame->leftWidth; rightOffset = contentWidth + frame->leftWidth; } if (frame->enabledBorders & FrameSvg::RightBorder && q->hasElement(prefix + "bottomright")) { q->paint(&p, QRect(rightOffset, bottomOffset, frame->rightWidth, frame->bottomHeight), prefix + "bottomright"); } } // Sides if (frame->stretchBorders) { if (frame->enabledBorders & FrameSvg::LeftBorder || frame->enabledBorders & FrameSvg::RightBorder) { if (q->hasElement(prefix + "left") && frame->enabledBorders & FrameSvg::LeftBorder) { q->paint(&p, QRect(leftOffset, contentTop, frame->leftWidth, contentHeight), prefix + "left"); } if (q->hasElement(prefix + "right") && frame->enabledBorders & FrameSvg::RightBorder) { q->paint(&p, QRect(rightOffset, contentTop, frame->rightWidth, contentHeight), prefix + "right"); } } if (frame->enabledBorders & FrameSvg::TopBorder || frame->enabledBorders & FrameSvg::BottomBorder) { if (frame->enabledBorders & FrameSvg::TopBorder && q->hasElement(prefix + "top")) { q->paint(&p, QRect(contentLeft, topOffset, contentWidth, frame->topHeight), prefix + "top"); } if (frame->enabledBorders & FrameSvg::BottomBorder && q->hasElement(prefix + "bottom")) { q->paint(&p, QRect(contentLeft, bottomOffset, contentWidth, frame->bottomHeight), prefix + "bottom"); } } } else { if (frame->enabledBorders & FrameSvg::LeftBorder && q->hasElement(prefix + "left") && leftHeight > 0 && frame->leftWidth > 0) { QPixmap left(frame->leftWidth, leftHeight); left.fill(Qt::transparent); QPainter sidePainter(&left); sidePainter.setCompositionMode(QPainter::CompositionMode_Source); q->paint(&sidePainter, QRect(QPoint(0, 0), left.size()), prefix + "left"); p.drawTiledPixmap(QRect(leftOffset, contentTop, frame->leftWidth, contentHeight), left); } if (frame->enabledBorders & FrameSvg::RightBorder && q->hasElement(prefix + "right") && leftHeight > 0 && frame->rightWidth > 0) { QPixmap right(frame->rightWidth, leftHeight); right.fill(Qt::transparent); QPainter sidePainter(&right); sidePainter.setCompositionMode(QPainter::CompositionMode_Source); q->paint(&sidePainter, QRect(QPoint(0, 0), right.size()), prefix + "right"); p.drawTiledPixmap(QRect(rightOffset, contentTop, frame->rightWidth, contentHeight), right); } if (frame->enabledBorders & FrameSvg::TopBorder && q->hasElement(prefix + "top") && topWidth > 0 && frame->topHeight > 0) { QPixmap top(topWidth, frame->topHeight); top.fill(Qt::transparent); QPainter sidePainter(&top); sidePainter.setCompositionMode(QPainter::CompositionMode_Source); q->paint(&sidePainter, QRect(QPoint(0, 0), top.size()), prefix + "top"); p.drawTiledPixmap(QRect(contentLeft, topOffset, contentWidth, frame->topHeight), top); } if (frame->enabledBorders & FrameSvg::BottomBorder && q->hasElement(prefix + "bottom") && topWidth > 0 && frame->bottomHeight > 0) { QPixmap bottom(topWidth, frame->bottomHeight); bottom.fill(Qt::transparent); QPainter sidePainter(&bottom); sidePainter.setCompositionMode(QPainter::CompositionMode_Source); q->paint(&sidePainter, QRect(QPoint(0, 0), bottom.size()), prefix + "bottom"); p.drawTiledPixmap(QRect(contentLeft, bottomOffset, contentWidth, frame->bottomHeight), bottom); } } if (!framesToSave.contains(prefix)) { framesToSave.append(prefix); } saveTimer->start(300); } void FrameSvgPrivate::scheduledCacheUpdate() { foreach ( QString prefixToSave, framesToSave) { FrameData *frame = frames[prefix]; framesToSave.removeAll(prefixToSave); QString id = QString::fromLatin1("%5_%4_%3_%2_%1_"). arg(frame->enabledBorders).arg(frame->frameSize.width()).arg(frame->frameSize.height()).arg(prefix).arg(q->imagePath()); //kDebug()<<"Saving to cache frame"<insertIntoCache(id, frame->cachedBackground); } } void FrameSvgPrivate::updateSizes() { //kDebug() << "!!!!!!!!!!!!!!!!!!!!!! updating sizes" << prefix; FrameData *frame = frames[prefix]; Q_ASSERT(frame); QSize s = q->size(); q->resize(); frame->cachedBackground = QPixmap(); frame->cachedMask = QRegion(); if (frame->enabledBorders & FrameSvg::TopBorder) { frame->topHeight = q->elementSize(prefix + "top").height(); if (q->hasElement(prefix + "hint-top-margin")) { frame->topMargin = q->elementSize(prefix + "hint-top-margin").height(); } else { frame->topMargin = frame->topHeight; } } else { frame->topMargin = frame->topHeight = 0; } if (frame->enabledBorders & FrameSvg::LeftBorder) { frame->leftWidth = q->elementSize(prefix + "left").width(); if (q->hasElement(prefix + "hint-left-margin")) { frame->leftMargin = q->elementSize(prefix + "hint-left-margin").height(); } else { frame->leftMargin = frame->leftWidth; } } else { frame->leftMargin = frame->leftWidth = 0; } if (frame->enabledBorders & FrameSvg::RightBorder) { frame->rightWidth = q->elementSize(prefix + "right").width(); if (q->hasElement(prefix + "hint-right-margin")) { frame->rightMargin = q->elementSize(prefix + "hint-right-margin").height(); } else { frame->rightMargin = frame->rightWidth; } } else { frame->rightMargin = frame->rightWidth = 0; } if (frame->enabledBorders & FrameSvg::BottomBorder) { frame->bottomHeight = q->elementSize(prefix + "bottom").height(); if (q->hasElement(prefix + "hint-bottom-margin")) { frame->bottomMargin = q->elementSize(prefix + "hint-bottom-margin").height(); } else { frame->bottomMargin = frame->bottomHeight; } } else { frame->bottomMargin = frame->bottomHeight = 0; } //since it's rectangular, topWidth and bottomWidth must be the same frame->tileCenter = q->hasElement("hint-tile-center"); frame->noBorderPadding = q->hasElement("hint-no-border-padding"); frame->stretchBorders = q->hasElement("hint-stretch-borders"); q->resize(s); } void FrameSvgPrivate::updateNeeded() { q->clearCache(); updateSizes(); } void FrameSvgPrivate::updateAndSignalSizes() { updateSizes(); emit q->repaintNeeded(); } } // Plasma namespace #include "framesvg.moc"