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