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

Last change on this file since 8127 was 8127, checked in by stoecker, 2 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.