地面站终端 App
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

802 lines
20 KiB

15 years ago
/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
* Qwt Widget Library
* Copyright (C) 1997 Josef Wilgen
* Copyright (C) 2002 Uwe Rathmann
*
15 years ago
* 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>
15 years ago
class QwtLegendMap
15 years ago
{
public:
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
15 years ago
{
public:
QVariant itemInfo;
QList<QWidget *> widgets;
};
15 years ago
QList< Entry > d_entries;
};
15 years ago
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;
}
}
15 years ago
Entry newEntry;
newEntry.itemInfo = itemInfo;
newEntry.widgets = widgets;
15 years ago
d_entries += newEntry;
}
15 years ago
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;
}
}
}
15 years ago
void QwtLegendMap::removeWidget( const QWidget *widget )
{
QWidget *w = const_cast<QWidget *>( widget );
15 years ago
for ( int i = 0; i < d_entries.size(); i++ )
d_entries[ i ].widgets.removeAll( w );
}
15 years ago
QVariant QwtLegendMap::itemInfo( const QWidget *widget ) const
{
if ( widget != NULL )
{
QWidget *w = const_cast<QWidget *>( widget );
15 years ago
for ( int i = 0; i < d_entries.size(); i++ )
{
const Entry &entry = d_entries[i];
if ( entry.widgets.indexOf( w ) >= 0 )
return entry.itemInfo;
}
}
15 years ago
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;
}
}
15 years ago
return QList<QWidget *>();
}
15 years ago
class QwtLegend::PrivateData
15 years ago
{
public:
PrivateData():
itemMode( QwtLegendData::ReadOnly ),
view( NULL )
{
}
15 years ago
QwtLegendData::Mode itemMode;
QwtLegendMap itemMap;
15 years ago
class LegendView;
LegendView *view;
};
15 years ago
class QwtLegend::PrivateData::LegendView: public QScrollArea
{
public:
LegendView( QWidget *parent ):
QScrollArea( parent )
{
contentsWidget = new QWidget( this );
contentsWidget->setObjectName( "QwtLegendViewContents" );
15 years ago
setWidget( contentsWidget );
setWidgetResizable( false );
15 years ago
viewport()->setObjectName( "QwtLegendViewport" );
15 years ago
// QScrollArea::setWidget internally sets autoFillBackground to true
// But we don't want a background.
contentsWidget->setAutoFillBackground( false );
viewport()->setAutoFillBackground( false );
15 years ago
}
virtual bool event( QEvent *event )
{
if ( event->type() == QEvent::PolishRequest )
{
setFocusPolicy( Qt::NoFocus );
}
15 years ago
if ( event->type() == QEvent::Resize )
{
// adjust the size to en/disable the scrollbars
// before QScrollArea adjusts the viewport size
15 years ago
const QRect cr = contentsRect();
15 years ago
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 );
}
15 years ago
return QScrollArea::event( event );
15 years ago
}
virtual bool viewportEvent( QEvent *event )
{
bool ok = QScrollArea::viewportEvent( event );
15 years ago
if ( event->type() == QEvent::Resize )
{
layoutContents();
15 years ago
}
return ok;
}
QSize viewportSize( int w, int h ) const
{
15 years ago
const int sbHeight = horizontalScrollBar()->sizeHint().height();
const int sbWidth = verticalScrollBar()->sizeHint().width();
15 years ago
const int cw = contentsRect().width();
const int ch = contentsRect().height();
int vw = cw;
int vh = ch;
if ( w > vw )
vh -= sbHeight;
if ( h > vh )
{
15 years ago
vw -= sbWidth;
if ( w > vw && vh == ch )
vh -= sbHeight;
}
return QSize( vw, vh );
15 years ago
}
void layoutContents()
{
const QwtDynGridLayout *tl = qobject_cast<QwtDynGridLayout *>(
contentsWidget->layout() );
if ( tl == NULL )
return;
const QSize visibleSize = viewport()->contentsRect().size();
15 years ago
const int minW = int( tl->maxItemWidth() ) + 2 * tl->margin();
15 years ago
int w = qMax( visibleSize.width(), minW );
int h = qMax( tl->heightForWidth( w ), visibleSize.height() );
15 years ago
const int vpWidth = viewportSize( w, h ).width();
if ( w > vpWidth )
{
w = qMax( vpWidth, minW );
h = qMax( tl->heightForWidth( w ), visibleSize.height() );
}
15 years ago
contentsWidget->resize( w, h );
}
15 years ago
QWidget *contentsWidget;
};
15 years ago
/*!
Constructor
\param parent Parent widget
*/
QwtLegend::QwtLegend( QWidget *parent ):
QwtAbstractLegend( parent )
15 years ago
{
setFrameStyle( NoFrame );
15 years ago
d_data = new QwtLegend::PrivateData;
15 years ago
d_data->view = new QwtLegend::PrivateData::LegendView( this );
d_data->view->setObjectName( "QwtLegendView" );
d_data->view->setFrameStyle( NoFrame );
15 years ago
QwtDynGridLayout *gridLayout = new QwtDynGridLayout(
d_data->view->contentsWidget );
gridLayout->setAlignment( Qt::AlignHCenter | Qt::AlignTop );
d_data->view->contentsWidget->installEventFilter( this );
15 years ago
QVBoxLayout *layout = new QVBoxLayout( this );
layout->setContentsMargins( 0, 0, 0, 0 );
layout->addWidget( d_data->view );
15 years ago
}
//! Destructor
QwtLegend::~QwtLegend()
15 years ago
{
delete d_data;
15 years ago
}
/*!
\brief Set the maximum number of entries in a row
15 years ago
F.e when the maximum is set to 1 all items are aligned
vertically. 0 means unlimited
15 years ago
\param numColums Maximum number of entries in a row
15 years ago
\sa maxColumns(), QwtDynGridLayout::setMaxColumns()
*/
void QwtLegend::setMaxColumns( uint numColums )
15 years ago
{
QwtDynGridLayout *tl = qobject_cast<QwtDynGridLayout *>(
d_data->view->contentsWidget->layout() );
if ( tl )
tl->setMaxColumns( numColums );
}
15 years ago
/*!
\return Maximum number of entries in a row
\sa setMaxColumns(), QwtDynGridLayout::maxColumns()
*/
uint QwtLegend::maxColumns() const
15 years ago
{
uint maxCols = 0;
15 years ago
const QwtDynGridLayout *tl = qobject_cast<const QwtDynGridLayout *>(
d_data->view->contentsWidget->layout() );
if ( tl )
maxCols = tl->maxColumns();
15 years ago
return maxCols;
15 years ago
}
/*!
\brief Set the default mode for legend labels
15 years ago
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.
15 years ago
\param mode Default item mode
15 years ago
\sa itemMode(), QwtLegendData::value(), QwtPlotItem::legendData()
\note Changing the mode doesn't have any effect on existing labels.
*/
void QwtLegend::setDefaultItemMode( QwtLegendData::Mode mode )
15 years ago
{
d_data->itemMode = mode;
}
/*!
\return Default item mode
\sa setDefaultItemMode()
15 years ago
*/
QwtLegendData::Mode QwtLegend::defaultItemMode() const
15 years ago
{
return d_data->itemMode;
15 years ago
}
/*!
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
15 years ago
*/
QWidget *QwtLegend::contentsWidget()
{
return d_data->view->contentsWidget;
15 years ago
}
/*!
\return Horizontal scrollbar
\sa verticalScrollBar()
*/
15 years ago
QScrollBar *QwtLegend::horizontalScrollBar() const
{
return d_data->view->horizontalScrollBar();
}
/*!
\return Vertical scrollbar
\sa horizontalScrollBar()
*/
15 years ago
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.
15 years ago
\return Container widget of the legend items
*/
const QWidget *QwtLegend::contentsWidget() const
{
return d_data->view->contentsWidget;
15 years ago
}
/*!
\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 )
15 years ago
{
QList<QWidget *> widgetList = legendWidgets( itemInfo );
15 years ago
if ( widgetList.size() != data.size() )
{
QLayout *contentsLayout = d_data->view->contentsWidget->layout();
15 years ago
while ( widgetList.size() > data.size() )
{
QWidget *w = widgetList.takeLast();
15 years ago
contentsLayout->removeWidget( w );
15 years ago
// updates might be triggered by signals from the legend widget
// itself. So we better don't delete it here.
15 years ago
w->hide();
w->deleteLater();
}
15 years ago
for ( int i = widgetList.size(); i < data.size(); i++ )
{
QWidget *widget = createWidget( data[i] );
15 years ago
if ( contentsLayout )
contentsLayout->addWidget( widget );
15 years ago
widgetList += widget;
}
15 years ago
if ( widgetList.isEmpty() )
{
d_data->itemMap.remove( itemInfo );
15 years ago
}
else
{
d_data->itemMap.insert( itemInfo, widgetList );
}
updateTabOrder();
15 years ago
}
for ( int i = 0; i < data.size(); i++ )
updateWidget( widgetList[i], data[i] );
15 years ago
}
/*!
\brief Create a widget to be inserted into the legend
15 years ago
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
15 years ago
{
Q_UNUSED( data );
15 years ago
QwtLegendLabel *label = new QwtLegendLabel();
label->setItemMode( defaultItemMode() );
15 years ago
connect( label, SIGNAL( clicked() ), SLOT( itemClicked() ) );
connect( label, SIGNAL( checked( bool ) ), SLOT( itemChecked( bool ) ) );
return label;
15 years ago
}
/*!
\brief Update the widget
15 years ago
\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() );
}
}
15 years ago
}
void QwtLegend::updateTabOrder()
15 years ago
{
QLayout *contentsLayout = d_data->view->contentsWidget->layout();
if ( contentsLayout )
{
// set tab focus chain
15 years ago
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() );
15 years ago
w = item->widget();
}
}
15 years ago
}
//! Return a size hint.
QSize QwtLegend::sizeHint() const
{
QSize hint = d_data->view->contentsWidget->sizeHint();
hint += QSize( 2 * frameWidth(), 2 * frameWidth() );
15 years ago
return hint;
}
/*!
\return The preferred height, for a width.
15 years ago
\param width Width
*/
int QwtLegend::heightForWidth( int width ) const
15 years ago
{
width -= 2 * frameWidth();
int h = d_data->view->contentsWidget->heightForWidth( width );
15 years ago
if ( h >= 0 )
h += 2 * frameWidth();
return h;
}
15 years ago
/*!
Handle QEvent::ChildRemoved andQEvent::LayoutRequest events
for the contentsWidget().
15 years ago
\param object Object to be filtered
\param event Event
15 years ago
\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;
}
}
15 years ago
return QwtAbstractLegend::eventFilter( object, event );
}
15 years ago
/*!
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 );
15 years ago
}
}
}
15 years ago
/*!
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 );
}
15 years ago
}
}
/*!
Render the legend into a given rectangle.
15 years ago
\param painter Painter
\param rect Bounding rectangle
\param fillBackground When true, fill rect with the widget background
15 years ago
\sa renderLegend() is used by QwtPlotRenderer - not by QwtLegend itself
*/
void QwtLegend::renderLegend( QPainter *painter,
const QRectF &rect, bool fillBackground ) const
15 years ago
{
if ( d_data->itemMap.isEmpty() )
return;
if ( fillBackground )
{
if ( autoFillBackground() ||
testAttribute( Qt::WA_StyledBackground ) )
{
QwtPainter::drawBackgound( painter, rect, this );
15 years ago
}
}
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();
}
}
15 years ago
}
/*!
Render a legend entry into a given rectangle.
15 years ago
\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
15 years ago
{
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 );
}
15 years ago
}
/*!
\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
15 years ago
{
return d_data->itemMap.legendWidgets( itemInfo );
15 years ago
}
/*!
\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
15 years ago
{
const QList<QWidget *> list = d_data->itemMap.legendWidgets( itemInfo );
if ( list.isEmpty() )
return NULL;
15 years ago
return list[0];
}
/*!
Find the item that is associated to a widget
15 years ago
\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 );
}
15 years ago
//! \return True, when no item is inserted
bool QwtLegend::isEmpty() const
{
return d_data->itemMap.isEmpty();
15 years ago
}
/*!
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
15 years ago
{
int extent = 0;
if ( orientation == Qt::Horizontal )
extent = verticalScrollBar()->sizeHint().width();
else
extent = horizontalScrollBar()->sizeHint().height();
return extent;
15 years ago
}