/* -*- 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_legenditem.h" #include "qwt_dyngrid_layout.h" #include "qwt_scale_map.h" #include "qwt_painter.h" #include #include #include #include #include class QwtLegendLayoutItem: public QLayoutItem { public: QwtLegendLayoutItem( const QwtPlotLegendItem *, const QwtPlotItem * ); virtual ~QwtLegendLayoutItem(); const QwtPlotItem *plotItem() const; void setData( const QwtLegendData & ); const QwtLegendData &data() const; virtual Qt::Orientations expandingDirections() const; virtual QRect geometry() const; virtual bool hasHeightForWidth() const; virtual int heightForWidth( int w ) const; virtual bool isEmpty() const; virtual QSize maximumSize() const; virtual int minimumHeightForWidth( int w ) const; virtual QSize minimumSize() const; virtual void setGeometry( const QRect & r ); virtual QSize sizeHint() const; private: const QwtPlotLegendItem *d_legendItem; const QwtPlotItem *d_plotItem; QwtLegendData d_data; QRect d_rect; }; QwtLegendLayoutItem::QwtLegendLayoutItem( const QwtPlotLegendItem *legendItem, const QwtPlotItem *plotItem ): d_legendItem( legendItem ), d_plotItem( plotItem) { } QwtLegendLayoutItem::~QwtLegendLayoutItem() { } const QwtPlotItem *QwtLegendLayoutItem::plotItem() const { return d_plotItem; } void QwtLegendLayoutItem::setData( const QwtLegendData &data ) { d_data = data; } const QwtLegendData &QwtLegendLayoutItem::data() const { return d_data; } Qt::Orientations QwtLegendLayoutItem::expandingDirections() const { return Qt::Horizontal; } bool QwtLegendLayoutItem::hasHeightForWidth() const { return !d_data.title().isEmpty(); } int QwtLegendLayoutItem::minimumHeightForWidth( int w ) const { return d_legendItem->heightForWidth( d_data, w ); } int QwtLegendLayoutItem::heightForWidth( int w ) const { return d_legendItem->heightForWidth( d_data, w ); } bool QwtLegendLayoutItem::isEmpty() const { return false; } QSize QwtLegendLayoutItem::maximumSize() const { return QSize( QLAYOUTSIZE_MAX, QLAYOUTSIZE_MAX ); } QSize QwtLegendLayoutItem::minimumSize() const { return d_legendItem->minimumSize( d_data ); } QSize QwtLegendLayoutItem::sizeHint() const { return minimumSize(); } void QwtLegendLayoutItem::setGeometry( const QRect &rect ) { d_rect = rect; } QRect QwtLegendLayoutItem::geometry() const { return d_rect; } class QwtPlotLegendItem::PrivateData { public: PrivateData(): itemMargin( 4 ), itemSpacing( 4 ), borderRadius( 0.0 ), borderPen( Qt::NoPen ), backgroundBrush( Qt::NoBrush ), backgroundMode( QwtPlotLegendItem::LegendBackground ), borderDistance( 10 ), alignment( Qt::AlignRight | Qt::AlignBottom ) { layout = new QwtDynGridLayout(); layout->setMaxColumns( 2 ); layout->setSpacing( 0 ); layout->setContentsMargins( 0, 0, 0, 0 ); } ~PrivateData() { delete layout; } QFont font; QPen textPen; int itemMargin; int itemSpacing; double borderRadius; QPen borderPen; QBrush backgroundBrush; QwtPlotLegendItem::BackgroundMode backgroundMode; int borderDistance; Qt::Alignment alignment; QMap< const QwtPlotItem *, QList > map; QwtDynGridLayout *layout; }; //! Constructor QwtPlotLegendItem::QwtPlotLegendItem(): QwtPlotItem( QwtText( "Legend" ) ) { d_data = new PrivateData; setItemInterest( QwtPlotItem::LegendInterest, true ); setZ( 100.0 ); } //! Destructor QwtPlotLegendItem::~QwtPlotLegendItem() { clearLegend(); delete d_data; } //! \return QwtPlotItem::Rtti_PlotLegend int QwtPlotLegendItem::rtti() const { return QwtPlotItem::Rtti_PlotLegend; } /*! \brief Set the alignmnet Alignment means the position of the legend relative to the geometry of the plot canvas. \param alignment Alignment flags \sa alignment(), setMaxColumns() \note To align a legend with many items horizontally the number of columns need to be limited */ void QwtPlotLegendItem::setAlignment( Qt::Alignment alignment ) { if ( d_data->alignment != alignment ) { d_data->alignment = alignment; itemChanged(); } } /*! \return Alignment flags \sa setAlignment() */ Qt::Alignment QwtPlotLegendItem::alignment() const { return d_data->alignment; } /*! \brief Limit the number of columns When aligning the legend horizontally ( Qt::AlignLeft, Qt::AlignRight ) the number of columns needs to be limited to avoid, that the width of the legend grows with an increasing number of entries. \param maxColumns Maximum number of columns. 0 means unlimited. \sa maxColumns(), QwtDynGridLayout::setMaxColumns() */ void QwtPlotLegendItem::setMaxColumns( uint maxColumns ) { if ( maxColumns != d_data->layout->maxColumns() ) { d_data->layout->setMaxColumns( maxColumns ); itemChanged(); } } /*! \return Maximum number of columns \sa maxColumns(), QwtDynGridLayout::maxColumns() */ uint QwtPlotLegendItem::maxColumns() const { return d_data->layout->maxColumns(); } /*! \brief Set the margin around legend items The default setting for the margin is 0. \param margin Margin in pixels \sa margin(), setSpacing(), setItemMargin(), setItemSpacing */ void QwtPlotLegendItem::setMargin( int margin ) { margin = qMax( margin, 0 ); if ( margin != this->margin() ) { d_data->layout->setContentsMargins( margin, margin, margin, margin ); itemChanged(); } } /*! \return Margin around the legend items \sa setMargin(), spacing(), itemMargin(), itemSpacing() */ int QwtPlotLegendItem::margin() const { int left; d_data->layout->getContentsMargins( &left, NULL, NULL, NULL ); return left; } /*! \brief Set the spacing between the legend items \param spacing Spacing in pixels \sa spacing(), setMargin() */ void QwtPlotLegendItem::setSpacing( int spacing ) { spacing = qMax( spacing, 0 ); if ( spacing != d_data->layout->spacing() ) { d_data->layout->setSpacing( spacing ); itemChanged(); } } /*! \return Spacing between the legend items \sa setSpacing(), margin(), itemSpacing(), itemMargin() */ int QwtPlotLegendItem::spacing() const { return d_data->layout->spacing(); } /*! Set the margin around each item \param margin Margin \sa itemMargin(), setItemSpacing(), setMargin(), setSpacing() */ void QwtPlotLegendItem::setItemMargin( int margin ) { margin = qMax( margin, 0 ); if ( margin != d_data->itemMargin ) { d_data->itemMargin = margin; d_data->layout->invalidate(); itemChanged(); } } /*! \return Margin around each item \sa setItemMargin(), itemSpacing(), margin(), spacing() */ int QwtPlotLegendItem::itemMargin() const { return d_data->itemMargin; } /*! Set the spacing inside of each item \param spacing Spacing \sa itemSpacing(), setItemMargin(), setMargin(), setSpacing() */ void QwtPlotLegendItem::setItemSpacing( int spacing ) { spacing = qMax( spacing, 0 ); if ( spacing != d_data->itemSpacing ) { d_data->itemSpacing = spacing; d_data->layout->invalidate(); itemChanged(); } } /*! \return Spacing inside of each item \sa setItemSpacing(), itemMargin(), margin(), spacing() */ int QwtPlotLegendItem::itemSpacing() const { return d_data->itemSpacing; } /*! Change the font used for drawing the text label \param font Legend font \sa font() */ void QwtPlotLegendItem::setFont( const QFont &font ) { if ( font != d_data->font ) { d_data->font = font; d_data->layout->invalidate(); itemChanged(); } } /*! \return Font used for drawing the text label \sa setFont() */ QFont QwtPlotLegendItem::font() const { return d_data->font; } /*! \brief Set the margin between the legend and the canvas border The default setting for the margin is 10 pixels. \param distance Margin in pixels \sa setMargin() */ void QwtPlotLegendItem::setBorderDistance( int distance ) { if ( distance < 0 ) distance = -1; if ( distance != d_data->borderDistance ) { d_data->borderDistance = distance; itemChanged(); } } /*! \return Margin between the legend and the canvas border \sa margin() */ int QwtPlotLegendItem::borderDistance() const { return d_data->borderDistance; } /*! Set the radius for the border \param radius A value <= 0 defines a rectangular border \sa borderRadius(), setBorderPen() */ void QwtPlotLegendItem::setBorderRadius( double radius ) { radius = qMax( 0.0, radius ); if ( radius != d_data->borderRadius ) { d_data->borderRadius = radius; itemChanged(); } } /*! \return Radius of the border \sa setBorderRadius(), setBorderPen() */ double QwtPlotLegendItem::borderRadius() const { return d_data->borderRadius; } /*! Set the pen for drawing the border \param pen Border pen \sa borderPen(), setBackgroundBrush() */ void QwtPlotLegendItem::setBorderPen( const QPen &pen ) { if ( d_data->borderPen != pen ) { d_data->borderPen = pen; itemChanged(); } } /*! \return Pen for drawing the border \sa setBorderPen(), backgroundBrush() */ QPen QwtPlotLegendItem::borderPen() const { return d_data->borderPen; } /*! \brief Set the background brush The brush is used to fill the background \param brush Brush \sa backgroundBrush(), setBackgroundMode(), drawBackground() */ void QwtPlotLegendItem::setBackgroundBrush( const QBrush &brush ) { if ( d_data->backgroundBrush != brush ) { d_data->backgroundBrush = brush; itemChanged(); } } /*! \return Brush is used to fill the background \sa setBackgroundBrush(), backgroundMode(), drawBackground() */ QBrush QwtPlotLegendItem::backgroundBrush() const { return d_data->backgroundBrush; } /*! \brief Set the background mode Depending on the mode the complete legend or each item might have an background. The default setting is LegendBackground. \sa backgroundMode(), setBackgroundBrush(), drawBackground() */ void QwtPlotLegendItem::setBackgroundMode( BackgroundMode mode ) { if ( mode != d_data->backgroundMode ) { d_data->backgroundMode = mode; itemChanged(); } } /*! \return backgroundMode \sa setBackgroundMode(), backgroundBrush(), drawBackground() */ QwtPlotLegendItem::BackgroundMode QwtPlotLegendItem::backgroundMode() const { return d_data->backgroundMode; } /*! \brief Set the pen for drawing text labels \param pen Text pen \sa textPen(), setFont() */ void QwtPlotLegendItem::setTextPen( const QPen &pen ) { if ( d_data->textPen != pen ) { d_data->textPen = pen; itemChanged(); } } /*! \return Pen for drawing text labels \sa setTextPen(), font() */ QPen QwtPlotLegendItem::textPen() const { return d_data->textPen; } /*! Draw the legend \param painter Painter \param xMap x Scale Map \param yMap y Scale Map \param canvasRect Contents rectangle of the canvas in painter coordinates */ void QwtPlotLegendItem::draw( QPainter *painter, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect ) const { Q_UNUSED( xMap ); Q_UNUSED( yMap ); d_data->layout->setGeometry( geometry( canvasRect ) ); if ( d_data->layout->geometry().isEmpty() ) { // don't draw a legend when having no content return; } if ( d_data->backgroundMode == QwtPlotLegendItem::LegendBackground ) drawBackground( painter, d_data->layout->geometry() ); for ( int i = 0; i < d_data->layout->count(); i++ ) { const QwtLegendLayoutItem *layoutItem = static_cast( d_data->layout->itemAt( i ) ); if ( d_data->backgroundMode == QwtPlotLegendItem::ItemBackground ) drawBackground( painter, layoutItem->geometry() ); painter->save(); drawLegendData( painter, layoutItem->plotItem(), layoutItem->data(), layoutItem->geometry() ); painter->restore(); } } /*! Draw a rounded rect \param painter Painter \param rect Bounding rectangle \sa setBorderRadius(), setBorderPen(), setBackgroundBrush(), setBackgroundMode() */ void QwtPlotLegendItem::drawBackground( QPainter *painter, const QRectF &rect ) const { painter->save(); painter->setPen( d_data->borderPen ); painter->setBrush( d_data->backgroundBrush ); const double radius = d_data->borderRadius; painter->drawRoundedRect( rect, radius, radius ); painter->restore(); } /*! Calculate the geometry of the legend on the canvas \param canvasRect Geometry of the canvas \return Geometry of the legend */ QRect QwtPlotLegendItem::geometry( const QRectF &canvasRect ) const { QRect rect; rect.setSize( d_data->layout->sizeHint() ); int margin = d_data->borderDistance; if ( d_data->alignment & Qt::AlignHCenter ) { int x = qRound( canvasRect.center().x() ); rect.moveCenter( QPoint( x, rect.center().y() ) ); } else if ( d_data->alignment & Qt::AlignRight ) { rect.moveRight( qFloor( canvasRect.right() - margin ) ); } else { rect.moveLeft( qCeil( canvasRect.left() + margin ) ); } if ( d_data->alignment & Qt::AlignVCenter ) { int y = qRound( canvasRect.center().y() ); rect.moveCenter( QPoint( rect.center().x(), y ) ); } else if ( d_data->alignment & Qt::AlignBottom ) { rect.moveBottom( qFloor( canvasRect.bottom() - margin ) ); } else { rect.moveTop( qCeil( canvasRect.top() + margin ) ); } return rect; } /*! Update the legend items according to modifications of a plot item \param plotItem Plot item \param data Attributes of the legend entries */ void QwtPlotLegendItem::updateLegend( const QwtPlotItem *plotItem, const QList &data ) { if ( plotItem == NULL ) return; QList layoutItems; QMap >::iterator it = d_data->map.find( plotItem ); if ( it != d_data->map.end() ) layoutItems = it.value(); bool changed = false; if ( data.size() != layoutItems.size() ) { changed = true; for ( int i = 0; i < layoutItems.size(); i++ ) { d_data->layout->removeItem( layoutItems[i] ); delete layoutItems[i]; } layoutItems.clear(); if ( it != d_data->map.end() ) d_data->map.remove( plotItem ); if ( !data.isEmpty() ) { for ( int i = 0; i < data.size(); i++ ) { QwtLegendLayoutItem *layoutItem = new QwtLegendLayoutItem( this, plotItem ); d_data->layout->addItem( layoutItem ); layoutItems += layoutItem; } d_data->map.insert( plotItem, layoutItems ); } } for ( int i = 0; i < data.size(); i++ ) { if ( layoutItems[i]->data().values() != data[i].values() ) { layoutItems[i]->setData( data[i] ); changed = true; } } if ( changed ) { d_data->layout->invalidate(); itemChanged(); } } //! Remove all items from the legend void QwtPlotLegendItem::clearLegend() { if ( !d_data->map.isEmpty() ) { d_data->map.clear(); for ( int i = d_data->layout->count() - 1; i >= 0; i-- ) delete d_data->layout->takeAt( i ); itemChanged(); } } /*! Draw an entry on the legend \param painter Qt Painter \param plotItem Plot item, represented by the entry \param data Attributes of the legend entry \param rect Bounding rectangle for the entry */ void QwtPlotLegendItem::drawLegendData( QPainter *painter, const QwtPlotItem *plotItem, const QwtLegendData &data, const QRectF &rect ) const { Q_UNUSED( plotItem ); const int m = d_data->itemMargin; const QRectF r = rect.toRect().adjusted( m, m, -m, -m ); painter->setClipRect( r, Qt::IntersectClip ); int titleOff = 0; const QwtGraphic graphic = data.icon(); if ( !graphic.isEmpty() ) { QRectF iconRect( r.topLeft(), graphic.defaultSize() ); iconRect.moveCenter( QPoint( iconRect.center().x(), rect.center().y() ) ); graphic.render( painter, iconRect, Qt::KeepAspectRatio ); titleOff += iconRect.width() + d_data->itemSpacing; } const QwtText text = data.title(); if ( !text.isEmpty() ) { painter->setPen( textPen() ); painter->setFont( font() ); const QRectF textRect = r.adjusted( titleOff, 0, 0, 0 ); text.draw( painter, textRect ); } } /*! Minimum size hint needed to display an entry \param data Attributes of the legend entry \return Minimum size */ QSize QwtPlotLegendItem::minimumSize( const QwtLegendData &data ) const { QSize size( 2 * d_data->itemMargin, 2 * d_data->itemMargin ); if ( !data.isValid() ) return size; const QwtGraphic graphic = data.icon(); const QwtText text = data.title(); int w = 0; int h = 0; if ( !graphic.isNull() ) { w = graphic.width(); h = graphic.height(); } if ( !text.isEmpty() ) { const QSizeF sz = text.textSize( font() ); w += qCeil( sz.width() ); h = qMax( h, qCeil( sz.height() ) ); } if ( graphic.width() > 0 && !text.isEmpty() ) w += d_data->itemSpacing; size += QSize( w, h ); return size; } /*! \return The preferred height, for a width. \param data Attributes of the legend entry \param width Width */ int QwtPlotLegendItem::heightForWidth( const QwtLegendData &data, int width ) const { width -= 2 * d_data->itemMargin; const QwtGraphic graphic = data.icon(); const QwtText text = data.title(); if ( text.isEmpty() ) return graphic.height(); if ( graphic.width() > 0 ) width -= graphic.width() + d_data->itemSpacing; int h = text.heightForWidth( width, font() ); h += 2 * d_data->itemMargin; return qMax( graphic.height(), h ); } /*! \return All plot items with an entry on the legend \note A plot item might have more than one entry on the legend */ QList< const QwtPlotItem * > QwtPlotLegendItem::plotItems() const { return d_data->map.keys(); } /*! \return Geometries of the items of a plot item \note Usually a plot item has only one entry on the legend */ QList< QRect > QwtPlotLegendItem::legendGeometries( const QwtPlotItem *plotItem ) const { QList layoutItems; QMap >::iterator it = d_data->map.find( plotItem ); if ( it != d_data->map.end() ) layoutItems = it.value(); QList geometries; for ( int i = 0; i < layoutItems.size(); i++ ) geometries += layoutItems[i]->geometry(); return geometries; }