1 /*
2  * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB.  All rights reserved.
3  *
4  * This file is part of the KD Chart library.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of
9  * the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
18  */
19 
20 #include "KChartLegend.h"
21 #include "KChartLegend_p.h"
22 #include <KChartTextAttributes.h>
23 #include <KChartMarkerAttributes.h>
24 #include <KChartPalette.h>
25 #include <KChartAbstractDiagram.h>
26 #include "KTextDocument.h"
27 #include <KChartDiagramObserver.h>
28 #include "KChartLayoutItems.h"
29 #include "KChartPrintingParameters.h"
30 
31 #include <QFont>
32 #include <QGridLayout>
33 #include <QPainter>
34 #include <QTextTableCell>
35 #include <QTextCursor>
36 #include <QTextCharFormat>
37 #include <QTextDocumentFragment>
38 #include <QTimer>
39 #include <QAbstractTextDocumentLayout>
40 #include <QtDebug>
41 #include <QLabel>
42 
43 using namespace KChart;
44 
Private()45 Legend::Private::Private() :
46     referenceArea( nullptr ),
47     position( Position::East ),
48     alignment( Qt::AlignCenter ),
49     textAlignment( Qt::AlignCenter ),
50     relativePosition( RelativePosition() ),
51     orientation( Qt::Vertical ),
52     order( Qt::AscendingOrder ),
53     showLines( false ),
54     titleText( QObject::tr( "Legend" ) ),
55     spacing( 1 ),
56     useAutomaticMarkerSize( true ),
57     legendStyle( MarkersOnly )
58 {
59     // By default we specify a simple, hard point as the 'relative' position's ref. point,
60     // since we can not be sure that there will be any parent specified for the legend.
61     relativePosition.setReferencePoints( PositionPoints( QPointF( 0.0, 0.0 ) ) );
62     relativePosition.setReferencePosition( Position::NorthWest );
63     relativePosition.setAlignment( Qt::AlignTop | Qt::AlignLeft );
64     relativePosition.setHorizontalPadding( Measure( 4.0, KChartEnums::MeasureCalculationModeAbsolute ) );
65     relativePosition.setVerticalPadding( Measure( 4.0, KChartEnums::MeasureCalculationModeAbsolute ) );
66 }
67 
~Private()68 Legend::Private::~Private()
69 {
70     // this bloc left empty intentionally
71 }
72 
73 
74 #define d d_func()
75 
76 
Legend(QWidget * parent)77 Legend::Legend( QWidget* parent ) :
78     AbstractAreaWidget( new Private(), parent )
79 {
80     d->referenceArea = parent;
81     init();
82 }
83 
Legend(AbstractDiagram * diagram,QWidget * parent)84 Legend::Legend( AbstractDiagram* diagram, QWidget* parent ) :
85     AbstractAreaWidget( new Private(), parent )
86 {
87     d->referenceArea = parent;
88     init();
89     setDiagram( diagram );
90 }
91 
~Legend()92 Legend::~Legend()
93 {
94     Q_EMIT destroyedLegend( this );
95 }
96 
init()97 void Legend::init()
98 {
99     setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
100 
101     d->layout = new QGridLayout( this );
102     d->layout->setContentsMargins( 2, 2, 2, 2 );
103     d->layout->setSpacing( d->spacing );
104 
105     const Measure normalFontSizeTitle( 12, KChartEnums::MeasureCalculationModeAbsolute );
106     const Measure normalFontSizeLabels( 10, KChartEnums::MeasureCalculationModeAbsolute );
107     const Measure minimalFontSize( 4, KChartEnums::MeasureCalculationModeAbsolute );
108 
109     TextAttributes textAttrs;
110     textAttrs.setPen( QPen( Qt::black ) );
111     textAttrs.setFont( QFont( QLatin1String( "helvetica" ), 10, QFont::Normal, false ) );
112     textAttrs.setFontSize( normalFontSizeLabels );
113     textAttrs.setMinimalFontSize( minimalFontSize );
114     setTextAttributes( textAttrs );
115 
116     TextAttributes titleTextAttrs;
117     titleTextAttrs.setPen( QPen( Qt::black ) );
118     titleTextAttrs.setFont( QFont( QLatin1String( "helvetica" ), 12, QFont::Bold, false ) );
119     titleTextAttrs.setFontSize( normalFontSizeTitle );
120     titleTextAttrs.setMinimalFontSize( minimalFontSize );
121     setTitleTextAttributes( titleTextAttrs );
122 
123     FrameAttributes frameAttrs;
124     frameAttrs.setVisible( true );
125     frameAttrs.setPen( QPen( Qt::black ) );
126     frameAttrs.setPadding( 1 );
127     setFrameAttributes( frameAttrs );
128 
129     d->position = Position::NorthEast;
130     d->alignment = Qt::AlignCenter;
131 }
132 
133 
minimumSizeHint() const134 QSize Legend::minimumSizeHint() const
135 {
136     return sizeHint();
137 }
138 
139 //#define DEBUG_LEGEND_PAINT
140 
sizeHint() const141 QSize Legend::sizeHint() const
142 {
143 #ifdef DEBUG_LEGEND_PAINT
144     qDebug()  << "Legend::sizeHint() started";
145 #endif
146     Q_FOREACH( AbstractLayoutItem* paintItem, d->paintItems ) {
147         paintItem->sizeHint();
148     }
149     return AbstractAreaWidget::sizeHint();
150 }
151 
needSizeHint()152 void Legend::needSizeHint()
153 {
154     buildLegend();
155 }
156 
resizeLayout(const QSize & size)157 void Legend::resizeLayout( const QSize& size )
158 {
159 #ifdef DEBUG_LEGEND_PAINT
160     qDebug() << "Legend::resizeLayout started";
161 #endif
162     if ( d->layout ) {
163         d->reflowHDatasetItems( this );
164         d->layout->setGeometry( QRect(QPoint( 0,0 ), size) );
165         activateTheLayout();
166     }
167 #ifdef DEBUG_LEGEND_PAINT
168     qDebug() << "Legend::resizeLayout done";
169 #endif
170 }
171 
activateTheLayout()172 void Legend::activateTheLayout()
173 {
174     if ( d->layout && d->layout->parent() ) {
175         d->layout->activate();
176     }
177 }
178 
setLegendStyle(LegendStyle style)179 void Legend::setLegendStyle( LegendStyle style )
180 {
181     if ( d->legendStyle == style ) {
182         return;
183     }
184     d->legendStyle = style;
185     setNeedRebuild();
186 }
187 
legendStyle() const188 Legend::LegendStyle Legend::legendStyle() const
189 {
190     return d->legendStyle;
191 }
192 
clone() const193 Legend* Legend::clone() const
194 {
195     Legend* legend = new Legend( new Private( *d ), nullptr );
196     legend->setTextAttributes( textAttributes() );
197     legend->setTitleTextAttributes( titleTextAttributes() );
198     legend->setFrameAttributes( frameAttributes() );
199     legend->setUseAutomaticMarkerSize( useAutomaticMarkerSize() );
200     legend->setPosition( position() );
201     legend->setAlignment( alignment() );
202     legend->setTextAlignment( textAlignment() );
203     legend->setLegendStyle( legendStyle() );
204     return legend;
205 }
206 
207 
compare(const Legend * other) const208 bool Legend::compare( const Legend* other ) const
209 {
210     if ( other == this ) {
211         return true;
212     }
213     if ( !other ) {
214         return false;
215     }
216 
217     return  ( AbstractAreaBase::compare( other ) ) &&
218             (isVisible()              == other->isVisible()) &&
219             (position()               == other->position()) &&
220             (alignment()              == other->alignment())&&
221             (textAlignment()          == other->textAlignment())&&
222             (floatingPosition()       == other->floatingPosition()) &&
223             (orientation()            == other->orientation())&&
224             (showLines()              == other->showLines())&&
225             (texts()                  == other->texts())&&
226             (brushes()                == other->brushes())&&
227             (pens()                   == other->pens())&&
228             (markerAttributes()       == other->markerAttributes())&&
229             (useAutomaticMarkerSize() == other->useAutomaticMarkerSize()) &&
230             (textAttributes()         == other->textAttributes()) &&
231             (titleText()              == other->titleText())&&
232             (titleTextAttributes()    == other->titleTextAttributes()) &&
233             (spacing()                == other->spacing()) &&
234             (legendStyle()            == other->legendStyle());
235 }
236 
237 
paint(QPainter * painter)238 void Legend::paint( QPainter* painter )
239 {
240 #ifdef DEBUG_LEGEND_PAINT
241     qDebug() << "entering Legend::paint( QPainter* painter )";
242 #endif
243     if ( !diagram() ) {
244         return;
245     }
246 
247     activateTheLayout();
248 
249     Q_FOREACH( AbstractLayoutItem* paintItem, d->paintItems ) {
250         paintItem->paint( painter );
251     }
252 
253 #ifdef DEBUG_LEGEND_PAINT
254     qDebug() << "leaving Legend::paint( QPainter* painter )";
255 #endif
256 }
257 
paint(QPainter * painter,const QRect & rect)258 void Legend::paint( QPainter *painter, const QRect& rect )
259 {
260     if ( rect.isEmpty() ) {
261         return;
262     }
263     // set up the contents of the widget so we get a useful geometry
264     QPaintDevice* prevDevice = GlobalMeasureScaling::paintDevice();
265     GlobalMeasureScaling::setPaintDevice( painter->device() );
266 
267     const QRect oldGeometry(geometry() );
268     const QRect newGeo( QPoint(0,0), rect.size() );
269     if (oldGeometry != newGeo) {
270         setGeometry(newGeo);
271         needSizeHint();
272     }
273     painter->translate( rect.left(), rect.top() );
274     paintAll( *painter );
275     painter->translate( -rect.left(), -rect.top() );
276 
277     if (oldGeometry != newGeo) {
278         setGeometry(oldGeometry);
279     }
280     GlobalMeasureScaling::setPaintDevice( prevDevice );
281 }
282 
datasetCount() const283 uint Legend::datasetCount() const
284 {
285     int modelLabelsCount = 0;
286     Q_FOREACH ( DiagramObserver* observer, d->observers ) {
287         AbstractDiagram* diagram = observer->diagram();
288         Q_ASSERT( diagram->datasetLabels().count() == diagram->datasetBrushes().count() );
289         modelLabelsCount += diagram->datasetLabels().count();
290     }
291     return modelLabelsCount;
292 }
293 
294 
setReferenceArea(const QWidget * area)295 void Legend::setReferenceArea( const QWidget* area )
296 {
297     if ( area == d->referenceArea ) {
298         return;
299     }
300     d->referenceArea = area;
301     setNeedRebuild();
302 }
303 
referenceArea() const304 const QWidget* Legend::referenceArea() const
305 {
306     return d->referenceArea ? d->referenceArea : qobject_cast< const QWidget* >( parent() );
307 }
308 
309 
diagram() const310 AbstractDiagram* Legend::diagram() const
311 {
312     if ( d->observers.isEmpty() ) {
313         return nullptr;
314     }
315     return d->observers.first()->diagram();
316 }
317 
diagrams() const318 DiagramList Legend::diagrams() const
319 {
320     DiagramList list;
321     for ( int i = 0; i < d->observers.size(); ++i ) {
322         list << d->observers.at(i)->diagram();
323     }
324     return list;
325 }
326 
constDiagrams() const327 ConstDiagramList Legend::constDiagrams() const
328 {
329     ConstDiagramList list;
330     for ( int i = 0; i < d->observers.size(); ++i ) {
331         list << d->observers.at(i)->diagram();
332     }
333     return list;
334 }
335 
addDiagram(AbstractDiagram * newDiagram)336 void Legend::addDiagram( AbstractDiagram* newDiagram )
337 {
338     if ( newDiagram ) {
339         DiagramObserver* observer = new DiagramObserver( newDiagram, this );
340 
341         DiagramObserver* oldObs = d->findObserverForDiagram( newDiagram );
342         if ( oldObs ) {
343             delete oldObs;
344             d->observers[ d->observers.indexOf( oldObs ) ] = observer;
345         } else {
346             d->observers.append( observer );
347         }
348         connect( observer, SIGNAL(diagramAboutToBeDestroyed(AbstractDiagram*)),
349                  SLOT(resetDiagram(AbstractDiagram*)));
350         connect( observer, SIGNAL(diagramDataChanged(AbstractDiagram*)),
351                  SLOT(setNeedRebuild()));
352         connect( observer, SIGNAL(diagramDataHidden(AbstractDiagram*)),
353                  SLOT(setNeedRebuild()));
354         connect( observer, SIGNAL(diagramAttributesChanged(AbstractDiagram*)),
355                  SLOT(setNeedRebuild()));
356         setNeedRebuild();
357     }
358 }
359 
removeDiagram(AbstractDiagram * oldDiagram)360 void Legend::removeDiagram( AbstractDiagram* oldDiagram )
361 {
362     int datasetBrushOffset = 0;
363     QList< AbstractDiagram * > diagrams = this->diagrams();
364     for ( int i = 0; i <diagrams.count(); i++ ) {
365         if ( diagrams.at( i ) == oldDiagram ) {
366             for ( int i = 0; i < oldDiagram->datasetBrushes().count(); i++ ) {
367                 d->brushes.remove(datasetBrushOffset + i);
368                 d->texts.remove(datasetBrushOffset + i);
369             }
370             for ( int i = 0; i < oldDiagram->datasetPens().count(); i++ ) {
371                 d->pens.remove(datasetBrushOffset + i);
372             }
373             break;
374         }
375         datasetBrushOffset += diagrams.at(i)->datasetBrushes().count();
376     }
377 
378     if ( oldDiagram ) {
379         DiagramObserver *oldObs = d->findObserverForDiagram( oldDiagram );
380         if ( oldObs ) {
381             delete oldObs;
382             d->observers.removeAt( d->observers.indexOf( oldObs ) );
383         }
384         setNeedRebuild();
385     }
386 }
387 
removeDiagrams()388 void Legend::removeDiagrams()
389 {
390     // removeDiagram() may change the d->observers list. So, build up the list of
391     // diagrams to remove first and then remove them one by one.
392     QList< AbstractDiagram * > diagrams;
393     for ( int i = 0; i < d->observers.size(); ++i ) {
394         diagrams.append( d->observers.at( i )->diagram() );
395     }
396     for ( int i = 0; i < diagrams.count(); ++i ) {
397         removeDiagram( diagrams[ i ] );
398     }
399 }
400 
replaceDiagram(AbstractDiagram * newDiagram,AbstractDiagram * oldDiagram)401 void Legend::replaceDiagram( AbstractDiagram* newDiagram,
402                              AbstractDiagram* oldDiagram )
403 {
404     AbstractDiagram* old = oldDiagram;
405     if ( !d->observers.isEmpty() && !old ) {
406         old = d->observers.first()->diagram();
407         if ( !old ) {
408             d->observers.removeFirst(); // first entry had a 0 diagram
409         }
410     }
411     if ( old ) {
412         removeDiagram( old );
413     }
414     if ( newDiagram ) {
415         addDiagram( newDiagram );
416     }
417 }
418 
dataSetOffset(AbstractDiagram * diagram)419 uint Legend::dataSetOffset( AbstractDiagram* diagram )
420 {
421     uint offset = 0;
422 
423     for ( int i = 0; i < d->observers.count(); ++i ) {
424         if ( d->observers.at(i)->diagram() == diagram ) {
425             return offset;
426         }
427         AbstractDiagram* diagram = d->observers.at(i)->diagram();
428         if ( !diagram->model() ) {
429             continue;
430         }
431         offset = offset + diagram->model()->columnCount();
432     }
433 
434     return offset;
435 }
436 
setDiagram(AbstractDiagram * newDiagram)437 void Legend::setDiagram( AbstractDiagram* newDiagram )
438 {
439     replaceDiagram( newDiagram );
440 }
441 
resetDiagram(AbstractDiagram * oldDiagram)442 void Legend::resetDiagram( AbstractDiagram* oldDiagram )
443 {
444     removeDiagram( oldDiagram );
445 }
446 
setVisible(bool visible)447 void Legend::setVisible( bool visible )
448 {
449     // do NOT bail out if visible == isVisible(), because the return value of isVisible() also depends
450     // on the visibility of the parent.
451     QWidget::setVisible( visible );
452     emitPositionChanged();
453 }
454 
setNeedRebuild()455 void Legend::setNeedRebuild()
456 {
457     buildLegend();
458     sizeHint();
459 }
460 
setPosition(Position position)461 void Legend::setPosition( Position position )
462 {
463     if ( d->position == position ) {
464         return;
465     }
466     d->position = position;
467     emitPositionChanged();
468 }
469 
emitPositionChanged()470 void Legend::emitPositionChanged()
471 {
472     Q_EMIT positionChanged( this );
473     Q_EMIT propertiesChanged();
474 }
475 
476 
position() const477 Position Legend::position() const
478 {
479     return d->position;
480 }
481 
setAlignment(Qt::Alignment alignment)482 void Legend::setAlignment( Qt::Alignment alignment )
483 {
484     if ( d->alignment == alignment ) {
485         return;
486     }
487     d->alignment = alignment;
488     emitPositionChanged();
489 }
490 
alignment() const491 Qt::Alignment Legend::alignment() const
492 {
493     return d->alignment;
494 }
495 
setTextAlignment(Qt::Alignment alignment)496 void Legend::setTextAlignment( Qt::Alignment alignment )
497 {
498     if ( d->textAlignment == alignment ) {
499         return;
500     }
501     d->textAlignment = alignment;
502     emitPositionChanged();
503 }
504 
textAlignment() const505 Qt::Alignment Legend::textAlignment() const
506 {
507     return d->textAlignment;
508 }
509 
setLegendSymbolAlignment(Qt::Alignment alignment)510 void Legend::setLegendSymbolAlignment( Qt::Alignment alignment )
511 {
512     if ( d->legendLineSymbolAlignment == alignment ) {
513         return;
514     }
515     d->legendLineSymbolAlignment = alignment;
516     emitPositionChanged();
517 }
518 
legendSymbolAlignment() const519 Qt::Alignment Legend::legendSymbolAlignment() const
520 {
521     return d->legendLineSymbolAlignment ;
522 }
523 
setFloatingPosition(const RelativePosition & relativePosition)524 void Legend::setFloatingPosition( const RelativePosition& relativePosition )
525 {
526     d->position = Position::Floating;
527     if ( d->relativePosition != relativePosition ) {
528         d->relativePosition  = relativePosition;
529         emitPositionChanged();
530     }
531 }
532 
floatingPosition() const533 const RelativePosition Legend::floatingPosition() const
534 {
535     return d->relativePosition;
536 }
537 
setOrientation(Qt::Orientation orientation)538 void Legend::setOrientation( Qt::Orientation orientation )
539 {
540     if ( d->orientation == orientation ) {
541         return;
542     }
543     d->orientation = orientation;
544     setNeedRebuild();
545     emitPositionChanged();
546 }
547 
orientation() const548 Qt::Orientation Legend::orientation() const
549 {
550     return d->orientation;
551 }
552 
setSortOrder(Qt::SortOrder order)553 void Legend::setSortOrder( Qt::SortOrder order )
554 {
555     if ( d->order == order ) {
556         return;
557     }
558     d->order = order;
559     setNeedRebuild();
560     emitPositionChanged();
561 }
562 
sortOrder() const563 Qt::SortOrder Legend::sortOrder() const
564 {
565     return d->order;
566 }
567 
setShowLines(bool legendShowLines)568 void Legend::setShowLines( bool legendShowLines )
569 {
570     if ( d->showLines == legendShowLines ) {
571         return;
572     }
573     d->showLines = legendShowLines;
574     setNeedRebuild();
575     emitPositionChanged();
576 }
577 
showLines() const578 bool Legend::showLines() const
579 {
580     return d->showLines;
581 }
582 
setUseAutomaticMarkerSize(bool useAutomaticMarkerSize)583 void Legend::setUseAutomaticMarkerSize( bool useAutomaticMarkerSize )
584 {
585     d->useAutomaticMarkerSize = useAutomaticMarkerSize;
586     setNeedRebuild();
587     emitPositionChanged();
588 }
589 
useAutomaticMarkerSize() const590 bool Legend::useAutomaticMarkerSize() const
591 {
592     return d->useAutomaticMarkerSize;
593 }
594 
resetTexts()595 void Legend::resetTexts()
596 {
597     if ( !d->texts.count() ) {
598         return;
599     }
600     d->texts.clear();
601     setNeedRebuild();
602 }
603 
setText(uint dataset,const QString & text)604 void Legend::setText( uint dataset, const QString& text )
605 {
606     if ( d->texts[ dataset ] == text ) {
607         return;
608     }
609     d->texts[ dataset ] = text;
610     setNeedRebuild();
611 }
612 
text(uint dataset) const613 QString Legend::text( uint dataset ) const
614 {
615     if ( d->texts.find( dataset ) != d->texts.end() ) {
616         return d->texts[ dataset ];
617     } else {
618         return d->modelLabels[ dataset ];
619     }
620 }
621 
texts() const622 const QMap<uint,QString> Legend::texts() const
623 {
624     return d->texts;
625 }
626 
setColor(uint dataset,const QColor & color)627 void Legend::setColor( uint dataset, const QColor& color )
628 {
629     if ( d->brushes[ dataset ] != color ) {
630         d->brushes[ dataset ] = color;
631         setNeedRebuild();
632         update();
633     }
634 }
635 
setBrush(uint dataset,const QBrush & brush)636 void Legend::setBrush( uint dataset, const QBrush& brush )
637 {
638     if ( d->brushes[ dataset ] != brush ) {
639         d->brushes[ dataset ] = brush;
640         setNeedRebuild();
641         update();
642     }
643 }
644 
brush(uint dataset) const645 QBrush Legend::brush( uint dataset ) const
646 {
647     if ( d->brushes.contains( dataset ) ) {
648         return d->brushes[ dataset ];
649     } else {
650         return d->modelBrushes[ dataset ];
651     }
652 }
653 
brushes() const654 const QMap<uint,QBrush> Legend::brushes() const
655 {
656     return d->brushes;
657 }
658 
659 
setBrushesFromDiagram(AbstractDiagram * diagram)660 void Legend::setBrushesFromDiagram( AbstractDiagram* diagram )
661 {
662     bool changed = false;
663     QList<QBrush> datasetBrushes = diagram->datasetBrushes();
664     for ( int i = 0; i < datasetBrushes.count(); i++ ) {
665         if ( d->brushes[ i ] != datasetBrushes[ i ] ) {
666             d->brushes[ i ]  = datasetBrushes[ i ];
667             changed = true;
668         }
669     }
670     if ( changed ) {
671         setNeedRebuild();
672         update();
673     }
674 }
675 
676 
setPen(uint dataset,const QPen & pen)677 void Legend::setPen( uint dataset, const QPen& pen )
678 {
679     if ( d->pens[dataset] == pen ) {
680         return;
681     }
682     d->pens[dataset] = pen;
683     setNeedRebuild();
684     update();
685 }
686 
pen(uint dataset) const687 QPen Legend::pen( uint dataset ) const
688 {
689     if ( d->pens.find( dataset ) != d->pens.end() ) {
690         return d->pens[ dataset ];
691     } else {
692         return d->modelPens[ dataset ];
693     }
694 }
695 
pens() const696 const QMap<uint,QPen> Legend::pens() const
697 {
698     return d->pens;
699 }
700 
701 
setMarkerAttributes(uint dataset,const MarkerAttributes & markerAttributes)702 void Legend::setMarkerAttributes( uint dataset, const MarkerAttributes& markerAttributes )
703 {
704     if ( d->markerAttributes[dataset] == markerAttributes ) {
705         return;
706     }
707     d->markerAttributes[ dataset ] = markerAttributes;
708     setNeedRebuild();
709     update();
710 }
711 
markerAttributes(uint dataset) const712 MarkerAttributes Legend::markerAttributes( uint dataset ) const
713 {
714     if ( d->markerAttributes.find( dataset ) != d->markerAttributes.end() ) {
715         return d->markerAttributes[ dataset ];
716     } else if ( static_cast<uint>( d->modelMarkers.count() ) > dataset ) {
717         return d->modelMarkers[ dataset ];
718     } else {
719         return MarkerAttributes();
720     }
721 }
722 
markerAttributes() const723 const QMap<uint, MarkerAttributes> Legend::markerAttributes() const
724 {
725     return d->markerAttributes;
726 }
727 
728 
setTextAttributes(const TextAttributes & a)729 void Legend::setTextAttributes( const TextAttributes &a )
730 {
731     if ( d->textAttributes == a ) {
732         return;
733     }
734     d->textAttributes = a;
735     setNeedRebuild();
736 }
737 
textAttributes() const738 TextAttributes Legend::textAttributes() const
739 {
740     return d->textAttributes;
741 }
742 
setTitleText(const QString & text)743 void Legend::setTitleText( const QString& text )
744 {
745     if ( d->titleText == text ) {
746         return;
747     }
748     d->titleText = text;
749     setNeedRebuild();
750 }
751 
titleText() const752 QString Legend::titleText() const
753 {
754     return d->titleText;
755 }
756 
setTitleTextAttributes(const TextAttributes & a)757 void Legend::setTitleTextAttributes( const TextAttributes &a )
758 {
759     if ( d->titleTextAttributes == a ) {
760         return;
761     }
762     d->titleTextAttributes = a;
763     setNeedRebuild();
764 }
765 
titleTextAttributes() const766 TextAttributes Legend::titleTextAttributes() const
767 {
768     return d->titleTextAttributes;
769 }
770 
forceRebuild()771 void Legend::forceRebuild()
772 {
773 #ifdef DEBUG_LEGEND_PAINT
774     qDebug() << "entering Legend::forceRebuild()";
775 #endif
776     buildLegend();
777 #ifdef DEBUG_LEGEND_PAINT
778     qDebug() << "leaving Legend::forceRebuild()";
779 #endif
780 }
781 
setSpacing(uint space)782 void Legend::setSpacing( uint space )
783 {
784     if ( d->spacing == space && d->layout->spacing() == int( space ) ) {
785         return;
786     }
787     d->spacing = space;
788     d->layout->setSpacing( space );
789     setNeedRebuild();
790 }
791 
spacing() const792 uint Legend::spacing() const
793 {
794     return d->spacing;
795 }
796 
setDefaultColors()797 void Legend::setDefaultColors()
798 {
799     Palette pal = Palette::defaultPalette();
800     for ( int i = 0; i < pal.size(); i++ ) {
801         setBrush( i, pal.getBrush( i ) );
802     }
803 }
804 
setRainbowColors()805 void Legend::setRainbowColors()
806 {
807     Palette pal = Palette::rainbowPalette();
808     for ( int i = 0; i < pal.size(); i++ ) {
809         setBrush( i, pal.getBrush( i ) );
810     }
811 }
812 
setSubduedColors(bool ordered)813 void Legend::setSubduedColors( bool ordered )
814 {
815     Palette pal = Palette::subduedPalette();
816     if ( ordered ) {
817         for ( int i = 0; i < pal.size(); i++ ) {
818             setBrush( i, pal.getBrush( i ) );
819         }
820     } else {
821         static const int s_subduedColorsCount = 18;
822         Q_ASSERT( pal.size() >= s_subduedColorsCount );
823         static const int order[ s_subduedColorsCount ] = {
824             0, 5, 10, 15, 2, 7, 12, 17, 4,
825             9, 14, 1, 6, 11, 16, 3, 8, 13
826         };
827         for ( int i = 0; i < s_subduedColorsCount; i++ ) {
828             setBrush( i, pal.getBrush( order[i] ) );
829         }
830     }
831 }
832 
resizeEvent(QResizeEvent * event)833 void Legend::resizeEvent( QResizeEvent * event )
834 {
835     Q_UNUSED( event );
836 #ifdef DEBUG_LEGEND_PAINT
837     qDebug() << "Legend::resizeEvent() called";
838 #endif
839     forceRebuild();
840     sizeHint();
841     QTimer::singleShot( 0, this, SLOT(emitPositionChanged()) );
842 }
843 
fetchPaintOptions(Legend * q)844 void Legend::Private::fetchPaintOptions( Legend *q )
845 {
846     modelLabels.clear();
847     modelBrushes.clear();
848     modelPens.clear();
849     modelMarkers.clear();
850     // retrieve the diagrams' settings for all non-hidden datasets
851     for ( int i = 0; i < observers.size(); ++i ) {
852         const AbstractDiagram* diagram = observers.at( i )->diagram();
853         if ( !diagram ) {
854             continue;
855         }
856         const QStringList diagramLabels = diagram->datasetLabels();
857         const QList<QBrush> diagramBrushes = diagram->datasetBrushes();
858         const QList<QPen> diagramPens = diagram->datasetPens();
859         const QList<MarkerAttributes> diagramMarkers = diagram->datasetMarkers();
860 
861         const bool ascend = q->sortOrder() == Qt::AscendingOrder;
862         int dataset = ascend ? 0 : diagramLabels.count() - 1;
863         const int end = ascend ? diagramLabels.count() : -1;
864         for ( ; dataset != end; dataset += ascend ? 1 : -1 ) {
865             if ( diagram->isHidden( dataset ) || q->datasetIsHidden( dataset ) ) {
866                 continue;
867             }
868             modelLabels += diagramLabels[ dataset ];
869             modelBrushes += diagramBrushes[ dataset ];
870             modelPens += diagramPens[ dataset ];
871             modelMarkers += diagramMarkers[ dataset ];
872         }
873     }
874 
875     Q_ASSERT( modelLabels.count() == modelBrushes.count() );
876 }
877 
markerSize(Legend * q,int dataset,qreal fontHeight) const878 QSizeF Legend::Private::markerSize( Legend *q, int dataset, qreal fontHeight ) const
879 {
880     QSizeF suppliedSize = q->markerAttributes( dataset ).markerSize();
881     if ( q->useAutomaticMarkerSize() || !suppliedSize.isValid() ) {
882         return QSizeF( fontHeight, fontHeight );
883     } else {
884         return suppliedSize;
885     }
886 }
887 
maxMarkerSize(Legend * q,qreal fontHeight) const888 QSizeF Legend::Private::maxMarkerSize( Legend *q, qreal fontHeight ) const
889 {
890     QSizeF ret( 1.0, 1.0 );
891     if ( q->legendStyle() != LinesOnly ) {
892         for ( int dataset = 0; dataset < modelLabels.count(); ++dataset ) {
893             ret = ret.expandedTo( markerSize( q, dataset, fontHeight ) );
894         }
895     }
896     return ret;
897 }
898 
HDatasetItem()899 HDatasetItem::HDatasetItem()
900    : markerLine(nullptr),
901      label(nullptr),
902      separatorLine(nullptr),
903      spacer(nullptr)
904 {}
905 
updateToplevelLayout(QWidget * w)906 static void updateToplevelLayout(QWidget *w)
907 {
908     while ( w ) {
909         if ( w->isTopLevel() ) {
910             // The null check has proved necessary during destruction of the Legend / Chart
911             if ( w->layout() ) {
912                 w->layout()->update();
913             }
914             break;
915         } else {
916             w = qobject_cast< QWidget * >( w->parent() );
917             Q_ASSERT( w );
918         }
919     }
920 }
921 
buildLegend()922 void Legend::buildLegend()
923 {
924     /* Grid layout partitioning (horizontal orientation): row zero is the title, row one the divider
925        line between title and dataset items, row two for each item: line, marker, text label and separator
926        line in that order.
927        In a vertically oriented legend, row pairs (2, 3), ... contain a possible separator line (first row)
928        and (second row) line, marker, text label each. */
929     d->destroyOldLayout();
930 
931     if ( orientation() == Qt::Vertical ) {
932         d->layout->setColumnStretch( 6, 1 );
933     } else {
934         d->layout->setColumnStretch( 6, 0 );
935     }
936 
937     d->fetchPaintOptions( this );
938 
939     const KChartEnums::MeasureOrientation measureOrientation =
940         orientation() == Qt::Vertical ? KChartEnums::MeasureOrientationMinimum
941                                       : KChartEnums::MeasureOrientationHorizontal;
942 
943     // legend caption
944     if ( !titleText().isEmpty() && titleTextAttributes().isVisible() ) {
945         TextLayoutItem* titleItem =
946             new TextLayoutItem( titleText(), titleTextAttributes(), referenceArea(),
947                                          measureOrientation, d->textAlignment );
948         titleItem->setParentWidget( this );
949 
950         d->paintItems << titleItem;
951         d->layout->addItem( titleItem, 0, 0, 1, 5, Qt::AlignCenter );
952 
953         // The line between the title and the legend items, if any.
954         if ( showLines() && d->modelLabels.count() ) {
955             HorizontalLineLayoutItem* lineItem = new HorizontalLineLayoutItem;
956             d->paintItems << lineItem;
957             d->layout->addItem( lineItem, 1, 0, 1, 5, Qt::AlignCenter );
958         }
959     }
960 
961     qreal fontHeight = textAttributes().calculatedFontSize( referenceArea(), measureOrientation );
962     {
963         QFont tmpFont = textAttributes().font();
964         tmpFont.setPointSizeF( fontHeight );
965         if ( GlobalMeasureScaling::paintDevice() ) {
966             fontHeight = QFontMetricsF( tmpFont, GlobalMeasureScaling::paintDevice() ).height();
967         } else {
968             fontHeight = QFontMetricsF( tmpFont ).height();
969         }
970     }
971 
972     const QSizeF maxMarkerSize = d->maxMarkerSize( this, fontHeight );
973 
974     // If we show a marker on a line, we paint it after 8 pixels
975     // of the line have been painted. This allows to see the line style
976     // at the right side of the marker without the line needing to
977     // be too long.
978     // (having the marker in the middle of the line would require longer lines)
979     const int lineLengthLeftOfMarker = 8;
980 
981     int maxLineLength = 18;
982     {
983         bool hasComplexPenStyle = false;
984         for ( int dataset = 0; dataset < d->modelLabels.count(); ++dataset ) {
985             const QPen pn = pen( dataset );
986             const Qt::PenStyle ps = pn.style();
987             if ( ps != Qt::NoPen ) {
988                 maxLineLength = qMin( pn.width() * 18, maxLineLength );
989                 if ( ps != Qt::SolidLine ) {
990                     hasComplexPenStyle = true;
991                 }
992             }
993         }
994         if ( legendStyle() != LinesOnly ) {
995             if ( hasComplexPenStyle )
996               maxLineLength += lineLengthLeftOfMarker;
997             maxLineLength += int( maxMarkerSize.width() );
998         }
999     }
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 
height() const1078 int HDatasetItem::height() const
1079 {
1080     return qMax( markerLine->sizeHint().height(), label->sizeHint().height() );
1081 }
1082 
reflowHDatasetItems(Legend * q)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
flowHDatasetItems(Legend * q)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 
hasHeightForWidth() const1190 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 
heightForWidth(int width) const1198 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 
destroyOldLayout()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 
setHiddenDatasets(const QList<uint> hiddenDatasets)1254 void Legend::setHiddenDatasets( const QList<uint> hiddenDatasets )
1255 {
1256     d->hiddenDatasets = hiddenDatasets;
1257 }
1258 
hiddenDatasets() const1259 const QList<uint> Legend::hiddenDatasets() const
1260 {
1261     return d->hiddenDatasets;
1262 }
1263 
setDatasetHidden(uint dataset,bool hidden)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 
datasetIsHidden(uint dataset) const1273 bool Legend::datasetIsHidden( uint dataset ) const
1274 {
1275     return d->hiddenDatasets.contains( dataset );
1276 }
1277