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

Last change on this file since 8127 was 8127, checked in by stoecker, 7 years ago

update qwt and qwtpolar, many QT5 fixes (unfinished)

File size: 20.8 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 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
300/*!
301 \return Maximum number of entries in a row
302 \sa setMaxColumns(), QwtDynGridLayout::maxColumns()
303 */
304uint QwtLegend::maxColumns() const
305{
306 uint maxCols = 0;
307
308 const QwtDynGridLayout *tl = qobject_cast<const QwtDynGridLayout *>(
309 d_data->view->contentsWidget->layout() );
310 if ( tl )
311 maxCols = tl->maxColumns();
312
313 return maxCols;
314}
315
316/*!
317 \brief Set the default mode for legend labels
318
319 Legend labels will be constructed according to the
320 attributes in a QwtLegendData object. When it doesn't
321 contain a value for the QwtLegendData::ModeRole the
322 label will be initialized with the default mode of the legend.
323
324 \param mode Default item mode
325
326 \sa itemMode(), QwtLegendData::value(), QwtPlotItem::legendData()
327 \note Changing the mode doesn't have any effect on existing labels.
328 */
329void QwtLegend::setDefaultItemMode( QwtLegendData::Mode mode )
330{
331 d_data->itemMode = mode;
332}
333
334/*!
335 \return Default item mode
336 \sa setDefaultItemMode()
337*/
338QwtLegendData::Mode QwtLegend::defaultItemMode() const
339{
340 return d_data->itemMode;
341}
342
343/*!
344 The contents widget is the only child of the viewport of
345 the internal QScrollArea and the parent widget of all legend items.
346
347 \return Container widget of the legend items
348*/
349QWidget *QwtLegend::contentsWidget()
350{
351 return d_data->view->contentsWidget;
352}
353
354/*!
355 \return Horizontal scrollbar
356 \sa verticalScrollBar()
357*/
358QScrollBar *QwtLegend::horizontalScrollBar() const
359{
360 return d_data->view->horizontalScrollBar();
361}
362
363/*!
364 \return Vertical scrollbar
365 \sa horizontalScrollBar()
366*/
367QScrollBar *QwtLegend::verticalScrollBar() const
368{
369 return d_data->view->verticalScrollBar();
370}
371
372/*!
373 The contents widget is the only child of the viewport of
374 the internal QScrollArea and the parent widget of all legend items.
375
376 \return Container widget of the legend items
377
378*/
379const QWidget *QwtLegend::contentsWidget() const
380{
381 return d_data->view->contentsWidget;
382}
383
384/*!
385 \brief Update the entries for an item
386
387 \param itemInfo Info for an item
388 \param data List of legend entry attributes for the item
389 */
390void QwtLegend::updateLegend( const QVariant &itemInfo,
391 const QList<QwtLegendData> &data )
392{
393 QList<QWidget *> widgetList = legendWidgets( itemInfo );
394
395 if ( widgetList.size() != data.size() )
396 {
397 QLayout *contentsLayout = d_data->view->contentsWidget->layout();
398
399 while ( widgetList.size() > data.size() )
400 {
401 QWidget *w = widgetList.takeLast();
402
403 contentsLayout->removeWidget( w );
404
405 // updates might be triggered by signals from the legend widget
406 // itself. So we better don't delete it here.
407
408 w->hide();
409 w->deleteLater();
410 }
411
412 for ( int i = widgetList.size(); i < data.size(); i++ )
413 {
414 QWidget *widget = createWidget( data[i] );
415
416 if ( contentsLayout )
417 contentsLayout->addWidget( widget );
418
419 if ( isVisible() )
420 {
421 // QLayout does a delayed show, with the effect, that
422 // the size hint will be wrong, when applications
423 // call replot() right after changing the list
424 // of plot items. So we better do the show now.
425
426 widget->setVisible( true );
427 }
428
429 widgetList += widget;
430 }
431
432 if ( widgetList.isEmpty() )
433 {
434 d_data->itemMap.remove( itemInfo );
435 }
436 else
437 {
438 d_data->itemMap.insert( itemInfo, widgetList );
439 }
440
441 updateTabOrder();
442 }
443
444 for ( int i = 0; i < data.size(); i++ )
445 updateWidget( widgetList[i], data[i] );
446}
447
448/*!
449 \brief Create a widget to be inserted into the legend
450
451 The default implementation returns a QwtLegendLabel.
452
453 \param data Attributes of the legend entry
454 \return Widget representing data on the legend
455
456 \note updateWidget() will called soon after createWidget()
457 with the same attributes.
458 */
459QWidget *QwtLegend::createWidget( const QwtLegendData &data ) const
460{
461 Q_UNUSED( data );
462
463 QwtLegendLabel *label = new QwtLegendLabel();
464 label->setItemMode( defaultItemMode() );
465
466 connect( label, SIGNAL( clicked() ), SLOT( itemClicked() ) );
467 connect( label, SIGNAL( checked( bool ) ), SLOT( itemChecked( bool ) ) );
468
469 return label;
470}
471
472/*!
473 \brief Update the widget
474
475 \param widget Usually a QwtLegendLabel
476 \param data Attributes to be displayed
477
478 \sa createWidget()
479 \note When widget is no QwtLegendLabel updateWidget() does nothing.
480 */
481void QwtLegend::updateWidget( QWidget *widget, const QwtLegendData &data )
482{
483 QwtLegendLabel *label = qobject_cast<QwtLegendLabel *>( widget );
484 if ( label )
485 {
486 label->setData( data );
487 if ( !data.value( QwtLegendData::ModeRole ).isValid() )
488 {
489 // use the default mode, when there is no specific
490 // hint from the legend data
491
492 label->setItemMode( defaultItemMode() );
493 }
494 }
495}
496
497void QwtLegend::updateTabOrder()
498{
499 QLayout *contentsLayout = d_data->view->contentsWidget->layout();
500 if ( contentsLayout )
501 {
502 // set tab focus chain
503
504 QWidget *w = NULL;
505
506 for ( int i = 0; i < contentsLayout->count(); i++ )
507 {
508 QLayoutItem *item = contentsLayout->itemAt( i );
509 if ( w && item->widget() )
510 QWidget::setTabOrder( w, item->widget() );
511
512 w = item->widget();
513 }
514 }
515}
516
517//! Return a size hint.
518QSize QwtLegend::sizeHint() const
519{
520 QSize hint = d_data->view->contentsWidget->sizeHint();
521 hint += QSize( 2 * frameWidth(), 2 * frameWidth() );
522
523 return hint;
524}
525
526/*!
527 \return The preferred height, for a width.
528 \param width Width
529*/
530int QwtLegend::heightForWidth( int width ) const
531{
532 width -= 2 * frameWidth();
533
534 int h = d_data->view->contentsWidget->heightForWidth( width );
535 if ( h >= 0 )
536 h += 2 * frameWidth();
537
538 return h;
539}
540
541
542/*!
543 Handle QEvent::ChildRemoved andQEvent::LayoutRequest events
544 for the contentsWidget().
545
546 \param object Object to be filtered
547 \param event Event
548
549 \return Forwarded to QwtAbstractLegend::eventFilter()
550*/
551bool QwtLegend::eventFilter( QObject *object, QEvent *event )
552{
553 if ( object == d_data->view->contentsWidget )
554 {
555 switch ( event->type() )
556 {
557 case QEvent::ChildRemoved:
558 {
559 const QChildEvent *ce =
560 static_cast<const QChildEvent *>(event);
561 if ( ce->child()->isWidgetType() )
562 {
563 QWidget *w = static_cast< QWidget * >( ce->child() );
564 d_data->itemMap.removeWidget( w );
565 }
566 break;
567 }
568 case QEvent::LayoutRequest:
569 {
570 d_data->view->layoutContents();
571
572 if ( parentWidget() && parentWidget()->layout() == NULL )
573 {
574 /*
575 We want the parent widget ( usually QwtPlot ) to recalculate
576 its layout, when the contentsWidget has changed. But
577 because of the scroll view we have to forward the LayoutRequest
578 event manually.
579
580 We don't use updateGeometry() because it doesn't post LayoutRequest
581 events when the legend is hidden. But we want the
582 parent widget notified, so it can show/hide the legend
583 depending on its items.
584 */
585 QApplication::postEvent( parentWidget(),
586 new QEvent( QEvent::LayoutRequest ) );
587 }
588 break;
589 }
590 default:
591 break;
592 }
593 }
594
595 return QwtAbstractLegend::eventFilter( object, event );
596}
597
598/*!
599 Called internally when the legend has been clicked on.
600 Emits a clicked() signal.
601*/
602void QwtLegend::itemClicked()
603{
604 QWidget *w = qobject_cast<QWidget *>( sender() );
605 if ( w )
606 {
607 const QVariant itemInfo = d_data->itemMap.itemInfo( w );
608 if ( itemInfo.isValid() )
609 {
610 const QList<QWidget *> widgetList =
611 d_data->itemMap.legendWidgets( itemInfo );
612
613 const int index = widgetList.indexOf( w );
614 if ( index >= 0 )
615 Q_EMIT clicked( itemInfo, index );
616 }
617 }
618}
619
620/*!
621 Called internally when the legend has been checked
622 Emits a checked() signal.
623*/
624void QwtLegend::itemChecked( bool on )
625{
626 QWidget *w = qobject_cast<QWidget *>( sender() );
627 if ( w )
628 {
629 const QVariant itemInfo = d_data->itemMap.itemInfo( w );
630 if ( itemInfo.isValid() )
631 {
632 const QList<QWidget *> widgetList =
633 d_data->itemMap.legendWidgets( itemInfo );
634
635 const int index = widgetList.indexOf( w );
636 if ( index >= 0 )
637 Q_EMIT checked( itemInfo, on, index );
638 }
639 }
640}
641
642/*!
643 Render the legend into a given rectangle.
644
645 \param painter Painter
646 \param rect Bounding rectangle
647 \param fillBackground When true, fill rect with the widget background
648
649 \sa renderLegend() is used by QwtPlotRenderer - not by QwtLegend itself
650*/
651void QwtLegend::renderLegend( QPainter *painter,
652 const QRectF &rect, bool fillBackground ) const
653{
654 if ( d_data->itemMap.isEmpty() )
655 return;
656
657 if ( fillBackground )
658 {
659 if ( autoFillBackground() ||
660 testAttribute( Qt::WA_StyledBackground ) )
661 {
662 QwtPainter::drawBackgound( painter, rect, this );
663 }
664 }
665
666 const QwtDynGridLayout *legendLayout =
667 qobject_cast<QwtDynGridLayout *>( contentsWidget()->layout() );
668 if ( legendLayout == NULL )
669 return;
670
671 int left, right, top, bottom;
672 getContentsMargins( &left, &top, &right, &bottom );
673
674 QRect layoutRect;
675 layoutRect.setLeft( qCeil( rect.left() ) + left );
676 layoutRect.setTop( qCeil( rect.top() ) + top );
677 layoutRect.setRight( qFloor( rect.right() ) - right );
678 layoutRect.setBottom( qFloor( rect.bottom() ) - bottom );
679
680 uint numCols = legendLayout->columnsForWidth( layoutRect.width() );
681 QList<QRect> itemRects =
682 legendLayout->layoutItems( layoutRect, numCols );
683
684 int index = 0;
685
686 for ( int i = 0; i < legendLayout->count(); i++ )
687 {
688 QLayoutItem *item = legendLayout->itemAt( i );
689 QWidget *w = item->widget();
690 if ( w )
691 {
692 painter->save();
693
694 painter->setClipRect( itemRects[index], Qt::IntersectClip );
695 renderItem( painter, w, itemRects[index], fillBackground );
696
697 index++;
698 painter->restore();
699 }
700 }
701}
702
703/*!
704 Render a legend entry into a given rectangle.
705
706 \param painter Painter
707 \param widget Widget representing a legend entry
708 \param rect Bounding rectangle
709 \param fillBackground When true, fill rect with the widget background
710
711 \note When widget is not derived from QwtLegendLabel renderItem
712 does nothing beside the background
713*/
714void QwtLegend::renderItem( QPainter *painter,
715 const QWidget *widget, const QRectF &rect, bool fillBackground ) const
716{
717 if ( fillBackground )
718 {
719 if ( widget->autoFillBackground() ||
720 widget->testAttribute( Qt::WA_StyledBackground ) )
721 {
722 QwtPainter::drawBackgound( painter, rect, widget );
723 }
724 }
725
726 const QwtLegendLabel *label = qobject_cast<const QwtLegendLabel *>( widget );
727 if ( label )
728 {
729 // icon
730
731 const QwtGraphic &icon = label->data().icon();
732 const QSizeF sz = icon.defaultSize();
733
734 const QRectF iconRect( rect.x() + label->margin(),
735 rect.center().y() - 0.5 * sz.height(),
736 sz.width(), sz.height() );
737
738 icon.render( painter, iconRect, Qt::KeepAspectRatio );
739
740 // title
741
742 QRectF titleRect = rect;
743 titleRect.setX( iconRect.right() + 2 * label->spacing() );
744
745 painter->setFont( label->font() );
746 painter->setPen( label->palette().color( QPalette::Text ) );
747 const_cast< QwtLegendLabel *>( label )->drawText( painter, titleRect );
748 }
749}
750
751/*!
752 \return List of widgets associated to a item
753 \param itemInfo Info about an item
754 \sa legendWidget(), itemInfo(), QwtPlot::itemToInfo()
755 */
756QList<QWidget *> QwtLegend::legendWidgets( const QVariant &itemInfo ) const
757{
758 return d_data->itemMap.legendWidgets( itemInfo );
759}
760
761/*!
762 \return First widget in the list of widgets associated to an item
763 \param itemInfo Info about an item
764 \sa itemInfo(), QwtPlot::itemToInfo()
765 \note Almost all types of items have only one widget
766*/
767QWidget *QwtLegend::legendWidget( const QVariant &itemInfo ) const
768{
769 const QList<QWidget *> list = d_data->itemMap.legendWidgets( itemInfo );
770 if ( list.isEmpty() )
771 return NULL;
772
773 return list[0];
774}
775
776/*!
777 Find the item that is associated to a widget
778
779 \param widget Widget on the legend
780 \return Associated item info
781 \sa legendWidget()
782 */
783QVariant QwtLegend::itemInfo( const QWidget *widget ) const
784{
785 return d_data->itemMap.itemInfo( widget );
786}
787
788//! \return True, when no item is inserted
789bool QwtLegend::isEmpty() const
790{
791 return d_data->itemMap.isEmpty();
792}
793
794/*!
795 Return the extent, that is needed for the scrollbars
796
797 \param orientation Orientation (
798 \return The width of the vertical scrollbar for Qt::Horizontal and v.v.
799 */
800int QwtLegend::scrollExtent( Qt::Orientation orientation ) const
801{
802 int extent = 0;
803
804 if ( orientation == Qt::Horizontal )
805 extent = verticalScrollBar()->sizeHint().width();
806 else
807 extent = horizontalScrollBar()->sizeHint().height();
808
809 return extent;
810}
811
Note: See TracBrowser for help on using the repository browser.