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