/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_plot_canvas.h" #include "qwt_painter.h" #include "qwt_null_paintdevice.h" #include "qwt_math.h" #include "qwt_plot.h" #include #include #include #include #include #include #ifdef Q_WS_X11 #include #endif class QwtStyleSheetRecorder: public QwtNullPaintDevice { public: QwtStyleSheetRecorder( const QSize &size ): QwtNullPaintDevice( QPaintEngine::AllFeatures ) { setSize( size ); } virtual void updateState( const QPaintEngineState &state ) { if ( state.state() & QPaintEngine::DirtyPen ) { d_pen = state.pen(); } if ( state.state() & QPaintEngine::DirtyBrush ) { d_brush = state.brush(); } if ( state.state() & QPaintEngine::DirtyBrushOrigin ) { d_origin = state.brushOrigin(); } } virtual void drawRects(const QRectF *rects, int count ) { for ( int i = 0; i < count; i++ ) border.rectList += rects[i]; } virtual void drawPath( const QPainterPath &path ) { const QRectF rect( QPointF( 0.0, 0.0 ) , size() ); if ( path.controlPointRect().contains( rect.center() ) ) { setCornerRects( path ); alignCornerRects( rect ); background.path = path; background.brush = d_brush; background.origin = d_origin; } else { border.pathList += path; } } void setCornerRects( const QPainterPath &path ) { QPointF pos( 0.0, 0.0 ); for ( int i = 0; i < path.elementCount(); i++ ) { QPainterPath::Element el = path.elementAt(i); switch( el.type ) { case QPainterPath::MoveToElement: case QPainterPath::LineToElement: { pos.setX( el.x ); pos.setY( el.y ); break; } case QPainterPath::CurveToElement: { QRectF r( pos, QPointF( el.x, el.y ) ); clipRects += r.normalized(); pos.setX( el.x ); pos.setY( el.y ); break; } case QPainterPath::CurveToDataElement: { if ( clipRects.size() > 0 ) { QRectF r = clipRects.last(); r.setCoords( qMin( r.left(), el.x ), qMin( r.top(), el.y ), qMax( r.right(), el.x ), qMax( r.bottom(), el.y ) ); clipRects.last() = r.normalized(); } break; } } } } private: void alignCornerRects( const QRectF &rect ) { for ( int i = 0; i < clipRects.size(); i++ ) { QRectF &r = clipRects[i]; if ( r.center().x() < rect.center().x() ) r.setLeft( rect.left() ); else r.setRight( rect.right() ); if ( r.center().y() < rect.center().y() ) r.setTop( rect.top() ); else r.setBottom( rect.bottom() ); } } public: QVector clipRects; struct Border { QList pathList; QList rectList; QRegion clipRegion; } border; struct Background { QPainterPath path; QBrush brush; QPointF origin; } background; private: QPen d_pen; QBrush d_brush; QPointF d_origin; }; static void qwtDrawBackground( QPainter *painter, QWidget *widget ) { const QBrush &brush = widget->palette().brush( widget->backgroundRole() ); if ( brush.style() == Qt::TexturePattern ) { QPixmap pm( widget->size() ); pm.fill( widget, 0, 0 ); painter->drawPixmap( 0, 0, pm ); } else if ( brush.gradient() ) { QVector rects; if ( brush.gradient()->coordinateMode() == QGradient::ObjectBoundingMode ) { rects += widget->rect(); } else { rects = painter->clipRegion().rects(); } #if 1 bool useRaster = false; if ( painter->paintEngine()->type() == QPaintEngine::X11 ) { // Qt 4.7.1: gradients on X11 are broken ( subrects + // QGradient::StretchToDeviceMode ) and horrible slow. // As workaround we have to use the raster paintengine. // Even if the QImage -> QPixmap translation is slow // it is three times faster, than using X11 directly useRaster = true; } #endif if ( useRaster ) { QImage::Format format = QImage::Format_RGB32; const QGradientStops stops = brush.gradient()->stops(); for ( int i = 0; i < stops.size(); i++ ) { if ( stops[i].second.alpha() != 255 ) { // don't use Format_ARGB32_Premultiplied. It's // recommended by the Qt docs, but QPainter::drawImage() // is horrible slow on X11. format = QImage::Format_ARGB32; break; } } QImage image( widget->size(), format ); QPainter p( &image ); p.setPen( Qt::NoPen ); p.setBrush( brush ); p.drawRects( rects ); p.end(); painter->drawImage( 0, 0, image ); } else { painter->save(); painter->setPen( Qt::NoPen ); painter->setBrush( brush ); painter->drawRects( rects ); painter->restore(); } } else { painter->save(); painter->setPen( Qt::NoPen ); painter->setBrush( brush ); painter->drawRects( painter->clipRegion().rects() ); painter->restore(); } } static inline void qwtRevertPath( QPainterPath &path ) { if ( path.elementCount() == 4 ) { QPainterPath::Element &el0 = const_cast( path.elementAt(0) ); QPainterPath::Element &el2 = const_cast( path.elementAt(3) ); qSwap( el0.x, el2.x ); qSwap( el0.y, el2.y ); } } static QPainterPath qwtCombinePathList( const QRectF &rect, const QList &pathList ) { if ( pathList.isEmpty() ) return QPainterPath(); QPainterPath ordered[8]; // starting top left for ( int i = 0; i < pathList.size(); i++ ) { int index = -1; QPainterPath subPath = pathList[i]; const QRectF br = pathList[i].controlPointRect(); if ( br.center().x() < rect.center().x() ) { if ( br.center().y() < rect.center().y() ) { if ( qAbs( br.top() - rect.top() ) < qAbs( br.left() - rect.left() ) ) { index = 1; } else { index = 0; } } else { if ( qAbs( br.bottom() - rect.bottom() ) < qAbs( br.left() - rect.left() ) ) { index = 6; } else { index = 7; } } if ( subPath.currentPosition().y() > br.center().y() ) qwtRevertPath( subPath ); } else { if ( br.center().y() < rect.center().y() ) { if ( qAbs( br.top() - rect.top() ) < qAbs( br.right() - rect.right() ) ) { index = 2; } else { index = 3; } } else { if ( qAbs( br.bottom() - rect.bottom() ) < qAbs( br.right() - rect.right() ) ) { index = 5; } else { index = 4; } } if ( subPath.currentPosition().y() < br.center().y() ) qwtRevertPath( subPath ); } ordered[index] = subPath; } for ( int i = 0; i < 4; i++ ) { if ( ordered[ 2 * i].isEmpty() != ordered[2 * i + 1].isEmpty() ) { // we don't accept incomplete rounded borders return QPainterPath(); } } const QPolygonF corners( rect ); QPainterPath path; //path.moveTo( rect.topLeft() ); for ( int i = 0; i < 4; i++ ) { if ( ordered[2 * i].isEmpty() ) { path.lineTo( corners[i] ); } else { path.connectPath( ordered[2 * i] ); path.connectPath( ordered[2 * i + 1] ); } } path.closeSubpath(); #if 0 return path.simplified(); #else return path; #endif } static inline void qwtDrawStyledBackground( QWidget *w, QPainter *painter ) { QStyleOption opt; opt.initFrom(w); w->style()->drawPrimitive( QStyle::PE_Widget, &opt, painter, w); } static QWidget *qwtBackgroundWidget( QWidget *w ) { if ( w->parentWidget() == NULL ) return w; if ( w->autoFillBackground() ) { const QBrush brush = w->palette().brush( w->backgroundRole() ); if ( brush.color().alpha() > 0 ) return w; } if ( w->testAttribute( Qt::WA_StyledBackground ) ) { QImage image( 1, 1, QImage::Format_ARGB32 ); image.fill( Qt::transparent ); QPainter painter( &image ); painter.translate( -w->rect().center() ); qwtDrawStyledBackground( w, &painter ); painter.end(); if ( qAlpha( image.pixel( 0, 0 ) ) != 0 ) return w; } return qwtBackgroundWidget( w->parentWidget() ); } static void qwtFillBackground( QPainter *painter, QWidget *widget, const QVector &fillRects ) { if ( fillRects.isEmpty() ) return; QRegion clipRegion; if ( painter->hasClipping() ) clipRegion = painter->transform().map( painter->clipRegion() ); else clipRegion = widget->contentsRect(); // Try to find out which widget fills // the unfilled areas of the styled background QWidget *bgWidget = qwtBackgroundWidget( widget->parentWidget() ); for ( int i = 0; i < fillRects.size(); i++ ) { const QRect rect = fillRects[i].toAlignedRect(); if ( clipRegion.intersects( rect ) ) { QPixmap pm( rect.size() ); pm.fill( bgWidget, widget->mapTo( bgWidget, rect.topLeft() ) ); painter->drawPixmap( rect, pm ); } } } static void qwtFillBackground( QPainter *painter, QwtPlotCanvas *canvas ) { QVector rects; if ( canvas->testAttribute( Qt::WA_StyledBackground ) ) { QwtStyleSheetRecorder recorder( canvas->size() ); QPainter p( &recorder ); qwtDrawStyledBackground( canvas, &p ); p.end(); if ( recorder.background.brush.isOpaque() ) rects = recorder.clipRects; else rects += canvas->rect(); } else { const QRectF r = canvas->rect(); const double radius = canvas->borderRadius(); if ( radius > 0.0 ) { QSize sz( radius, radius ); rects += QRectF( r.topLeft(), sz ); rects += QRectF( r.topRight() - QPointF( radius, 0 ), sz ); rects += QRectF( r.bottomRight() - QPointF( radius, radius ), sz ); rects += QRectF( r.bottomLeft() - QPointF( 0, radius ), sz ); } } qwtFillBackground( painter, canvas, rects); } class QwtPlotCanvas::PrivateData { public: PrivateData(): focusIndicator( NoFocusIndicator ), borderRadius( 0 ), paintAttributes( 0 ), backingStore( NULL ) { styleSheet.hasBorder = false; } ~PrivateData() { delete backingStore; } FocusIndicator focusIndicator; double borderRadius; QwtPlotCanvas::PaintAttributes paintAttributes; QPixmap *backingStore; struct StyleSheet { bool hasBorder; QPainterPath borderPath; QVector cornerRects; struct StyleSheetBackground { QBrush brush; QPointF origin; } background; } styleSheet; }; //! Sets a cross cursor, enables QwtPlotCanvas::BackingStore QwtPlotCanvas::QwtPlotCanvas( QwtPlot *plot ): QFrame( plot ) { d_data = new PrivateData; #ifndef QT_NO_CURSOR setCursor( Qt::CrossCursor ); #endif setAutoFillBackground( true ); setPaintAttribute( QwtPlotCanvas::BackingStore, true ); setPaintAttribute( QwtPlotCanvas::Opaque, true ); setPaintAttribute( QwtPlotCanvas::HackStyledBackground, true ); } //! Destructor QwtPlotCanvas::~QwtPlotCanvas() { delete d_data; } //! Return parent plot widget QwtPlot *QwtPlotCanvas::plot() { return qobject_cast( parentWidget() ); } //! Return parent plot widget const QwtPlot *QwtPlotCanvas::plot() const { return qobject_cast( parentWidget() ); } /*! \brief Changing the paint attributes \param attribute Paint attribute \param on On/Off \sa testPaintAttribute(), backingStore() */ void QwtPlotCanvas::setPaintAttribute( PaintAttribute attribute, bool on ) { if ( bool( d_data->paintAttributes & attribute ) == on ) return; if ( on ) d_data->paintAttributes |= attribute; else d_data->paintAttributes &= ~attribute; switch ( attribute ) { case BackingStore: { if ( on ) { if ( d_data->backingStore == NULL ) d_data->backingStore = new QPixmap(); if ( isVisible() ) { *d_data->backingStore = QPixmap::grabWidget( this, rect() ); } } else { delete d_data->backingStore; d_data->backingStore = NULL; } break; } case Opaque: { if ( on ) setAttribute( Qt::WA_OpaquePaintEvent, true ); break; } case HackStyledBackground: case ImmediatePaint: { break; } } } /*! Test wether a paint attribute is enabled \param attribute Paint attribute \return true if the attribute is enabled \sa setPaintAttribute() */ bool QwtPlotCanvas::testPaintAttribute( PaintAttribute attribute ) const { return d_data->paintAttributes & attribute; } //! \return Backing store, might be null const QPixmap *QwtPlotCanvas::backingStore() const { return d_data->backingStore; } //! Invalidate the internal backing store void QwtPlotCanvas::invalidateBackingStore() { if ( d_data->backingStore ) *d_data->backingStore = QPixmap(); } /*! Set the focus indicator \sa FocusIndicator, focusIndicator() */ void QwtPlotCanvas::setFocusIndicator( FocusIndicator focusIndicator ) { d_data->focusIndicator = focusIndicator; } /*! \return Focus indicator \sa FocusIndicator, setFocusIndicator() */ QwtPlotCanvas::FocusIndicator QwtPlotCanvas::focusIndicator() const { return d_data->focusIndicator; } /*! Set the radius for the corners of the border frame \param radius Radius of a rounded corner \sa borderRadius() */ void QwtPlotCanvas::setBorderRadius( double radius ) { d_data->borderRadius = qMax( 0.0, radius ); } /*! \return Radius for the corners of the border frame \sa setBorderRadius() */ double QwtPlotCanvas::borderRadius() const { return d_data->borderRadius; } /*! Qt event handler for QEvent::PolishRequest and QEvent::StyleChange \param event Qt Event */ bool QwtPlotCanvas::event( QEvent *event ) { if ( event->type() == QEvent::PolishRequest ) { if ( testPaintAttribute( QwtPlotCanvas::Opaque ) ) { // Setting a style sheet changes the // Qt::WA_OpaquePaintEvent attribute, but we insist // on painting the background. setAttribute( Qt::WA_OpaquePaintEvent, true ); } } if ( event->type() == QEvent::PolishRequest || event->type() == QEvent::StyleChange ) { updateStyleSheetInfo(); } return QFrame::event( event ); } /*! Paint event \param event Paint event */ void QwtPlotCanvas::paintEvent( QPaintEvent *event ) { QPainter painter( this ); painter.setClipRegion( event->region() ); if ( testPaintAttribute( QwtPlotCanvas::BackingStore ) && d_data->backingStore != NULL ) { QPixmap &bs = *d_data->backingStore; if ( bs.size() != size() ) { bs = QPixmap( size() ); #ifdef Q_WS_X11 if ( bs.x11Info().screen() != x11Info().screen() ) bs.x11SetScreen( x11Info().screen() ); #endif if ( testAttribute(Qt::WA_StyledBackground) ) { QPainter p( &bs ); qwtFillBackground( &p, this ); drawCanvas( &p, true ); } else { QPainter p; if ( d_data->borderRadius <= 0.0 ) { bs.fill( this, 0, 0 ); p.begin( &bs ); drawCanvas( &p, false ); } else { p.begin( &bs ); qwtFillBackground( &p, this ); drawCanvas( &p, true ); } if ( frameWidth() > 0 ) drawBorder( &p ); } } painter.drawPixmap( 0, 0, *d_data->backingStore ); } else { if ( testAttribute(Qt::WA_StyledBackground ) ) { if ( testAttribute( Qt::WA_OpaquePaintEvent ) ) { qwtFillBackground( &painter, this ); drawCanvas( &painter, true ); } else { drawCanvas( &painter, false ); } } else { if ( testAttribute( Qt::WA_OpaquePaintEvent ) ) { if ( autoFillBackground() ) qwtDrawBackground( &painter, this ); } drawCanvas( &painter, false ); if ( frameWidth() > 0 ) drawBorder( &painter ); } } if ( hasFocus() && focusIndicator() == CanvasFocusIndicator ) drawFocusIndicator( &painter ); } void QwtPlotCanvas::drawCanvas( QPainter *painter, bool withBackground ) { bool hackStyledBackground = false; if ( withBackground && testAttribute( Qt::WA_StyledBackground ) && testPaintAttribute( HackStyledBackground ) ) { // Antialiasing rounded borders is done by // inserting pixels with colors between the // border color and the color on the canvas, // When the border is painted before the plot items // these colors are interpolated for the canvas // and the plot items need to be clipped excluding // the anialiased pixels. In situations, where // the plot items fill the area at the rounded // borders this is noticeable. // The only way to avoid these annoying "artefacts" // is to paint the border on top of the plot items. if ( d_data->styleSheet.hasBorder && !d_data->styleSheet.borderPath.isEmpty() ) { // We have a border with at least one rounded corner hackStyledBackground = true; } } if ( withBackground ) { painter->save(); if ( testAttribute( Qt::WA_StyledBackground ) ) { if ( hackStyledBackground ) { // paint background without border painter->setPen( Qt::NoPen ); painter->setBrush( d_data->styleSheet.background.brush ); painter->setBrushOrigin( d_data->styleSheet.background.origin ); painter->setClipPath( d_data->styleSheet.borderPath ); painter->drawRect( contentsRect() ); } else { qwtDrawStyledBackground( this, painter ); } } else if ( autoFillBackground() ) { painter->setPen( Qt::NoPen ); painter->setBrush( palette().brush( backgroundRole() ) ); if ( d_data->borderRadius > 0.0 ) { if ( frameWidth() > 0 ) { painter->setClipPath( borderPath( rect() ) ); painter->drawRect( rect() ); } else { painter->setRenderHint( QPainter::Antialiasing, true ); painter->drawPath( borderPath( rect() ) ); } } else { painter->drawRect( contentsRect() ); } } painter->restore(); } painter->save(); if ( !d_data->styleSheet.borderPath.isEmpty() ) { painter->setClipPath( d_data->styleSheet.borderPath, Qt::IntersectClip ); } else { if ( d_data->borderRadius > 0.0 ) painter->setClipPath( borderPath( rect() ), Qt::IntersectClip ); else painter->setClipRect( contentsRect(), Qt::IntersectClip ); } plot()->drawCanvas( painter ); painter->restore(); if ( withBackground && hackStyledBackground ) { // Now paint the border on top QStyleOptionFrame opt; opt.initFrom(this); style()->drawPrimitive( QStyle::PE_Frame, &opt, painter, this); } } /*! Draw the border of the plot canvas \param painter Painter \sa setBorderRadius(), QFrame::drawFrame() */ void QwtPlotCanvas::drawBorder( QPainter *painter ) { if ( d_data->borderRadius > 0 ) { if ( frameWidth() > 0 ) { QwtPainter::drawRoundedFrame( painter, QRectF( rect() ), d_data->borderRadius, d_data->borderRadius, palette(), frameWidth(), frameStyle() ); } } else { drawFrame( painter ); } } /*! Resize event \param event Resize event */ void QwtPlotCanvas::resizeEvent( QResizeEvent *event ) { QFrame::resizeEvent( event ); updateStyleSheetInfo(); } /*! Draw the focus indication \param painter Painter */ void QwtPlotCanvas::drawFocusIndicator( QPainter *painter ) { const int margin = 1; QRect focusRect = contentsRect(); focusRect.setRect( focusRect.x() + margin, focusRect.y() + margin, focusRect.width() - 2 * margin, focusRect.height() - 2 * margin ); QwtPainter::drawFocusRect( painter, this, focusRect ); } /*! Invalidate the paint cache and repaint the canvas \sa invalidatePaintCache() */ void QwtPlotCanvas::replot() { invalidateBackingStore(); if ( testPaintAttribute( QwtPlotCanvas::ImmediatePaint ) ) repaint( contentsRect() ); else update( contentsRect() ); } //! Update the cached informations about the current style sheet void QwtPlotCanvas::updateStyleSheetInfo() { if ( !testAttribute(Qt::WA_StyledBackground ) ) return; QwtStyleSheetRecorder recorder( size() ); QPainter painter( &recorder ); QStyleOption opt; opt.initFrom(this); style()->drawPrimitive( QStyle::PE_Widget, &opt, &painter, this); painter.end(); d_data->styleSheet.hasBorder = !recorder.border.rectList.isEmpty(); d_data->styleSheet.cornerRects = recorder.clipRects; if ( recorder.background.path.isEmpty() ) { if ( !recorder.border.rectList.isEmpty() ) { d_data->styleSheet.borderPath = qwtCombinePathList( rect(), recorder.border.pathList ); } } else { d_data->styleSheet.borderPath = recorder.background.path; d_data->styleSheet.background.brush = recorder.background.brush; d_data->styleSheet.background.origin = recorder.background.origin; } } /*! Calculate the painter path for a styled or rounded border When the canvas has no styled background or rounded borders the painter path is empty. \param rect Bounding rectangle of the canvas \return Painter path, that can be used for clipping */ QPainterPath QwtPlotCanvas::borderPath( const QRect &rect ) const { if ( testAttribute(Qt::WA_StyledBackground ) ) { QwtStyleSheetRecorder recorder( rect.size() ); QPainter painter( &recorder ); QStyleOption opt; opt.initFrom(this); opt.rect = rect; style()->drawPrimitive( QStyle::PE_Widget, &opt, &painter, this); painter.end(); if ( !recorder.background.path.isEmpty() ) return recorder.background.path; if ( !recorder.border.rectList.isEmpty() ) return qwtCombinePathList( rect, recorder.border.pathList ); } else if ( d_data->borderRadius > 0.0 ) { double fw2 = frameWidth() * 0.5; QRectF r = QRectF(rect).adjusted( fw2, fw2, -fw2, -fw2 ); QPainterPath path; path.addRoundedRect( r, d_data->borderRadius, d_data->borderRadius ); return path; } return QPainterPath(); } /*! Calculate a mask, that can be used to clip away the border frame \param size Size including the frame */ QBitmap QwtPlotCanvas::borderMask( const QSize &size ) const { const QRect r( 0, 0, size.width(), size.height() ); const QPainterPath path = borderPath( r ); if ( path.isEmpty() ) return QBitmap(); QImage image( size, QImage::Format_ARGB32_Premultiplied ); image.fill( Qt::color0 ); QPainter painter( &image ); painter.setClipPath( path ); painter.fillRect( r, Qt::color1 ); // now erase the frame painter.setCompositionMode( QPainter::CompositionMode_DestinationOut ); if ( testAttribute(Qt::WA_StyledBackground ) ) { QStyleOptionFrame opt; opt.initFrom(this); opt.rect = r; style()->drawPrimitive( QStyle::PE_Frame, &opt, &painter, this ); } else { if ( d_data->borderRadius > 0 && frameWidth() > 0 ) { painter.setPen( QPen( Qt::color1, frameWidth() ) ); painter.setBrush( Qt::NoBrush ); painter.setRenderHint( QPainter::Antialiasing, true ); painter.drawPath( path ); } } painter.end(); const QImage mask = image.createMaskFromColor( QColor( Qt::color1 ).rgb(), Qt::MaskOutColor ); return QBitmap::fromImage( mask ); }