1149 lines
35 KiB
C++
1149 lines
35 KiB
C++
/****************************************************************************
|
|
** Copyright (C) 2001-2011 Klaralvdalens Datakonsult AB. All rights reserved.
|
|
**
|
|
** This file is part of the KD Chart library.
|
|
**
|
|
** Licensees holding valid commercial KD Chart licenses may use this file in
|
|
** accordance with the KD Chart Commercial License Agreement provided with
|
|
** the Software.
|
|
**
|
|
**
|
|
** This file may be distributed and/or modified under the terms of the
|
|
** GNU General Public License version 2 and version 3 as published by the
|
|
** Free Software Foundation and appearing in the file LICENSE.GPL.txt included.
|
|
**
|
|
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
|
|
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
|
**
|
|
** Contact info@kdab.com if any conditions of this licensing are not
|
|
** clear to you.
|
|
**
|
|
**********************************************************************/
|
|
|
|
#include "KDChartLegend.h"
|
|
#include "KDChartLegend_p.h"
|
|
#include <KDChartTextAttributes.h>
|
|
#include <KDChartMarkerAttributes.h>
|
|
#include <QFont>
|
|
#include <QPainter>
|
|
#include <QTextTableCell>
|
|
#include <QTextCursor>
|
|
#include <QTextCharFormat>
|
|
#include <QTextDocumentFragment>
|
|
#include <QTimer>
|
|
#include <QAbstractTextDocumentLayout>
|
|
#include <QtDebug>
|
|
#include <QLabel>
|
|
#include <KDChartAbstractDiagram.h>
|
|
#include "KDTextDocument.h"
|
|
#include <KDChartDiagramObserver.h>
|
|
#include <QGridLayout>
|
|
#include "KDChartLayoutItems.h"
|
|
|
|
#include <KDABLibFakes>
|
|
|
|
using namespace KDChart;
|
|
|
|
Legend::Private::Private() :
|
|
referenceArea(0),
|
|
position( Position::East ),
|
|
alignment( Qt::AlignCenter ),
|
|
textAlignment( Qt::AlignCenter ),
|
|
relativePosition( RelativePosition() ),
|
|
orientation( Qt::Vertical ),
|
|
order( Qt::AscendingOrder ),
|
|
showLines( false ),
|
|
texts(),
|
|
textAttributes(),
|
|
titleText( QObject::tr( "Legend" ) ),
|
|
titleTextAttributes(),
|
|
spacing( 1 ),
|
|
useAutomaticMarkerSize( true ),
|
|
legendStyle( MarkersOnly )
|
|
//needRebuild( true )
|
|
{
|
|
// 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( KDChart::Measure( 4.0, KDChartEnums::MeasureCalculationModeAbsolute ) );
|
|
relativePosition.setVerticalPadding( KDChart::Measure( 4.0, KDChartEnums::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( KDChart::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 );
|
|
//setLayout( d->layout );
|
|
|
|
const Measure normalFontSizeTitle( 12, KDChartEnums::MeasureCalculationModeAbsolute );
|
|
const Measure normalFontSizeLabels( 10, KDChartEnums::MeasureCalculationModeAbsolute );
|
|
const Measure minimalFontSize( 4, KDChartEnums::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( KDChart::AbstractLayoutItem* layoutItem, d->layoutItems ) {
|
|
layoutItem->sizeHint();
|
|
}
|
|
return AbstractAreaWidget::sizeHint();
|
|
}
|
|
|
|
void Legend::needSizeHint()
|
|
{
|
|
// Re-build the Legend's content, if it has not been build yet,
|
|
// or if the Legend's geometry has changed, resp.
|
|
buildLegend();
|
|
}
|
|
|
|
void Legend::resizeLayout( const QSize& size )
|
|
{
|
|
#ifdef DEBUG_LEGEND_PAINT
|
|
qDebug() << "Legend::resizeLayout started";
|
|
#endif
|
|
if( d->layout ){
|
|
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 ){
|
|
//qDebug() << "Legend::compare() cannot compare to Null pointer";
|
|
return false;
|
|
}
|
|
|
|
qDebug() << ( static_cast<const AbstractAreaBase*>(this)->compare( other ) );
|
|
qDebug() << (isVisible() == other->isVisible());
|
|
qDebug() << (position() == other->position());
|
|
qDebug() << (alignment() == other->alignment());
|
|
qDebug() << (textAlignment() == other->textAlignment());
|
|
qDebug() << (floatingPosition() == other->floatingPosition());
|
|
qDebug() << (orientation() == other->orientation());
|
|
qDebug() << (showLines() == other->showLines());
|
|
qDebug() << (texts() == other->texts());
|
|
qDebug() << (brushes() == other->brushes());
|
|
qDebug() << (pens() == other->pens());
|
|
qDebug() << (markerAttributes() == other->markerAttributes());
|
|
qDebug() << (useAutomaticMarkerSize() == other->useAutomaticMarkerSize());
|
|
qDebug() << (textAttributes() == other->textAttributes());
|
|
qDebug() << (titleText() == other->titleText());
|
|
qDebug() << (titleTextAttributes() == other->titleTextAttributes());
|
|
qDebug() << (spacing() == other->spacing());
|
|
qDebug() << (legendStyle() == other->legendStyle());
|
|
|
|
return ( static_cast<const AbstractAreaBase*>(this)->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
|
|
// rule: We do not show a legend, if there is no diagram.
|
|
if( ! diagram() ) return;
|
|
|
|
// re-calculate/adjust the Legend's internal layout and contents, if needed:
|
|
//buildLegend();
|
|
|
|
// PENDING(kalle) Support palette
|
|
|
|
Q_FOREACH( KDChart::AbstractLayoutItem* layoutItem, d->layoutItems ) {
|
|
layoutItem->paint( painter );
|
|
}
|
|
#ifdef DEBUG_LEGEND_PAINT
|
|
qDebug() << "leaving Legend::paint( QPainter* painter )";
|
|
#endif
|
|
}
|
|
|
|
|
|
uint Legend::datasetCount() const
|
|
{
|
|
int modelLabelsCount = 0;
|
|
int modelBrushesCount = 0;
|
|
for (int i = 0; i < d->observers.size(); ++i) {
|
|
DiagramObserver * obs = d->observers.at(i);
|
|
modelLabelsCount += obs->diagram()->datasetLabels().count();
|
|
modelBrushesCount += obs->diagram()->datasetBrushes().count();
|
|
}
|
|
Q_ASSERT( modelLabelsCount == modelBrushesCount );
|
|
return modelLabelsCount;
|
|
}
|
|
|
|
|
|
void Legend::setReferenceArea( const QWidget* area )
|
|
{
|
|
if( area == d->referenceArea ) return;
|
|
d->referenceArea = area;
|
|
setNeedRebuild();
|
|
}
|
|
|
|
const QWidget* Legend::referenceArea() const
|
|
{
|
|
//qDebug() << d->referenceArea;
|
|
return (d->referenceArea ? d->referenceArea : static_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 <diagrams.count(); i++)
|
|
{
|
|
if(diagrams.at(i) == oldDiagram)
|
|
{
|
|
for( int i = 0; i < oldDiagram->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 ){
|
|
//qDebug() << "before delete oldObs;";
|
|
delete oldObs;
|
|
//qDebug() << "after delete oldObs;";
|
|
d->observers.removeAt( d->observers.indexOf( oldObs ) );
|
|
//qDebug() << "after d->observers.removeAt()";
|
|
}
|
|
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 )
|
|
{
|
|
KDChart::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(KDChart::AbstractDiagram* diagram)
|
|
{
|
|
uint offset = 0;
|
|
|
|
for (int i = 0; i < d->observers.count(); ++i)
|
|
{
|
|
if(d->observers.at(i)->diagram() == diagram)
|
|
return offset;
|
|
|
|
KDChart::AbstractDiagram* diagram = d->observers.at(i)->diagram();
|
|
if(!diagram->model())
|
|
continue;
|
|
|
|
offset = offset + diagram->model()->columnCount();
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
void Legend::setDiagram( KDChart::AbstractDiagram* newDiagram )
|
|
{
|
|
replaceDiagram( newDiagram );
|
|
}
|
|
|
|
void Legend::resetDiagram( AbstractDiagram* oldDiagram )
|
|
{
|
|
//qDebug() << oldDiagram;
|
|
removeDiagram( oldDiagram );
|
|
}
|
|
|
|
void Legend::setVisible( bool visible )
|
|
{
|
|
if( isVisible() == visible )
|
|
return;
|
|
QWidget::setVisible( visible );
|
|
emitPositionChanged();
|
|
}
|
|
|
|
void Legend::setNeedRebuild()
|
|
{
|
|
//qDebug() << "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::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<uint,QString> Legend::texts() const
|
|
{
|
|
return d->texts;
|
|
}
|
|
|
|
void Legend::setColor( uint dataset, const QColor& color )
|
|
{
|
|
if( d->brushes[ dataset ] == color ) return;
|
|
d->brushes[ dataset ] = color;
|
|
setNeedRebuild();
|
|
update();
|
|
}
|
|
|
|
void Legend::setBrush( uint dataset, const QBrush& brush )
|
|
{
|
|
if( d->brushes[ dataset ] == brush ) return;
|
|
d->brushes[ dataset ] = brush;
|
|
setNeedRebuild();
|
|
update();
|
|
}
|
|
|
|
QBrush Legend::brush( uint dataset ) const
|
|
{
|
|
if( d->brushes.find( dataset ) != d->brushes.end() )
|
|
return d->brushes[ dataset ];
|
|
else
|
|
return d->modelBrushes[ dataset ];
|
|
}
|
|
|
|
const QMap<uint,QBrush> Legend::brushes() const
|
|
{
|
|
return d->brushes;
|
|
}
|
|
|
|
|
|
void Legend::setBrushesFromDiagram( KDChart::AbstractDiagram* diagram )
|
|
{
|
|
bool bChangesDone = false;
|
|
QList<QBrush> datasetBrushes = diagram->datasetBrushes();
|
|
for( int i = 0; i < datasetBrushes.count(); i++ ){
|
|
if( d->brushes[ i ] != datasetBrushes[ i ] ){
|
|
d->brushes[ i ] = datasetBrushes[ i ];
|
|
bChangesDone = true;
|
|
}
|
|
}
|
|
if( bChangesDone ) {
|
|
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<uint,QPen> 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<uint>( d->modelMarkers.count() ) > dataset )
|
|
return d->modelMarkers[ dataset ];
|
|
return MarkerAttributes();
|
|
}
|
|
|
|
const QMap<uint, MarkerAttributes> 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
|
|
//setSpacing(d->layout->spacing());
|
|
buildLegend();
|
|
#ifdef DEBUG_LEGEND_PAINT
|
|
qDebug() << "leaving Legend::forceRebuild()";
|
|
#endif
|
|
}
|
|
|
|
void Legend::setSpacing( uint space )
|
|
{
|
|
if( d->spacing == space && d->layout->spacing() == static_cast<int>(space) ) return;
|
|
d->spacing = space;
|
|
d->layout->setSpacing( space );
|
|
setNeedRebuild();
|
|
}
|
|
|
|
uint Legend::spacing() const
|
|
{
|
|
return d->spacing;
|
|
}
|
|
|
|
void Legend::setDefaultColors()
|
|
{
|
|
setColor( 0, Qt::red );
|
|
setColor( 1, Qt::green );
|
|
setColor( 2, Qt::blue );
|
|
setColor( 3, Qt::cyan );
|
|
setColor( 4, Qt::magenta );
|
|
setColor( 5, Qt::yellow );
|
|
setColor( 6, Qt::darkRed );
|
|
setColor( 7, Qt::darkGreen );
|
|
setColor( 8, Qt::darkBlue );
|
|
setColor( 9, Qt::darkCyan );
|
|
setColor( 10, Qt::darkMagenta );
|
|
setColor( 11, Qt::darkYellow );
|
|
}
|
|
|
|
void Legend::setRainbowColors()
|
|
{
|
|
setColor( 0, QColor(255, 0,196) );
|
|
setColor( 1, QColor(255, 0, 96) );
|
|
setColor( 2, QColor(255, 128,64) );
|
|
setColor( 3, Qt::yellow );
|
|
setColor( 4, Qt::green );
|
|
setColor( 5, Qt::cyan );
|
|
setColor( 6, QColor( 96, 96,255) );
|
|
setColor( 7, QColor(160, 0,255) );
|
|
for( int i = 8; i < 16; ++i )
|
|
setColor( i, brush( i - 8 ).color().light() );
|
|
}
|
|
|
|
void Legend::setSubduedColors( bool ordered )
|
|
{
|
|
static const int NUM_SUBDUEDCOLORS = 18;
|
|
static const QColor SUBDUEDCOLORS[ NUM_SUBDUEDCOLORS ] = {
|
|
QColor( 0xe0,0x7f,0x70 ),
|
|
QColor( 0xe2,0xa5,0x6f ),
|
|
QColor( 0xe0,0xc9,0x70 ),
|
|
QColor( 0xd1,0xe0,0x70 ),
|
|
QColor( 0xac,0xe0,0x70 ),
|
|
QColor( 0x86,0xe0,0x70 ),
|
|
QColor( 0x70,0xe0,0x7f ),
|
|
QColor( 0x70,0xe0,0xa4 ),
|
|
QColor( 0x70,0xe0,0xc9 ),
|
|
QColor( 0x70,0xd1,0xe0 ),
|
|
QColor( 0x70,0xac,0xe0 ),
|
|
QColor( 0x70,0x86,0xe0 ),
|
|
QColor( 0x7f,0x70,0xe0 ),
|
|
QColor( 0xa4,0x70,0xe0 ),
|
|
QColor( 0xc9,0x70,0xe0 ),
|
|
QColor( 0xe0,0x70,0xd1 ),
|
|
QColor( 0xe0,0x70,0xac ),
|
|
QColor( 0xe0,0x70,0x86 ),
|
|
};
|
|
if( ordered )
|
|
for(int i=0; i<NUM_SUBDUEDCOLORS; ++i)
|
|
setColor( i, SUBDUEDCOLORS[i] );
|
|
else{
|
|
setColor( 0, SUBDUEDCOLORS[ 0] );
|
|
setColor( 1, SUBDUEDCOLORS[ 5] );
|
|
setColor( 2, SUBDUEDCOLORS[10] );
|
|
setColor( 3, SUBDUEDCOLORS[15] );
|
|
setColor( 4, SUBDUEDCOLORS[ 2] );
|
|
setColor( 5, SUBDUEDCOLORS[ 7] );
|
|
setColor( 6, SUBDUEDCOLORS[12] );
|
|
setColor( 7, SUBDUEDCOLORS[17] );
|
|
setColor( 8, SUBDUEDCOLORS[ 4] );
|
|
setColor( 9, SUBDUEDCOLORS[ 9] );
|
|
setColor(10, SUBDUEDCOLORS[14] );
|
|
setColor(11, SUBDUEDCOLORS[ 1] );
|
|
setColor(12, SUBDUEDCOLORS[ 6] );
|
|
setColor(13, SUBDUEDCOLORS[11] );
|
|
setColor(14, SUBDUEDCOLORS[16] );
|
|
setColor(15, SUBDUEDCOLORS[ 3] );
|
|
setColor(16, SUBDUEDCOLORS[ 8] );
|
|
setColor(17, SUBDUEDCOLORS[13] );
|
|
}
|
|
}
|
|
|
|
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::buildLegend()
|
|
{
|
|
/*
|
|
if( !d->needRebuild ) {
|
|
#ifdef DEBUG_LEGEND_PAINT
|
|
qDebug() << "leaving Legend::buildLegend() with NO action (was already build)";
|
|
#endif
|
|
// Note: We do *not* need to send positionChanged here,
|
|
// because we send it in the resizeEvent, so layouting
|
|
// is done at the right time.
|
|
return;
|
|
}
|
|
#ifdef DEBUG_LEGEND_PAINT
|
|
qDebug() << "entering Legend::buildLegend() **********************************";
|
|
#endif
|
|
d->needRebuild = false;
|
|
*/
|
|
|
|
Q_FOREACH( QLayoutItem* layoutItem, d->layoutItems ) {
|
|
d->layout->removeItem( layoutItem );
|
|
}
|
|
qDeleteAll( d->layoutItems );
|
|
d->layoutItems.clear();
|
|
|
|
if( orientation() == Qt::Vertical ) {
|
|
d->layout->setColumnStretch( 6, 1 );
|
|
} else {
|
|
d->layout->setColumnStretch( 6, 0 );
|
|
}
|
|
|
|
d->modelLabels.clear();
|
|
d->modelBrushes.clear();
|
|
d->modelPens.clear();
|
|
d->modelMarkers.clear();
|
|
// retrieve the diagrams' settings for all non-hidden datasets
|
|
for (int i = 0; i < d->observers.size(); ++i){
|
|
const AbstractDiagram* diagram = d->observers.at(i)->diagram();
|
|
if( diagram ){
|
|
//qDebug() << "accessing" << diagram;
|
|
const QStringList diagramLabels( diagram->datasetLabels() );
|
|
const QList<QBrush> diagramBrushes( diagram->datasetBrushes() );
|
|
const QList<QPen> diagramPens( diagram->datasetPens() );
|
|
const QList<MarkerAttributes> diagramMarkers( diagram->datasetMarkers() );
|
|
const int begin = sortOrder() == Qt::AscendingOrder ? 0 : diagramLabels.count() - 1;
|
|
const int end = sortOrder() == Qt::AscendingOrder ? diagramLabels.count() : -1;
|
|
for ( int dataset = begin; dataset != end; dataset += begin < end ? 1 : -1 )
|
|
{
|
|
// only show the label if the diagrams is NOT having the dataset set to hidden
|
|
// and the dataset is not hidden in this legend either
|
|
if( !diagram->isHidden( dataset ) && !datasetIsHidden( dataset ) ){
|
|
d->modelLabels += diagramLabels[ dataset ];
|
|
d->modelBrushes += diagramBrushes[ dataset ];
|
|
d->modelPens += diagramPens[ dataset ];
|
|
d->modelMarkers += diagramMarkers[ dataset ];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Q_ASSERT( d->modelLabels.count() == d->modelBrushes.count() );
|
|
|
|
// legend caption
|
|
if( !titleText().isEmpty() && titleTextAttributes().isVisible() ) {
|
|
// PENDING(kalle) Other properties!
|
|
KDChart::TextLayoutItem* titleItem =
|
|
new KDChart::TextLayoutItem( titleText(),
|
|
titleTextAttributes(),
|
|
referenceArea(),
|
|
(orientation() == Qt::Vertical)
|
|
? KDChartEnums::MeasureOrientationMinimum
|
|
: KDChartEnums::MeasureOrientationHorizontal,
|
|
d->textAlignment );
|
|
titleItem->setParentWidget( this );
|
|
|
|
d->layoutItems << titleItem;
|
|
if( orientation() == Qt::Vertical )
|
|
d->layout->addItem( titleItem, 0, 0, 1, 5, Qt::AlignCenter );
|
|
else
|
|
d->layout->addItem( titleItem, 0, 0, 1, d->modelLabels.count() ? (d->modelLabels.count()*4) : 1, Qt::AlignCenter );
|
|
|
|
// The line between the title and the legend items, if any.
|
|
if( showLines() && d->modelLabels.count() ) {
|
|
KDChart::HorizontalLineLayoutItem* lineItem = new KDChart::HorizontalLineLayoutItem();
|
|
d->layoutItems << lineItem;
|
|
if( orientation() == Qt::Vertical ){
|
|
d->layout->addItem( lineItem, 1, 0, 1, 5, Qt::AlignCenter );
|
|
}else{
|
|
// we have 1+count*4 columns, because we have both, a leading and a trailing spacer
|
|
d->layout->addItem( lineItem, 1, 0, 1, 1+d->modelLabels.count()*4, Qt::AlignCenter );
|
|
}
|
|
}
|
|
}
|
|
|
|
const KDChartEnums::MeasureOrientation orient =
|
|
(orientation() == Qt::Vertical)
|
|
? KDChartEnums::MeasureOrientationMinimum
|
|
: KDChartEnums::MeasureOrientationHorizontal;
|
|
const TextAttributes labelAttrs( textAttributes() );
|
|
const qreal fontHeight = labelAttrs.calculatedFontSize( referenceArea(), orient );
|
|
const LegendStyle style = legendStyle();
|
|
//qDebug() << "fontHeight:" << fontHeight;
|
|
|
|
const bool bShowMarkers = (style != LinesOnly);
|
|
|
|
QSizeF maxMarkersSize(1.0, 1.0);
|
|
QVector <MarkerAttributes> markerAttrs( d->modelLabels.count() );
|
|
if( bShowMarkers ){
|
|
for ( int dataset = 0; dataset < d->modelLabels.count(); ++dataset ) {
|
|
markerAttrs[dataset] = markerAttributes( dataset );
|
|
QSizeF siz;
|
|
if( useAutomaticMarkerSize() ||
|
|
! markerAttrs[dataset].markerSize().isValid() )
|
|
{
|
|
siz = QSizeF(fontHeight, fontHeight);
|
|
markerAttrs[dataset].setMarkerSize( siz );
|
|
}else{
|
|
siz = markerAttrs[dataset].markerSize();
|
|
}
|
|
maxMarkersSize =
|
|
QSizeF(qMax(maxMarkersSize.width(), siz.width()),
|
|
qMax(maxMarkersSize.height(), siz.height()));
|
|
}
|
|
}
|
|
|
|
// 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 markerOffsOnLine = 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 );
|
|
if( ps != Qt::SolidLine )
|
|
hasComplexPenStyle = true;
|
|
}
|
|
}
|
|
if( hasComplexPenStyle && bShowMarkers )
|
|
maxLineLength =
|
|
maxLineLength + markerOffsOnLine +
|
|
static_cast<int>(maxMarkersSize.width());
|
|
}
|
|
|
|
#define ADD_MARKER_SPACER_FOR_HORIZONTAL_MODE( column ) \
|
|
{ \
|
|
if( orientation() != Qt::Vertical ) \
|
|
d->layout->addItem( new QSpacerItem( spacing(), 1 ), \
|
|
2, \
|
|
column ); \
|
|
}
|
|
|
|
// Horizontal needs a leading spacer
|
|
ADD_MARKER_SPACER_FOR_HORIZONTAL_MODE( 0 )
|
|
|
|
// for all datasets: add (line)marker items and text items to the layout
|
|
for ( int dataset = 0; dataset < d->modelLabels.count(); ++dataset ) {
|
|
KDChart::AbstractLayoutItem* markerLineItem = 0;
|
|
// It is possible to set the marker brush both through the MarkerAttributes,
|
|
// as well as through the dataset brush set in the diagram, whereas the
|
|
// MarkerAttributes are preferred.
|
|
const QBrush markerBrush = markerAttrs[dataset].markerColor().isValid() ?
|
|
QBrush(markerAttrs[dataset].markerColor()) : brush( dataset );
|
|
switch( style ){
|
|
case( MarkersOnly ):
|
|
markerLineItem = new KDChart::MarkerLayoutItem(
|
|
diagram(),
|
|
markerAttrs[dataset],
|
|
markerBrush,
|
|
markerAttrs[dataset].pen(),
|
|
Qt::AlignLeft );
|
|
break;
|
|
case( LinesOnly ):
|
|
markerLineItem = new KDChart::LineLayoutItem(
|
|
diagram(),
|
|
maxLineLength,
|
|
pen( dataset ),
|
|
Qt::AlignCenter );
|
|
break;
|
|
case( MarkersAndLines ):
|
|
markerLineItem = new KDChart::LineWithMarkerLayoutItem(
|
|
diagram(),
|
|
maxLineLength,
|
|
pen( dataset ),
|
|
markerOffsOnLine,
|
|
markerAttrs[dataset],
|
|
markerBrush,
|
|
markerAttrs[dataset].pen(),
|
|
Qt::AlignCenter );
|
|
break;
|
|
default:
|
|
Q_ASSERT( false ); // all styles need to be handled
|
|
}
|
|
if( markerLineItem ){
|
|
d->layoutItems << markerLineItem;
|
|
if( orientation() == Qt::Vertical )
|
|
d->layout->addItem( markerLineItem,
|
|
dataset*2+2, // first row is title, second is line
|
|
1,
|
|
1, 1, Qt::AlignCenter );
|
|
else
|
|
d->layout->addItem( markerLineItem,
|
|
2, // all in row two
|
|
dataset*4+1 );
|
|
}
|
|
|
|
// PENDING(kalle) Other properties!
|
|
KDChart::TextLayoutItem* labelItem =
|
|
new KDChart::TextLayoutItem( text( dataset ),
|
|
labelAttrs,
|
|
referenceArea(), orient,
|
|
d->textAlignment );
|
|
labelItem->setParentWidget( this );
|
|
|
|
d->layoutItems << labelItem;
|
|
if( orientation() == Qt::Vertical )
|
|
d->layout->addItem( labelItem,
|
|
dataset*2+2, // first row is title, second is line
|
|
3 );
|
|
else
|
|
d->layout->addItem( labelItem,
|
|
2, // all in row two
|
|
dataset*4+2 );
|
|
|
|
// horizontal lines (only in vertical mode, and not after the last item)
|
|
if( orientation() == Qt::Vertical && showLines() && dataset != d->modelLabels.count()-1 ) {
|
|
KDChart::HorizontalLineLayoutItem* lineItem = new KDChart::HorizontalLineLayoutItem();
|
|
d->layoutItems << lineItem;
|
|
d->layout->addItem( lineItem,
|
|
dataset*2+1+2,
|
|
0,
|
|
1, 5, Qt::AlignCenter );
|
|
}
|
|
|
|
// vertical lines (only in horizontal mode, and not after the last item)
|
|
if( orientation() == Qt::Horizontal && showLines() && dataset != d->modelLabels.count()-1 ) {
|
|
KDChart::VerticalLineLayoutItem* lineItem = new KDChart::VerticalLineLayoutItem();
|
|
d->layoutItems << lineItem;
|
|
d->layout->addItem( lineItem,
|
|
2, // all in row two
|
|
style == MarkersAndLines ? dataset*4+4 : dataset*4+3 ,
|
|
1, 1, Qt::AlignCenter );
|
|
}
|
|
|
|
// Horizontal needs a spacer
|
|
ADD_MARKER_SPACER_FOR_HORIZONTAL_MODE( dataset*4+4 )
|
|
}
|
|
|
|
// vertical line (only in vertical mode)
|
|
if( orientation() == Qt::Vertical && showLines() && d->modelLabels.count() ) {
|
|
KDChart::VerticalLineLayoutItem* lineItem = new KDChart::VerticalLineLayoutItem();
|
|
d->layoutItems << lineItem;
|
|
d->layout->addItem( lineItem, 2, 2, d->modelLabels.count()*2, 1 );
|
|
}
|
|
|
|
// This line is absolutely necessary, otherwise: #2516.
|
|
activateTheLayout();
|
|
|
|
emit propertiesChanged();
|
|
//emit positionChanged( this );
|
|
//emitPositionChanged();
|
|
#ifdef DEBUG_LEGEND_PAINT
|
|
qDebug() << "leaving Legend::buildLegend()";
|
|
#endif
|
|
}
|
|
|
|
void Legend::setHiddenDatasets( const QList<uint> hiddenDatasets )
|
|
{
|
|
d->hiddenDatasets = hiddenDatasets;
|
|
}
|
|
|
|
const QList<uint> 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 );
|
|
}
|
|
|