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