diff --git a/src/KChart/KChartLegend.cpp b/src/KChart/KChartLegend.cpp
index 1abed71..1da818f 100644
--- a/src/KChart/KChartLegend.cpp
+++ b/src/KChart/KChartLegend.cpp
@@ -1,1249 +1,1251 @@
/**
* Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved.
*
* This file is part of the KD Chart library.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "KChartLegend.h"
#include "KChartLegend_p.h"
#include
#include
#include
#include
#include "KTextDocument.h"
#include
#include "KChartLayoutItems.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace KChart;
Legend::Private::Private() :
referenceArea( 0 ),
position( Position::East ),
alignment( Qt::AlignCenter ),
textAlignment( Qt::AlignCenter ),
relativePosition( RelativePosition() ),
orientation( Qt::Vertical ),
order( Qt::AscendingOrder ),
showLines( false ),
titleText( QObject::tr( "Legend" ) ),
spacing( 1 ),
useAutomaticMarkerSize( true ),
legendStyle( MarkersOnly )
{
// By default we specify a simple, hard point as the 'relative' position's ref. point,
// since we can not be sure that there will be any parent specified for the legend.
relativePosition.setReferencePoints( PositionPoints( QPointF( 0.0, 0.0 ) ) );
relativePosition.setReferencePosition( Position::NorthWest );
relativePosition.setAlignment( Qt::AlignTop | Qt::AlignLeft );
relativePosition.setHorizontalPadding( Measure( 4.0, KChartEnums::MeasureCalculationModeAbsolute ) );
relativePosition.setVerticalPadding( Measure( 4.0, KChartEnums::MeasureCalculationModeAbsolute ) );
}
Legend::Private::~Private()
{
// this bloc left empty intentionally
}
#define d d_func()
Legend::Legend( QWidget* parent ) :
AbstractAreaWidget( new Private(), parent )
{
d->referenceArea = parent;
init();
}
Legend::Legend( AbstractDiagram* diagram, QWidget* parent ) :
AbstractAreaWidget( new Private(), parent )
{
d->referenceArea = parent;
init();
setDiagram( diagram );
}
Legend::~Legend()
{
emit destroyedLegend( this );
}
void Legend::init()
{
setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
d->layout = new QGridLayout( this );
d->layout->setMargin( 2 );
d->layout->setSpacing( d->spacing );
const Measure normalFontSizeTitle( 12, KChartEnums::MeasureCalculationModeAbsolute );
const Measure normalFontSizeLabels( 10, KChartEnums::MeasureCalculationModeAbsolute );
const Measure minimalFontSize( 4, KChartEnums::MeasureCalculationModeAbsolute );
TextAttributes textAttrs;
textAttrs.setPen( QPen( Qt::black ) );
textAttrs.setFont( QFont( QLatin1String( "helvetica" ), 10, QFont::Normal, false ) );
textAttrs.setFontSize( normalFontSizeLabels );
textAttrs.setMinimalFontSize( minimalFontSize );
setTextAttributes( textAttrs );
TextAttributes titleTextAttrs;
titleTextAttrs.setPen( QPen( Qt::black ) );
titleTextAttrs.setFont( QFont( QLatin1String( "helvetica" ), 12, QFont::Bold, false ) );
titleTextAttrs.setFontSize( normalFontSizeTitle );
titleTextAttrs.setMinimalFontSize( minimalFontSize );
setTitleTextAttributes( titleTextAttrs );
FrameAttributes frameAttrs;
frameAttrs.setVisible( true );
frameAttrs.setPen( QPen( Qt::black ) );
frameAttrs.setPadding( 1 );
setFrameAttributes( frameAttrs );
d->position = Position::NorthEast;
d->alignment = Qt::AlignCenter;
}
QSize Legend::minimumSizeHint() const
{
return sizeHint();
}
//#define DEBUG_LEGEND_PAINT
QSize Legend::sizeHint() const
{
#ifdef DEBUG_LEGEND_PAINT
qDebug() << "Legend::sizeHint() started";
#endif
Q_FOREACH( AbstractLayoutItem* paintItem, d->paintItems ) {
paintItem->sizeHint();
}
return AbstractAreaWidget::sizeHint();
}
void Legend::needSizeHint()
{
buildLegend();
}
void Legend::resizeLayout( const QSize& size )
{
#ifdef DEBUG_LEGEND_PAINT
qDebug() << "Legend::resizeLayout started";
#endif
if ( d->layout ) {
d->reflowHDatasetItems( this );
d->layout->setGeometry( QRect(QPoint( 0,0 ), size) );
activateTheLayout();
}
#ifdef DEBUG_LEGEND_PAINT
qDebug() << "Legend::resizeLayout done";
#endif
}
void Legend::activateTheLayout()
{
if ( d->layout && d->layout->parent() ) {
d->layout->activate();
}
}
void Legend::setLegendStyle( LegendStyle style )
{
if ( d->legendStyle == style ) {
return;
}
d->legendStyle = style;
setNeedRebuild();
}
Legend::LegendStyle Legend::legendStyle() const
{
return d->legendStyle;
}
/**
* Creates an exact copy of this legend.
*/
Legend* Legend::clone() const
{
Legend* legend = new Legend( new Private( *d ), 0 );
legend->setTextAttributes( textAttributes() );
legend->setTitleTextAttributes( titleTextAttributes() );
legend->setFrameAttributes( frameAttributes() );
legend->setUseAutomaticMarkerSize( useAutomaticMarkerSize() );
legend->setPosition( position() );
legend->setAlignment( alignment() );
legend->setTextAlignment( textAlignment() );
legend->setLegendStyle( legendStyle() );
return legend;
}
bool Legend::compare( const Legend* other ) const
{
if ( other == this ) {
return true;
}
if ( !other ) {
return false;
}
return ( AbstractAreaBase::compare( other ) ) &&
(isVisible() == other->isVisible()) &&
(position() == other->position()) &&
(alignment() == other->alignment())&&
(textAlignment() == other->textAlignment())&&
(floatingPosition() == other->floatingPosition()) &&
(orientation() == other->orientation())&&
(showLines() == other->showLines())&&
(texts() == other->texts())&&
(brushes() == other->brushes())&&
(pens() == other->pens())&&
(markerAttributes() == other->markerAttributes())&&
(useAutomaticMarkerSize() == other->useAutomaticMarkerSize()) &&
(textAttributes() == other->textAttributes()) &&
(titleText() == other->titleText())&&
(titleTextAttributes() == other->titleTextAttributes()) &&
(spacing() == other->spacing()) &&
(legendStyle() == other->legendStyle());
}
void Legend::paint( QPainter* painter )
{
#ifdef DEBUG_LEGEND_PAINT
qDebug() << "entering Legend::paint( QPainter* painter )";
#endif
if ( !diagram() ) {
return;
}
activateTheLayout();
Q_FOREACH( AbstractLayoutItem* paintItem, d->paintItems ) {
paintItem->paint( painter );
}
#ifdef DEBUG_LEGEND_PAINT
qDebug() << "leaving Legend::paint( QPainter* painter )";
#endif
}
uint Legend::datasetCount() const
{
int modelLabelsCount = 0;
Q_FOREACH ( DiagramObserver* observer, d->observers ) {
AbstractDiagram* diagram = observer->diagram();
Q_ASSERT( diagram->datasetLabels().count() == diagram->datasetBrushes().count() );
modelLabelsCount += diagram->datasetLabels().count();
}
return modelLabelsCount;
}
void Legend::setReferenceArea( const QWidget* area )
{
if ( area == d->referenceArea ) {
return;
}
d->referenceArea = area;
setNeedRebuild();
}
const QWidget* Legend::referenceArea() const
{
return d->referenceArea ? d->referenceArea : qobject_cast< const QWidget* >( parent() );
}
AbstractDiagram* Legend::diagram() const
{
if ( d->observers.isEmpty() ) {
return 0;
}
return d->observers.first()->diagram();
}
DiagramList Legend::diagrams() const
{
DiagramList list;
for ( int i = 0; i < d->observers.size(); ++i ) {
list << d->observers.at(i)->diagram();
}
return list;
}
ConstDiagramList Legend::constDiagrams() const
{
ConstDiagramList list;
for ( int i = 0; i < d->observers.size(); ++i ) {
list << d->observers.at(i)->diagram();
}
return list;
}
void Legend::addDiagram( AbstractDiagram* newDiagram )
{
if ( newDiagram ) {
DiagramObserver* observer = new DiagramObserver( newDiagram, this );
DiagramObserver* oldObs = d->findObserverForDiagram( newDiagram );
if ( oldObs ) {
delete oldObs;
d->observers[ d->observers.indexOf( oldObs ) ] = observer;
} else {
d->observers.append( observer );
}
connect( observer, SIGNAL( diagramAboutToBeDestroyed(AbstractDiagram*) ),
SLOT( resetDiagram(AbstractDiagram*) ));
connect( observer, SIGNAL( diagramDataChanged(AbstractDiagram*) ),
SLOT( setNeedRebuild() ));
connect( observer, SIGNAL( diagramDataHidden(AbstractDiagram*) ),
SLOT( setNeedRebuild() ));
connect( observer, SIGNAL( diagramAttributesChanged(AbstractDiagram*) ),
SLOT( setNeedRebuild() ));
setNeedRebuild();
}
}
void Legend::removeDiagram( AbstractDiagram* oldDiagram )
{
int datasetBrushOffset = 0;
QList< AbstractDiagram * > diagrams = this->diagrams();
for ( int i = 0; i datasetBrushes().count(); i++ ) {
d->brushes.remove(datasetBrushOffset + i);
d->texts.remove(datasetBrushOffset + i);
}
for ( int i = 0; i < oldDiagram->datasetPens().count(); i++ ) {
d->pens.remove(datasetBrushOffset + i);
}
break;
}
datasetBrushOffset += diagrams.at(i)->datasetBrushes().count();
}
if ( oldDiagram ) {
DiagramObserver *oldObs = d->findObserverForDiagram( oldDiagram );
if ( oldObs ) {
delete oldObs;
d->observers.removeAt( d->observers.indexOf( oldObs ) );
}
setNeedRebuild();
}
}
void Legend::removeDiagrams()
{
// removeDiagram() may change the d->observers list. So, build up the list of
// diagrams to remove first and then remove them one by one.
QList< AbstractDiagram * > diagrams;
for ( int i = 0; i < d->observers.size(); ++i ) {
diagrams.append( d->observers.at( i )->diagram() );
}
for ( int i = 0; i < diagrams.count(); ++i ) {
removeDiagram( diagrams[ i ] );
}
}
void Legend::replaceDiagram( AbstractDiagram* newDiagram,
AbstractDiagram* oldDiagram )
{
AbstractDiagram* old = oldDiagram;
if ( !d->observers.isEmpty() && !old ) {
old = d->observers.first()->diagram();
if ( !old ) {
d->observers.removeFirst(); // first entry had a 0 diagram
}
}
if ( old ) {
removeDiagram( old );
}
if ( newDiagram ) {
addDiagram( newDiagram );
}
}
uint Legend::dataSetOffset( AbstractDiagram* diagram )
{
uint offset = 0;
for ( int i = 0; i < d->observers.count(); ++i ) {
if ( d->observers.at(i)->diagram() == diagram ) {
return offset;
}
AbstractDiagram* diagram = d->observers.at(i)->diagram();
if ( !diagram->model() ) {
continue;
}
offset = offset + diagram->model()->columnCount();
}
return offset;
}
void Legend::setDiagram( AbstractDiagram* newDiagram )
{
replaceDiagram( newDiagram );
}
void Legend::resetDiagram( AbstractDiagram* oldDiagram )
{
removeDiagram( oldDiagram );
}
void Legend::setVisible( bool visible )
{
// do NOT bail out if visible == isVisible(), because the return value of isVisible() also depends
// on the visibility of the parent.
QWidget::setVisible( visible );
emitPositionChanged();
}
void Legend::setNeedRebuild()
{
buildLegend();
sizeHint();
}
void Legend::setPosition( Position position )
{
if ( d->position == position ) {
return;
}
d->position = position;
emitPositionChanged();
}
void Legend::emitPositionChanged()
{
emit positionChanged( this );
emit propertiesChanged();
}
Position Legend::position() const
{
return d->position;
}
void Legend::setAlignment( Qt::Alignment alignment )
{
if ( d->alignment == alignment ) {
return;
}
d->alignment = alignment;
emitPositionChanged();
}
Qt::Alignment Legend::alignment() const
{
return d->alignment;
}
void Legend::setTextAlignment( Qt::Alignment alignment )
{
if ( d->textAlignment == alignment ) {
return;
}
d->textAlignment = alignment;
emitPositionChanged();
}
Qt::Alignment Legend::textAlignment() const
{
return d->textAlignment;
}
void Legend::setLegendSymbolAlignment( Qt::Alignment alignment )
{
if ( d->legendLineSymbolAlignment == alignment ) {
return;
}
d->legendLineSymbolAlignment = alignment;
emitPositionChanged();
}
Qt::Alignment Legend::legendSymbolAlignment() const
{
return d->legendLineSymbolAlignment ;
}
void Legend::setFloatingPosition( const RelativePosition& relativePosition )
{
d->position = Position::Floating;
if ( d->relativePosition != relativePosition ) {
d->relativePosition = relativePosition;
emitPositionChanged();
}
}
const RelativePosition Legend::floatingPosition() const
{
return d->relativePosition;
}
void Legend::setOrientation( Qt::Orientation orientation )
{
if ( d->orientation == orientation ) {
return;
}
d->orientation = orientation;
setNeedRebuild();
emitPositionChanged();
}
Qt::Orientation Legend::orientation() const
{
return d->orientation;
}
void Legend::setSortOrder( Qt::SortOrder order )
{
if ( d->order == order ) {
return;
}
d->order = order;
setNeedRebuild();
emitPositionChanged();
}
Qt::SortOrder Legend::sortOrder() const
{
return d->order;
}
void Legend::setShowLines( bool legendShowLines )
{
if ( d->showLines == legendShowLines ) {
return;
}
d->showLines = legendShowLines;
setNeedRebuild();
emitPositionChanged();
}
bool Legend::showLines() const
{
return d->showLines;
}
void Legend::setUseAutomaticMarkerSize( bool useAutomaticMarkerSize )
{
d->useAutomaticMarkerSize = useAutomaticMarkerSize;
setNeedRebuild();
emitPositionChanged();
}
bool Legend::useAutomaticMarkerSize() const
{
return d->useAutomaticMarkerSize;
}
/**
\brief Removes all legend texts that might have been set by setText.
This resets the Legend to default behaviour: Texts are created automatically.
*/
void Legend::resetTexts()
{
if ( !d->texts.count() ) {
return;
}
d->texts.clear();
setNeedRebuild();
}
void Legend::setText( uint dataset, const QString& text )
{
if ( d->texts[ dataset ] == text ) {
return;
}
d->texts[ dataset ] = text;
setNeedRebuild();
}
QString Legend::text( uint dataset ) const
{
if ( d->texts.find( dataset ) != d->texts.end() ) {
return d->texts[ dataset ];
} else {
return d->modelLabels[ dataset ];
}
}
const QMap Legend::texts() const
{
return d->texts;
}
void Legend::setColor( uint dataset, const QColor& color )
{
if ( d->brushes[ dataset ] != color ) {
d->brushes[ dataset ] = color;
setNeedRebuild();
update();
}
}
void Legend::setBrush( uint dataset, const QBrush& brush )
{
if ( d->brushes[ dataset ] != brush ) {
d->brushes[ dataset ] = brush;
setNeedRebuild();
update();
}
}
QBrush Legend::brush( uint dataset ) const
{
if ( d->brushes.contains( dataset ) ) {
return d->brushes[ dataset ];
} else {
return d->modelBrushes[ dataset ];
}
}
const QMap Legend::brushes() const
{
return d->brushes;
}
void Legend::setBrushesFromDiagram( AbstractDiagram* diagram )
{
bool changed = false;
QList datasetBrushes = diagram->datasetBrushes();
for ( int i = 0; i < datasetBrushes.count(); i++ ) {
if ( d->brushes[ i ] != datasetBrushes[ i ] ) {
d->brushes[ i ] = datasetBrushes[ i ];
changed = true;
}
}
if ( changed ) {
setNeedRebuild();
update();
}
}
void Legend::setPen( uint dataset, const QPen& pen )
{
if ( d->pens[dataset] == pen ) {
return;
}
d->pens[dataset] = pen;
setNeedRebuild();
update();
}
QPen Legend::pen( uint dataset ) const
{
if ( d->pens.find( dataset ) != d->pens.end() ) {
return d->pens[ dataset ];
} else {
return d->modelPens[ dataset ];
}
}
const QMap Legend::pens() const
{
return d->pens;
}
void Legend::setMarkerAttributes( uint dataset, const MarkerAttributes& markerAttributes )
{
if ( d->markerAttributes[dataset] == markerAttributes ) {
return;
}
d->markerAttributes[ dataset ] = markerAttributes;
setNeedRebuild();
update();
}
MarkerAttributes Legend::markerAttributes( uint dataset ) const
{
if ( d->markerAttributes.find( dataset ) != d->markerAttributes.end() ) {
return d->markerAttributes[ dataset ];
} else if ( static_cast( d->modelMarkers.count() ) > dataset ) {
return d->modelMarkers[ dataset ];
} else {
return MarkerAttributes();
}
}
const QMap Legend::markerAttributes() const
{
return d->markerAttributes;
}
void Legend::setTextAttributes( const TextAttributes &a )
{
if ( d->textAttributes == a ) {
return;
}
d->textAttributes = a;
setNeedRebuild();
}
TextAttributes Legend::textAttributes() const
{
return d->textAttributes;
}
void Legend::setTitleText( const QString& text )
{
if ( d->titleText == text ) {
return;
}
d->titleText = text;
setNeedRebuild();
}
QString Legend::titleText() const
{
return d->titleText;
}
void Legend::setTitleTextAttributes( const TextAttributes &a )
{
if ( d->titleTextAttributes == a ) {
return;
}
d->titleTextAttributes = a;
setNeedRebuild();
}
TextAttributes Legend::titleTextAttributes() const
{
return d->titleTextAttributes;
}
void Legend::forceRebuild()
{
#ifdef DEBUG_LEGEND_PAINT
qDebug() << "entering Legend::forceRebuild()";
#endif
buildLegend();
#ifdef DEBUG_LEGEND_PAINT
qDebug() << "leaving Legend::forceRebuild()";
#endif
}
void Legend::setSpacing( uint space )
{
if ( d->spacing == space && d->layout->spacing() == int( space ) ) {
return;
}
d->spacing = space;
d->layout->setSpacing( space );
setNeedRebuild();
}
uint Legend::spacing() const
{
return d->spacing;
}
void Legend::setDefaultColors()
{
Palette pal = Palette::defaultPalette();
for ( int i = 0; i < pal.size(); i++ ) {
setBrush( i, pal.getBrush( i ) );
}
}
void Legend::setRainbowColors()
{
Palette pal = Palette::rainbowPalette();
for ( int i = 0; i < pal.size(); i++ ) {
setBrush( i, pal.getBrush( i ) );
}
}
void Legend::setSubduedColors( bool ordered )
{
Palette pal = Palette::subduedPalette();
if ( ordered ) {
for ( int i = 0; i < pal.size(); i++ ) {
setBrush( i, pal.getBrush( i ) );
}
} else {
static const int s_subduedColorsCount = 18;
Q_ASSERT( pal.size() >= s_subduedColorsCount );
static const int order[ s_subduedColorsCount ] = {
0, 5, 10, 15, 2, 7, 12, 17, 4,
9, 14, 1, 6, 11, 16, 3, 8, 13
};
for ( int i = 0; i < s_subduedColorsCount; i++ ) {
setBrush( i, pal.getBrush( order[i] ) );
}
}
}
void Legend::resizeEvent( QResizeEvent * event )
{
Q_UNUSED( event );
#ifdef DEBUG_LEGEND_PAINT
qDebug() << "Legend::resizeEvent() called";
#endif
forceRebuild();
sizeHint();
QTimer::singleShot( 0, this, SLOT(emitPositionChanged()) );
}
void Legend::Private::fetchPaintOptions( Legend *q )
{
modelLabels.clear();
modelBrushes.clear();
modelPens.clear();
modelMarkers.clear();
// retrieve the diagrams' settings for all non-hidden datasets
for ( int i = 0; i < observers.size(); ++i ) {
const AbstractDiagram* diagram = observers.at( i )->diagram();
if ( !diagram ) {
continue;
}
const QStringList diagramLabels = diagram->datasetLabels();
const QList diagramBrushes = diagram->datasetBrushes();
const QList diagramPens = diagram->datasetPens();
const QList diagramMarkers = diagram->datasetMarkers();
const bool ascend = q->sortOrder() == Qt::AscendingOrder;
int dataset = ascend ? 0 : diagramLabels.count() - 1;
const int end = ascend ? diagramLabels.count() : -1;
for ( ; dataset != end; dataset += ascend ? 1 : -1 ) {
if ( diagram->isHidden( dataset ) || q->datasetIsHidden( dataset ) ) {
continue;
}
modelLabels += diagramLabels[ dataset ];
modelBrushes += diagramBrushes[ dataset ];
modelPens += diagramPens[ dataset ];
modelMarkers += diagramMarkers[ dataset ];
}
}
Q_ASSERT( modelLabels.count() == modelBrushes.count() );
}
QSizeF Legend::Private::markerSize( Legend *q, int dataset, qreal fontHeight ) const
{
QSizeF suppliedSize = q->markerAttributes( dataset ).markerSize();
if ( q->useAutomaticMarkerSize() || !suppliedSize.isValid() ) {
return QSizeF( fontHeight, fontHeight );
} else {
return suppliedSize;
}
}
QSizeF Legend::Private::maxMarkerSize( Legend *q, qreal fontHeight ) const
{
QSizeF ret( 1.0, 1.0 );
if ( q->legendStyle() != LinesOnly ) {
for ( int dataset = 0; dataset < modelLabels.count(); ++dataset ) {
ret = ret.expandedTo( markerSize( q, dataset, fontHeight ) );
}
}
return ret;
}
HDatasetItem::HDatasetItem()
: markerLine(0),
label(0),
separatorLine(0),
spacer(0)
{}
static void updateToplevelLayout(QWidget *w)
{
while ( w ) {
if ( w->isTopLevel() ) {
// The null check has proved necessary during destruction of the Legend / Chart
if ( w->layout() ) {
w->layout()->update();
}
break;
} else {
w = qobject_cast< QWidget * >( w->parent() );
Q_ASSERT( w );
}
}
}
void Legend::buildLegend()
{
/* Grid layout partitioning (horizontal orientation): row zero is the title, row one the divider
line between title and dataset items, row two for each item: line, marker, text label and separator
line in that order.
In a vertically oriented legend, row pairs (2, 3), ... contain a possible separator line (first row)
and (second row) line, marker, text label each. */
d->destroyOldLayout();
if ( orientation() == Qt::Vertical ) {
d->layout->setColumnStretch( 6, 1 );
} else {
d->layout->setColumnStretch( 6, 0 );
}
d->fetchPaintOptions( this );
const KChartEnums::MeasureOrientation measureOrientation =
orientation() == Qt::Vertical ? KChartEnums::MeasureOrientationMinimum
: KChartEnums::MeasureOrientationHorizontal;
// legend caption
if ( !titleText().isEmpty() && titleTextAttributes().isVisible() ) {
TextLayoutItem* titleItem =
new TextLayoutItem( titleText(), titleTextAttributes(), referenceArea(),
measureOrientation, d->textAlignment );
titleItem->setParentWidget( this );
d->paintItems << titleItem;
d->layout->addItem( titleItem, 0, 0, 1, 5, Qt::AlignCenter );
// The line between the title and the legend items, if any.
if ( showLines() && d->modelLabels.count() ) {
HorizontalLineLayoutItem* lineItem = new HorizontalLineLayoutItem;
d->paintItems << lineItem;
d->layout->addItem( lineItem, 1, 0, 1, 5, Qt::AlignCenter );
}
}
qreal fontHeight = textAttributes().calculatedFontSize( referenceArea(), measureOrientation );
{
QFont tmpFont = textAttributes().font();
tmpFont.setPointSizeF( fontHeight );
if ( GlobalMeasureScaling::paintDevice() ) {
fontHeight = QFontMetricsF( tmpFont, GlobalMeasureScaling::paintDevice() ).height();
} else {
fontHeight = QFontMetricsF( tmpFont ).height();
}
}
const QSizeF maxMarkerSize = d->maxMarkerSize( this, fontHeight );
// If we show a marker on a line, we paint it after 8 pixels
// of the line have been painted. This allows to see the line style
// at the right side of the marker without the line needing to
// be too long.
// (having the marker in the middle of the line would require longer lines)
const int lineLengthLeftOfMarker = 8;
int maxLineLength = 18;
{
bool hasComplexPenStyle = false;
for ( int dataset = 0; dataset < d->modelLabels.count(); ++dataset ) {
const QPen pn = pen( dataset );
const Qt::PenStyle ps = pn.style();
if ( ps != Qt::NoPen ) {
- maxLineLength = qMax( pn.width() * 18, maxLineLength );
+ maxLineLength = qMin( pn.width() * 18, maxLineLength );
if ( ps != Qt::SolidLine ) {
hasComplexPenStyle = true;
}
}
}
- if ( hasComplexPenStyle && legendStyle() != LinesOnly ) {
- maxLineLength += lineLengthLeftOfMarker + int( maxMarkerSize.width() );
+ if ( legendStyle() != LinesOnly ) {
+ if ( hasComplexPenStyle )
+ maxLineLength += lineLengthLeftOfMarker;
+ maxLineLength += int( maxMarkerSize.width() );
}
}
// for all datasets: add (line)marker items and text items to the layout;
// actual layout happens in flowHDatasetItems() for horizontal layout, here for vertical
for ( int dataset = 0; dataset < d->modelLabels.count(); ++dataset ) {
const int vLayoutRow = 2 + dataset * 2;
HDatasetItem dsItem;
// It is possible to set the marker brush through markerAttributes as well as
// the dataset brush set in the diagram - the markerAttributes have higher precedence.
MarkerAttributes markerAttrs = markerAttributes( dataset );
markerAttrs.setMarkerSize( d->markerSize( this, dataset, fontHeight ) );
const QBrush markerBrush = markerAttrs.markerColor().isValid() ?
QBrush( markerAttrs.markerColor() ) : brush( dataset );
switch ( legendStyle() ) {
case MarkersOnly:
dsItem.markerLine = new MarkerLayoutItem( diagram(), markerAttrs, markerBrush,
markerAttrs.pen(), Qt::AlignLeft | Qt::AlignVCenter );
break;
case LinesOnly:
dsItem.markerLine = new LineLayoutItem( diagram(), maxLineLength, pen( dataset ),
d->legendLineSymbolAlignment, Qt::AlignCenter );
break;
case MarkersAndLines:
dsItem.markerLine = new LineWithMarkerLayoutItem(
diagram(), maxLineLength, pen( dataset ), lineLengthLeftOfMarker, markerAttrs,
markerBrush, markerAttrs.pen(), Qt::AlignCenter );
break;
default:
Q_ASSERT( false );
}
dsItem.label = new TextLayoutItem( text( dataset ), textAttributes(), referenceArea(),
measureOrientation, d->textAlignment );
dsItem.label->setParentWidget( this );
// horizontal layout is deferred to flowDatasetItems()
if ( orientation() == Qt::Horizontal ) {
d->hLayoutDatasets << dsItem;
continue;
}
// (actual) vertical layout here
if ( dsItem.markerLine ) {
d->layout->addItem( dsItem.markerLine, vLayoutRow, 1, 1, 1, Qt::AlignCenter );
d->paintItems << dsItem.markerLine;
}
d->layout->addItem( dsItem.label, vLayoutRow, 3, 1, 1, Qt::AlignLeft | Qt::AlignVCenter );
d->paintItems << dsItem.label;
// horizontal separator line, only between items
if ( showLines() && dataset != d->modelLabels.count() - 1 ) {
HorizontalLineLayoutItem* lineItem = new HorizontalLineLayoutItem;
d->layout->addItem( lineItem, vLayoutRow + 1, 0, 1, 5, Qt::AlignCenter );
d->paintItems << lineItem;
}
}
if ( orientation() == Qt::Horizontal ) {
d->flowHDatasetItems( this );
}
// vertical line (only in vertical mode)
if ( orientation() == Qt::Vertical && showLines() && d->modelLabels.count() ) {
VerticalLineLayoutItem* lineItem = new VerticalLineLayoutItem;
d->paintItems << lineItem;
d->layout->addItem( lineItem, 2, 2, d->modelLabels.count() * 2, 1 );
}
updateToplevelLayout( this );
emit propertiesChanged();
#ifdef DEBUG_LEGEND_PAINT
qDebug() << "leaving Legend::buildLegend()";
#endif
}
int HDatasetItem::height() const
{
return qMax( markerLine->sizeHint().height(), label->sizeHint().height() );
}
void Legend::Private::reflowHDatasetItems( Legend *q )
{
if (hLayoutDatasets.isEmpty()) {
return;
}
paintItems.clear();
// Dissolve exactly the QHBoxLayout(s) created as "currentLine" in flowHDatasetItems - don't remove the
// caption and line under the caption! Those are easily identified because they aren't QLayouts.
for ( int i = layout->count() - 1; i >= 0; i-- ) {
QLayoutItem *const item = layout->itemAt( i );
QLayout *const hbox = item->layout();
if ( !hbox ) {
AbstractLayoutItem *alItem = dynamic_cast< AbstractLayoutItem * >( item );
Q_ASSERT( alItem );
paintItems << alItem;
continue;
}
Q_ASSERT( dynamic_cast< QHBoxLayout * >( hbox ) );
layout->takeAt( i );
// detach children so they aren't deleted with the parent
for ( int j = hbox->count() - 1; j >= 0; j-- ) {
hbox->takeAt( j );
}
delete hbox;
}
flowHDatasetItems( q );
}
// this works pretty much like flow layout for text, and it is only applicable to dataset items
// laid out horizontally
void Legend::Private::flowHDatasetItems( Legend *q )
{
const int separatorLineWidth = 3; // hardcoded in VerticalLineLayoutItem::sizeHint()
const int allowedWidth = q->areaGeometry().width();
QHBoxLayout *currentLine = new QHBoxLayout;
int mainLayoutRow = 1;
layout->addItem( currentLine, mainLayoutRow++, /*column*/0,
/*rowSpan*/1 , /*columnSpan*/5, Qt::AlignLeft | Qt::AlignVCenter );
for ( int dataset = 0; dataset < hLayoutDatasets.size(); dataset++ ) {
HDatasetItem *hdsItem = &hLayoutDatasets[ dataset ];
bool spacerUsed = false;
bool separatorUsed = false;
if ( !currentLine->isEmpty() ) {
const int separatorWidth = ( q->showLines() ? separatorLineWidth : 0 ) + q->spacing();
const int payloadWidth = hdsItem->markerLine->sizeHint().width() +
hdsItem->label->sizeHint().width();
if ( currentLine->sizeHint().width() + separatorWidth + payloadWidth > allowedWidth ) {
// too wide, "line break"
#ifdef DEBUG_LEGEND_PAINT
qDebug() << Q_FUNC_INFO << "break" << mainLayoutRow
<< currentLine->sizeHint().width()
<< currentLine->sizeHint().width() + separatorWidth + payloadWidth
<< allowedWidth;
#endif
currentLine = new QHBoxLayout;
layout->addItem( currentLine, mainLayoutRow++, /*column*/0,
/*rowSpan*/1 , /*columnSpan*/5, Qt::AlignLeft | Qt::AlignVCenter );
} else {
// > 1 dataset item in line, put spacing and maybe a separator between them
if ( !hdsItem->spacer ) {
hdsItem->spacer = new QSpacerItem( q->spacing(), 1 );
}
currentLine->addItem( hdsItem->spacer );
spacerUsed = true;
if ( q->showLines() ) {
if ( !hdsItem->separatorLine ) {
hdsItem->separatorLine = new VerticalLineLayoutItem;
}
paintItems << hdsItem->separatorLine;
currentLine->addItem( hdsItem->separatorLine );
separatorUsed = true;
}
}
}
// those have no parents in the current layout, so they wouldn't get cleaned up otherwise
if ( !spacerUsed ) {
delete hdsItem->spacer;
hdsItem->spacer = 0;
}
if ( !separatorUsed ) {
delete hdsItem->separatorLine;
hdsItem->separatorLine = 0;
}
currentLine->addItem( hdsItem->markerLine );
paintItems << hdsItem->markerLine;
currentLine->addItem( hdsItem->label );
paintItems << hdsItem->label;
}
}
bool Legend::hasHeightForWidth() const
{
// this is better than using orientation() because, for layout purposes, we're not height-for-width
// *yet* before buildLegend() has been called, and the layout logic might get upset if we say
// something that will only be true in the future
return !d->hLayoutDatasets.isEmpty();
}
int Legend::heightForWidth( int width ) const
{
if ( d->hLayoutDatasets.isEmpty() ) {
return -1;
}
int ret = 0;
// space for caption and line under caption (if any)
for (int i = 0; i < 2; i++) {
if ( QLayoutItem *item = d->layout->itemAtPosition( i, 0 ) ) {
ret += item->sizeHint().height();
}
}
const int separatorLineWidth = 3; // ### hardcoded in VerticalLineLayoutItem::sizeHint()
int currentLineWidth = 0;
int currentLineHeight = 0;
Q_FOREACH( const HDatasetItem &hdsItem, d->hLayoutDatasets ) {
const int payloadWidth = hdsItem.markerLine->sizeHint().width() +
hdsItem.label->sizeHint().width();
if ( !currentLineWidth ) {
// first iteration
currentLineWidth = payloadWidth;
} else {
const int separatorWidth = ( showLines() ? separatorLineWidth : 0 ) + spacing();
currentLineWidth += separatorWidth + payloadWidth;
if ( currentLineWidth > width ) {
// too wide, "line break"
#ifdef DEBUG_LEGEND_PAINT
qDebug() << Q_FUNC_INFO << "heightForWidth break" << currentLineWidth
<< currentLineWidth + separatorWidth + payloadWidth
<< width;
#endif
ret += currentLineHeight + spacing();
currentLineWidth = payloadWidth;
currentLineHeight = 0;
}
}
currentLineHeight = qMax( currentLineHeight, hdsItem.height() );
}
ret += currentLineHeight; // one less spacings than lines
return ret;
}
void Legend::Private::destroyOldLayout()
{
// in the horizontal layout case, the QHBoxLayout destructor also deletes child layout items
// (it isn't documented that QLayoutItems delete their children)
for ( int i = layout->count() - 1; i >= 0; i-- ) {
delete layout->takeAt( i );
}
Q_ASSERT( !layout->count() );
hLayoutDatasets.clear();
paintItems.clear();
}
void Legend::setHiddenDatasets( const QList hiddenDatasets )
{
d->hiddenDatasets = hiddenDatasets;
}
const QList Legend::hiddenDatasets() const
{
return d->hiddenDatasets;
}
void Legend::setDatasetHidden( uint dataset, bool hidden )
{
if ( hidden && !d->hiddenDatasets.contains( dataset ) ) {
d->hiddenDatasets.append( dataset );
} else if ( !hidden && d->hiddenDatasets.contains( dataset ) ) {
d->hiddenDatasets.removeAll( dataset );
}
}
bool Legend::datasetIsHidden( uint dataset ) const
{
return d->hiddenDatasets.contains( dataset );
}