File indexing completed on 2024-05-12 15:54:17

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