diff --git a/widgets/widget.cpp b/widgets/widget.cpp index 60266f6e2..f421d1b9e 100644 --- a/widgets/widget.cpp +++ b/widgets/widget.cpp @@ -25,6 +25,8 @@ #include #include +#include +#include #include @@ -43,6 +45,7 @@ class Widget::Private std::numeric_limits::infinity()) , parent(0) , opacity(1.0) + , cachePaintMode(Widget::NoCacheMode) { } ~Private() { } @@ -54,6 +57,15 @@ class Widget::Private QList childList; qreal opacity; + + // Replace with CacheMode in 4.4 +#if QT_VERSION >= 0x040400 +#warning Replace Plasma::Widget::CachePaintMode with QGraphicsItem::CacheMode +#endif + Widget::CachePaintMode cachePaintMode; + QSize cacheSize; + QString cacheKey; + QRectF cacheInvalidated; bool shouldPaint(QPainter *painter, const QTransform &transform); }; @@ -99,6 +111,35 @@ qreal Widget::opacity() const return d->opacity; } +void Widget::setCachePaintMode(CachePaintMode mode, const QSize &size) +{ + d->cachePaintMode = mode; + d->cacheSize = size; + if (mode == NoCacheMode) { + QPixmapCache::remove(d->cacheKey); + d->cacheKey.clear(); + update(); + } else { + if (mode == ItemCoordinateCacheMode) { + d->cacheKey = QString("%1").arg(long(this)); + d->cacheSize = size; + } + invalidate(); + } +} + +Widget::CachePaintMode Widget::cachePaintMode() const +{ + return d->cachePaintMode; +} + +void Widget::invalidate(const QRectF &rect) +{ + if (d->cachePaintMode != NoCacheMode) + d->cacheInvalidated |= rect.isNull() ? boundingRect() : rect; + update(rect); +} + Qt::Orientations Widget::expandingDirections() const { return Qt::Horizontal | Qt::Vertical; @@ -240,7 +281,98 @@ void Widget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QW painter->setOpacity(d->opacity); if (d->shouldPaint(painter, transform())) { - paintWidget(painter, option, widget); + if (d->cachePaintMode == NoCacheMode) { + paintWidget(painter, option, widget); + } else { + QRectF brect = boundingRect(); + QRect boundingRectInt = brect.toRect(); + + // Fetch the off-screen transparent buffer and exposed area info. + QPixmap pix; + QPixmapCache::find(d->cacheKey, pix); + QRectF exposed = d->cacheInvalidated; + + // Render using item coodinate cache mode. + if (d->cachePaintMode == ItemCoordinateCacheMode) { + // Recreate the pixmap if it's gone. + if (pix.isNull()) { + pix = QPixmap(d->cacheSize); + pix.fill(Qt::transparent); + exposed = brect; + } + + // Check for newly invalidated areas. + if (!exposed.isNull()) { + d->cacheInvalidated = QRectF(); + + QStyleOptionGraphicsItem cacheOption = *option; + cacheOption.exposedRect = exposed.toRect(); // <- truncation + + QPainter pixmapPainter(&pix); + // Fit the item's bounding rect into the pixmap's coordinates. + pixmapPainter.scale(pix.width() / brect.width(), + pix.height() / brect.height()); + pixmapPainter.translate(-brect.topLeft()); + + // Re-render the invalidated areas of the pixmap. Important: don't + // fool the item into using the widget - pass 0 instead of \a + // widget. + paintWidget(&pixmapPainter, &cacheOption, 0); + pixmapPainter.end(); + + // Reinsert this pixmap into the cache + QPixmapCache::insert(d->cacheKey, pix); + } + + // Redraw the exposed area using the transformed painter. Depending on + // the hardware, this may be a server-side operation, or an expensive + // qpixmap-image-transform-pixmap roundtrip. + painter->drawPixmap(brect, pix, QRectF(QPointF(), pix.size())); + return; + } + + // Render using device coordinate cache mode. + if (d->cachePaintMode == DeviceCoordinateCacheMode) { + QTransform transform = painter->worldTransform(); + QRect deviceRect = transform.mapRect(brect).toRect(); + + // Auto-adjust the pixmap size. + if (deviceRect.size() != pix.size()) { + pix = QPixmap(deviceRect.size()); + pix.fill(Qt::transparent); + exposed = brect; + } + + // Check for newly invalidated areas. + if (!exposed.isNull()) { + d->cacheInvalidated = QRectF(); + + // Construct the new styleoption, reset the exposed rect. + QStyleOptionGraphicsItem cacheOption = *option; + cacheOption.exposedRect = exposed.toRect(); // <- truncation + + QPointF viewOrigo = transform.map(QPointF(0, 0)); + QPointF offset = viewOrigo - deviceRect.topLeft(); + + // Transform the painter, and render the item in device coordinates. + QPainter pixmapPainter(&pix); + pixmapPainter.translate(offset); + pixmapPainter.setWorldTransform(transform, true); + pixmapPainter.translate(transform.inverted().map(QPointF(0, 0))); + paintWidget(&pixmapPainter, &cacheOption, 0); + pixmapPainter.end(); + + // Reinsert this pixmap into the cache + QPixmapCache::insert(d->cacheKey, pix); + } + + // Redraw the exposed area using an untransformed painter. This + // effectively becomes a bitblit that does not transform the cache. + painter->setWorldTransform(QTransform()); + painter->drawPixmap(deviceRect.topLeft(), pix); + return; + } + } } return; } diff --git a/widgets/widget.h b/widgets/widget.h index 498ed3c57..67dfe840a 100644 --- a/widgets/widget.h +++ b/widgets/widget.h @@ -59,6 +59,11 @@ class PLASMA_EXPORT Widget : public QObject, Q_PROPERTY( qreal opacity READ opacity WRITE setOpacity ) public: + enum CachePaintMode { + NoCacheMode, + ItemCoordinateCacheMode, + DeviceCoordinateCacheMode + }; /** @@ -204,6 +209,26 @@ public: void setOpacity(qreal opacity); qreal opacity() const; + /** + * Sets the widget's cache paint mode and cache size. + * @param mode the new cache paint mode + * @param mode the new cache size, only applies to ItemCoordinateCacheMode + */ + void setCachePaintMode(CachePaintMode mode, const QSize &size = QSize()); + + /** + * The current cache paint mode. + */ + CachePaintMode cachePaintMode() const; + + /** + * Invalidates the widget's cache paint mode for a given item rectangle. + * @param rect the optional invalidated rectangle; if null, defaults to boundingRect(). + */ + void invalidate(const QRectF &rect = QRectF()); + inline void invalidate(qreal _x, qreal _y, qreal w, qreal h) + { invalidate(QRectF(_x, _y, w, h)); } + virtual QGraphicsItem* graphicsItem(); protected: