Qwt User's Guide 6.3.0
Loading...
Searching...
No Matches
qwt_legend.cpp
1/******************************************************************************
2 * Qwt Widget Library
3 * Copyright (C) 1997 Josef Wilgen
4 * Copyright (C) 2002 Uwe Rathmann
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the Qwt License, Version 1.0
8 *****************************************************************************/
9
10#include "qwt_legend.h"
11#include "qwt_legend_label.h"
12#include "qwt_dyngrid_layout.h"
13#include "qwt_math.h"
14#include "qwt_painter.h"
15#include "qwt_graphic.h"
16
17#include <qapplication.h>
18#include <qscrollbar.h>
19#include <qscrollarea.h>
20#include <qpainter.h>
21#include <qmargins.h>
22
23namespace
24{
25 class LegendMap
26 {
27 public:
28 inline bool isEmpty() const { return m_entries.isEmpty(); }
29
30 void insert( const QVariant&, const QList< QWidget* >& );
31 void remove( const QVariant& );
32
33 void removeWidget( const QWidget* );
34
35 QList< QWidget* > legendWidgets( const QVariant& ) const;
36 QVariant itemInfo( const QWidget* ) const;
37
38 private:
39 // we don't know anything about itemInfo and therefore don't have
40 // any key that can be used for a map or hashtab.
41 // But a simple linear list is o.k. here, as we will never have
42 // more than a few entries.
43
44 class Entry
45 {
46 public:
47 QVariant itemInfo;
48 QList< QWidget* > widgets;
49 };
50
51 QList< Entry > m_entries;
52 };
53
54 void LegendMap::insert( const QVariant& itemInfo,
55 const QList< QWidget* >& widgets )
56 {
57 for ( int i = 0; i < m_entries.size(); i++ )
58 {
59 Entry& entry = m_entries[i];
60 if ( entry.itemInfo == itemInfo )
61 {
62 entry.widgets = widgets;
63 return;
64 }
65 }
66
67 Entry newEntry;
68 newEntry.itemInfo = itemInfo;
69 newEntry.widgets = widgets;
70
71 m_entries += newEntry;
72 }
73
74 void LegendMap::remove( const QVariant& itemInfo )
75 {
76 for ( int i = 0; i < m_entries.size(); i++ )
77 {
78 Entry& entry = m_entries[i];
79 if ( entry.itemInfo == itemInfo )
80 {
81 m_entries.removeAt( i );
82 return;
83 }
84 }
85 }
86
87 void LegendMap::removeWidget( const QWidget* widget )
88 {
89 QWidget* w = const_cast< QWidget* >( widget );
90
91 for ( int i = 0; i < m_entries.size(); i++ )
92 m_entries[ i ].widgets.removeAll( w );
93 }
94
95 QVariant LegendMap::itemInfo( const QWidget* widget ) const
96 {
97 if ( widget != NULL )
98 {
99 QWidget* w = const_cast< QWidget* >( widget );
100
101 for ( int i = 0; i < m_entries.size(); i++ )
102 {
103 const Entry& entry = m_entries[i];
104 if ( entry.widgets.indexOf( w ) >= 0 )
105 return entry.itemInfo;
106 }
107 }
108
109 return QVariant();
110 }
111
112 QList< QWidget* > LegendMap::legendWidgets( const QVariant& itemInfo ) const
113 {
114 if ( itemInfo.isValid() )
115 {
116 for ( int i = 0; i < m_entries.size(); i++ )
117 {
118 const Entry& entry = m_entries[i];
119 if ( entry.itemInfo == itemInfo )
120 return entry.widgets;
121 }
122 }
123
124 return QList< QWidget* >();
125 }
126}
127
128class QwtLegend::PrivateData
129{
130 public:
131 PrivateData()
132 : itemMode( QwtLegendData::ReadOnly )
133 , view( NULL )
134 {
135 }
136
137 QwtLegendData::Mode itemMode;
138 LegendMap itemMap;
139
140 class LegendView;
141 LegendView* view;
142};
143
144class QwtLegend::PrivateData::LegendView QWT_FINAL : public QScrollArea
145{
146 public:
147 explicit LegendView( QWidget * parent ) :
148 QScrollArea( parent )
149 {
150 contentsWidget = new QWidget( this );
151 contentsWidget->setObjectName( "QwtLegendViewContents" );
152
153 setWidget( contentsWidget );
154 setWidgetResizable( false );
155
156 viewport()->setObjectName( "QwtLegendViewport" );
157
158 // QScrollArea::setWidget internally sets autoFillBackground to true
159 // But we don't want a background.
160 contentsWidget->setAutoFillBackground( false );
161 viewport()->setAutoFillBackground( false );
162 }
163
164 virtual bool event( QEvent* event ) QWT_OVERRIDE
165 {
166 if ( event->type() == QEvent::PolishRequest )
167 {
168 setFocusPolicy( Qt::NoFocus );
169 }
170
171 if ( event->type() == QEvent::Resize )
172 {
173 // adjust the size to en/disable the scrollbars
174 // before QScrollArea adjusts the viewport size
175
176 const QRect cr = contentsRect();
177
178 int w = cr.width();
179 int h = contentsWidget->heightForWidth( cr.width() );
180 if ( h > w )
181 {
182 w -= verticalScrollBar()->sizeHint().width();
183 h = contentsWidget->heightForWidth( w );
184 }
185
186 contentsWidget->resize( w, h );
187 }
188
189 return QScrollArea::event( event );
190 }
191
192 virtual bool viewportEvent( QEvent* event ) QWT_OVERRIDE
193 {
194 bool ok = QScrollArea::viewportEvent( event );
195
196 if ( event->type() == QEvent::Resize )
197 {
198 layoutContents();
199 }
200 return ok;
201 }
202
203 QSize viewportSize( int w, int h ) const
204 {
205 const int sbHeight = horizontalScrollBar()->sizeHint().height();
206 const int sbWidth = verticalScrollBar()->sizeHint().width();
207
208 const int cw = contentsRect().width();
209 const int ch = contentsRect().height();
210
211 int vw = cw;
212 int vh = ch;
213
214 if ( w > vw )
215 vh -= sbHeight;
216
217 if ( h > vh )
218 {
219 vw -= sbWidth;
220 if ( w > vw && vh == ch )
221 vh -= sbHeight;
222 }
223 return QSize( vw, vh );
224 }
225
226 void layoutContents()
227 {
228 const QwtDynGridLayout* tl = qobject_cast< QwtDynGridLayout* >(
229 contentsWidget->layout() );
230 if ( tl == NULL )
231 return;
232
233 const QSize visibleSize = viewport()->contentsRect().size();
234
235 const QMargins m = tl->contentsMargins();
236 const int minW = tl->maxItemWidth() + m.left() + m.right();
237
238 int w = qMax( visibleSize.width(), minW );
239 int h = qMax( tl->heightForWidth( w ), visibleSize.height() );
240
241 const int vpWidth = viewportSize( w, h ).width();
242 if ( w > vpWidth )
243 {
244 w = qMax( vpWidth, minW );
245 h = qMax( tl->heightForWidth( w ), visibleSize.height() );
246 }
247
248 contentsWidget->resize( w, h );
249 }
250
251 QWidget* contentsWidget;
252};
253
258QwtLegend::QwtLegend( QWidget* parent )
259 : QwtAbstractLegend( parent )
260{
261 setFrameStyle( NoFrame );
262
263 m_data = new QwtLegend::PrivateData;
264
265 m_data->view = new QwtLegend::PrivateData::LegendView( this );
266 m_data->view->setObjectName( "QwtLegendView" );
267 m_data->view->setFrameStyle( NoFrame );
268
269 QwtDynGridLayout* gridLayout = new QwtDynGridLayout(
270 m_data->view->contentsWidget );
271 gridLayout->setAlignment( Qt::AlignHCenter | Qt::AlignTop );
272
273 m_data->view->contentsWidget->installEventFilter( this );
274
275 QVBoxLayout* layout = new QVBoxLayout( this );
276 layout->setContentsMargins( 0, 0, 0, 0 );
277 layout->addWidget( m_data->view );
278}
279
282{
283 delete m_data;
284}
285
296void QwtLegend::setMaxColumns( uint numColums )
297{
298 QwtDynGridLayout* tl = qobject_cast< QwtDynGridLayout* >(
299 m_data->view->contentsWidget->layout() );
300 if ( tl )
301 tl->setMaxColumns( numColums );
302
303 updateGeometry();
304}
305
311{
312 uint maxCols = 0;
313
314 const QwtDynGridLayout* tl = qobject_cast< const QwtDynGridLayout* >(
315 m_data->view->contentsWidget->layout() );
316 if ( tl )
317 maxCols = tl->maxColumns();
318
319 return maxCols;
320}
321
336{
337 m_data->itemMode = mode;
338}
339
345{
346 return m_data->itemMode;
347}
348
356{
357 return m_data->view->contentsWidget;
358}
359
365{
366 return m_data->view->horizontalScrollBar();
367}
368
374{
375 return m_data->view->verticalScrollBar();
376}
377
385const QWidget* QwtLegend::contentsWidget() const
386{
387 return m_data->view->contentsWidget;
388}
389
396void QwtLegend::updateLegend( const QVariant& itemInfo,
397 const QList< QwtLegendData >& legendData )
398{
400
401 if ( widgetList.size() != legendData.size() )
402 {
403 QLayout* contentsLayout = m_data->view->contentsWidget->layout();
404
405 while ( widgetList.size() > legendData.size() )
406 {
407 QWidget* w = widgetList.takeLast();
408
409 contentsLayout->removeWidget( w );
410
411 // updates might be triggered by signals from the legend widget
412 // itself. So we better don't delete it here.
413
414 w->hide();
415 w->deleteLater();
416 }
417
418 widgetList.reserve( legendData.size() );
419
420 for ( int i = widgetList.size(); i < legendData.size(); i++ )
421 {
422 QWidget* widget = createWidget( legendData[i] );
423
424 if ( contentsLayout )
425 contentsLayout->addWidget( widget );
426
427 if ( isVisible() )
428 {
429 // QLayout does a delayed show, with the effect, that
430 // the size hint will be wrong, when applications
431 // call replot() right after changing the list
432 // of plot items. So we better do the show now.
433
434 widget->setVisible( true );
435 }
436
437 widgetList += widget;
438 }
439
440 if ( widgetList.isEmpty() )
441 {
442 m_data->itemMap.remove( itemInfo );
443 }
444 else
445 {
446 m_data->itemMap.insert( itemInfo, widgetList );
447 }
448
449 updateTabOrder();
450 }
451
452 for ( int i = 0; i < legendData.size(); i++ )
453 updateWidget( widgetList[i], legendData[i] );
454}
455
467QWidget* QwtLegend::createWidget( const QwtLegendData& legendData ) const
468{
469 Q_UNUSED( legendData );
470
471 QwtLegendLabel* label = new QwtLegendLabel();
472 label->setItemMode( defaultItemMode() );
473
474 connect( label, SIGNAL(clicked()), SLOT(itemClicked()) );
475 connect( label, SIGNAL(checked(bool)), SLOT(itemChecked(bool)) );
476
477 return label;
478}
479
489void QwtLegend::updateWidget( QWidget* widget, const QwtLegendData& legendData )
490{
491 QwtLegendLabel* label = qobject_cast< QwtLegendLabel* >( widget );
492 if ( label )
493 {
494 label->setData( legendData );
495 if ( !legendData.value( QwtLegendData::ModeRole ).isValid() )
496 {
497 // use the default mode, when there is no specific
498 // hint from the legend data
499
500 label->setItemMode( defaultItemMode() );
501 }
502 }
503}
504
505void QwtLegend::updateTabOrder()
506{
507 QLayout* contentsLayout = m_data->view->contentsWidget->layout();
508 if ( contentsLayout )
509 {
510 // set tab focus chain
511
512 QWidget* w = NULL;
513
514 for ( int i = 0; i < contentsLayout->count(); i++ )
515 {
516 QLayoutItem* item = contentsLayout->itemAt( i );
517 if ( w && item->widget() )
518 QWidget::setTabOrder( w, item->widget() );
519
520 w = item->widget();
521 }
522 }
523}
524
527{
528 QSize hint = m_data->view->contentsWidget->sizeHint();
529 hint += QSize( 2 * frameWidth(), 2 * frameWidth() );
530
531 return hint;
532}
533
538int QwtLegend::heightForWidth( int width ) const
539{
540 width -= 2 * frameWidth();
541
542 int h = m_data->view->contentsWidget->heightForWidth( width );
543 if ( h >= 0 )
544 h += 2 * frameWidth();
545
546 return h;
547}
548
549
559bool QwtLegend::eventFilter( QObject* object, QEvent* event )
560{
561 if ( object == m_data->view->contentsWidget )
562 {
563 switch ( event->type() )
564 {
565 case QEvent::ChildRemoved:
566 {
567 const QChildEvent* ce =
568 static_cast< const QChildEvent* >( event );
569
570 if ( ce->child()->isWidgetType() )
571 {
572 /*
573 We are called from the ~QObject and ce->child() is
574 no widget anymore. But all we need is the address
575 to remove it from the map.
576 */
577 QWidget* w = reinterpret_cast< QWidget* >( ce->child() );
578 m_data->itemMap.removeWidget( w );
579 }
580 break;
581 }
582 case QEvent::LayoutRequest:
583 {
584 m_data->view->layoutContents();
585
586 if ( parentWidget() && parentWidget()->layout() == NULL )
587 {
588 /*
589 We want the parent widget ( usually QwtPlot ) to recalculate
590 its layout, when the contentsWidget has changed. But
591 because of the scroll view we have to forward the LayoutRequest
592 event manually.
593
594 We don't use updateGeometry() because it doesn't post LayoutRequest
595 events when the legend is hidden. But we want the
596 parent widget notified, so it can show/hide the legend
597 depending on its items.
598 */
599 QApplication::postEvent( parentWidget(),
600 new QEvent( QEvent::LayoutRequest ) );
601 }
602 break;
603 }
604 default:
605 break;
606 }
607 }
608
609 return QwtAbstractLegend::eventFilter( object, event );
610}
611
617{
618 QWidget* w = qobject_cast< QWidget* >( sender() );
619 if ( w )
620 {
621 const QVariant itemInfo = m_data->itemMap.itemInfo( w );
622 if ( itemInfo.isValid() )
623 {
624 const QList< QWidget* > widgetList =
625 m_data->itemMap.legendWidgets( itemInfo );
626
627 const int index = widgetList.indexOf( w );
628 if ( index >= 0 )
629 Q_EMIT clicked( itemInfo, index );
630 }
631 }
632}
633
639{
640 QWidget* w = qobject_cast< QWidget* >( sender() );
641 if ( w )
642 {
643 const QVariant itemInfo = m_data->itemMap.itemInfo( w );
644 if ( itemInfo.isValid() )
645 {
646 const QList< QWidget* > widgetList =
647 m_data->itemMap.legendWidgets( itemInfo );
648
649 const int index = widgetList.indexOf( w );
650 if ( index >= 0 )
651 Q_EMIT checked( itemInfo, on, index );
652 }
653 }
654}
655
665void QwtLegend::renderLegend( QPainter* painter,
666 const QRectF& rect, bool fillBackground ) const
667{
668 if ( m_data->itemMap.isEmpty() )
669 return;
670
671 if ( fillBackground )
672 {
673 if ( autoFillBackground() ||
674 testAttribute( Qt::WA_StyledBackground ) )
675 {
676 QwtPainter::drawBackgound( painter, rect, this );
677 }
678 }
679
680 const QwtDynGridLayout* legendLayout =
681 qobject_cast< QwtDynGridLayout* >( contentsWidget()->layout() );
682 if ( legendLayout == NULL )
683 return;
684
685 const QMargins m = contentsMargins();
686
687 QRect layoutRect;
688 layoutRect.setLeft( qwtCeil( rect.left() ) + m.left() );
689 layoutRect.setTop( qwtCeil( rect.top() ) + m.top() );
690 layoutRect.setRight( qwtFloor( rect.right() ) - m.right() );
691 layoutRect.setBottom( qwtFloor( rect.bottom() ) - m.bottom() );
692
693 uint numCols = legendLayout->columnsForWidth( layoutRect.width() );
694 const QList< QRect > itemRects =
695 legendLayout->layoutItems( layoutRect, numCols );
696
697 int index = 0;
698
699 for ( int i = 0; i < legendLayout->count(); i++ )
700 {
701 QLayoutItem* item = legendLayout->itemAt( i );
702 QWidget* w = item->widget();
703 if ( w )
704 {
705 painter->save();
706
707 painter->setClipRect( itemRects[index], Qt::IntersectClip );
708 renderItem( painter, w, itemRects[index], fillBackground );
709
710 index++;
711 painter->restore();
712 }
713 }
714}
715
727void QwtLegend::renderItem( QPainter* painter,
728 const QWidget* widget, const QRectF& rect, bool fillBackground ) const
729{
730 if ( fillBackground )
731 {
732 if ( widget->autoFillBackground() ||
733 widget->testAttribute( Qt::WA_StyledBackground ) )
734 {
735 QwtPainter::drawBackgound( painter, rect, widget );
736 }
737 }
738
739 const QwtLegendLabel* label = qobject_cast< const QwtLegendLabel* >( widget );
740 if ( label )
741 {
742 // icon
743
744 const QwtGraphic& icon = label->data().icon();
745 const QSizeF sz = icon.defaultSize();
746
747 const QRectF iconRect( rect.x() + label->margin(),
748 rect.center().y() - 0.5 * sz.height(),
749 sz.width(), sz.height() );
750
751 icon.render( painter, iconRect, Qt::KeepAspectRatio );
752
753 // title
754
755 QRectF titleRect = rect;
756 titleRect.setX( iconRect.right() + 2 * label->spacing() );
757
758 QFont labelFont = label->font();
759#if QT_VERSION >= 0x060000
760 labelFont.setResolveMask( QFont::AllPropertiesResolved );
761#else
762 labelFont.resolve( QFont::AllPropertiesResolved );
763#endif
764
765 painter->setFont( labelFont );
766 painter->setPen( label->palette().color( QPalette::Text ) );
767
768 const_cast< QwtLegendLabel* >( label )->drawText( painter, titleRect );
769 }
770}
771
777QList< QWidget* > QwtLegend::legendWidgets( const QVariant& itemInfo ) const
778{
779 return m_data->itemMap.legendWidgets( itemInfo );
780}
781
788QWidget* QwtLegend::legendWidget( const QVariant& itemInfo ) const
789{
790 const QList< QWidget* > list = m_data->itemMap.legendWidgets( itemInfo );
791 if ( list.isEmpty() )
792 return NULL;
793
794 return list[0];
795}
796
804QVariant QwtLegend::itemInfo( const QWidget* widget ) const
805{
806 return m_data->itemMap.itemInfo( widget );
807}
808
811{
812 return m_data->itemMap.isEmpty();
813}
814
821int QwtLegend::scrollExtent( Qt::Orientation orientation ) const
822{
823 int extent = 0;
824
825 if ( orientation == Qt::Horizontal )
826 extent = verticalScrollBar()->sizeHint().width();
827 else
828 extent = horizontalScrollBar()->sizeHint().height();
829
830 return extent;
831}
832
833#include "moc_qwt_legend.cpp"
Abstract base class for legend widgets.
The QwtDynGridLayout class lays out widgets in a grid, adjusting the number of columns and rows to th...
virtual int heightForWidth(int) const override
virtual QLayoutItem * itemAt(int index) const override
virtual uint columnsForWidth(int width) const
Calculate the number of columns for a given width.
virtual int maxItemWidth() const
uint maxColumns() const
Return the upper limit for the number of columns.
void setMaxColumns(uint maxColumns)
QList< QRect > layoutItems(const QRect &, uint numColumns) const
virtual int count() const override
A paint device for scalable graphics.
Definition qwt_graphic.h:76
QSizeF defaultSize() const
Default size.
void render(QPainter *) const
Replay all recorded painter commands.
Attributes of an entry on a legend.
QVariant value(int role) const
Mode
Mode defining how a legend entry interacts.
QwtGraphic icon() const
void itemChecked(bool)
virtual QWidget * createWidget(const QwtLegendData &) const
Create a widget to be inserted into the legend.
virtual bool eventFilter(QObject *, QEvent *) override
virtual void renderLegend(QPainter *, const QRectF &, bool fillBackground) const override
void clicked(const QVariant &itemInfo, int index)
QVariant itemInfo(const QWidget *) const
virtual ~QwtLegend()
Destructor.
QScrollBar * verticalScrollBar() const
virtual bool isEmpty() const override
virtual QSize sizeHint() const override
Return a size hint.
QScrollBar * horizontalScrollBar() const
virtual int heightForWidth(int w) const override
QWidget * legendWidget(const QVariant &) const
virtual void renderItem(QPainter *, const QWidget *, const QRectF &, bool fillBackground) const
void itemClicked()
QWidget * contentsWidget()
QwtLegendData::Mode defaultItemMode() const
uint maxColumns() const
virtual int scrollExtent(Qt::Orientation) const override
void setMaxColumns(uint numColums)
Set the maximum number of entries in a row.
QwtLegend(QWidget *parent=NULL)
virtual void updateWidget(QWidget *, const QwtLegendData &)
Update the widget.
void checked(const QVariant &itemInfo, bool on, int index)
virtual void updateLegend(const QVariant &, const QList< QwtLegendData > &) override
Update the entries for an item.
QList< QWidget * > legendWidgets(const QVariant &) const
void setDefaultItemMode(QwtLegendData::Mode)
Set the default mode for legend labels.
A widget representing something on a QwtLegend.
void setItemMode(QwtLegendData::Mode)
const QwtLegendData & data() const
void setData(const QwtLegendData &)
static void drawBackgound(QPainter *, const QRectF &, const QWidget *)
int margin() const
Return label's text margin in pixels.