source: ntrip/trunk/BNC/qwt/qwt_legend.cpp@ 10430

Last change on this file since 10430 was 9383, checked in by stoecker, 4 years ago

update to qwt verion 6.1.1 to fix build with newer Qt5

File size: 21.3 KB
Line 
1/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
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_plot_item.h"
15#include "qwt_painter.h"
16#include <qapplication.h>
17#include <qscrollbar.h>
18#include <qscrollarea.h>
19#include <qpainter.h>
20#include <qstyle.h>
21#include <qstyleoption.h>
22
23class QwtLegendMap
24{
25public:
26 inline bool isEmpty() const { return d_entries.isEmpty(); }
27
28 void insert( const QVariant &, const QList<QWidget *> & );
29 void remove( const QVariant & );
30
31 void removeWidget( const QWidget * );
32
33 QList<QWidget *> legendWidgets( const QVariant & ) const;
34 QVariant itemInfo( const QWidget * ) const;
35
36private:
37 // we don't know anything about itemInfo and therefore don't have
38 // any key that can be used for a map or hashtab.
39 // But a simple linear list is o.k. here, as we will never have
40 // more than a few entries.
41
42 class Entry
43 {
44 public:
45 QVariant itemInfo;
46 QList<QWidget *> widgets;
47 };
48
49 QList< Entry > d_entries;
50};
51
52void QwtLegendMap::insert( const QVariant &itemInfo,
53 const QList<QWidget *> &widgets )
54{
55 for ( int i = 0; i < d_entries.size(); i++ )
56 {
57 Entry &entry = d_entries[i];
58 if ( entry.itemInfo == itemInfo )
59 {
60 entry.widgets = widgets;
61 return;
62 }
63 }
64
65 Entry newEntry;
66 newEntry.itemInfo = itemInfo;
67 newEntry.widgets = widgets;
68
69 d_entries += newEntry;
70}
71
72void QwtLegendMap::remove( const QVariant &itemInfo )
73{
74 for ( int i = 0; i < d_entries.size(); i++ )
75 {
76 Entry &entry = d_entries[i];
77 if ( entry.itemInfo == itemInfo )
78 {
79 d_entries.removeAt( i );
80 return;
81 }
82 }
83}
84
85void QwtLegendMap::removeWidget( const QWidget *widget )
86{
87 QWidget *w = const_cast<QWidget *>( widget );
88
89 for ( int i = 0; i < d_entries.size(); i++ )
90 d_entries[ i ].widgets.removeAll( w );
91}
92
93QVariant QwtLegendMap::itemInfo( const QWidget *widget ) const
94{
95 if ( widget != NULL )
96 {
97 QWidget *w = const_cast<QWidget *>( widget );
98
99 for ( int i = 0; i < d_entries.size(); i++ )
100 {
101 const Entry &entry = d_entries[i];
102 if ( entry.widgets.indexOf( w ) >= 0 )
103 return entry.itemInfo;
104 }
105 }
106
107 return QVariant();
108}
109
110QList<QWidget *> QwtLegendMap::legendWidgets( const QVariant &itemInfo ) const
111{
112 if ( itemInfo.isValid() )
113 {
114 for ( int i = 0; i < d_entries.size(); i++ )
115 {
116 const Entry &entry = d_entries[i];
117 if ( entry.itemInfo == itemInfo )
118 return entry.widgets;
119 }
120 }
121
122 return QList<QWidget *>();
123}
124
125class QwtLegend::PrivateData
126{
127public:
128 PrivateData():
129 itemMode( QwtLegendData::ReadOnly ),
130 view( NULL )
131 {
132 }
133
134 QwtLegendData::Mode itemMode;
135 QwtLegendMap itemMap;
136
137 class LegendView;
138 LegendView *view;
139};
140
141class QwtLegend::PrivateData::LegendView: public QScrollArea
142{
143public:
144 explicit LegendView( QWidget *parent ):
145 QScrollArea( parent )
146 {
147 contentsWidget = new QWidget( this );
148 contentsWidget->setObjectName( "QwtLegendViewContents" );
149
150 setWidget( contentsWidget );
151 setWidgetResizable( false );
152
153 viewport()->setObjectName( "QwtLegendViewport" );
154
155 // QScrollArea::setWidget internally sets autoFillBackground to true
156 // But we don't want a background.
157 contentsWidget->setAutoFillBackground( false );
158 viewport()->setAutoFillBackground( false );
159 }
160
161 virtual bool event( QEvent *event )
162 {
163 if ( event->type() == QEvent::PolishRequest )
164 {
165 setFocusPolicy( Qt::NoFocus );
166 }
167
168 if ( event->type() == QEvent::Resize )
169 {
170 // adjust the size to en/disable the scrollbars
171 // before QScrollArea adjusts the viewport size
172
173 const QRect cr = contentsRect();
174
175 int w = cr.width();
176 int h = contentsWidget->heightForWidth( cr.width() );
177 if ( h > w )
178 {
179 w -= verticalScrollBar()->sizeHint().width();
180 h = contentsWidget->heightForWidth( w );
181 }
182
183 contentsWidget->resize( w, h );
184 }
185
186 return QScrollArea::event( event );
187 }
188
189 virtual bool viewportEvent( QEvent *event )
190 {
191 bool ok = QScrollArea::viewportEvent( event );
192
193 if ( event->type() == QEvent::Resize )
194 {
195 layoutContents();
196 }
197 return ok;
198 }
199
200 QSize viewportSize( int w, int h ) const
201 {
202 const int sbHeight = horizontalScrollBar()->sizeHint().height();
203 const int sbWidth = verticalScrollBar()->sizeHint().width();
204
205 const int cw = contentsRect().width();
206 const int ch = contentsRect().height();
207
208 int vw = cw;
209 int vh = ch;
210
211 if ( w > vw )
212 vh -= sbHeight;
213
214 if ( h > vh )
215 {
216 vw -= sbWidth;
217 if ( w > vw && vh == ch )
218 vh -= sbHeight;
219 }
220 return QSize( vw, vh );
221 }
222
223 void layoutContents()
224 {
225 const QwtDynGridLayout *tl = qobject_cast<QwtDynGridLayout *>(
226 contentsWidget->layout() );
227 if ( tl == NULL )
228 return;
229
230 const QSize visibleSize = viewport()->contentsRect().size();
231
232 const int minW = int( tl->maxItemWidth() ) + 2 * tl->margin();
233
234 int w = qMax( visibleSize.width(), minW );
235 int h = qMax( tl->heightForWidth( w ), visibleSize.height() );
236
237 const int vpWidth = viewportSize( w, h ).width();
238 if ( w > vpWidth )
239 {
240 w = qMax( vpWidth, minW );
241 h = qMax( tl->heightForWidth( w ), visibleSize.height() );
242 }
243
244 contentsWidget->resize( w, h );
245 }
246
247 QWidget *contentsWidget;
248};
249
250/*!
251 Constructor
252 \param parent Parent widget
253*/
254QwtLegend::QwtLegend( QWidget *parent ):
255 QwtAbstractLegend( parent )
256{
257 setFrameStyle( NoFrame );
258
259 d_data = new QwtLegend::PrivateData;
260
261 d_data->view = new QwtLegend::PrivateData::LegendView( this );
262 d_data->view->setObjectName( "QwtLegendView" );
263 d_data->view->setFrameStyle( NoFrame );
264
265 QwtDynGridLayout *gridLayout = new QwtDynGridLayout(
266 d_data->view->contentsWidget );
267 gridLayout->setAlignment( Qt::AlignHCenter | Qt::AlignTop );
268
269 d_data->view->contentsWidget->installEventFilter( this );
270
271 QVBoxLayout *layout = new QVBoxLayout( this );
272 layout->setContentsMargins( 0, 0, 0, 0 );
273 layout->addWidget( d_data->view );
274}
275
276//! Destructor
277QwtLegend::~QwtLegend()
278{
279 delete d_data;
280}
281
282/*!
283 \brief Set the maximum number of entries in a row
284
285 F.e when the maximum is set to 1 all items are aligned
286 vertically. 0 means unlimited
287
288 \param numColums Maximum number of entries in a row
289
290 \sa maxColumns(), QwtDynGridLayout::setMaxColumns()
291 */
292void QwtLegend::setMaxColumns( uint numColums )
293{
294 QwtDynGridLayout *tl = qobject_cast<QwtDynGridLayout *>(
295 d_data->view->contentsWidget->layout() );
296 if ( tl )
297 tl->setMaxColumns( numColums );
298
299 updateGeometry();
300}
301
302/*!
303 \return Maximum number of entries in a row
304 \sa setMaxColumns(), QwtDynGridLayout::maxColumns()
305 */
306uint QwtLegend::maxColumns() const
307{
308 uint maxCols = 0;
309
310 const QwtDynGridLayout *tl = qobject_cast<const QwtDynGridLayout *>(
311 d_data->view->contentsWidget->layout() );
312 if ( tl )
313 maxCols = tl->maxColumns();
314
315 return maxCols;
316}
317
318/*!
319 \brief Set the default mode for legend labels
320
321 Legend labels will be constructed according to the
322 attributes in a QwtLegendData object. When it doesn't
323 contain a value for the QwtLegendData::ModeRole the
324 label will be initialized with the default mode of the legend.
325
326 \param mode Default item mode
327
328 \sa itemMode(), QwtLegendData::value(), QwtPlotItem::legendData()
329 \note Changing the mode doesn't have any effect on existing labels.
330 */
331void QwtLegend::setDefaultItemMode( QwtLegendData::Mode mode )
332{
333 d_data->itemMode = mode;
334}
335
336/*!
337 \return Default item mode
338 \sa setDefaultItemMode()
339*/
340QwtLegendData::Mode QwtLegend::defaultItemMode() const
341{
342 return d_data->itemMode;
343}
344
345/*!
346 The contents widget is the only child of the viewport of
347 the internal QScrollArea and the parent widget of all legend items.
348
349 \return Container widget of the legend items
350*/
351QWidget *QwtLegend::contentsWidget()
352{
353 return d_data->view->contentsWidget;
354}
355
356/*!
357 \return Horizontal scrollbar
358 \sa verticalScrollBar()
359*/
360QScrollBar *QwtLegend::horizontalScrollBar() const
361{
362 return d_data->view->horizontalScrollBar();
363}
364
365/*!
366 \return Vertical scrollbar
367 \sa horizontalScrollBar()
368*/
369QScrollBar *QwtLegend::verticalScrollBar() const
370{
371 return d_data->view->verticalScrollBar();
372}
373
374/*!
375 The contents widget is the only child of the viewport of
376 the internal QScrollArea and the parent widget of all legend items.
377
378 \return Container widget of the legend items
379
380*/
381const QWidget *QwtLegend::contentsWidget() const
382{
383 return d_data->view->contentsWidget;
384}
385
386/*!
387 \brief Update the entries for an item
388
389 \param itemInfo Info for an item
390 \param legendData List of legend entry attributes for the item
391 */
392void QwtLegend::updateLegend( const QVariant &itemInfo,
393 const QList<QwtLegendData> &legendData )
394{
395 QList<QWidget *> widgetList = legendWidgets( itemInfo );
396
397 if ( widgetList.size() != legendData.size() )
398 {
399 QLayout *contentsLayout = d_data->view->contentsWidget->layout();
400
401 while ( widgetList.size() > legendData.size() )
402 {
403 QWidget *w = widgetList.takeLast();
404
405 contentsLayout->removeWidget( w );
406
407 // updates might be triggered by signals from the legend widget
408 // itself. So we better don't delete it here.
409
410 w->hide();
411 w->deleteLater();
412 }
413
414#if QT_VERSION >= 0x040700
415 widgetList.reserve( legendData.size() );
416#endif
417
418 for ( int i = widgetList.size(); i < legendData.size(); i++ )
419 {
420 QWidget *widget = createWidget( legendData[i] );
421
422 if ( contentsLayout )
423 contentsLayout->addWidget( widget );
424
425 if ( isVisible() )
426 {
427 // QLayout does a delayed show, with the effect, that
428 // the size hint will be wrong, when applications
429 // call replot() right after changing the list
430 // of plot items. So we better do the show now.
431
432 widget->setVisible( true );
433 }
434
435 widgetList += widget;
436 }
437
438 if ( widgetList.isEmpty() )
439 {
440 d_data->itemMap.remove( itemInfo );
441 }
442 else
443 {
444 d_data->itemMap.insert( itemInfo, widgetList );
445 }
446
447 updateTabOrder();
448 }
449
450 for ( int i = 0; i < legendData.size(); i++ )
451 updateWidget( widgetList[i], legendData[i] );
452}
453
454/*!
455 \brief Create a widget to be inserted into the legend
456
457 The default implementation returns a QwtLegendLabel.
458
459 \param legendData Attributes of the legend entry
460 \return Widget representing data on the legend
461
462 \note updateWidget() will called soon after createWidget()
463 with the same attributes.
464 */
465QWidget *QwtLegend::createWidget( const QwtLegendData &legendData ) const
466{
467 Q_UNUSED( legendData );
468
469 QwtLegendLabel *label = new QwtLegendLabel();
470 label->setItemMode( defaultItemMode() );
471
472 connect( label, SIGNAL(clicked()), SLOT(itemClicked()) );
473 connect( label, SIGNAL(checked(bool)), SLOT(itemChecked(bool)) );
474
475 return label;
476}
477
478/*!
479 \brief Update the widget
480
481 \param widget Usually a QwtLegendLabel
482 \param legendData Attributes to be displayed
483
484 \sa createWidget()
485 \note When widget is no QwtLegendLabel updateWidget() does nothing.
486 */
487void QwtLegend::updateWidget( QWidget *widget, const QwtLegendData &legendData )
488{
489 QwtLegendLabel *label = qobject_cast<QwtLegendLabel *>( widget );
490 if ( label )
491 {
492 label->setData( legendData );
493 if ( !legendData.value( QwtLegendData::ModeRole ).isValid() )
494 {
495 // use the default mode, when there is no specific
496 // hint from the legend data
497
498 label->setItemMode( defaultItemMode() );
499 }
500 }
501}
502
503void QwtLegend::updateTabOrder()
504{
505 QLayout *contentsLayout = d_data->view->contentsWidget->layout();
506 if ( contentsLayout )
507 {
508 // set tab focus chain
509
510 QWidget *w = NULL;
511
512 for ( int i = 0; i < contentsLayout->count(); i++ )
513 {
514 QLayoutItem *item = contentsLayout->itemAt( i );
515 if ( w && item->widget() )
516 QWidget::setTabOrder( w, item->widget() );
517
518 w = item->widget();
519 }
520 }
521}
522
523//! Return a size hint.
524QSize QwtLegend::sizeHint() const
525{
526 QSize hint = d_data->view->contentsWidget->sizeHint();
527 hint += QSize( 2 * frameWidth(), 2 * frameWidth() );
528
529 return hint;
530}
531
532/*!
533 \return The preferred height, for a width.
534 \param width Width
535*/
536int QwtLegend::heightForWidth( int width ) const
537{
538 width -= 2 * frameWidth();
539
540 int h = d_data->view->contentsWidget->heightForWidth( width );
541 if ( h >= 0 )
542 h += 2 * frameWidth();
543
544 return h;
545}
546
547
548/*!
549 Handle QEvent::ChildRemoved andQEvent::LayoutRequest events
550 for the contentsWidget().
551
552 \param object Object to be filtered
553 \param event Event
554
555 \return Forwarded to QwtAbstractLegend::eventFilter()
556*/
557bool QwtLegend::eventFilter( QObject *object, QEvent *event )
558{
559 if ( object == d_data->view->contentsWidget )
560 {
561 switch ( event->type() )
562 {
563 case QEvent::ChildRemoved:
564 {
565 const QChildEvent *ce =
566 static_cast<const QChildEvent *>(event);
567
568 if ( ce->child()->isWidgetType() )
569 {
570 /*
571 We are called from the ~QObject and ce->child() is
572 no widget anymore. But all we need is the address
573 to remove it from the map.
574 */
575 QWidget *w = reinterpret_cast< QWidget * >( ce->child() );
576 d_data->itemMap.removeWidget( w );
577 }
578 break;
579 }
580 case QEvent::LayoutRequest:
581 {
582 d_data->view->layoutContents();
583
584 if ( parentWidget() && parentWidget()->layout() == NULL )
585 {
586 /*
587 We want the parent widget ( usually QwtPlot ) to recalculate
588 its layout, when the contentsWidget has changed. But
589 because of the scroll view we have to forward the LayoutRequest
590 event manually.
591
592 We don't use updateGeometry() because it doesn't post LayoutRequest
593 events when the legend is hidden. But we want the
594 parent widget notified, so it can show/hide the legend
595 depending on its items.
596 */
597 QApplication::postEvent( parentWidget(),
598 new QEvent( QEvent::LayoutRequest ) );
599 }
600 break;
601 }
602 default:
603 break;
604 }
605 }
606
607 return QwtAbstractLegend::eventFilter( object, event );
608}
609
610/*!
611 Called internally when the legend has been clicked on.
612 Emits a clicked() signal.
613*/
614void QwtLegend::itemClicked()
615{
616 QWidget *w = qobject_cast<QWidget *>( sender() );
617 if ( w )
618 {
619 const QVariant itemInfo = d_data->itemMap.itemInfo( w );
620 if ( itemInfo.isValid() )
621 {
622 const QList<QWidget *> widgetList =
623 d_data->itemMap.legendWidgets( itemInfo );
624
625 const int index = widgetList.indexOf( w );
626 if ( index >= 0 )
627 Q_EMIT clicked( itemInfo, index );
628 }
629 }
630}
631
632/*!
633 Called internally when the legend has been checked
634 Emits a checked() signal.
635*/
636void QwtLegend::itemChecked( bool on )
637{
638 QWidget *w = qobject_cast<QWidget *>( sender() );
639 if ( w )
640 {
641 const QVariant itemInfo = d_data->itemMap.itemInfo( w );
642 if ( itemInfo.isValid() )
643 {
644 const QList<QWidget *> widgetList =
645 d_data->itemMap.legendWidgets( itemInfo );
646
647 const int index = widgetList.indexOf( w );
648 if ( index >= 0 )
649 Q_EMIT checked( itemInfo, on, index );
650 }
651 }
652}
653
654/*!
655 Render the legend into a given rectangle.
656
657 \param painter Painter
658 \param rect Bounding rectangle
659 \param fillBackground When true, fill rect with the widget background
660
661 \sa renderLegend() is used by QwtPlotRenderer - not by QwtLegend itself
662*/
663void QwtLegend::renderLegend( QPainter *painter,
664 const QRectF &rect, bool fillBackground ) const
665{
666 if ( d_data->itemMap.isEmpty() )
667 return;
668
669 if ( fillBackground )
670 {
671 if ( autoFillBackground() ||
672 testAttribute( Qt::WA_StyledBackground ) )
673 {
674 QwtPainter::drawBackgound( painter, rect, this );
675 }
676 }
677
678 const QwtDynGridLayout *legendLayout =
679 qobject_cast<QwtDynGridLayout *>( contentsWidget()->layout() );
680 if ( legendLayout == NULL )
681 return;
682
683 int left, right, top, bottom;
684 getContentsMargins( &left, &top, &right, &bottom );
685
686 QRect layoutRect;
687 layoutRect.setLeft( qCeil( rect.left() ) + left );
688 layoutRect.setTop( qCeil( rect.top() ) + top );
689 layoutRect.setRight( qFloor( rect.right() ) - right );
690 layoutRect.setBottom( qFloor( rect.bottom() ) - bottom );
691
692 uint numCols = legendLayout->columnsForWidth( layoutRect.width() );
693 const QList<QRect> itemRects =
694 legendLayout->layoutItems( layoutRect, numCols );
695
696 int index = 0;
697
698 for ( int i = 0; i < legendLayout->count(); i++ )
699 {
700 QLayoutItem *item = legendLayout->itemAt( i );
701 QWidget *w = item->widget();
702 if ( w )
703 {
704 painter->save();
705
706 painter->setClipRect( itemRects[index], Qt::IntersectClip );
707 renderItem( painter, w, itemRects[index], fillBackground );
708
709 index++;
710 painter->restore();
711 }
712 }
713}
714
715/*!
716 Render a legend entry into a given rectangle.
717
718 \param painter Painter
719 \param widget Widget representing a legend entry
720 \param rect Bounding rectangle
721 \param fillBackground When true, fill rect with the widget background
722
723 \note When widget is not derived from QwtLegendLabel renderItem
724 does nothing beside the background
725*/
726void QwtLegend::renderItem( QPainter *painter,
727 const QWidget *widget, const QRectF &rect, bool fillBackground ) const
728{
729 if ( fillBackground )
730 {
731 if ( widget->autoFillBackground() ||
732 widget->testAttribute( Qt::WA_StyledBackground ) )
733 {
734 QwtPainter::drawBackgound( painter, rect, widget );
735 }
736 }
737
738 const QwtLegendLabel *label = qobject_cast<const QwtLegendLabel *>( widget );
739 if ( label )
740 {
741 // icon
742
743 const QwtGraphic &icon = label->data().icon();
744 const QSizeF sz = icon.defaultSize();
745
746 const QRectF iconRect( rect.x() + label->margin(),
747 rect.center().y() - 0.5 * sz.height(),
748 sz.width(), sz.height() );
749
750 icon.render( painter, iconRect, Qt::KeepAspectRatio );
751
752 // title
753
754 QRectF titleRect = rect;
755 titleRect.setX( iconRect.right() + 2 * label->spacing() );
756
757 QFont labelFont = label->font();
758 labelFont.resolve( QFont::AllPropertiesResolved );
759
760 painter->setFont( labelFont );
761 painter->setPen( label->palette().color( QPalette::Text ) );
762
763 const_cast< QwtLegendLabel *>( label )->drawText( painter, titleRect );
764 }
765}
766
767/*!
768 \return List of widgets associated to a item
769 \param itemInfo Info about an item
770 \sa legendWidget(), itemInfo(), QwtPlot::itemToInfo()
771 */
772QList<QWidget *> QwtLegend::legendWidgets( const QVariant &itemInfo ) const
773{
774 return d_data->itemMap.legendWidgets( itemInfo );
775}
776
777/*!
778 \return First widget in the list of widgets associated to an item
779 \param itemInfo Info about an item
780 \sa itemInfo(), QwtPlot::itemToInfo()
781 \note Almost all types of items have only one widget
782*/
783QWidget *QwtLegend::legendWidget( const QVariant &itemInfo ) const
784{
785 const QList<QWidget *> list = d_data->itemMap.legendWidgets( itemInfo );
786 if ( list.isEmpty() )
787 return NULL;
788
789 return list[0];
790}
791
792/*!
793 Find the item that is associated to a widget
794
795 \param widget Widget on the legend
796 \return Associated item info
797 \sa legendWidget()
798 */
799QVariant QwtLegend::itemInfo( const QWidget *widget ) const
800{
801 return d_data->itemMap.itemInfo( widget );
802}
803
804//! \return True, when no item is inserted
805bool QwtLegend::isEmpty() const
806{
807 return d_data->itemMap.isEmpty();
808}
809
810/*!
811 Return the extent, that is needed for the scrollbars
812
813 \param orientation Orientation
814 \return The width of the vertical scrollbar for Qt::Horizontal and v.v.
815 */
816int QwtLegend::scrollExtent( Qt::Orientation orientation ) const
817{
818 int extent = 0;
819
820 if ( orientation == Qt::Horizontal )
821 extent = verticalScrollBar()->sizeHint().width();
822 else
823 extent = horizontalScrollBar()->sizeHint().height();
824
825 return extent;
826}
827
Note: See TracBrowser for help on using the repository browser.