File indexing completed on 2024-05-12 04:20:35

0001 /*
0002  * SPDX-FileCopyrightText: 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved.
0003  *
0004  * This file is part of the KD Chart library.
0005  *
0006  * SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "KChartLegend.h"
0010 #include "KChartLegend_p.h"
0011 #include <KChartTextAttributes.h>
0012 #include <KChartMarkerAttributes.h>
0013 #include <KChartPalette.h>
0014 #include <KChartAbstractDiagram.h>
0015 #include "KTextDocument.h"
0016 #include <KChartDiagramObserver.h>
0017 #include "KChartLayoutItems.h"
0018 #include "KChartPrintingParameters.h"
0019 
0020 #include <QFont>
0021 #include <QGridLayout>
0022 #include <QPainter>
0023 #include <QTextTableCell>
0024 #include <QTextCursor>
0025 #include <QTextCharFormat>
0026 #include <QTextDocumentFragment>
0027 #include <QTimer>
0028 #include <QAbstractTextDocumentLayout>
0029 #include <QtDebug>
0030 #include <QLabel>
0031 
0032 using namespace KChart;
0033 
0034 Legend::Private::Private() :
0035     referenceArea( nullptr ),
0036     position( Position::East ),
0037     alignment( Qt::AlignCenter ),
0038     textAlignment( Qt::AlignCenter ),
0039     relativePosition( RelativePosition() ),
0040     orientation( Qt::Vertical ),
0041     order( Qt::AscendingOrder ),
0042     showLines( false ),
0043     titleText( QObject::tr( "Legend" ) ),
0044     spacing( 1 ),
0045     useAutomaticMarkerSize( true ),
0046     legendStyle( MarkersOnly )
0047 {
0048     // By default we specify a simple, hard point as the 'relative' position's ref. point,
0049     // since we can not be sure that there will be any parent specified for the legend.
0050     relativePosition.setReferencePoints( PositionPoints( QPointF( 0.0, 0.0 ) ) );
0051     relativePosition.setReferencePosition( Position::NorthWest );
0052     relativePosition.setAlignment( Qt::AlignTop | Qt::AlignLeft );
0053     relativePosition.setHorizontalPadding( Measure( 4.0, KChartEnums::MeasureCalculationModeAbsolute ) );
0054     relativePosition.setVerticalPadding( Measure( 4.0, KChartEnums::MeasureCalculationModeAbsolute ) );
0055 }
0056 
0057 Legend::Private::~Private()
0058 {
0059     // this block left empty intentionally
0060 }
0061 
0062 
0063 #define d d_func()
0064 
0065 
0066 Legend::Legend( QWidget* parent ) :
0067     AbstractAreaWidget( new Private(), parent )
0068 {
0069     d->referenceArea = parent;
0070     init();
0071 }
0072 
0073 Legend::Legend( AbstractDiagram* diagram, QWidget* parent ) :
0074     AbstractAreaWidget( new Private(), parent )
0075 {
0076     d->referenceArea = parent;
0077     init();
0078     setDiagram( diagram );
0079 }
0080 
0081 Legend::~Legend()
0082 {
0083     Q_EMIT destroyedLegend( this );
0084 }
0085 
0086 void Legend::init()
0087 {
0088     setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
0089 
0090     d->layout = new QGridLayout( this );
0091     d->layout->setContentsMargins( 2, 2, 2, 2 );
0092     d->layout->setSpacing( d->spacing );
0093 
0094     const Measure normalFontSizeTitle( 12, KChartEnums::MeasureCalculationModeAbsolute );
0095     const Measure normalFontSizeLabels( 10, KChartEnums::MeasureCalculationModeAbsolute );
0096     const Measure minimalFontSize( 4, KChartEnums::MeasureCalculationModeAbsolute );
0097 
0098     TextAttributes textAttrs;
0099     textAttrs.setPen( QPen( Qt::black ) );
0100     textAttrs.setFont( QFont( QLatin1String( "helvetica" ), 10, QFont::Normal, false ) );
0101     textAttrs.setFontSize( normalFontSizeLabels );
0102     textAttrs.setMinimalFontSize( minimalFontSize );
0103     setTextAttributes( textAttrs );
0104 
0105     TextAttributes titleTextAttrs;
0106     titleTextAttrs.setPen( QPen( Qt::black ) );
0107     titleTextAttrs.setFont( QFont( QLatin1String( "helvetica" ), 12, QFont::Bold, false ) );
0108     titleTextAttrs.setFontSize( normalFontSizeTitle );
0109     titleTextAttrs.setMinimalFontSize( minimalFontSize );
0110     setTitleTextAttributes( titleTextAttrs );
0111 
0112     FrameAttributes frameAttrs;
0113     frameAttrs.setVisible( true );
0114     frameAttrs.setPen( QPen( Qt::black ) );
0115     frameAttrs.setPadding( 1 );
0116     setFrameAttributes( frameAttrs );
0117 
0118     d->position = Position::NorthEast;
0119     d->alignment = Qt::AlignCenter;
0120 }
0121 
0122 
0123 QSize Legend::minimumSizeHint() const
0124 {
0125     return sizeHint();
0126 }
0127 
0128 //#define DEBUG_LEGEND_PAINT
0129 
0130 QSize Legend::sizeHint() const
0131 {
0132 #ifdef DEBUG_LEGEND_PAINT
0133     qDebug()  << "Legend::sizeHint() started";
0134 #endif
0135     for ( AbstractLayoutItem* paintItem : d->paintItems ) {
0136         paintItem->sizeHint();
0137     }
0138     return AbstractAreaWidget::sizeHint();
0139 }
0140 
0141 void Legend::needSizeHint()
0142 {
0143     buildLegend();
0144 }
0145 
0146 void Legend::resizeLayout( const QSize& size )
0147 {
0148 #ifdef DEBUG_LEGEND_PAINT
0149     qDebug() << "Legend::resizeLayout started";
0150 #endif
0151     if ( d->layout ) {
0152         d->reflowHDatasetItems( this );
0153         d->layout->setGeometry( QRect(QPoint( 0,0 ), size) );
0154         activateTheLayout();
0155     }
0156 #ifdef DEBUG_LEGEND_PAINT
0157     qDebug() << "Legend::resizeLayout done";
0158 #endif
0159 }
0160 
0161 void Legend::activateTheLayout()
0162 {
0163     if ( d->layout && d->layout->parent() ) {
0164         d->layout->activate();
0165     }
0166 }
0167 
0168 void Legend::setLegendStyle( LegendStyle style )
0169 {
0170     if ( d->legendStyle == style ) {
0171         return;
0172     }
0173     d->legendStyle = style;
0174     setNeedRebuild();
0175 }
0176 
0177 Legend::LegendStyle Legend::legendStyle() const
0178 {
0179     return d->legendStyle;
0180 }
0181 
0182 Legend* Legend::clone() const
0183 {
0184     Legend* legend = new Legend( new Private( *d ), nullptr );
0185     legend->setTextAttributes( textAttributes() );
0186     legend->setTitleTextAttributes( titleTextAttributes() );
0187     legend->setFrameAttributes( frameAttributes() );
0188     legend->setUseAutomaticMarkerSize( useAutomaticMarkerSize() );
0189     legend->setPosition( position() );
0190     legend->setAlignment( alignment() );
0191     legend->setTextAlignment( textAlignment() );
0192     legend->setLegendStyle( legendStyle() );
0193     return legend;
0194 }
0195 
0196 
0197 bool Legend::compare( const Legend* other ) const
0198 {
0199     if ( other == this ) {
0200         return true;
0201     }
0202     if ( !other ) {
0203         return false;
0204     }
0205 
0206     return  ( AbstractAreaBase::compare( other ) ) &&
0207             (isVisible()              == other->isVisible()) &&
0208             (position()               == other->position()) &&
0209             (alignment()              == other->alignment())&&
0210             (textAlignment()          == other->textAlignment())&&
0211             (floatingPosition()       == other->floatingPosition()) &&
0212             (orientation()            == other->orientation())&&
0213             (showLines()              == other->showLines())&&
0214             (texts()                  == other->texts())&&
0215             (brushes()                == other->brushes())&&
0216             (pens()                   == other->pens())&&
0217             (markerAttributes()       == other->markerAttributes())&&
0218             (useAutomaticMarkerSize() == other->useAutomaticMarkerSize()) &&
0219             (textAttributes()         == other->textAttributes()) &&
0220             (titleText()              == other->titleText())&&
0221             (titleTextAttributes()    == other->titleTextAttributes()) &&
0222             (spacing()                == other->spacing()) &&
0223             (legendStyle()            == other->legendStyle());
0224 }
0225 
0226 
0227 void Legend::paint( QPainter* painter )
0228 {
0229 #ifdef DEBUG_LEGEND_PAINT
0230     qDebug() << "entering Legend::paint( QPainter* painter )";
0231 #endif
0232     if ( !diagram() ) {
0233         return;
0234     }
0235 
0236     activateTheLayout();
0237 
0238     for ( AbstractLayoutItem* paintItem : qAsConst(d->paintItems) ) {
0239         paintItem->paint( painter );
0240     }
0241 
0242 #ifdef DEBUG_LEGEND_PAINT
0243     qDebug() << "leaving Legend::paint( QPainter* painter )";
0244 #endif
0245 }
0246 
0247 void Legend::paint( QPainter *painter, const QRect& rect )
0248 {
0249     if ( rect.isEmpty() ) {
0250         return;
0251     }
0252     // set up the contents of the widget so we get a useful geometry
0253     QPaintDevice* prevDevice = GlobalMeasureScaling::paintDevice();
0254     GlobalMeasureScaling::setPaintDevice( painter->device() );
0255 
0256     const QRect oldGeometry(geometry() );
0257     const QRect newGeo( QPoint(0,0), rect.size() );
0258     if (oldGeometry != newGeo) {
0259         setGeometry(newGeo);
0260         needSizeHint();
0261     }
0262     painter->translate( rect.left(), rect.top() );
0263     paintAll( *painter );
0264     painter->translate( -rect.left(), -rect.top() );
0265 
0266     if (oldGeometry != newGeo) {
0267         setGeometry(oldGeometry);
0268     }
0269     GlobalMeasureScaling::setPaintDevice( prevDevice );
0270 }
0271 
0272 uint Legend::datasetCount() const
0273 {
0274     int modelLabelsCount = 0;
0275     for ( DiagramObserver* observer : d->observers ) {
0276         AbstractDiagram* diagram = observer->diagram();
0277         Q_ASSERT( diagram->datasetLabels().count() == diagram->datasetBrushes().count() );
0278         modelLabelsCount += diagram->datasetLabels().count();
0279     }
0280     return modelLabelsCount;
0281 }
0282 
0283 
0284 void Legend::setReferenceArea( const QWidget* area )
0285 {
0286     if ( area == d->referenceArea ) {
0287         return;
0288     }
0289     d->referenceArea = area;
0290     setNeedRebuild();
0291 }
0292 
0293 const QWidget* Legend::referenceArea() const
0294 {
0295     return d->referenceArea ? d->referenceArea : qobject_cast< const QWidget* >( parent() );
0296 }
0297 
0298 
0299 AbstractDiagram* Legend::diagram() const
0300 {
0301     if ( d->observers.isEmpty() ) {
0302         return nullptr;
0303     }
0304     return d->observers.first()->diagram();
0305 }
0306 
0307 DiagramList Legend::diagrams() const
0308 {
0309     DiagramList list;
0310     for ( int i = 0; i < d->observers.size(); ++i ) {
0311         list << d->observers.at(i)->diagram();
0312     }
0313     return list;
0314 }
0315 
0316 ConstDiagramList Legend::constDiagrams() const
0317 {
0318     ConstDiagramList list;
0319     for ( int i = 0; i < d->observers.size(); ++i ) {
0320         list << d->observers.at(i)->diagram();
0321     }
0322     return list;
0323 }
0324 
0325 void Legend::addDiagram( AbstractDiagram* newDiagram )
0326 {
0327     if ( newDiagram ) {
0328         DiagramObserver* observer = new DiagramObserver( newDiagram, this );
0329 
0330         DiagramObserver* oldObs = d->findObserverForDiagram( newDiagram );
0331         if ( oldObs ) {
0332             delete oldObs;
0333             d->observers[ d->observers.indexOf( oldObs ) ] = observer;
0334         } else {
0335             d->observers.append( observer );
0336         }
0337         connect( observer, SIGNAL(diagramAboutToBeDestroyed(KChart::AbstractDiagram*)),
0338                  SLOT(resetDiagram(KChart::AbstractDiagram*)));
0339         connect( observer, SIGNAL(diagramDataChanged(KChart::AbstractDiagram*)),
0340                  SLOT(setNeedRebuild()));
0341         connect( observer, SIGNAL(diagramDataHidden(KChart::AbstractDiagram*)),
0342                  SLOT(setNeedRebuild()));
0343         connect( observer, SIGNAL(diagramAttributesChanged(KChart::AbstractDiagram*)),
0344                  SLOT(setNeedRebuild()));
0345         setNeedRebuild();
0346     }
0347 }
0348 
0349 void Legend::removeDiagram( AbstractDiagram* oldDiagram )
0350 {
0351     int datasetBrushOffset = 0;
0352     QList< AbstractDiagram * > diagrams = this->diagrams();
0353     for ( int i = 0; i <diagrams.count(); i++ ) {
0354         if ( diagrams.at( i ) == oldDiagram ) {
0355             for ( int i = 0; i < oldDiagram->datasetBrushes().count(); i++ ) {
0356                 d->brushes.remove(datasetBrushOffset + i);
0357                 d->texts.remove(datasetBrushOffset + i);
0358             }
0359             for ( int i = 0; i < oldDiagram->datasetPens().count(); i++ ) {
0360                 d->pens.remove(datasetBrushOffset + i);
0361             }
0362             break;
0363         }
0364         datasetBrushOffset += diagrams.at(i)->datasetBrushes().count();
0365     }
0366 
0367     if ( oldDiagram ) {
0368         DiagramObserver *oldObs = d->findObserverForDiagram( oldDiagram );
0369         if ( oldObs ) {
0370             delete oldObs;
0371             d->observers.removeAt( d->observers.indexOf( oldObs ) );
0372         }
0373         setNeedRebuild();
0374     }
0375 }
0376 
0377 void Legend::removeDiagrams()
0378 {
0379     // removeDiagram() may change the d->observers list. So, build up the list of
0380     // diagrams to remove first and then remove them one by one.
0381     QList< AbstractDiagram * > diagrams;
0382     for ( int i = 0; i < d->observers.size(); ++i ) {
0383         diagrams.append( d->observers.at( i )->diagram() );
0384     }
0385     for ( int i = 0; i < diagrams.count(); ++i ) {
0386         removeDiagram( diagrams[ i ] );
0387     }
0388 }
0389 
0390 void Legend::replaceDiagram( AbstractDiagram* newDiagram,
0391                              AbstractDiagram* oldDiagram )
0392 {
0393     AbstractDiagram* old = oldDiagram;
0394     if ( !d->observers.isEmpty() && !old ) {
0395         old = d->observers.first()->diagram();
0396         if ( !old ) {
0397             d->observers.removeFirst(); // first entry had a 0 diagram
0398         }
0399     }
0400     if ( old ) {
0401         removeDiagram( old );
0402     }
0403     if ( newDiagram ) {
0404         addDiagram( newDiagram );
0405     }
0406 }
0407 
0408 uint Legend::dataSetOffset( AbstractDiagram* diagram )
0409 {
0410     uint offset = 0;
0411 
0412     for ( int i = 0; i < d->observers.count(); ++i ) {
0413         if ( d->observers.at(i)->diagram() == diagram ) {
0414             return offset;
0415         }
0416         AbstractDiagram* diagram = d->observers.at(i)->diagram();
0417         if ( !diagram->model() ) {
0418             continue;
0419         }
0420         offset = offset + diagram->model()->columnCount();
0421     }
0422 
0423     return offset;
0424 }
0425 
0426 void Legend::setDiagram( AbstractDiagram* newDiagram )
0427 {
0428     replaceDiagram( newDiagram );
0429 }
0430 
0431 void Legend::resetDiagram( AbstractDiagram* oldDiagram )
0432 {
0433     removeDiagram( oldDiagram );
0434 }
0435 
0436 void Legend::setVisible( bool visible )
0437 {
0438     // do NOT bail out if visible == isVisible(), because the return value of isVisible() also depends
0439     // on the visibility of the parent.
0440     QWidget::setVisible( visible );
0441     emitPositionChanged();
0442 }
0443 
0444 void Legend::setNeedRebuild()
0445 {
0446     buildLegend();
0447     sizeHint();
0448 }
0449 
0450 void Legend::setPosition( Position position )
0451 {
0452     if ( d->position == position ) {
0453         return;
0454     }
0455     d->position = position;
0456     emitPositionChanged();
0457 }
0458 
0459 void Legend::emitPositionChanged()
0460 {
0461     Q_EMIT positionChanged( this );
0462     Q_EMIT propertiesChanged();
0463 }
0464 
0465 
0466 Position Legend::position() const
0467 {
0468     return d->position;
0469 }
0470 
0471 void Legend::setAlignment( Qt::Alignment alignment )
0472 {
0473     if ( d->alignment == alignment ) {
0474         return;
0475     }
0476     d->alignment = alignment;
0477     emitPositionChanged();
0478 }
0479 
0480 Qt::Alignment Legend::alignment() const
0481 {
0482     return d->alignment;
0483 }
0484 
0485 void Legend::setTextAlignment( Qt::Alignment alignment )
0486 {
0487     if ( d->textAlignment == alignment ) {
0488         return;
0489     }
0490     d->textAlignment = alignment;
0491     emitPositionChanged();
0492 }
0493 
0494 Qt::Alignment Legend::textAlignment() const
0495 {
0496     return d->textAlignment;
0497 }
0498 
0499 void Legend::setLegendSymbolAlignment( Qt::Alignment alignment )
0500 {
0501     if ( d->legendLineSymbolAlignment == alignment ) {
0502         return;
0503     }
0504     d->legendLineSymbolAlignment = alignment;
0505     emitPositionChanged();
0506 }
0507 
0508 Qt::Alignment Legend::legendSymbolAlignment() const
0509 {
0510     return d->legendLineSymbolAlignment ;
0511 }
0512 
0513 void Legend::setFloatingPosition( const RelativePosition& relativePosition )
0514 {
0515     d->position = Position::Floating;
0516     if ( d->relativePosition != relativePosition ) {
0517         d->relativePosition  = relativePosition;
0518         emitPositionChanged();
0519     }
0520 }
0521 
0522 const RelativePosition Legend::floatingPosition() const
0523 {
0524     return d->relativePosition;
0525 }
0526 
0527 void Legend::setOrientation( Qt::Orientation orientation )
0528 {
0529     if ( d->orientation == orientation ) {
0530         return;
0531     }
0532     d->orientation = orientation;
0533     setNeedRebuild();
0534     emitPositionChanged();
0535 }
0536 
0537 Qt::Orientation Legend::orientation() const
0538 {
0539     return d->orientation;
0540 }
0541 
0542 void Legend::setSortOrder( Qt::SortOrder order )
0543 {
0544     if ( d->order == order ) {
0545         return;
0546     }
0547     d->order = order;
0548     setNeedRebuild();
0549     emitPositionChanged();
0550 }
0551 
0552 Qt::SortOrder Legend::sortOrder() const
0553 {
0554     return d->order;
0555 }
0556 
0557 void Legend::setShowLines( bool legendShowLines )
0558 {
0559     if ( d->showLines == legendShowLines ) {
0560         return;
0561     }
0562     d->showLines = legendShowLines;
0563     setNeedRebuild();
0564     emitPositionChanged();
0565 }
0566 
0567 bool Legend::showLines() const
0568 {
0569     return d->showLines;
0570 }
0571 
0572 void Legend::setUseAutomaticMarkerSize( bool useAutomaticMarkerSize )
0573 {
0574     d->useAutomaticMarkerSize = useAutomaticMarkerSize;
0575     setNeedRebuild();
0576     emitPositionChanged();
0577 }
0578 
0579 bool Legend::useAutomaticMarkerSize() const
0580 {
0581     return d->useAutomaticMarkerSize;
0582 }
0583 
0584 void Legend::resetTexts()
0585 {
0586     if ( !d->texts.count() ) {
0587         return;
0588     }
0589     d->texts.clear();
0590     setNeedRebuild();
0591 }
0592 
0593 void Legend::setText( uint dataset, const QString& text )
0594 {
0595     if ( d->texts[ dataset ] == text ) {
0596         return;
0597     }
0598     d->texts[ dataset ] = text;
0599     setNeedRebuild();
0600 }
0601 
0602 QString Legend::text( uint dataset ) const
0603 {
0604     if ( d->texts.find( dataset ) != d->texts.end() ) {
0605         return d->texts[ dataset ];
0606     } else {
0607         return d->modelLabels[ dataset ];
0608     }
0609 }
0610 
0611 const QMap<uint,QString> Legend::texts() const
0612 {
0613     return d->texts;
0614 }
0615 
0616 void Legend::setColor( uint dataset, const QColor& color )
0617 {
0618     if ( d->brushes[ dataset ] != color ) {
0619         d->brushes[ dataset ] = color;
0620         setNeedRebuild();
0621         update();
0622     }
0623 }
0624 
0625 void Legend::setBrush( uint dataset, const QBrush& brush )
0626 {
0627     if ( d->brushes[ dataset ] != brush ) {
0628         d->brushes[ dataset ] = brush;
0629         setNeedRebuild();
0630         update();
0631     }
0632 }
0633 
0634 QBrush Legend::brush( uint dataset ) const
0635 {
0636     if ( d->brushes.contains( dataset ) ) {
0637         return d->brushes[ dataset ];
0638     } else {
0639         return d->modelBrushes[ dataset ];
0640     }
0641 }
0642 
0643 const QMap<uint,QBrush> Legend::brushes() const
0644 {
0645     return d->brushes;
0646 }
0647 
0648 
0649 void Legend::setBrushesFromDiagram( AbstractDiagram* diagram )
0650 {
0651     bool changed = false;
0652     QList<QBrush> datasetBrushes = diagram->datasetBrushes();
0653     for ( int i = 0; i < datasetBrushes.count(); i++ ) {
0654         if ( d->brushes[ i ] != datasetBrushes[ i ] ) {
0655             d->brushes[ i ]  = datasetBrushes[ i ];
0656             changed = true;
0657         }
0658     }
0659     if ( changed ) {
0660         setNeedRebuild();
0661         update();
0662     }
0663 }
0664 
0665 
0666 void Legend::setPen( uint dataset, const QPen& pen )
0667 {
0668     if ( d->pens[dataset] == pen ) {
0669         return;
0670     }
0671     d->pens[dataset] = pen;
0672     setNeedRebuild();
0673     update();
0674 }
0675 
0676 QPen Legend::pen( uint dataset ) const
0677 {
0678     if ( d->pens.find( dataset ) != d->pens.end() ) {
0679         return d->pens[ dataset ];
0680     } else {
0681         return d->modelPens[ dataset ];
0682     }
0683 }
0684 
0685 const QMap<uint,QPen> Legend::pens() const
0686 {
0687     return d->pens;
0688 }
0689 
0690 
0691 void Legend::setMarkerAttributes( uint dataset, const MarkerAttributes& markerAttributes )
0692 {
0693     if ( d->markerAttributes[dataset] == markerAttributes ) {
0694         return;
0695     }
0696     d->markerAttributes[ dataset ] = markerAttributes;
0697     setNeedRebuild();
0698     update();
0699 }
0700 
0701 MarkerAttributes Legend::markerAttributes( uint dataset ) const
0702 {
0703     if ( d->markerAttributes.find( dataset ) != d->markerAttributes.end() ) {
0704         return d->markerAttributes[ dataset ];
0705     } else if ( static_cast<uint>( d->modelMarkers.count() ) > dataset ) {
0706         return d->modelMarkers[ dataset ];
0707     } else {
0708         return MarkerAttributes();
0709     }
0710 }
0711 
0712 const QMap<uint, MarkerAttributes> Legend::markerAttributes() const
0713 {
0714     return d->markerAttributes;
0715 }
0716 
0717 
0718 void Legend::setTextAttributes( const TextAttributes &a )
0719 {
0720     if ( d->textAttributes == a ) {
0721         return;
0722     }
0723     d->textAttributes = a;
0724     setNeedRebuild();
0725 }
0726 
0727 TextAttributes Legend::textAttributes() const
0728 {
0729     return d->textAttributes;
0730 }
0731 
0732 void Legend::setTitleText( const QString& text )
0733 {
0734     if ( d->titleText == text ) {
0735         return;
0736     }
0737     d->titleText = text;
0738     setNeedRebuild();
0739 }
0740 
0741 QString Legend::titleText() const
0742 {
0743     return d->titleText;
0744 }
0745 
0746 void Legend::setTitleTextAttributes( const TextAttributes &a )
0747 {
0748     if ( d->titleTextAttributes == a ) {
0749         return;
0750     }
0751     d->titleTextAttributes = a;
0752     setNeedRebuild();
0753 }
0754 
0755 TextAttributes Legend::titleTextAttributes() const
0756 {
0757     return d->titleTextAttributes;
0758 }
0759 
0760 void Legend::forceRebuild()
0761 {
0762 #ifdef DEBUG_LEGEND_PAINT
0763     qDebug() << "entering Legend::forceRebuild()";
0764 #endif
0765     buildLegend();
0766 #ifdef DEBUG_LEGEND_PAINT
0767     qDebug() << "leaving Legend::forceRebuild()";
0768 #endif
0769 }
0770 
0771 void Legend::setSpacing( uint space )
0772 {
0773     if ( d->spacing == space && d->layout->spacing() == int( space ) ) {
0774         return;
0775     }
0776     d->spacing = space;
0777     d->layout->setSpacing( space );
0778     setNeedRebuild();
0779 }
0780 
0781 uint Legend::spacing() const
0782 {
0783     return d->spacing;
0784 }
0785 
0786 void Legend::setDefaultColors()
0787 {
0788     Palette pal = Palette::defaultPalette();
0789     for ( int i = 0; i < pal.size(); i++ ) {
0790         setBrush( i, pal.getBrush( i ) );
0791     }
0792 }
0793 
0794 void Legend::setRainbowColors()
0795 {
0796     Palette pal = Palette::rainbowPalette();
0797     for ( int i = 0; i < pal.size(); i++ ) {
0798         setBrush( i, pal.getBrush( i ) );
0799     }
0800 }
0801 
0802 void Legend::setSubduedColors( bool ordered )
0803 {
0804     Palette pal = Palette::subduedPalette();
0805     if ( ordered ) {
0806         for ( int i = 0; i < pal.size(); i++ ) {
0807             setBrush( i, pal.getBrush( i ) );
0808         }
0809     } else {
0810         static const int s_subduedColorsCount = 18;
0811         Q_ASSERT( pal.size() >= s_subduedColorsCount );
0812         static const int order[ s_subduedColorsCount ] = {
0813             0, 5, 10, 15, 2, 7, 12, 17, 4,
0814             9, 14, 1, 6, 11, 16, 3, 8, 13
0815         };
0816         for ( int i = 0; i < s_subduedColorsCount; i++ ) {
0817             setBrush( i, pal.getBrush( order[i] ) );
0818         }
0819     }
0820 }
0821 
0822 void Legend::resizeEvent( QResizeEvent * event )
0823 {
0824     Q_UNUSED( event );
0825 #ifdef DEBUG_LEGEND_PAINT
0826     qDebug() << "Legend::resizeEvent() called";
0827 #endif
0828     forceRebuild();
0829     sizeHint();
0830     QTimer::singleShot( 0, this, SLOT(emitPositionChanged()) );
0831 }
0832 
0833 void Legend::Private::fetchPaintOptions( Legend *q )
0834 {
0835     modelLabels.clear();
0836     modelBrushes.clear();
0837     modelPens.clear();
0838     modelMarkers.clear();
0839     // retrieve the diagrams' settings for all non-hidden datasets
0840     for ( int i = 0; i < observers.size(); ++i ) {
0841         const AbstractDiagram* diagram = observers.at( i )->diagram();
0842         if ( !diagram ) {
0843             continue;
0844         }
0845         const QStringList diagramLabels = diagram->datasetLabels();
0846         const QList<QBrush> diagramBrushes = diagram->datasetBrushes();
0847         const QList<QPen> diagramPens = diagram->datasetPens();
0848         const QList<MarkerAttributes> diagramMarkers = diagram->datasetMarkers();
0849 
0850         const bool ascend = q->sortOrder() == Qt::AscendingOrder;
0851         int dataset = ascend ? 0 : diagramLabels.count() - 1;
0852         const int end = ascend ? diagramLabels.count() : -1;
0853         for ( ; dataset != end; dataset += ascend ? 1 : -1 ) {
0854             if ( diagram->isHidden( dataset ) || q->datasetIsHidden( dataset ) ) {
0855                 continue;
0856             }
0857             modelLabels += diagramLabels[ dataset ];
0858             modelBrushes += diagramBrushes[ dataset ];
0859             modelPens += diagramPens[ dataset ];
0860             modelMarkers += diagramMarkers[ dataset ];
0861         }
0862     }
0863 
0864     Q_ASSERT( modelLabels.count() == modelBrushes.count() );
0865 }
0866 
0867 QSizeF Legend::Private::markerSize( Legend *q, int dataset, qreal fontHeight ) const
0868 {
0869     QSizeF suppliedSize = q->markerAttributes( dataset ).markerSize();
0870     if ( q->useAutomaticMarkerSize() || !suppliedSize.isValid() ) {
0871         return QSizeF( fontHeight, fontHeight );
0872     } else {
0873         return suppliedSize;
0874     }
0875 }
0876 
0877 QSizeF Legend::Private::maxMarkerSize( Legend *q, qreal fontHeight ) const
0878 {
0879     QSizeF ret( 1.0, 1.0 );
0880     if ( q->legendStyle() != LinesOnly ) {
0881         for ( int dataset = 0; dataset < modelLabels.count(); ++dataset ) {
0882             ret = ret.expandedTo( markerSize( q, dataset, fontHeight ) );
0883         }
0884     }
0885     return ret;
0886 }
0887 
0888 HDatasetItem::HDatasetItem()
0889    : markerLine(nullptr),
0890      label(nullptr),
0891      separatorLine(nullptr),
0892      spacer(nullptr)
0893 {}
0894 
0895 static void updateToplevelLayout(QWidget *w)
0896 {
0897     while ( w ) {
0898         if ( w->isTopLevel() ) {
0899             // The null check has proved necessary during destruction of the Legend / Chart
0900             if ( w->layout() ) {
0901                 w->layout()->update();
0902             }
0903             break;
0904         } else {
0905             w = qobject_cast< QWidget * >( w->parent() );
0906             Q_ASSERT( w );
0907         }
0908     }
0909 }
0910 
0911 void Legend::buildLegend()
0912 {
0913     /* Grid layout partitioning (horizontal orientation): row zero is the title, row one the divider
0914        line between title and dataset items, row two for each item: line, marker, text label and separator
0915        line in that order.
0916        In a vertically oriented legend, row pairs (2, 3), ... contain a possible separator line (first row)
0917        and (second row) line, marker, text label each. */
0918     d->destroyOldLayout();
0919 
0920     if ( orientation() == Qt::Vertical ) {
0921         d->layout->setColumnStretch( 6, 1 );
0922     } else {
0923         d->layout->setColumnStretch( 6, 0 );
0924     }
0925 
0926     d->fetchPaintOptions( this );
0927 
0928     const KChartEnums::MeasureOrientation measureOrientation =
0929         orientation() == Qt::Vertical ? KChartEnums::MeasureOrientationMinimum
0930                                       : KChartEnums::MeasureOrientationHorizontal;
0931 
0932     // legend caption
0933     if ( !titleText().isEmpty() && titleTextAttributes().isVisible() ) {
0934         TextLayoutItem* titleItem =
0935             new TextLayoutItem( titleText(), titleTextAttributes(), referenceArea(),
0936                                          measureOrientation, d->textAlignment );
0937         titleItem->setParentWidget( this );
0938 
0939         d->paintItems << titleItem;
0940         d->layout->addItem( titleItem, 0, 0, 1, 5, Qt::AlignCenter );
0941 
0942         // The line between the title and the legend items, if any.
0943         if ( showLines() && d->modelLabels.count() ) {
0944             HorizontalLineLayoutItem* lineItem = new HorizontalLineLayoutItem;
0945             d->paintItems << lineItem;
0946             d->layout->addItem( lineItem, 1, 0, 1, 5, Qt::AlignCenter );
0947         }
0948     }
0949 
0950     qreal fontHeight = textAttributes().calculatedFontSize( referenceArea(), measureOrientation );
0951     {
0952         QFont tmpFont = textAttributes().font();
0953         tmpFont.setPointSizeF( fontHeight );
0954         if ( GlobalMeasureScaling::paintDevice() ) {
0955             fontHeight = QFontMetricsF( tmpFont, GlobalMeasureScaling::paintDevice() ).height();
0956         } else {
0957             fontHeight = QFontMetricsF( tmpFont ).height();
0958         }
0959     }
0960 
0961     const QSizeF maxMarkerSize = d->maxMarkerSize( this, fontHeight );
0962 
0963     // If we show a marker on a line, we paint it after 8 pixels
0964     // of the line have been painted. This allows to see the line style
0965     // at the right side of the marker without the line needing to
0966     // be too long.
0967     // (having the marker in the middle of the line would require longer lines)
0968     const int lineLengthLeftOfMarker = 8;
0969 
0970     int maxLineLength = 18;
0971     {
0972         bool hasComplexPenStyle = false;
0973         for ( int dataset = 0; dataset < d->modelLabels.count(); ++dataset ) {
0974             const QPen pn = pen( dataset );
0975             const Qt::PenStyle ps = pn.style();
0976             if ( ps != Qt::NoPen ) {
0977                 maxLineLength = qMin( pn.width() * 18, maxLineLength );
0978                 if ( ps != Qt::SolidLine ) {
0979                     hasComplexPenStyle = true;
0980                 }
0981             }
0982         }
0983         if ( legendStyle() != LinesOnly ) {
0984             if ( hasComplexPenStyle )
0985               maxLineLength += lineLengthLeftOfMarker;
0986             maxLineLength += int( maxMarkerSize.width() );
0987         }
0988     }
0989 
0990     // for all datasets: add (line)marker items and text items to the layout;
0991     // actual layout happens in flowHDatasetItems() for horizontal layout, here for vertical
0992     for ( int dataset = 0; dataset < d->modelLabels.count(); ++dataset ) {
0993         const int vLayoutRow = 2 + dataset * 2;
0994         HDatasetItem dsItem;
0995 
0996         // It is possible to set the marker brush through markerAttributes as well as
0997         // the dataset brush set in the diagram - the markerAttributes have higher precedence.
0998         MarkerAttributes markerAttrs = markerAttributes( dataset );
0999         markerAttrs.setMarkerSize( d->markerSize( this, dataset, fontHeight ) );
1000         const QBrush markerBrush = markerAttrs.markerColor().isValid() ?
1001                                    QBrush( markerAttrs.markerColor() ) : brush( dataset );
1002 
1003         switch ( legendStyle() ) {
1004         case MarkersOnly:
1005             dsItem.markerLine = new MarkerLayoutItem( diagram(), markerAttrs, markerBrush,
1006                                                       markerAttrs.pen(), Qt::AlignLeft | Qt::AlignVCenter );
1007             break;
1008         case LinesOnly:
1009             dsItem.markerLine = new LineLayoutItem( diagram(), maxLineLength, pen( dataset ),
1010                                                     d->legendLineSymbolAlignment, Qt::AlignCenter );
1011             break;
1012         case MarkersAndLines:
1013             dsItem.markerLine = new LineWithMarkerLayoutItem(
1014                 diagram(), maxLineLength, pen( dataset ), lineLengthLeftOfMarker, markerAttrs,
1015                 markerBrush, markerAttrs.pen(), Qt::AlignCenter );
1016             break;
1017         default:
1018             Q_ASSERT( false );
1019         }
1020 
1021         dsItem.label = new TextLayoutItem( text( dataset ), textAttributes(), referenceArea(),
1022                                            measureOrientation, d->textAlignment );
1023         dsItem.label->setParentWidget( this );
1024 
1025         // horizontal layout is deferred to flowDatasetItems()
1026 
1027         if ( orientation() == Qt::Horizontal ) {
1028             d->hLayoutDatasets << dsItem;
1029             continue;
1030         }
1031 
1032         // (actual) vertical layout here
1033         if ( dsItem.markerLine ) {
1034             d->layout->addItem( dsItem.markerLine, vLayoutRow, 1, 1, 1, Qt::AlignCenter );
1035             d->paintItems << dsItem.markerLine;
1036         }
1037         d->layout->addItem( dsItem.label, vLayoutRow, 3, 1, 1, Qt::AlignLeft | Qt::AlignVCenter );
1038         d->paintItems << dsItem.label;
1039 
1040         // horizontal separator line, only between items
1041         if ( showLines() && dataset != d->modelLabels.count() - 1 ) {
1042             HorizontalLineLayoutItem* lineItem = new HorizontalLineLayoutItem;
1043             d->layout->addItem( lineItem, vLayoutRow + 1, 0, 1, 5, Qt::AlignCenter );
1044             d->paintItems << lineItem;
1045         }
1046     }
1047 
1048     if ( orientation() == Qt::Horizontal ) {
1049         d->flowHDatasetItems( this );
1050     }
1051 
1052     // vertical line (only in vertical mode)
1053     if ( orientation() == Qt::Vertical && showLines() && d->modelLabels.count() ) {
1054         VerticalLineLayoutItem* lineItem = new VerticalLineLayoutItem;
1055         d->paintItems << lineItem;
1056         d->layout->addItem( lineItem, 2, 2, d->modelLabels.count() * 2, 1 );
1057     }
1058 
1059     updateToplevelLayout( this );
1060 
1061     Q_EMIT propertiesChanged();
1062 #ifdef DEBUG_LEGEND_PAINT
1063     qDebug() << "leaving Legend::buildLegend()";
1064 #endif
1065 }
1066 
1067 int HDatasetItem::height() const
1068 {
1069     return qMax( markerLine->sizeHint().height(), label->sizeHint().height() );
1070 }
1071 
1072 void Legend::Private::reflowHDatasetItems( Legend *q )
1073 {
1074     if (hLayoutDatasets.isEmpty()) {
1075         return;
1076     }
1077 
1078     paintItems.clear();
1079     // Dissolve exactly the QHBoxLayout(s) created as "currentLine" in flowHDatasetItems - don't remove the
1080     // caption and line under the caption! Those are easily identified because they aren't QLayouts.
1081     for ( int i = layout->count() - 1; i >= 0; i-- ) {
1082         QLayoutItem *const item = layout->itemAt( i );
1083         QLayout *const hbox = item->layout();
1084         if ( !hbox ) {
1085             AbstractLayoutItem *alItem = dynamic_cast< AbstractLayoutItem * >( item );
1086             Q_ASSERT( alItem );
1087             paintItems << alItem;
1088             continue;
1089         }
1090         Q_ASSERT( dynamic_cast< QHBoxLayout * >( hbox ) );
1091         layout->takeAt( i );
1092         // detach children so they aren't deleted with the parent
1093         for ( int j = hbox->count() - 1; j >= 0; j-- ) {
1094             hbox->takeAt( j );
1095         }
1096         delete hbox;
1097     }
1098 
1099     flowHDatasetItems( q );
1100 }
1101 
1102 // this works pretty much like flow layout for text, and it is only applicable to dataset items
1103 // laid out horizontally
1104 void Legend::Private::flowHDatasetItems( Legend *q )
1105 {
1106     const int separatorLineWidth = 3; // hardcoded in VerticalLineLayoutItem::sizeHint()
1107 
1108     const int allowedWidth = q->areaGeometry().width();
1109     QHBoxLayout *currentLine = new QHBoxLayout;
1110     int columnSpan = 5;
1111     int mainLayoutColumn = 0;
1112     int row = 0;
1113     if ( !titleText.isEmpty() && titleTextAttributes.isVisible() ) {
1114         ++row;
1115         if (q->showLines()){
1116             ++row;
1117         }
1118     }
1119     layout->addItem( currentLine, row, mainLayoutColumn,
1120                      /*rowSpan*/1 , columnSpan, Qt::AlignLeft | Qt::AlignVCenter );
1121     mainLayoutColumn += columnSpan;
1122 
1123     for ( int dataset = 0; dataset < hLayoutDatasets.size(); dataset++ ) {
1124         HDatasetItem *hdsItem = &hLayoutDatasets[ dataset ];
1125 
1126         bool spacerUsed = false;
1127         bool separatorUsed = false;
1128         if ( !currentLine->isEmpty() ) {
1129             const int separatorWidth = ( q->showLines() ? separatorLineWidth : 0 ) + q->spacing();
1130             const int payloadWidth = hdsItem->markerLine->sizeHint().width() +
1131                                      hdsItem->label->sizeHint().width();
1132             if ( currentLine->sizeHint().width() + separatorWidth + payloadWidth > allowedWidth ) {
1133                 // too wide, "line break"
1134 #ifdef DEBUG_LEGEND_PAINT
1135                 qDebug() << Q_FUNC_INFO << "break" << mainLayoutColumn
1136                          << currentLine->sizeHint().width()
1137                          << currentLine->sizeHint().width() + separatorWidth + payloadWidth
1138                          << allowedWidth;
1139 #endif
1140                 currentLine = new QHBoxLayout;
1141                 layout->addItem( currentLine, row, mainLayoutColumn,
1142                                  /*rowSpan*/1 , columnSpan, Qt::AlignLeft | Qt::AlignVCenter );
1143                 mainLayoutColumn += columnSpan;
1144             } else {
1145                 // > 1 dataset item in line, put spacing and maybe a separator between them
1146                 if ( !hdsItem->spacer ) {
1147                     hdsItem->spacer = new QSpacerItem( q->spacing(), 1 );
1148                 }
1149                 currentLine->addItem( hdsItem->spacer );
1150                 spacerUsed = true;
1151 
1152                 if ( q->showLines() ) {
1153                     if ( !hdsItem->separatorLine ) {
1154                         hdsItem->separatorLine = new VerticalLineLayoutItem;
1155                     }
1156                     paintItems << hdsItem->separatorLine;
1157                     currentLine->addItem( hdsItem->separatorLine );
1158                     separatorUsed = true;
1159                 }
1160             }
1161         }
1162         // those have no parents in the current layout, so they wouldn't get cleaned up otherwise
1163         if ( !spacerUsed ) {
1164             delete hdsItem->spacer;
1165             hdsItem->spacer = nullptr;
1166         }
1167         if ( !separatorUsed ) {
1168             delete hdsItem->separatorLine;
1169             hdsItem->separatorLine = nullptr;
1170         }
1171 
1172         currentLine->addItem( hdsItem->markerLine );
1173         paintItems << hdsItem->markerLine;
1174         currentLine->addItem( hdsItem->label );
1175         paintItems << hdsItem->label;
1176     }
1177 }
1178 
1179 bool Legend::hasHeightForWidth() const
1180 {
1181     // this is better than using orientation() because, for layout purposes, we're not height-for-width
1182     // *yet* before buildLegend() has been called, and the layout logic might get upset if we say
1183     // something that will only be true in the future
1184     return !d->hLayoutDatasets.isEmpty();
1185 }
1186 
1187 int Legend::heightForWidth( int width ) const
1188 {
1189     if ( d->hLayoutDatasets.isEmpty() ) {
1190         return -1;
1191     }
1192 
1193     int ret = 0;
1194     // space for caption and line under caption (if any)
1195     for (int i = 0; i < 2; i++) {
1196         if ( QLayoutItem *item = d->layout->itemAtPosition( i, 0 ) ) {
1197             ret += item->sizeHint().height();
1198         }
1199     }
1200     const int separatorLineWidth = 3; // ### hardcoded in VerticalLineLayoutItem::sizeHint()
1201 
1202     int currentLineWidth = 0;
1203     int currentLineHeight = 0;
1204     for ( const HDatasetItem &hdsItem : d->hLayoutDatasets ) {
1205         const int payloadWidth = hdsItem.markerLine->sizeHint().width() +
1206                                  hdsItem.label->sizeHint().width();
1207         if ( !currentLineWidth ) {
1208             // first iteration
1209             currentLineWidth = payloadWidth;
1210         } else {
1211             const int separatorWidth = ( showLines() ? separatorLineWidth : 0 ) + spacing();
1212             currentLineWidth += separatorWidth + payloadWidth;
1213             if ( currentLineWidth > width ) {
1214                 // too wide, "line break"
1215 #ifdef DEBUG_LEGEND_PAINT
1216                 qDebug() << Q_FUNC_INFO << "heightForWidth break" << currentLineWidth
1217                          << currentLineWidth + separatorWidth + payloadWidth
1218                          << width;
1219 #endif
1220                 ret += currentLineHeight + spacing();
1221                 currentLineWidth = payloadWidth;
1222                 currentLineHeight = 0;
1223             }
1224         }
1225         currentLineHeight = qMax( currentLineHeight, hdsItem.height() );
1226     }
1227     ret += currentLineHeight; // one less spacings than lines
1228     return ret;
1229 }
1230 
1231 void Legend::Private::destroyOldLayout()
1232 {
1233     // in the horizontal layout case, the QHBoxLayout destructor also deletes child layout items
1234     // (it isn't documented that QLayoutItems delete their children)
1235     for ( int i = layout->count() - 1; i >= 0; i-- ) {
1236         delete layout->takeAt( i );
1237     }
1238     Q_ASSERT( !layout->count() );
1239     hLayoutDatasets.clear();
1240     paintItems.clear();
1241 }
1242 
1243 void Legend::setHiddenDatasets( const QList<uint> hiddenDatasets )
1244 {
1245     d->hiddenDatasets = hiddenDatasets;
1246 }
1247 
1248 const QList<uint> Legend::hiddenDatasets() const
1249 {
1250     return d->hiddenDatasets;
1251 }
1252 
1253 void Legend::setDatasetHidden( uint dataset, bool hidden )
1254 {
1255     if ( hidden && !d->hiddenDatasets.contains( dataset ) ) {
1256         d->hiddenDatasets.append( dataset );
1257     } else if ( !hidden && d->hiddenDatasets.contains( dataset ) ) {
1258         d->hiddenDatasets.removeAll( dataset );
1259     }
1260 }
1261 
1262 bool Legend::datasetIsHidden( uint dataset ) const
1263 {
1264     return d->hiddenDatasets.contains( dataset );
1265 }