Newer
Older
/* -*- 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_legend.h"
#include "qwt_legend_label.h"
#include "qwt_dyngrid_layout.h"
#include "qwt_math.h"
#include "qwt_plot_item.h"
#include "qwt_painter.h"
#include <qapplication.h>
#include <qscrollbar.h>
#include <qscrollarea.h>
#include <qpainter.h>
#include <qstyle.h>
#include <qstyleoption.h>
inline bool isEmpty() const { return d_entries.isEmpty(); }
void insert( const QVariant &, const QList<QWidget *> & );
void remove( const QVariant & );
void removeWidget( const QWidget * );
QList<QWidget *> legendWidgets( const QVariant & ) const;
QVariant itemInfo( const QWidget * ) const;
private:
// we don't know anything about itemInfo and therefore don't have
// any key that can be used for a map or hashtab.
// But a simple linear list is o.k. here, as we will never have
// more than a few entries.
class Entry
void QwtLegendMap::insert( const QVariant &itemInfo,
const QList<QWidget *> &widgets )
{
for ( int i = 0; i < d_entries.size(); i++ )
{
Entry &entry = d_entries[i];
if ( entry.itemInfo == itemInfo )
{
entry.widgets = widgets;
return;
}
}
Entry newEntry;
newEntry.itemInfo = itemInfo;
newEntry.widgets = widgets;
void QwtLegendMap::remove( const QVariant &itemInfo )
{
for ( int i = 0; i < d_entries.size(); i++ )
{
Entry &entry = d_entries[i];
if ( entry.itemInfo == itemInfo )
{
d_entries.removeAt( i );
return;
}
}
}
void QwtLegendMap::removeWidget( const QWidget *widget )
{
QWidget *w = const_cast<QWidget *>( widget );
for ( int i = 0; i < d_entries.size(); i++ )
d_entries[ i ].widgets.removeAll( w );
}
QVariant QwtLegendMap::itemInfo( const QWidget *widget ) const
{
if ( widget != NULL )
{
QWidget *w = const_cast<QWidget *>( widget );
for ( int i = 0; i < d_entries.size(); i++ )
{
const Entry &entry = d_entries[i];
if ( entry.widgets.indexOf( w ) >= 0 )
return entry.itemInfo;
}
}
return QVariant();
}
QList<QWidget *> QwtLegendMap::legendWidgets( const QVariant &itemInfo ) const
{
if ( itemInfo.isValid() )
{
for ( int i = 0; i < d_entries.size(); i++ )
{
const Entry &entry = d_entries[i];
if ( entry.itemInfo == itemInfo )
return entry.widgets;
}
}
PrivateData():
itemMode( QwtLegendData::ReadOnly ),
view( NULL )
{
}
class QwtLegend::PrivateData::LegendView: public QScrollArea
{
public:
LegendView( QWidget *parent ):
QScrollArea( parent )
{
contentsWidget = new QWidget( this );
contentsWidget->setObjectName( "QwtLegendViewContents" );
setWidget( contentsWidget );
setWidgetResizable( false );
// QScrollArea::setWidget internally sets autoFillBackground to true
// But we don't want a background.
contentsWidget->setAutoFillBackground( false );
viewport()->setAutoFillBackground( false );
virtual bool event( QEvent *event )
{
if ( event->type() == QEvent::PolishRequest )
{
setFocusPolicy( Qt::NoFocus );
}
if ( event->type() == QEvent::Resize )
{
// adjust the size to en/disable the scrollbars
// before QScrollArea adjusts the viewport size
int w = cr.width();
int h = contentsWidget->heightForWidth( cr.width() );
if ( h > w )
{
w -= verticalScrollBar()->sizeHint().width();
h = contentsWidget->heightForWidth( w );
}
contentsWidget->resize( w, h );
}
virtual bool viewportEvent( QEvent *event )
{
bool ok = QScrollArea::viewportEvent( event );
if ( event->type() == QEvent::Resize )
{
layoutContents();
const int sbHeight = horizontalScrollBar()->sizeHint().height();
const int sbWidth = verticalScrollBar()->sizeHint().width();
const int cw = contentsRect().width();
const int ch = contentsRect().height();
int vw = cw;
int vh = ch;
if ( w > vw )
vh -= sbHeight;
vw -= sbWidth;
if ( w > vw && vh == ch )
vh -= sbHeight;
}
void layoutContents()
{
const QwtDynGridLayout *tl = qobject_cast<QwtDynGridLayout *>(
contentsWidget->layout() );
if ( tl == NULL )
return;
const QSize visibleSize = viewport()->contentsRect().size();
const int minW = int( tl->maxItemWidth() ) + 2 * tl->margin();
int w = qMax( visibleSize.width(), minW );
int h = qMax( tl->heightForWidth( w ), visibleSize.height() );
const int vpWidth = viewportSize( w, h ).width();
if ( w > vpWidth )
{
w = qMax( vpWidth, minW );
h = qMax( tl->heightForWidth( w ), visibleSize.height() );
}
/*!
Constructor
\param parent Parent widget
*/
QwtLegend::QwtLegend( QWidget *parent ):
QwtAbstractLegend( parent )
d_data->view = new QwtLegend::PrivateData::LegendView( this );
d_data->view->setObjectName( "QwtLegendView" );
d_data->view->setFrameStyle( NoFrame );
QwtDynGridLayout *gridLayout = new QwtDynGridLayout(
d_data->view->contentsWidget );
gridLayout->setAlignment( Qt::AlignHCenter | Qt::AlignTop );
d_data->view->contentsWidget->installEventFilter( this );
QVBoxLayout *layout = new QVBoxLayout( this );
layout->setContentsMargins( 0, 0, 0, 0 );
layout->addWidget( d_data->view );
F.e when the maximum is set to 1 all items are aligned
vertically. 0 means unlimited
\sa maxColumns(), QwtDynGridLayout::setMaxColumns()
*/
void QwtLegend::setMaxColumns( uint numColums )
QwtDynGridLayout *tl = qobject_cast<QwtDynGridLayout *>(
d_data->view->contentsWidget->layout() );
if ( tl )
tl->setMaxColumns( numColums );
\return Maximum number of entries in a row
\sa setMaxColumns(), QwtDynGridLayout::maxColumns()
*/
uint QwtLegend::maxColumns() const
const QwtDynGridLayout *tl = qobject_cast<const QwtDynGridLayout *>(
d_data->view->contentsWidget->layout() );
if ( tl )
maxCols = tl->maxColumns();
Legend labels will be constructed according to the
attributes in a QwtLegendData object. When it doesn't
contain a value for the QwtLegendData::ModeRole the
label will be initialized with the default mode of the legend.
\sa itemMode(), QwtLegendData::value(), QwtPlotItem::legendData()
\note Changing the mode doesn't have any effect on existing labels.
*/
void QwtLegend::setDefaultItemMode( QwtLegendData::Mode mode )
The contents widget is the only child of the viewport of
the internal QScrollArea and the parent widget of all legend items.
\return Container widget of the legend items
QWidget *QwtLegend::contentsWidget()
{
return d_data->view->contentsWidget;
/*!
\return Horizontal scrollbar
\sa verticalScrollBar()
*/
QScrollBar *QwtLegend::horizontalScrollBar() const
{
return d_data->view->horizontalScrollBar();
}
/*!
\return Vertical scrollbar
\sa horizontalScrollBar()
*/
QScrollBar *QwtLegend::verticalScrollBar() const
{
return d_data->view->verticalScrollBar();
}
The contents widget is the only child of the viewport of
the internal QScrollArea and the parent widget of all legend items.
const QWidget *QwtLegend::contentsWidget() const
{
return d_data->view->contentsWidget;
\brief Update the entries for an item
\param itemInfo Info for an item
\param data List of legend entry attributes for the item
*/
void QwtLegend::updateLegend( const QVariant &itemInfo,
const QList<QwtLegendData> &data )
if ( widgetList.size() != data.size() )
{
QLayout *contentsLayout = d_data->view->contentsWidget->layout();
while ( widgetList.size() > data.size() )
{
QWidget *w = widgetList.takeLast();
// updates might be triggered by signals from the legend widget
// itself. So we better don't delete it here.
for ( int i = widgetList.size(); i < data.size(); i++ )
{
QWidget *widget = createWidget( data[i] );
if ( contentsLayout )
contentsLayout->addWidget( widget );
if ( widgetList.isEmpty() )
{
d_data->itemMap.remove( itemInfo );
else
{
d_data->itemMap.insert( itemInfo, widgetList );
}
updateTabOrder();
for ( int i = 0; i < data.size(); i++ )
updateWidget( widgetList[i], data[i] );
The default implementation returns a QwtLegendLabel.
\param data Attributes of the legend entry
\return Widget representing data on the legend
\note updateWidget() will called soon after createWidget()
with the same attributes.
*/
QWidget *QwtLegend::createWidget( const QwtLegendData &data ) const
QwtLegendLabel *label = new QwtLegendLabel();
label->setItemMode( defaultItemMode() );
connect( label, SIGNAL( clicked() ), SLOT( itemClicked() ) );
connect( label, SIGNAL( checked( bool ) ), SLOT( itemChecked( bool ) ) );
return label;
\param widget Usually a QwtLegendLabel
\param data Attributes to be displayed
\sa createWidget()
\note When widget is no QwtLegendLabel updateWidget() does nothing.
*/
void QwtLegend::updateWidget( QWidget *widget, const QwtLegendData &data )
QwtLegendLabel *label = qobject_cast<QwtLegendLabel *>( widget );
if ( label )
{
label->setData( data );
if ( !data.value( QwtLegendData::ModeRole ).isValid() )
{
// use the default mode, when there is no specific
// hint from the legend data
label->setItemMode( defaultItemMode() );
}
}
QLayout *contentsLayout = d_data->view->contentsWidget->layout();
if ( contentsLayout )
{
// set tab focus chain
QWidget *w = NULL;
for ( int i = 0; i < contentsLayout->count(); i++ )
{
QLayoutItem *item = contentsLayout->itemAt( i );
if ( w && item->widget() )
QWidget::setTabOrder( w, item->widget() );
}
//! Return a size hint.
QSize QwtLegend::sizeHint() const
{
QSize hint = d_data->view->contentsWidget->sizeHint();
int h = d_data->view->contentsWidget->heightForWidth( width );
if ( h >= 0 )
h += 2 * frameWidth();
return h;
}
Handle QEvent::ChildRemoved andQEvent::LayoutRequest events
for the contentsWidget().
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
\return Forwarded to QwtAbstractLegend::eventFilter()
*/
bool QwtLegend::eventFilter( QObject *object, QEvent *event )
{
if ( object == d_data->view->contentsWidget )
{
switch ( event->type() )
{
case QEvent::ChildRemoved:
{
const QChildEvent *ce =
static_cast<const QChildEvent *>(event);
if ( ce->child()->isWidgetType() )
{
QWidget *w = static_cast< QWidget * >( ce->child() );
d_data->itemMap.removeWidget( w );
}
break;
}
case QEvent::LayoutRequest:
{
d_data->view->layoutContents();
if ( parentWidget() && parentWidget()->layout() == NULL )
{
/*
We want the parent widget ( usually QwtPlot ) to recalculate
its layout, when the contentsWidget has changed. But
because of the scroll view we have to forward the LayoutRequest
event manually.
We don't use updateGeometry() because it doesn't post LayoutRequest
events when the legend is hidden. But we want the
parent widget notified, so it can show/hide the legend
depending on its items.
*/
QApplication::postEvent( parentWidget(),
new QEvent( QEvent::LayoutRequest ) );
}
break;
}
default:
break;
}
}
return QwtAbstractLegend::eventFilter( object, event );
}
/*!
Called internally when the legend has been clicked on.
Emits a clicked() signal.
*/
void QwtLegend::itemClicked()
{
QWidget *w = qobject_cast<QWidget *>( sender() );
if ( w )
{
const QVariant itemInfo = d_data->itemMap.itemInfo( w );
if ( itemInfo.isValid() )
{
const QList<QWidget *> widgetList =
d_data->itemMap.legendWidgets( itemInfo );
const int index = widgetList.indexOf( w );
if ( index >= 0 )
Q_EMIT clicked( itemInfo, index );
/*!
Called internally when the legend has been checked
Emits a checked() signal.
*/
void QwtLegend::itemChecked( bool on )
{
QWidget *w = qobject_cast<QWidget *>( sender() );
if ( w )
{
const QVariant itemInfo = d_data->itemMap.itemInfo( w );
if ( itemInfo.isValid() )
{
const QList<QWidget *> widgetList =
d_data->itemMap.legendWidgets( itemInfo );
const int index = widgetList.indexOf( w );
if ( index >= 0 )
Q_EMIT checked( itemInfo, on, index );
}
\param painter Painter
\param rect Bounding rectangle
\param fillBackground When true, fill rect with the widget background
\sa renderLegend() is used by QwtPlotRenderer - not by QwtLegend itself
*/
void QwtLegend::renderLegend( QPainter *painter,
const QRectF &rect, bool fillBackground ) const
if ( d_data->itemMap.isEmpty() )
return;
if ( fillBackground )
{
if ( autoFillBackground() ||
testAttribute( Qt::WA_StyledBackground ) )
{
QwtPainter::drawBackgound( painter, rect, this );
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
const QwtDynGridLayout *legendLayout =
qobject_cast<QwtDynGridLayout *>( contentsWidget()->layout() );
if ( legendLayout == NULL )
return;
int left, right, top, bottom;
getContentsMargins( &left, &top, &right, &bottom );
QRect layoutRect;
layoutRect.setLeft( qCeil( rect.left() ) + left );
layoutRect.setTop( qCeil( rect.top() ) + top );
layoutRect.setRight( qFloor( rect.right() ) - right );
layoutRect.setBottom( qFloor( rect.bottom() ) - bottom );
uint numCols = legendLayout->columnsForWidth( layoutRect.width() );
QList<QRect> itemRects =
legendLayout->layoutItems( layoutRect, numCols );
int index = 0;
for ( int i = 0; i < legendLayout->count(); i++ )
{
QLayoutItem *item = legendLayout->itemAt( i );
QWidget *w = item->widget();
if ( w )
{
painter->save();
painter->setClipRect( itemRects[index], Qt::IntersectClip );
renderItem( painter, w, itemRects[index], fillBackground );
index++;
painter->restore();
}
}
\param painter Painter
\param widget Widget representing a legend entry
\param rect Bounding rectangle
\param fillBackground When true, fill rect with the widget background
\note When widget is not derived from QwtLegendLabel renderItem
does nothing beside the background
*/
void QwtLegend::renderItem( QPainter *painter,
const QWidget *widget, const QRectF &rect, bool fillBackground ) const
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
if ( fillBackground )
{
if ( widget->autoFillBackground() ||
widget->testAttribute( Qt::WA_StyledBackground ) )
{
QwtPainter::drawBackgound( painter, rect, widget );
}
}
const QwtLegendLabel *label = qobject_cast<const QwtLegendLabel *>( widget );
if ( label )
{
// icon
const QwtGraphic &icon = label->data().icon();
const QSizeF sz = icon.defaultSize();
const QRectF iconRect( rect.x() + label->margin(),
rect.center().y() - 0.5 * sz.height(),
sz.width(), sz.height() );
icon.render( painter, iconRect, Qt::KeepAspectRatio );
// title
QRectF titleRect = rect;
titleRect.setX( iconRect.right() + 2 * label->spacing() );
painter->setFont( label->font() );
painter->setPen( label->palette().color( QPalette::Text ) );
const_cast< QwtLegendLabel *>( label )->drawText( painter, titleRect );
}
/*!
\return List of widgets associated to a item
\param itemInfo Info about an item
\sa legendWidget(), itemInfo(), QwtPlot::itemToInfo()
*/
QList<QWidget *> QwtLegend::legendWidgets( const QVariant &itemInfo ) const
/*!
\return First widget in the list of widgets associated to an item
\param itemInfo Info about an item
\sa itemInfo(), QwtPlot::itemToInfo()
\note Almost all types of items have only one widget
*/
QWidget *QwtLegend::legendWidget( const QVariant &itemInfo ) const
const QList<QWidget *> list = d_data->itemMap.legendWidgets( itemInfo );
if ( list.isEmpty() )
return NULL;
return list[0];
}
/*!
Find the item that is associated to a widget
\param widget Widget on the legend
\return Associated item info
\sa legendWidget()
*/
QVariant QwtLegend::itemInfo( const QWidget *widget ) const
{
return d_data->itemMap.itemInfo( widget );
}
//! \return True, when no item is inserted
bool QwtLegend::isEmpty() const
{
return d_data->itemMap.isEmpty();
Return the extent, that is needed for the scrollbars
\param orientation Orientation (
\return The width of the vertical scrollbar for Qt::Horizontal and v.v.
*/
int QwtLegend::scrollExtent( Qt::Orientation orientation ) const
int extent = 0;
if ( orientation == Qt::Horizontal )
extent = verticalScrollBar()->sizeHint().width();
else
extent = horizontalScrollBar()->sizeHint().height();
return extent;