diff --git a/src/KChart/Cartesian/KChartStockDiagram.cpp b/src/KChart/Cartesian/KChartStockDiagram.cpp
index 71abece..b5abd2e 100644
--- a/src/KChart/Cartesian/KChartStockDiagram.cpp
+++ b/src/KChart/Cartesian/KChartStockDiagram.cpp
@@ -1,378 +1,378 @@
/*
* 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 "KChartStockDiagram.h"
#include "KChartStockDiagram_p.h"
#include "KChartPaintContext.h"
#include "KChartPainterSaver_p.h"
using namespace KChart;
#define d d_func()
StockDiagram::StockDiagram( QWidget *parent, CartesianCoordinatePlane *plane )
: AbstractCartesianDiagram( new Private(), parent, plane )
{
init();
}
StockDiagram::~StockDiagram()
{
}
-/**
+/*
* Initializes the diagram
*/
void StockDiagram::init()
{
d->diagram = this;
d->compressor.setModel( attributesModel() );
// Set properties to defaults
d->type = HighLowClose;
d->upTrendCandlestickBrush = QBrush( Qt::white );
d->downTrendCandlestickBrush = QBrush( Qt::black );
d->upTrendCandlestickPen = QPen( Qt::black );
d->downTrendCandlestickPen = QPen( Qt::black );
d->lowHighLinePen = QPen( Qt::black );
setDatasetDimensionInternal( 3 );
//setDatasetDimension( 3 );
setPen( QPen( Qt::black ) );
}
/**
* Switches between the supported types of stock charts,
* depending on \a type
*/
void StockDiagram::setType( Type type )
{
d->type = type;
emit propertiesChanged();
}
/**
* @return the type of this diagram
*/
StockDiagram::Type StockDiagram::type() const
{
return d->type;
}
void StockDiagram::setStockBarAttributes( const StockBarAttributes &attr )
{
attributesModel()->setModelData(
QVariant::fromValue( attr ),
StockBarAttributesRole );
emit propertiesChanged();
}
StockBarAttributes StockDiagram::stockBarAttributes() const
{
return attributesModel()->modelData( StockBarAttributesRole ).value();
}
void StockDiagram::setStockBarAttributes( int column, const StockBarAttributes &attr )
{
d->setDatasetAttrs( column, QVariant::fromValue( attr ), StockBarAttributesRole );
emit propertiesChanged();
}
StockBarAttributes StockDiagram::stockBarAttributes( int column ) const
{
const QVariant attr( d->datasetAttrs( column, StockBarAttributesRole ) );
if ( attr.isValid() )
return attr.value();
return stockBarAttributes();
}
/**
* Sets the 3D attributes for all bars (i.e. candlesticks)
*
* @param attr The 3D attributes to set
*/
void StockDiagram::setThreeDBarAttributes( const ThreeDBarAttributes &attr )
{
attributesModel()->setModelData(
QVariant::fromValue( attr ),
ThreeDBarAttributesRole );
emit propertiesChanged();
}
/**
* Returns the 3D attributes for all bars (i.e. candlesticks)
*
* @return the 3D bar attributes
*/
ThreeDBarAttributes StockDiagram::threeDBarAttributes() const
{
return attributesModel()->modelData( ThreeDBarAttributesRole ).value();
}
/**
* Sets the 3D attributes for the bar (i.e. candlestick) in certain column
* of the diagram
*
* Note: Every column in a StockDiagram is represented by a row in the model
*
* @param column The column to set the 3D bar attributes for
* @param attr The 3D attributes to set
*/
void StockDiagram::setThreeDBarAttributes( int column, const ThreeDBarAttributes &attr )
{
d->setDatasetAttrs( column, QVariant::fromValue( attr ), StockBarAttributesRole );
emit propertiesChanged();
}
/**
* Returns the 3D attributes for a bars (i.e. candlestick) in a certain column
* of the diagram
*
* Note: Every column in a StockDiagram is represented by a row in the model
*
* @param column The column to get the 3D bar attributes for
* @return The 3D attributes for the specified column
*/
ThreeDBarAttributes StockDiagram::threeDBarAttributes( int column ) const
{
const QVariant attr( d->datasetAttrs( column, ThreeDBarAttributesRole ) );
if ( attr.isValid() )
return attr.value();
return threeDBarAttributes();
}
void StockDiagram::setLowHighLinePen( const QPen &pen )
{
d->lowHighLinePen = pen;
}
QPen StockDiagram::lowHighLinePen() const
{
return d->lowHighLinePen;
}
void StockDiagram::setLowHighLinePen( int column, const QPen &pen )
{
d->lowHighLinePens[column] = pen;
}
QPen StockDiagram::lowHighLinePen( int column ) const
{
if ( d->lowHighLinePens.contains( column ) )
return d->lowHighLinePens[column];
return d->lowHighLinePen;
}
void StockDiagram::setUpTrendCandlestickBrush( const QBrush &brush )
{
d->upTrendCandlestickBrush = brush;
}
QBrush StockDiagram::upTrendCandlestickBrush() const
{
return d->upTrendCandlestickBrush;
}
void StockDiagram::setDownTrendCandlestickBrush( const QBrush &brush )
{
d->downTrendCandlestickBrush = brush;
}
QBrush StockDiagram::downTrendCandlestickBrush() const
{
return d->downTrendCandlestickBrush;
}
void StockDiagram::setUpTrendCandlestickBrush( int column, const QBrush &brush )
{
d->upTrendCandlestickBrushes[column] = brush;
}
QBrush StockDiagram::upTrendCandlestickBrush( int column ) const
{
if ( d->upTrendCandlestickBrushes.contains( column ) )
return d->upTrendCandlestickBrushes[column];
return d->upTrendCandlestickBrush;
}
void StockDiagram::setDownTrendCandlestickBrush( int column, const QBrush &brush )
{
d->downTrendCandlestickBrushes[column] = brush;
}
QBrush StockDiagram::downTrendCandlestickBrush( int column ) const
{
if ( d->downTrendCandlestickBrushes.contains( column ) )
return d->downTrendCandlestickBrushes[column];
return d->downTrendCandlestickBrush;
}
void StockDiagram::setUpTrendCandlestickPen( const QPen &pen )
{
d->upTrendCandlestickPen = pen;
}
QPen StockDiagram::upTrendCandlestickPen() const
{
return d->upTrendCandlestickPen;
}
void StockDiagram::setDownTrendCandlestickPen( const QPen &pen )
{
d->downTrendCandlestickPen = pen;
}
QPen StockDiagram::downTrendCandlestickPen() const
{
return d->downTrendCandlestickPen;
}
void StockDiagram::setUpTrendCandlestickPen( int column, const QPen &pen )
{
d->upTrendCandlestickPens[column] = pen;
}
QPen StockDiagram::upTrendCandlestickPen( int column ) const
{
if ( d->upTrendCandlestickPens.contains( column ) )
return d->upTrendCandlestickPens[column];
return d->upTrendCandlestickPen;
}
void StockDiagram::setDownTrendCandlestickPen( int column, const QPen &pen )
{
d->downTrendCandlestickPens[column] = pen;
}
QPen StockDiagram::downTrendCandlestickPen( int column ) const
{
if ( d->downTrendCandlestickPens.contains( column ) )
return d->downTrendCandlestickPens[column];
return d->downTrendCandlestickPen;
}
#if defined(Q_COMPILER_MANGLES_RETURN_TYPE)
const
#endif
int StockDiagram::numberOfAbscissaSegments() const { return 1; }
#if defined(Q_COMPILER_MANGLES_RETURN_TYPE)
const
#endif
int StockDiagram::numberOfOrdinateSegments() const { return 1; }
void StockDiagram::paint( PaintContext *context )
{
// Clear old reverse mapping data and create new
// reverse mapping scene
d->reverseMapper.clear();
PainterSaver painterSaver( context->painter() );
const int rowCount = attributesModel()->rowCount( attributesModelRootIndex() );
const int divisor = ( d->type == OpenHighLowClose || d->type == Candlestick ) ? 4 : 3;
const int colCount = attributesModel()->columnCount( attributesModelRootIndex() ) / divisor;
for ( int col = 0; col < colCount; ++col )
{
for ( int row = 0; row < rowCount; row++ ) {
CartesianDiagramDataCompressor::DataPoint low;
CartesianDiagramDataCompressor::DataPoint high;
CartesianDiagramDataCompressor::DataPoint open;
CartesianDiagramDataCompressor::DataPoint close;
CartesianDiagramDataCompressor::DataPoint volume;
if ( d->type == HighLowClose ) {
const CartesianDiagramDataCompressor::CachePosition highPos( row, col * divisor );
const CartesianDiagramDataCompressor::CachePosition lowPos( row, col * divisor + 1 );
const CartesianDiagramDataCompressor::CachePosition closePos( row, col * divisor + 2 );
low = d->compressor.data( lowPos );
high = d->compressor.data( highPos );
close = d->compressor.data( closePos );
} else if ( d->type == OpenHighLowClose || d->type == Candlestick ) {
const CartesianDiagramDataCompressor::CachePosition openPos( row, col * divisor );
const CartesianDiagramDataCompressor::CachePosition highPos( row, col * divisor + 1 );
const CartesianDiagramDataCompressor::CachePosition lowPos( row, col * divisor + 2 );
const CartesianDiagramDataCompressor::CachePosition closePos( row, col * divisor + 3 );
open = d->compressor.data( openPos );
low = d->compressor.data( lowPos );
high = d->compressor.data( highPos );
close = d->compressor.data( closePos );
}
switch ( d->type ) {
case HighLowClose:
open.hidden = true;
// Fall-through intended!
case OpenHighLowClose:
if ( close.index.isValid() && low.index.isValid() && high.index.isValid() )
d->drawOHLCBar( col, open, high, low, close, context );
break;
case Candlestick:
d->drawCandlestick( col, open, high, low, close, context );
break;
}
}
}
}
void StockDiagram::resize( const QSizeF &size )
{
d->compressor.setResolution( static_cast< int >( size.width() * coordinatePlane()->zoomFactorX() ),
static_cast< int >( size.height() * coordinatePlane()->zoomFactorY() ) );
setDataBoundariesDirty();
AbstractCartesianDiagram::resize( size );
}
qreal StockDiagram::threeDItemDepth( int column ) const
{
Q_UNUSED( column );
//FIXME: Implement threeD functionality
return 1.0;
}
qreal StockDiagram::threeDItemDepth( const QModelIndex &index ) const
{
Q_UNUSED( index );
//FIXME: Implement threeD functionality
return 1.0;
}
const QPair StockDiagram::calculateDataBoundaries() const
{
const int rowCount = attributesModel()->rowCount( attributesModelRootIndex() );
const int colCount = attributesModel()->columnCount( attributesModelRootIndex() );
qreal xMin = 0.0;
qreal xMax = rowCount;
qreal yMin = 0.0;
qreal yMax = 0.0;
for ( int row = 0; row < rowCount; row++ ) {
for ( int col = 0; col < colCount; col++ ) {
const CartesianDiagramDataCompressor::CachePosition pos( row, col );
const CartesianDiagramDataCompressor::DataPoint point = d->compressor.data( pos );
yMax = qMax( yMax, point.value );
yMin = qMin( yMin, point.value ); // FIXME: Can stock charts really have negative values?
}
}
return QPair( QPointF( xMin, yMin ), QPointF( xMax, yMax ) );
}
diff --git a/src/KChart/Cartesian/KChartStockDiagram_p.cpp b/src/KChart/Cartesian/KChartStockDiagram_p.cpp
index ae6beb8..b234a37 100644
--- a/src/KChart/Cartesian/KChartStockDiagram_p.cpp
+++ b/src/KChart/Cartesian/KChartStockDiagram_p.cpp
@@ -1,537 +1,537 @@
/*
* 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 "KChartStockDiagram_p.h"
#include "KChartPainterSaver_p.h"
using namespace KChart;
class Q_DECL_HIDDEN StockDiagram::Private::ThreeDPainter
{
public:
struct ThreeDProperties {
qreal depth;
qreal angle;
bool useShadowColors;
};
ThreeDPainter( QPainter *p )
: painter( p ) {};
QPolygonF drawTwoDLine( const QLineF &line, const QPen &pen,
const ThreeDProperties &props );
QPolygonF drawThreeDLine( const QLineF &line, const QBrush &brush,
const QPen &pen, const ThreeDProperties &props );
QPolygonF drawThreeDRect( const QRectF &rect, const QBrush &brush,
const QPen &pen, const ThreeDProperties &props );
private:
QPointF projectPoint( const QPointF &point, qreal depth, qreal angle ) const;
QColor calcShadowColor( const QColor &color, qreal angle ) const;
QPainter *painter;
};
-/**
+/*
* Projects a point in 3D space
*
* @param depth The distance from the point and the projected point
* @param angle The angle the projected point is rotated by around the original point
*/
QPointF StockDiagram::Private::ThreeDPainter::projectPoint( const QPointF &point, qreal depth, qreal angle ) const
{
const qreal angleInRad = DEGTORAD( angle );
const qreal distX = depth * cos( angleInRad );
// Y coordinates are reversed on our coordinate plane
const qreal distY = depth * -sin( angleInRad );
return QPointF( point.x() + distX, point.y() + distY );
}
-/**
+/*
* Returns the shadow color for a given color, depending on the angle of rotation
*
* @param color The color to calculate the shadow color for
* @param angle The angle that the colored area is rotated by
*/
QColor StockDiagram::Private::ThreeDPainter::calcShadowColor( const QColor &color, qreal angle ) const
{
// The shadow factor determines to how many percent the brightness
// of the color can be reduced. That is, the darkest shadow color
// is color * shadowFactor.
const qreal shadowFactor = 0.5;
const qreal sinAngle = 1.0 - qAbs( sin( DEGTORAD( angle ) ) ) * shadowFactor;
return QColor( qRound( color.red() * sinAngle ),
qRound( color.green() * sinAngle ),
qRound( color.blue() * sinAngle ) );
}
-/**
+/*
* Draws a 2D line in 3D space by painting it with a z-coordinate of props.depth / 2.0
*
* @param line The line to draw
* @param pen The pen to use to draw the line
* @param props The 3D properties to draw the line with
* @return The drawn line, but with a width of 2px, as a polygon
*/
QPolygonF StockDiagram::Private::ThreeDPainter::drawTwoDLine( const QLineF &line, const QPen &pen,
const ThreeDProperties &props )
{
// Restores the painting properties when destroyed
PainterSaver painterSaver( painter );
// The z coordinate to use (i.e., at what depth to draw the line)
const qreal z = props.depth / 2.0;
// Projec the 2D points of the line in 3D
const QPointF deepP1 = projectPoint( line.p1(), z, props.angle );
const QPointF deepP2 = projectPoint( line.p2(), z, props.angle );
// The drawn line with a width of 2px
QPolygonF threeDArea;
// The offset of the line "borders" from the center to each side
const QPointF offset( 0.0, 1.0 );
threeDArea << deepP1 - offset << deepP2 - offset
<< deepP1 + offset << deepP2 + offset << deepP1 - offset;
painter->setPen( pen );
painter->drawLine( QLineF( deepP1, deepP2 ) );
return threeDArea;
}
-/**
+/*
* Draws an ordinary line in 3D by expanding it in the z-axis by the given depth.
*
* @param line The line to draw
* @param brush The brush to fill the resulting polygon with
* @param pen The pen to paint the borders of the resulting polygon with
* @param props The 3D properties to draw the line with
* @return The 3D shape drawn
*/
QPolygonF StockDiagram::Private::ThreeDPainter::drawThreeDLine( const QLineF &line, const QBrush &brush,
const QPen &pen, const ThreeDProperties &props )
{
// Restores the painting properties when destroyed
PainterSaver painterSaver( painter );
const QPointF p1 = line.p1();
const QPointF p2 = line.p2();
// Project the 2D points of the line in 3D
const QPointF deepP1 = projectPoint( p1, props.depth, props.angle );
const QPointF deepP2 = projectPoint( p2, props.depth, props.angle );
// The result is a 3D representation of the 2D line
QPolygonF threeDArea;
threeDArea << p1 << p2 << deepP2 << deepP1 << p1;
// Use shadow colors if ThreeDProperties::useShadowColors is set
// Note: Setting a new color on a brush or pen does not effect gradients or textures
if ( props.useShadowColors ) {
QBrush shadowBrush( brush );
QPen shadowPen( pen );
shadowBrush.setColor( calcShadowColor( brush.color(), props.angle ) );
shadowPen.setColor( calcShadowColor( pen.color(), props.angle ) );
painter->setBrush( shadowBrush );
painter->setPen( shadowPen );
} else {
painter->setBrush( brush );
painter->setPen( pen );
}
painter->drawPolygon( threeDArea );
return threeDArea;
}
-/**
+/*
* Draws a 3D cuboid by extending a 2D rectangle in the z-axis
*
* @param rect The rectangle to draw
* @param brush The brush fill the surfaces of the cuboid with
* @param pen The pen to draw the edges with
* @param props The 3D properties to use for drawing the cuboid
* @return The drawn cuboid as a polygon
*/
QPolygonF StockDiagram::Private::ThreeDPainter::drawThreeDRect( const QRectF &rect, const QBrush &brush,
const QPen &pen, const ThreeDProperties &props )
{
// Restores the painting properties when destroyed
PainterSaver painterSaver( painter );
// Make sure that the top really is the top
const QRectF normalizedRect = rect.normalized();
// Calculate all the four sides of the rectangle
const QLineF topSide = QLineF( normalizedRect.topLeft(), normalizedRect.topRight() );
const QLineF bottomSide = QLineF( normalizedRect.bottomLeft(), normalizedRect.bottomRight() );
const QLineF leftSide = QLineF( normalizedRect.topLeft(), normalizedRect.bottomLeft() );
const QLineF rightSide = QLineF( normalizedRect.topRight(), normalizedRect.bottomRight() );
QPolygonF drawnPolygon;
// Shorter names are easier on the eyes
const qreal angle = props.angle;
// Only top and right side is visible
if ( angle >= 0.0 && angle < 90.0 ) {
drawnPolygon = drawnPolygon.united( drawThreeDLine( topSide, brush, pen, props ) );
drawnPolygon = drawnPolygon.united( drawThreeDLine( rightSide, brush, pen, props ) );
// Only top and left side is visible
} else if ( angle >= 90.0 && angle < 180.0 ) {
drawnPolygon = drawnPolygon.united( drawThreeDLine( topSide, brush, pen, props ) );
drawnPolygon = drawnPolygon.united( drawThreeDLine( leftSide, brush, pen, props ) );
// Only bottom and left side is visible
} else if ( angle >= 180.0 && angle < 270.0 ) {
drawnPolygon = drawnPolygon.united( drawThreeDLine( bottomSide, brush, pen, props ) );
drawnPolygon = drawnPolygon.united( drawThreeDLine( leftSide, brush, pen, props ) );
// Only bottom and right side is visible
} else if ( angle >= 270.0 && angle <= 360.0 ) {
drawnPolygon = drawnPolygon.united( drawThreeDLine( bottomSide, brush, pen, props ) );
drawnPolygon = drawnPolygon.united( drawThreeDLine( rightSide, brush, pen, props ) );
}
// Draw the front side
painter->setPen( pen );
painter->setBrush( brush );
painter->drawRect( normalizedRect );
return drawnPolygon;
}
StockDiagram::Private::Private()
: AbstractCartesianDiagram::Private()
{
}
StockDiagram::Private::Private( const Private& r )
: AbstractCartesianDiagram::Private( r )
{
}
StockDiagram::Private::~Private()
{
}
-/**
+/*
* Projects a point onto the coordinate plane
*
* @param context The context to paint the point in
* @point The point to project onto the coordinate plane
* @return The projected point
*/
QPointF StockDiagram::Private::projectPoint( PaintContext *context, const QPointF &point ) const
{
return context->coordinatePlane()->translate( QPointF( point.x() + 0.5, point.y() ) );
}
-/**
+/*
* Projects a candlestick onto the coordinate plane
*
* @param context The context to paint the candlestick in
* @param low The
*/
QRectF StockDiagram::Private::projectCandlestick( PaintContext *context, const QPointF &open, const QPointF &close, qreal width ) const
{
const QPointF leftHighPoint = context->coordinatePlane()->translate( QPointF( close.x() + 0.5 - width / 2.0, close.y() ) );
const QPointF rightLowPoint = context->coordinatePlane()->translate( QPointF( open.x() + 0.5 + width / 2.0, open.y() ) );
const QPointF rightHighPoint = context->coordinatePlane()->translate( QPointF( close.x() + 0.5 + width / 2.0, close.y() ) );
return QRectF( leftHighPoint, QSizeF( rightHighPoint.x() - leftHighPoint.x(),
rightLowPoint.y() - leftHighPoint.y() ) );
}
void StockDiagram::Private::drawOHLCBar( int dataset, const CartesianDiagramDataCompressor::DataPoint &open,
const CartesianDiagramDataCompressor::DataPoint &high,
const CartesianDiagramDataCompressor::DataPoint &low,
const CartesianDiagramDataCompressor::DataPoint &close,
PaintContext *context )
{
// Note: A row in the model is a column in a StockDiagram
const int col = low.index.row();
StockBarAttributes attr = stockDiagram()->stockBarAttributes( col );
ThreeDBarAttributes threeDAttr = stockDiagram()->threeDBarAttributes( col );
const qreal tickLength = attr.tickLength();
const QPointF leftOpenPoint( open.key + 0.5 - tickLength, open.value );
const QPointF rightOpenPoint( open.key + 0.5, open.value );
const QPointF highPoint( high.key + 0.5, high.value );
const QPointF lowPoint( low.key + 0.5, low.value );
const QPointF leftClosePoint( close.key + 0.5, close.value );
const QPointF rightClosePoint( close.key + 0.5 + tickLength, close.value );
bool reversedOrder = false;
// If 3D mode is enabled, we have to make sure the z-order is right
if ( threeDAttr.isEnabled() ) {
const int angle = threeDAttr.angle();
// Z-order is from right to left
if ( ( angle >= 0 && angle < 90 ) || ( angle >= 180 && angle < 270 ) )
reversedOrder = true;
// Z-order is from left to right
if ( ( angle >= 90 && angle < 180 ) || ( angle >= 270 && angle <= 360 ) )
reversedOrder = false;
}
if ( reversedOrder ) {
if ( !open.hidden )
drawLine( dataset, col, leftOpenPoint, rightOpenPoint, context ); // Open marker
if ( !low.hidden && !high.hidden )
drawLine( dataset, col, lowPoint, highPoint, context ); // Low-High line
if ( !close.hidden )
drawLine( dataset, col, leftClosePoint, rightClosePoint, context ); // Close marker
} else {
if ( !close.hidden )
drawLine( dataset, col, leftClosePoint, rightClosePoint, context ); // Close marker
if ( !low.hidden && !high.hidden )
drawLine( dataset, col, lowPoint, highPoint, context ); // Low-High line
if ( !open.hidden )
drawLine( dataset, col, leftOpenPoint, rightOpenPoint, context ); // Open marker
}
LabelPaintCache lpc;
if ( !open.hidden ) {
addLabel( &lpc, diagram->attributesModel()->mapToSource( open.index ), nullptr,
PositionPoints( leftOpenPoint ), Position::South, Position::South, open.value );
}
if ( !high.hidden ) {
addLabel( &lpc, diagram->attributesModel()->mapToSource( high.index ), nullptr,
PositionPoints( highPoint ), Position::South, Position::South, high.value );
}
if ( !low.hidden ) {
addLabel( &lpc, diagram->attributesModel()->mapToSource( low.index ), nullptr,
PositionPoints( lowPoint ), Position::South, Position::South, low.value );
}
if ( !close.hidden ) {
addLabel( &lpc, diagram->attributesModel()->mapToSource( close.index ), nullptr,
PositionPoints( rightClosePoint ), Position::South, Position::South, close.value );
}
paintDataValueTextsAndMarkers( context, lpc, false );
}
-/**
+/*
* Draws a line connecting the low and the high value of an OHLC chart
*
* @param low The low data point
* @param high The high data point
* @param context The context to draw the candlestick in
*/
void StockDiagram::Private::drawCandlestick( int /*dataset*/, const CartesianDiagramDataCompressor::DataPoint &open,
const CartesianDiagramDataCompressor::DataPoint &high,
const CartesianDiagramDataCompressor::DataPoint &low,
const CartesianDiagramDataCompressor::DataPoint &close,
PaintContext *context )
{
PainterSaver painterSaver( context->painter() );
// Note: A row in the model is a column in a StockDiagram, and the other way around
const int row = low.index.row();
const int col = low.index.column();
QPointF bottomCandlestickPoint;
QPointF topCandlestickPoint;
QBrush brush;
QPen pen;
bool drawLowerLine;
bool drawCandlestick = !open.hidden && !close.hidden;
bool drawUpperLine;
// Find out if we need to paint a down-trend or up-trend candlestick
// and set brush and pen accordingly
// Also, determine what the top and bottom points of the candlestick are
if ( open.value <= close.value ) {
pen = stockDiagram()->upTrendCandlestickPen( row );
brush = stockDiagram()->upTrendCandlestickBrush( row );
bottomCandlestickPoint = QPointF( open.key, open.value );
topCandlestickPoint = QPointF( close.key, close.value );
drawLowerLine = !low.hidden && !open.hidden;
drawUpperLine = !low.hidden && !close.hidden;
} else {
pen = stockDiagram()->downTrendCandlestickPen( row );
brush = stockDiagram()->downTrendCandlestickBrush( row );
bottomCandlestickPoint = QPointF( close.key, close.value );
topCandlestickPoint = QPointF( open.key, open.value );
drawLowerLine = !low.hidden && !close.hidden;
drawUpperLine = !low.hidden && !open.hidden;
}
StockBarAttributes attr = stockDiagram()->stockBarAttributes( col );
ThreeDBarAttributes threeDAttr = stockDiagram()->threeDBarAttributes( col );
const QPointF lowPoint = projectPoint( context, QPointF( low.key, low.value ) );
const QPointF highPoint = projectPoint( context, QPointF( high.key, high.value ) );
const QLineF lowerLine = QLineF( lowPoint, projectPoint( context, bottomCandlestickPoint ) );
const QLineF upperLine = QLineF( projectPoint( context, topCandlestickPoint ), highPoint );
// Convert the data point into coordinates on the coordinate plane
QRectF candlestick = projectCandlestick( context, bottomCandlestickPoint,
topCandlestickPoint, attr.candlestickWidth() );
// Remember the drawn polygon to add it to the ReverseMapper later
QPolygonF drawnPolygon;
// Use the ThreeDPainter class to draw a 3D candlestick
if ( threeDAttr.isEnabled() ) {
ThreeDPainter threeDPainter( context->painter() );
ThreeDPainter::ThreeDProperties threeDProps;
threeDProps.depth = threeDAttr.depth();
threeDProps.angle = threeDAttr.angle();
threeDProps.useShadowColors = threeDAttr.useShadowColors();
// If the perspective angle is within [0,180], we paint from bottom to top,
// otherwise from top to bottom to ensure the correct z order
if ( threeDProps.angle > 0.0 && threeDProps.angle < 180.0 ) {
if ( drawLowerLine )
drawnPolygon = threeDPainter.drawTwoDLine( lowerLine, pen, threeDProps );
if ( drawCandlestick )
drawnPolygon = threeDPainter.drawThreeDRect( candlestick, brush, pen, threeDProps );
if ( drawUpperLine )
drawnPolygon = threeDPainter.drawTwoDLine( upperLine, pen, threeDProps );
} else {
if ( drawUpperLine )
drawnPolygon = threeDPainter.drawTwoDLine( upperLine, pen, threeDProps );
if ( drawCandlestick )
drawnPolygon = threeDPainter.drawThreeDRect( candlestick, brush, pen, threeDProps );
if ( drawLowerLine )
drawnPolygon = threeDPainter.drawTwoDLine( lowerLine, pen, threeDProps );
}
} else {
QPainter *const painter = context->painter();
painter->setBrush( brush );
painter->setPen( pen );
if ( drawLowerLine )
painter->drawLine( lowerLine );
if ( drawUpperLine )
painter->drawLine( upperLine );
if ( drawCandlestick )
painter->drawRect( candlestick );
// The 2D representation is the projected candlestick itself
drawnPolygon = candlestick;
// FIXME: Add lower and upper line to reverse mapper
}
LabelPaintCache lpc;
if ( !low.hidden )
addLabel( &lpc, diagram->attributesModel()->mapToSource( low.index ), nullptr,
PositionPoints( lowPoint ), Position::South, Position::South, low.value );
if ( drawCandlestick ) {
// Both, the open as well as the close value are represented by this candlestick
reverseMapper.addPolygon( row, openValueColumn(), drawnPolygon );
reverseMapper.addPolygon( row, closeValueColumn(), drawnPolygon );
addLabel( &lpc, diagram->attributesModel()->mapToSource( open.index ), nullptr,
PositionPoints( candlestick.bottomRight() ), Position::South, Position::South, open.value );
addLabel( &lpc, diagram->attributesModel()->mapToSource( close.index ), nullptr,
PositionPoints( candlestick.topRight() ), Position::South, Position::South, close.value );
}
if ( !high.hidden )
addLabel( &lpc, diagram->attributesModel()->mapToSource( high.index ), nullptr,
PositionPoints( highPoint ), Position::South, Position::South, high.value );
paintDataValueTextsAndMarkers( context, lpc, false );
}
-/**
+/*
* Draws a line connecting two points
*
* @param col The column of the diagram to paint the line in
* @param point1 The first point
* @param point2 The second point
* @param context The context to draw the low-high line in
*/
void StockDiagram::Private::drawLine( int dataset, int col, const QPointF &point1, const QPointF &point2, PaintContext *context )
{
PainterSaver painterSaver( context->painter() );
// A row in the model is a column in the diagram
const int modelRow = col;
const int modelCol = 0;
const QPen pen = diagram->pen( dataset );
const QBrush brush = diagram->brush( dataset );
const ThreeDBarAttributes threeDBarAttr = stockDiagram()->threeDBarAttributes( col );
QPointF transP1 = context->coordinatePlane()->translate( point1 );
QPointF transP2 = context->coordinatePlane()->translate( point2 );
QLineF line = QLineF( transP1, transP2 );
if ( threeDBarAttr.isEnabled() ) {
ThreeDPainter::ThreeDProperties threeDProps;
threeDProps.angle = threeDBarAttr.angle();
threeDProps.depth = threeDBarAttr.depth();
threeDProps.useShadowColors = threeDBarAttr.useShadowColors();
ThreeDPainter painter( context->painter() );
reverseMapper.addPolygon( modelCol, modelRow, painter.drawThreeDLine( line, brush, pen, threeDProps ) );
} else {
context->painter()->setPen( pen );
//context->painter()->setBrush( brush );
reverseMapper.addLine( modelCol, modelRow, transP1, transP2 );
context->painter()->drawLine( line );
}
}
-/**
+/*
* Returns the column of the open value in the model
*
* @return The column of the open value
*/
int StockDiagram::Private::openValueColumn() const
{
// Return an invalid column if diagram has no open values
return type == HighLowClose ? -1 : 0;
}
-/**
+/*
* Returns the column of the high value in the model
*
* @return The column of the high value
*/
int StockDiagram::Private::highValueColumn() const
{
return type == HighLowClose ? 0 : 1;
}
-/**
+/*
* Returns the column of the low value in the model
*
* @return The column of the low value
*/
int StockDiagram::Private::lowValueColumn() const
{
return type == HighLowClose ? 1 : 2;
}
-/**
+/*
* Returns the column of the close value in the model
*
* @return The column of the close value
*/
int StockDiagram::Private::closeValueColumn() const
{
return type == HighLowClose ? 2 : 3;
}
diff --git a/src/KChart/Cartesian/PaintingHelpers_p.cpp b/src/KChart/Cartesian/PaintingHelpers_p.cpp
index b2dcbb1..49c03bb 100644
--- a/src/KChart/Cartesian/PaintingHelpers_p.cpp
+++ b/src/KChart/Cartesian/PaintingHelpers_p.cpp
@@ -1,299 +1,299 @@
/*
* 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 "PaintingHelpers_p.h"
#include "KChartGlobal.h"
#include "KChartAbstractDiagram.h"
#include "KChartAbstractDiagram_p.h"
#include "KChartCartesianCoordinatePlane.h"
#include "KChartLineDiagram.h"
#include "KChartValueTrackerAttributes.h"
#include "KChartPaintContext.h"
#include "KChartPainterSaver_p.h"
#include "KChartPlotter.h"
#include "KChartPrintingParameters.h"
#include "KChartLineAttributes.h"
#include "KChartThreeDLineAttributes.h"
#include "ReverseMapper.h"
namespace KChart {
namespace PaintingHelpers {
-/*!
+/*
Projects a point in a space defined by its x, y, and z coordinates
into a point on a plane, given two rotation angles around the x
resp. y axis.
*/
const QPointF project( const QPointF& point, const ThreeDLineAttributes& tdAttributes )
{
//Pending Michel FIXME - the rotation does not work as expected atm
qreal xrad = DEGTORAD( tdAttributes.lineXRotation() );
qreal yrad = DEGTORAD( tdAttributes.lineYRotation() );
return QPointF( point.x() * cos( yrad ) + tdAttributes.depth() * sin( yrad ),
point.y() * cos( xrad ) - tdAttributes.depth() * sin( xrad ) );
}
void paintPolyline( PaintContext* ctx, const QBrush& brush, const QPen& pen, const QPolygonF& points )
{
ctx->painter()->setBrush( brush );
ctx->painter()->setPen( PrintingParameters::scalePen(
QPen( pen.color(), pen.width(), pen.style(), Qt::FlatCap, Qt::MiterJoin ) ) );
ctx->painter()->drawPolyline( points );
}
void paintThreeDLines( PaintContext* ctx, AbstractDiagram *diagram, const QModelIndex& index,
const QPointF& from, const QPointF& to, const ThreeDLineAttributes& tdAttributes,
ReverseMapper* reverseMapper )
{
const QPointF topLeft = project( from, tdAttributes );
const QPointF topRight = project ( to, tdAttributes );
const QPolygonF segment = QPolygonF() << from << topLeft << topRight << to;
QBrush indexBrush( diagram->brush( index ) );
indexBrush = tdAttributes.threeDBrush( indexBrush, QRectF(topLeft, topRight) );
const PainterSaver painterSaver( ctx->painter() );
ctx->painter()->setRenderHint( QPainter::Antialiasing, diagram->antiAliasing() );
ctx->painter()->setBrush( indexBrush );
ctx->painter()->setPen( PrintingParameters::scalePen( diagram->pen( index ) ) );
reverseMapper->addPolygon( index.row(), index.column(), segment );
ctx->painter()->drawPolygon( segment );
}
void paintValueTracker( PaintContext* ctx, const ValueTrackerAttributes& vt, const QPointF& at )
{
CartesianCoordinatePlane* plane = qobject_cast( ctx->coordinatePlane() );
if ( !plane )
return;
DataDimensionsList gridDimensions = ctx->coordinatePlane()->gridDimensionsList();
const QPointF bottomLeft( ctx->coordinatePlane()->translate(
QPointF( plane->isHorizontalRangeReversed() ?
gridDimensions.at( 0 ).end :
gridDimensions.at( 0 ).start,
plane->isVerticalRangeReversed() ?
gridDimensions.at( 1 ).end :
gridDimensions.at( 1 ).start ) ) );
const QPointF topRight( ctx->coordinatePlane()->translate(
QPointF( plane->isHorizontalRangeReversed() ?
gridDimensions.at( 0 ).start :
gridDimensions.at( 0 ).end,
plane->isVerticalRangeReversed() ?
gridDimensions.at( 1 ).start :
gridDimensions.at( 1 ).end ) ) );
const QPointF markerPoint = at;
QPointF startPoint;
if ( vt.orientations() & Qt::Horizontal ) {
startPoint = QPointF( bottomLeft.x(), at.y() );
} else {
startPoint = QPointF( at.x(), topRight.y() );
}
QPointF endPoint;
if ( vt.orientations() & Qt::Vertical ) {
endPoint = QPointF( at.x(), bottomLeft.y() );
} else {
endPoint = QPointF( topRight.x(), at.y() );
}
const QSizeF markerSize = vt.markerSize();
const QRectF ellipseMarker = QRectF( at.x() - markerSize.width() / 2,
at.y() - markerSize.height() / 2,
markerSize.width(), markerSize.height() );
QPointF startMarker[3];
if ( vt.orientations() & Qt::Horizontal ) {
startMarker[0] = startPoint + QPointF( 0, markerSize.height() / 2 );
startMarker[1] = startPoint + QPointF( markerSize.width() / 2, 0 );
startMarker[2] = startPoint - QPointF( 0, markerSize.height() / 2 );
} else {
startMarker[0] = startPoint + QPointF( 0, markerSize.height() / 2 );
startMarker[1] = startPoint + QPointF( markerSize.width() / 2, 0 );
startMarker[2] = startPoint - QPointF( markerSize.width() / 2, 0 );
}
QPointF endMarker[3];
if ( vt.orientations() & Qt::Vertical ) {
endMarker[0] = endPoint + QPointF( markerSize.width() / 2, 0 );
endMarker[1] = endPoint - QPointF( 0, markerSize.height() / 2 );
endMarker[2] = endPoint - QPointF( markerSize.width() / 2, 0 );
} else {
endMarker[0] = endPoint + QPointF( 0, markerSize.width() / 2 );
endMarker[1] = endPoint - QPointF( 0, markerSize.height() / 2 );
endMarker[2] = endPoint - QPointF( markerSize.width() / 2, 0 );
}
QPointF topLeft = startPoint;
QPointF bottomRightOffset = endPoint - topLeft;
QSizeF size( bottomRightOffset.x(), bottomRightOffset.y() );
QRectF area( topLeft, size );
PainterSaver painterSaver( ctx->painter() );
ctx->painter()->setPen( PrintingParameters::scalePen( vt.linePen() ) );
ctx->painter()->setBrush( QBrush() );
ctx->painter()->drawLine( markerPoint, startPoint );
ctx->painter()->drawLine( markerPoint, endPoint );
ctx->painter()->fillRect( area, vt.areaBrush() );
ctx->painter()->setPen( PrintingParameters::scalePen( vt.markerPen() ) );
ctx->painter()->setBrush( vt.markerBrush() );
ctx->painter()->drawEllipse( ellipseMarker );
ctx->painter()->setPen( PrintingParameters::scalePen( vt.arrowBrush().color() ) );
ctx->painter()->setBrush( vt.arrowBrush() );
ctx->painter()->drawPolygon( startMarker, 3 );
ctx->painter()->drawPolygon( endMarker, 3 );
}
// ### for BC reasons we cannot insert a common interface for LineDiagram and Plotter into the class
// hierarchy, so we have to use hacks to use their common methods
static ThreeDLineAttributes threeDLineAttributes( AbstractDiagram* diagram, const QModelIndex& index )
{
if ( Plotter *plotter = qobject_cast< Plotter* >( diagram ) ) {
return plotter->threeDLineAttributes( index );
} else if ( LineDiagram *lineDiagram = qobject_cast< LineDiagram* >( diagram ) ) {
return lineDiagram->threeDLineAttributes( index );
}
Q_ASSERT( false );
return ThreeDLineAttributes();
}
static LineAttributes lineAttributes( AbstractDiagram* diagram, const QModelIndex& index )
{
if ( Plotter *plotter = qobject_cast< Plotter* >( diagram ) ) {
return plotter->lineAttributes( index );
} else if ( LineDiagram *lineDiagram = qobject_cast< LineDiagram* >( diagram ) ) {
return lineDiagram->lineAttributes( index );
}
Q_ASSERT( false );
return LineAttributes();
}
static ValueTrackerAttributes valueTrackerAttributes( AbstractDiagram* diagram, const QModelIndex& index )
{
if ( Plotter *plotter = qobject_cast< Plotter* >( diagram ) ) {
return plotter->valueTrackerAttributes( index );
} else if ( LineDiagram *lineDiagram = qobject_cast< LineDiagram* >( diagram ) ) {
return lineDiagram->valueTrackerAttributes( index );
}
Q_ASSERT( false );
return ValueTrackerAttributes();
}
void paintElements( AbstractDiagram::Private *diagramPrivate, PaintContext* ctx,
const LabelPaintCache& lpc, const LineAttributesInfoList& lineList )
{
AbstractDiagram* diagram = diagramPrivate->diagram;
// paint all lines and their attributes
const PainterSaver painterSaver( ctx->painter() );
ctx->painter()->setRenderHint( QPainter::Antialiasing, diagram->antiAliasing() );
QBrush curBrush;
QPen curPen;
QPolygonF points;
Q_FOREACH ( const LineAttributesInfo& lineInfo, lineList ) {
const QModelIndex& index = lineInfo.index;
const ThreeDLineAttributes td = threeDLineAttributes( diagram, index );
const LineAttributes la = lineAttributes( diagram, index );
if ( !la.isVisible() ) {
// Do not draw lines, but do draw text and markers
} else if( td.isEnabled() ){
PaintingHelpers::paintThreeDLines( ctx, diagram, index, lineInfo.value,
lineInfo.nextValue, td, &diagramPrivate->reverseMapper );
} else {
const QBrush brush( diagram->brush( index ) );
const QPen pen( diagram->pen( index ) );
// line goes from lineInfo.value to lineInfo.nextValue
diagramPrivate->reverseMapper.addLine( lineInfo.index.row(), lineInfo.index.column(),
lineInfo.value, lineInfo.nextValue );
if ( points.count() && points.last() == lineInfo.value && curBrush == brush && curPen == pen ) {
// continue the current run of lines
} else {
// different painter settings or discontinuous line: start a new run of lines
if ( points.count() ) {
PaintingHelpers::paintPolyline( ctx, curBrush, curPen, points );
}
curBrush = brush;
curPen = pen;
points.clear();
points << lineInfo.value;
}
points << lineInfo.nextValue;
}
}
if ( points.count() ) {
// the last run of lines is yet to be painted - do it now
PaintingHelpers::paintPolyline( ctx, curBrush, curPen, points );
}
Q_FOREACH ( const LineAttributesInfo& lineInfo, lineList ) {
const ValueTrackerAttributes vt = valueTrackerAttributes( diagram, lineInfo.index );
if ( vt.isEnabled() ) {
PaintingHelpers::paintValueTracker( ctx, vt, lineInfo.nextValue );
}
}
// paint all data value texts and the point markers
diagramPrivate->paintDataValueTextsAndMarkers( ctx, lpc, true );
}
void paintAreas( AbstractDiagram::Private* diagramPrivate, PaintContext* ctx, const QModelIndex& index,
const QList< QPolygonF >& areas, uint opacity )
{
AbstractDiagram* diagram = diagramPrivate->diagram;
QPainterPath path;
for ( int i = 0; i < areas.count(); ++i )
{
const QPolygonF& p = areas[ i ];
path.addPolygon( p );
diagramPrivate->reverseMapper.addPolygon( index.row(), index.column(), p );
path.closeSubpath();
}
ThreeDLineAttributes threeDAttrs = threeDLineAttributes( diagram, index );
QBrush trans = diagram->brush( index );
if ( threeDAttrs.isEnabled() ) {
trans = threeDAttrs.threeDBrush( trans, path.boundingRect() );
}
QColor transColor = trans.color();
transColor.setAlpha( opacity );
trans.setColor(transColor);
QPen indexPen = diagram->pen(index);
indexPen.setBrush( trans );
const PainterSaver painterSaver( ctx->painter() );
ctx->painter()->setRenderHint( QPainter::Antialiasing, diagram->antiAliasing() );
ctx->painter()->setPen( PrintingParameters::scalePen( indexPen ) );
ctx->painter()->setBrush( trans );
ctx->painter()->drawPath( path );
}
} // namespace PaintingHelpers
} // namespace KChart
diff --git a/src/KChart/KChartAbstractDiagram_p.cpp b/src/KChart/KChartAbstractDiagram_p.cpp
index ed4d691..92331d9 100644
--- a/src/KChart/KChartAbstractDiagram_p.cpp
+++ b/src/KChart/KChartAbstractDiagram_p.cpp
@@ -1,678 +1,678 @@
/*
* 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 .
*/
//
// W A R N I N G
// -------------
//
// This file is not part of the KD Chart API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "KChartAbstractDiagram_p.h"
#include "KChartBarDiagram.h"
#include "KChartFrameAttributes.h"
#include "KChartPainterSaver_p.h"
#include
#include
#include
using namespace KChart;
LabelPaintInfo::LabelPaintInfo() :
isValuePositive( false )
{
}
LabelPaintInfo::LabelPaintInfo( const QModelIndex& _index, const DataValueAttributes& _attrs,
const QPainterPath& _labelArea, const QPointF& _markerPos,
bool _isValuePositive, const QString& _value )
: index( _index )
, attrs( _attrs )
, labelArea( _labelArea )
, markerPos( _markerPos )
, isValuePositive( _isValuePositive )
, value( _value )
{
}
LabelPaintInfo::LabelPaintInfo( const LabelPaintInfo& other )
: index( other.index )
, attrs( other.attrs )
, labelArea( other.labelArea )
, markerPos( other.markerPos )
, isValuePositive( other.isValuePositive )
, value( other.value )
{
}
AbstractDiagram::Private::Private()
: diagram( nullptr )
, doDumpPaintTime( false )
, plane( nullptr )
, attributesModel( new PrivateAttributesModel(nullptr,nullptr) )
, allowOverlappingDataValueTexts( false )
, antiAliasing( true )
, percent( false )
, datasetDimension( 1 )
, databoundariesDirty( true )
, mCachedFontMetrics( QFontMetrics( qApp->font() ) )
{
}
AbstractDiagram::Private::~Private()
{
if ( attributesModel && qobject_cast(attributesModel) )
delete attributesModel;
}
void AbstractDiagram::Private::init()
{
}
void AbstractDiagram::Private::init( AbstractCoordinatePlane* newPlane )
{
plane = newPlane;
}
bool AbstractDiagram::Private::usesExternalAttributesModel() const
{
return ( ! attributesModel.isNull() ) &&
( ! qobject_cast(attributesModel) );
}
void AbstractDiagram::Private::setAttributesModel( AttributesModel* amodel )
{
if ( attributesModel == amodel ) {
return;
}
if ( !attributesModel.isNull() ) {
if ( qobject_cast< PrivateAttributesModel* >( attributesModel ) ) {
delete attributesModel;
} else {
disconnect( attributesModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
diagram, SLOT(setDataBoundariesDirty()) );
disconnect( attributesModel, SIGNAL(columnsInserted(QModelIndex,int,int)),
diagram, SLOT(setDataBoundariesDirty()) );
disconnect( attributesModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
diagram, SLOT(setDataBoundariesDirty()) );
disconnect( attributesModel, SIGNAL(columnsRemoved(QModelIndex,int,int)),
diagram, SLOT(setDataBoundariesDirty()) );
disconnect( attributesModel, SIGNAL(modelReset()),
diagram, SLOT(setDataBoundariesDirty()) );
disconnect( attributesModel, SIGNAL(layoutChanged()),
diagram, SLOT(setDataBoundariesDirty()) );
disconnect( attributesModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
diagram, SIGNAL(modelDataChanged()));
}
}
emit diagram->attributesModelAboutToChange( amodel, attributesModel );
connect( amodel, SIGNAL(rowsInserted(QModelIndex,int,int)),
diagram, SLOT(setDataBoundariesDirty()) );
connect( amodel, SIGNAL(columnsInserted(QModelIndex,int,int)),
diagram, SLOT(setDataBoundariesDirty()) );
connect( amodel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
diagram, SLOT(setDataBoundariesDirty()) );
connect( amodel, SIGNAL(columnsRemoved(QModelIndex,int,int)),
diagram, SLOT(setDataBoundariesDirty()) );
connect( amodel, SIGNAL(modelReset()),
diagram, SLOT(setDataBoundariesDirty()) );
connect( amodel, SIGNAL(layoutChanged()),
diagram, SLOT(setDataBoundariesDirty()) );
connect( amodel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
diagram, SIGNAL(modelDataChanged()));
attributesModel = amodel;
}
AbstractDiagram::Private::Private( const AbstractDiagram::Private& rhs ) :
diagram( nullptr ),
doDumpPaintTime( rhs.doDumpPaintTime ),
// Do not copy the plane
plane( nullptr ),
attributesModelRootIndex( QModelIndex() ),
attributesModel( rhs.attributesModel ),
allowOverlappingDataValueTexts( rhs.allowOverlappingDataValueTexts ),
antiAliasing( rhs.antiAliasing ),
percent( rhs.percent ),
datasetDimension( rhs.datasetDimension ),
mCachedFontMetrics( rhs.cachedFontMetrics() )
{
attributesModel = new PrivateAttributesModel( nullptr, nullptr);
attributesModel->initFrom( rhs.attributesModel );
}
// FIXME: Optimize if necessary
qreal AbstractDiagram::Private::calcPercentValue( const QModelIndex & index ) const
{
qreal sum = 0.0;
for ( int col = 0; col < attributesModel->columnCount( QModelIndex() ); col++ )
sum += attributesModel->data( attributesModel->index( index.row(), col, QModelIndex() ) ).toReal(); // checked
if ( sum == 0.0 )
return 0.0;
return attributesModel->data( attributesModel->mapFromSource( index ) ).toReal() / sum * 100.0;
}
void AbstractDiagram::Private::addLabel(
LabelPaintCache* cache,
const QModelIndex& index,
const CartesianDiagramDataCompressor::CachePosition* position,
const PositionPoints& points,
const Position& autoPositionPositive, const Position& autoPositionNegative,
const qreal value, qreal favoriteAngle /* = 0.0 */ )
{
CartesianDiagramDataCompressor::AggregatedDataValueAttributes allAttrs(
aggregatedAttrs( index, position ) );
QMap::const_iterator it;
for ( it = allAttrs.constBegin(); it != allAttrs.constEnd(); ++it ) {
DataValueAttributes dva = it.value();
if ( !dva.isVisible() ) {
continue;
}
const bool isPositive = ( value >= 0.0 );
RelativePosition relPos( dva.position( isPositive ) );
relPos.setReferencePoints( points );
if ( relPos.referencePosition().isUnknown() ) {
relPos.setReferencePosition( isPositive ? autoPositionPositive : autoPositionNegative );
}
// Rotate the label position (not the label itself) if the diagram is rotated so that the defaults still work
if ( isTransposed() ) {
KChartEnums::PositionValue posValue = relPos.referencePosition().value();
if ( posValue >= KChartEnums::PositionNorthWest && posValue <= KChartEnums::PositionWest ) {
// rotate 90 degrees clockwise
posValue = static_cast< KChartEnums::PositionValue >( posValue + 2 );
if ( posValue > KChartEnums::PositionWest ) {
// wraparound
posValue = static_cast< KChartEnums::PositionValue >( posValue -
( KChartEnums::PositionWest - KChartEnums::PositionNorthWest ) );
}
relPos.setReferencePosition( Position( posValue ) );
}
}
const QPointF referencePoint = relPos.referencePoint();
if ( !diagram->coordinatePlane()->isVisiblePoint( referencePoint ) ) {
continue;
}
const qreal fontHeight = cachedFontMetrics( dva.textAttributes().
calculatedFont( plane, KChartEnums::MeasureOrientationMinimum ), diagram )->height();
// Note: When printing data value texts and padding's Measure is using automatic reference area
// detection, the font height is used as reference size for both horizontal and vertical
// padding.
QSizeF relativeMeasureSize( fontHeight, fontHeight );
if ( !dva.textAttributes().hasRotation() ) {
TextAttributes ta = dva.textAttributes();
ta.setRotation( favoriteAngle );
dva.setTextAttributes( ta );
}
// get the size of the label text using a subset of the information going into the final layout
const QString text = formatDataValueText( dva, index, value );
QTextDocument doc;
doc.setDocumentMargin( 0 );
if ( Qt::mightBeRichText( text ) ) {
doc.setHtml( text );
} else {
doc.setPlainText( text );
}
const QFont calculatedFont( dva.textAttributes()
.calculatedFont( plane, KChartEnums::MeasureOrientationMinimum ) );
doc.setDefaultFont( calculatedFont );
const QRectF plainRect = doc.documentLayout()->frameBoundingRect( doc.rootFrame() );
- /**
+ /*
* A few hints on how the positioning of the text frame is done:
*
* Let's assume we have a bar chart, a text for a positive value
* to be drawn, and "North" as attrs.positivePosition().
*
* The reference point (pos) is then set to the top center point
* of a bar. The offset now depends on the alignment:
*
* Top: text is centered horizontally to the bar, bottom of
* text frame starts at top of bar
*
* Bottom: text is centered horizontally to the bar, top of
* text frame starts at top of bar
*
* Center: text is centered horizontally to the bar, center
* line of text frame is same as top of bar
*
* TopLeft: right edge of text frame is horizontal center of
* bar, bottom of text frame is top of bar.
*
* ...
*
* Positive and negative value labels are treated equally, "North"
* also refers to the top of a negative bar, and *not* to the bottom.
*
*
* "NorthEast" likewise refers to the top right edge of the bar,
* "NorthWest" to the top left edge of the bar, and so on.
*
* In other words, attrs.positivePosition() always refers to a
* position of the *bar*, and relPos.alignment() always refers
* to an alignment of the text frame relative to this position.
*/
QTransform transform;
{
// move to the general area where the label should be
QPointF calcPoint = relPos.calculatedPoint( relativeMeasureSize );
transform.translate( calcPoint.x(), calcPoint.y() );
// align the text rect; find out by how many half-widths / half-heights to move.
int dx = -1;
if ( relPos.alignment() & Qt::AlignLeft ) {
dx -= 1;
} else if ( relPos.alignment() & Qt::AlignRight ) {
dx += 1;
}
int dy = -1;
if ( relPos.alignment() & Qt::AlignTop ) {
dy -= 1;
} else if ( relPos.alignment() & Qt::AlignBottom ) {
dy += 1;
}
transform.translate( qreal( dx ) * plainRect.width() * 0.5,
qreal( dy ) * plainRect.height() * 0.5 );
// rotate the text rect around its center
transform.translate( plainRect.center().x(), plainRect.center().y() );
int rotation = dva.textAttributes().rotation();
if ( !isPositive && dva.mirrorNegativeValueTextRotation() ) {
rotation *= -1;
}
transform.rotate( rotation );
transform.translate( -plainRect.center().x(), -plainRect.center().y() );
}
QPainterPath labelArea;
//labelArea.addPolygon( transform.mapToPolygon( plainRect.toRect() ) );
//labelArea.closeSubpath();
// Not doing that because QTransform has a special case for 180° that gives a different than
// usual ordering of the points in the polygon returned by mapToPolygon( const QRect & ).
// We expect a particular ordering in paintDataValueTextsAndMarkers() by using elementAt( 0 ),
// and similar things might happen elsewhere.
labelArea.addPolygon( transform.map( QPolygon( plainRect.toRect(), true ) ) );
// store the label geometry and auxiliary data
cache->paintReplay.append( LabelPaintInfo( it.key(), dva, labelArea,
referencePoint, value >= 0.0, text ) );
}
}
const QFontMetrics* AbstractDiagram::Private::cachedFontMetrics( const QFont& font,
const QPaintDevice* paintDevice) const
{
if ( ( font != mCachedFont ) || ( paintDevice != mCachedPaintDevice ) ) {
mCachedFontMetrics = QFontMetrics( font, const_cast( paintDevice ) );
// TODO what about setting mCachedFont and mCachedPaintDevice?
}
return &mCachedFontMetrics;
}
const QFontMetrics AbstractDiagram::Private::cachedFontMetrics() const
{
return mCachedFontMetrics;
}
QString AbstractDiagram::Private::formatNumber( qreal value, int decimalDigits ) const
{
const int digits = qMax(decimalDigits, 0);
const qreal roundingEpsilon = pow( 0.1, digits ) * ( value >= 0.0 ? 0.5 : -0.5 );
QString asString = QString::number( value + roundingEpsilon, 'f' );
const int decimalPos = asString.indexOf( QLatin1Char( '.' ) );
if ( decimalPos < 0 ) {
return asString;
}
int last = qMin( decimalPos + digits, asString.length() - 1 );
// remove trailing zeros (and maybe decimal dot)
while ( last > decimalPos && asString[ last ] == QLatin1Char( '0' ) ) {
last--;
}
if ( last == decimalPos ) {
last--;
}
asString.chop( asString.length() - last - 1 );
return asString;
}
void AbstractDiagram::Private::forgetAlreadyPaintedDataValues()
{
alreadyDrawnDataValueTexts.clear();
prevPaintedDataValueText.clear();
}
void AbstractDiagram::Private::paintDataValueTextsAndMarkers(
PaintContext* ctx,
const LabelPaintCache &cache,
bool paintMarkers,
bool justCalculateRect /* = false */,
QRectF* cumulatedBoundingRect /* = 0 */ )
{
if ( justCalculateRect && !cumulatedBoundingRect ) {
qWarning() << Q_FUNC_INFO << "Neither painting nor finding the bounding rect, what are we doing?";
}
const PainterSaver painterSaver( ctx->painter() );
ctx->painter()->setClipping( false );
if ( paintMarkers && !justCalculateRect ) {
Q_FOREACH ( const LabelPaintInfo& info, cache.paintReplay ) {
diagram->paintMarker( ctx->painter(), info.index, info.markerPos );
}
}
TextAttributes ta;
{
Measure m( 18.0, KChartEnums::MeasureCalculationModeRelative,
KChartEnums::MeasureOrientationMinimum );
m.setReferenceArea( ctx->coordinatePlane() );
ta.setFontSize( m );
m.setAbsoluteValue( 6.0 );
ta.setMinimalFontSize( m );
}
forgetAlreadyPaintedDataValues();
Q_FOREACH ( const LabelPaintInfo& info, cache.paintReplay ) {
const QPointF pos = info.labelArea.elementAt( 0 );
paintDataValueText( ctx->painter(), info.attrs, pos, info.isValuePositive,
info.value, justCalculateRect, cumulatedBoundingRect );
const QString comment = info.index.data( KChart::CommentRole ).toString();
if ( comment.isEmpty() ) {
continue;
}
TextBubbleLayoutItem item( comment, ta, ctx->coordinatePlane()->parent(),
KChartEnums::MeasureOrientationMinimum,
Qt::AlignHCenter | Qt::AlignVCenter );
const QRect rect( pos.toPoint(), item.sizeHint() );
if (cumulatedBoundingRect) {
(*cumulatedBoundingRect) |= rect;
}
if ( !justCalculateRect ) {
item.setGeometry( rect );
item.paint( ctx->painter() );
}
}
if ( cumulatedBoundingRect ) {
*cumulatedBoundingRect = ctx->painter()->transform().inverted().mapRect( *cumulatedBoundingRect );
}
}
QString AbstractDiagram::Private::formatDataValueText( const DataValueAttributes &dva,
const QModelIndex& index, qreal value ) const
{
if ( !dva.isVisible() ) {
return QString();
}
if ( dva.usePercentage() ) {
value = calcPercentValue( index );
}
QString ret;
if ( dva.dataLabel().isNull() ) {
ret = formatNumber( value, dva.decimalDigits() );
} else {
ret = dva.dataLabel();
}
ret.prepend( dva.prefix() );
ret.append( dva.suffix() );
return ret;
}
void AbstractDiagram::Private::paintDataValueText(
QPainter* painter,
const QModelIndex& index,
const QPointF& pos,
qreal value,
bool justCalculateRect /* = false */,
QRectF* cumulatedBoundingRect /* = 0 */ )
{
const DataValueAttributes dva( diagram->dataValueAttributes( index ) );
const QString text = formatDataValueText( dva, index, value );
paintDataValueText( painter, dva, pos, value >= 0.0, text,
justCalculateRect, cumulatedBoundingRect );
}
void AbstractDiagram::Private::paintDataValueText(
QPainter* painter,
const DataValueAttributes& attrs,
const QPointF& pos,
bool valueIsPositive,
const QString& text,
bool justCalculateRect /* = false */,
QRectF* cumulatedBoundingRect /* = 0 */ )
{
if ( !attrs.isVisible() ) {
return;
}
const TextAttributes ta( attrs.textAttributes() );
if ( !ta.isVisible() || ( !attrs.showRepetitiveDataLabels() && prevPaintedDataValueText == text ) ) {
return;
}
prevPaintedDataValueText = text;
QTextDocument doc;
doc.setDocumentMargin( 0.0 );
if ( Qt::mightBeRichText( text ) ) {
doc.setHtml( text );
} else {
doc.setPlainText( text );
}
const QFont calculatedFont( ta.calculatedFont( plane, KChartEnums::MeasureOrientationMinimum ) );
const PainterSaver painterSaver( painter );
painter->setPen( PrintingParameters::scalePen( ta.pen() ) );
doc.setDefaultFont( calculatedFont );
QAbstractTextDocumentLayout::PaintContext context;
context.palette = diagram->palette();
context.palette.setColor( QPalette::Text, ta.pen().color() );
QAbstractTextDocumentLayout* const layout = doc.documentLayout();
layout->setPaintDevice( painter->device() );
painter->translate( pos.x(), pos.y() );
int rotation = ta.rotation();
if ( !valueIsPositive && attrs.mirrorNegativeValueTextRotation() ) {
rotation *= -1;
}
painter->rotate( rotation );
// do overlap detection "as seen by the painter"
QTransform transform = painter->worldTransform();
bool drawIt = true;
// note: This flag can be set differently for every label text!
// In theory a user could e.g. have some small red text on one of the
// values that she wants to have written in any case - so we just
// do not test if such texts would cover some of the others.
if ( !attrs.showOverlappingDataLabels() ) {
const QRectF br( layout->frameBoundingRect( doc.rootFrame() ) );
QPolygon pr = transform.mapToPolygon( br.toRect() );
// Using QPainterPath allows us to use intersects() (which has many early-exits)
// instead of QPolygon::intersected (which calculates a slow and precise intersection polygon)
QPainterPath path;
path.addPolygon( pr );
// iterate backwards because recently added items are more likely to overlap, so we spend
// less time checking irrelevant items when there is overlap
for ( int i = alreadyDrawnDataValueTexts.count() - 1; i >= 0; i-- ) {
if ( alreadyDrawnDataValueTexts.at( i ).intersects( path ) ) {
// qDebug() << "not painting this label due to overlap";
drawIt = false;
break;
}
}
if ( drawIt ) {
alreadyDrawnDataValueTexts << path;
}
}
if ( drawIt ) {
QRectF rect = layout->frameBoundingRect( doc.rootFrame() );
if ( cumulatedBoundingRect ) {
(*cumulatedBoundingRect) |= transform.mapRect( rect );
}
if ( !justCalculateRect ) {
bool paintBack = false;
BackgroundAttributes back( attrs.backgroundAttributes() );
if ( back.isVisible() ) {
paintBack = true;
painter->setBrush( back.brush() );
} else {
painter->setBrush( QBrush() );
}
qreal radius = 0.0;
FrameAttributes frame( attrs.frameAttributes() );
if ( frame.isVisible() ) {
paintBack = true;
painter->setPen( frame.pen() );
radius = frame.cornerRadius();
}
if ( paintBack ) {
QRectF borderRect( QPointF( 0, 0 ), rect.size() );
painter->drawRoundedRect( borderRect, radius, radius );
}
layout->draw( painter, context );
}
}
}
QModelIndex AbstractDiagram::Private::indexAt( const QPoint& point ) const
{
QModelIndexList l = indexesAt( point );
std::sort(l.begin(), l.end());
if ( !l.isEmpty() )
return l.first();
else
return QModelIndex();
}
QModelIndexList AbstractDiagram::Private::indexesAt( const QPoint& point ) const
{
return reverseMapper.indexesAt( point ); // which could be empty
}
QModelIndexList AbstractDiagram::Private::indexesIn( const QRect& rect ) const
{
return reverseMapper.indexesIn( rect );
}
CartesianDiagramDataCompressor::AggregatedDataValueAttributes AbstractDiagram::Private::aggregatedAttrs(
const QModelIndex& index,
const CartesianDiagramDataCompressor::CachePosition* position ) const
{
Q_UNUSED( position ); // used by cartesian diagrams only
CartesianDiagramDataCompressor::AggregatedDataValueAttributes allAttrs;
allAttrs[index] = diagram->dataValueAttributes( index );
return allAttrs;
}
void AbstractDiagram::Private::setDatasetAttrs( int dataset, const QVariant& data, int role )
{
// To store attributes for a dataset, we use the first column
// that's associated with it. (i.e., with a dataset dimension
// of two, the column of the keys). In most cases however, there's
// only one data dimension, and thus also only one column per data set.
int column = dataset * datasetDimension;
// For DataHiddenRole, also store the flag in the other data points that belong to this data set,
// otherwise it's impossible to hide data points in a plotter diagram because there will always
// be one model index that belongs to this data point that is not hidden.
// For more details on how hiding works, see the data compressor.
// Also see KDCH-503 for which this is a workaround.
int columnSpan = role == DataHiddenRole ? datasetDimension : 1;
for ( int i = 0; i < columnSpan; i++ ) {
attributesModel->setHeaderData( column + i, Qt::Horizontal, data, role );
}
}
QVariant AbstractDiagram::Private::datasetAttrs( int dataset, int role ) const
{
// See setDataSetAttrs for explanation of column
int column = dataset * datasetDimension;
return attributesModel->headerData( column, Qt::Horizontal, role );
}
void AbstractDiagram::Private::resetDatasetAttrs( int dataset, int role )
{
// See setDataSetAttrs for explanation of column
int column = dataset * datasetDimension;
attributesModel->resetHeaderData( column, Qt::Horizontal, role );
}
bool AbstractDiagram::Private::isTransposed() const
{
// Determine the diagram that specifies the orientation.
// That diagram is the reference diagram, if it exists, or otherwise the diagram itself.
// Note: In KChart 2.3 or earlier, only a bar diagram can be transposed.
const AbstractCartesianDiagram* refDiagram = qobject_cast< const AbstractCartesianDiagram * >( diagram );
if ( !refDiagram ) {
return false;
}
if ( refDiagram->referenceDiagram() ) {
refDiagram = refDiagram->referenceDiagram();
}
const BarDiagram* barDiagram = qobject_cast< const BarDiagram* >( refDiagram );
if ( !barDiagram ) {
return false;
}
return barDiagram->orientation() == Qt::Horizontal;
}
LineAttributesInfo::LineAttributesInfo()
{
}
LineAttributesInfo::LineAttributesInfo( const QModelIndex& _index, const QPointF& _value, const QPointF& _nextValue )
: index( _index )
, value ( _value )
, nextValue ( _nextValue )
{
}
diff --git a/src/KChart/KChartChart.cpp b/src/KChart/KChartChart.cpp
index fe3babc..dae6e2c 100644
--- a/src/KChart/KChartChart.cpp
+++ b/src/KChart/KChartChart.cpp
@@ -1,1773 +1,1773 @@
/*
* 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 "KChartChart.h"
#include "KChartChart_p.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "KChartCartesianCoordinatePlane.h"
#include "KChartAbstractCartesianDiagram.h"
#include "KChartHeaderFooter.h"
#include "KChartEnums.h"
#include "KChartLegend.h"
#include "KChartLayoutItems.h"
#include
#include
#include "KChartPainterSaver_p.h"
#include "KChartPrintingParameters.h"
#include
#if defined KDAB_EVAL
#include "../evaldialog/evaldialog.h"
#endif
#if 0
// dumpLayoutTree dumps a QLayout tree in a hopefully easy to read format to stderr - feel free to
// use, improve and extend; it is very useful for looking at any layout problem.
#include
// this is this different from both QRect::isEmpty() and QRect::isNull() for "wrong" QRects,
// i.e. those where topLeft() is actually below and / or right of bottomRight().
static bool isZeroArea(const QRect &r)
{
return !r.width() || !r.height();
}
static QString lineProlog(int nestingDepth, int lineno)
{
QString numbering(QString::number(lineno).rightJustified(5).append(QChar::fromAscii(':')));
QString indent(nestingDepth * 4, QLatin1Char(' '));
return numbering + indent;
}
static void dumpLayoutTreeRecurse(QLayout *l, int *counter, int depth)
{
const QLatin1String colorOn(isZeroArea(l->geometry()) ? "\033[0m" : "\033[32m");
const QLatin1String colorOff("\033[0m");
QString prolog = lineProlog(depth, *counter);
(*counter)++;
qDebug() << colorOn + prolog << l->metaObject()->className() << l->geometry()
<< "hint" << l->sizeHint()
<< l->hasHeightForWidth() << "min" << l->minimumSize()
<< "max" << l->maximumSize()
<< l->expandingDirections() << l->alignment()
<< colorOff;
for (int i = 0; i < l->count(); i++) {
QLayoutItem *child = l->itemAt(i);
if (QLayout *childL = child->layout()) {
dumpLayoutTreeRecurse(childL, counter, depth + 1);
} else {
// The isZeroArea check culls usually largely useless output - you might want to remove it in
// some debugging situations. Add a boolean parameter to this and dumpLayoutTree() if you do.
if (!isZeroArea(child->geometry())) {
prolog = lineProlog(depth + 1, *counter);
(*counter)++;
qDebug() << colorOn + prolog << typeid(*child).name() << child->geometry()
<< "hint" << child->sizeHint()
<< child->hasHeightForWidth() << "min" << child->minimumSize()
<< "max" << child->maximumSize()
<< child->expandingDirections() << child->alignment()
<< colorOff;
}
}
}
}
static void dumpLayoutTree(QLayout *l)
{
int counter = 0;
dumpLayoutTreeRecurse(l, &counter, 0);
}
#endif
static const Qt::Alignment s_gridAlignments[ 3 ][ 3 ] = { // [ row ][ column ]
{ Qt::AlignTop | Qt::AlignLeft, Qt::AlignTop | Qt::AlignHCenter, Qt::AlignTop | Qt::AlignRight },
{ Qt::AlignVCenter | Qt::AlignLeft, Qt::AlignVCenter | Qt::AlignHCenter, Qt::AlignVCenter | Qt::AlignRight },
{ Qt::AlignBottom | Qt::AlignLeft, Qt::AlignBottom | Qt::AlignHCenter, Qt::AlignBottom | Qt::AlignRight }
};
static void getRowAndColumnForPosition(KChartEnums::PositionValue pos, int* row, int* column)
{
switch ( pos ) {
case KChartEnums::PositionNorthWest: *row = 0; *column = 0;
break;
case KChartEnums::PositionNorth: *row = 0; *column = 1;
break;
case KChartEnums::PositionNorthEast: *row = 0; *column = 2;
break;
case KChartEnums::PositionEast: *row = 1; *column = 2;
break;
case KChartEnums::PositionSouthEast: *row = 2; *column = 2;
break;
case KChartEnums::PositionSouth: *row = 2; *column = 1;
break;
case KChartEnums::PositionSouthWest: *row = 2; *column = 0;
break;
case KChartEnums::PositionWest: *row = 1; *column = 0;
break;
case KChartEnums::PositionCenter: *row = 1; *column = 1;
break;
default: *row = -1; *column = -1;
break;
}
}
using namespace KChart;
// Layout widgets even if they are not visible (that's why isEmpty() is overridden) - at least that
// was the original reason...
class MyWidgetItem : public QWidgetItem
{
public:
explicit MyWidgetItem(QWidget *w, Qt::Alignment alignment = Qt::Alignment())
: QWidgetItem( w )
{
setAlignment( alignment );
}
// All of the methods are reimplemented from QWidgetItem, and work around some oddity in QLayout and / or
// KD Chart - I forgot the details between writing this code as an experiment and committing it, very
// sorry about that!
// Feel free to comment out any of them and then try the line-breaking feature in horizontal legends in
// the Legends/Advanced example. It will not work well in various ways - won't get enough space and look
// very broken, will inhibit resizing the window etc.
QSize sizeHint() const override
{
QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
return w->sizeHint();
}
QSize minimumSize() const override
{
QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
return w->minimumSize();
}
QSize maximumSize() const override
{
// Not just passing on w->maximumSize() fixes that the size policy of Legend is disregarded, making
// Legend take all available space, which makes both the Legend internal layout and the overall
// layout of chart + legend look bad. QWidget::maximumSize() is not a virtual method, it's a
// property, so "overriding" that one would be even uglier, and prevent user-set property
// values from doing anything.
QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
QSize ret = w->maximumSize();
const QSize hint = w->sizeHint();
const QSizePolicy::Policy hPolicy = w->sizePolicy().horizontalPolicy();
if (hPolicy == QSizePolicy::Fixed || hPolicy == QSizePolicy::Maximum) {
ret.rwidth() = hint.width();
}
const QSizePolicy::Policy vPolicy = w->sizePolicy().verticalPolicy();
if (vPolicy == QSizePolicy::Fixed || vPolicy == QSizePolicy::Maximum) {
ret.rheight() = hint.height();
}
return ret;
}
Qt::Orientations expandingDirections() const override
{
QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
if ( isEmpty() ) {
return Qt::Orientations();
}
Qt::Orientations e = w->sizePolicy().expandingDirections();
return e;
}
void setGeometry(const QRect &g) override
{
QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
w->setGeometry(g);
}
QRect geometry() const override
{
QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
return w->geometry();
}
bool hasHeightForWidth() const override
{
QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
bool ret = !isEmpty() &&
qobject_cast< Legend* >( w )->hasHeightForWidth();
return ret;
}
int heightForWidth( int width ) const override
{
QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
int ret = w->heightForWidth( width );
return ret;
}
bool isEmpty() const override {
QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
// legend->hide() should indeed hide the legend,
// but a legend in a chart that hasn't been shown yet isn't hidden
// (as can happen when using Chart::paint() without showing the chart)
return w->isHidden() && w->testAttribute( Qt::WA_WState_ExplicitShowHide );
}
};
// When "abusing" QLayouts to lay out items with different geometry from the backing QWidgets,
// some manual work is required to correctly update all the sublayouts.
// This is because all the convenient ways to deal with QLayouts assume QWidgets somewhere.
// What this does is somewhat similar to QLayout::activate(), but it never refers to the parent
// QWidget which has the wrong geometry.
static void invalidateLayoutTree( QLayoutItem *item )
{
QLayout *layout = item->layout();
if ( layout ) {
const int count = layout->count();
for ( int i = 0; i < count; i++ ) {
invalidateLayoutTree( layout->itemAt( i ) );
}
}
item->invalidate();
}
void Chart::Private::slotUnregisterDestroyedLegend( Legend *l )
{
chart->takeLegend( l );
}
void Chart::Private::slotUnregisterDestroyedHeaderFooter( HeaderFooter* hf )
{
chart->takeHeaderFooter( hf );
}
void Chart::Private::slotUnregisterDestroyedPlane( AbstractCoordinatePlane* plane )
{
coordinatePlanes.removeAll( plane );
Q_FOREACH ( AbstractCoordinatePlane* p, coordinatePlanes ) {
if ( p->referenceCoordinatePlane() == plane) {
p->setReferenceCoordinatePlane( nullptr );
}
}
plane->layoutPlanes();
}
Chart::Private::Private( Chart* chart_ )
: chart( chart_ )
, useNewLayoutSystem( false )
, layout(nullptr)
, vLayout(nullptr)
, planesLayout(nullptr)
, headerLayout(nullptr)
, footerLayout(nullptr)
, dataAndLegendLayout(nullptr)
, leftOuterSpacer(nullptr)
, rightOuterSpacer(nullptr)
, topOuterSpacer(nullptr)
, bottomOuterSpacer(nullptr)
, isFloatingLegendsLayoutDirty( true )
, isPlanesLayoutDirty( true )
, globalLeadingLeft(0)
, globalLeadingRight(0)
, globalLeadingTop(0)
, globalLeadingBottom(0)
{
for ( int row = 0; row < 3; ++row ) {
for ( int column = 0; column < 3; ++column ) {
for ( int i = 0; i < 2; i++ ) {
innerHdFtLayouts[ i ][ row ][ column ] = nullptr;
}
}
}
}
Chart::Private::~Private()
{
}
enum VisitorState{ Visited, Unknown };
struct ConnectedComponentsComparator{
bool operator()( const LayoutGraphNode *lhs, const LayoutGraphNode *rhs ) const
{
return lhs->priority < rhs->priority;
}
};
static QVector< LayoutGraphNode* > getPrioritySortedConnectedComponents( QVector< LayoutGraphNode* > &nodeList )
{
QVector< LayoutGraphNode* >connectedComponents;
QHash< LayoutGraphNode*, VisitorState > visitedComponents;
Q_FOREACH ( LayoutGraphNode* node, nodeList )
visitedComponents[ node ] = Unknown;
for ( int i = 0; i < nodeList.size(); ++i )
{
LayoutGraphNode *curNode = nodeList[ i ];
LayoutGraphNode *representativeNode = curNode;
if ( visitedComponents[ curNode ] != Visited )
{
QStack< LayoutGraphNode* > stack;
stack.push( curNode );
while ( !stack.isEmpty() )
{
curNode = stack.pop();
Q_ASSERT( visitedComponents[ curNode ] != Visited );
visitedComponents[ curNode ] = Visited;
if ( curNode->bottomSuccesor && visitedComponents[ curNode->bottomSuccesor ] != Visited )
stack.push( curNode->bottomSuccesor );
if ( curNode->leftSuccesor && visitedComponents[ curNode->leftSuccesor ] != Visited )
stack.push( curNode->leftSuccesor );
if ( curNode->sharedSuccesor && visitedComponents[ curNode->sharedSuccesor ] != Visited )
stack.push( curNode->sharedSuccesor );
if ( curNode->priority < representativeNode->priority )
representativeNode = curNode;
}
connectedComponents.append( representativeNode );
}
}
std::sort( connectedComponents.begin(), connectedComponents.end(), ConnectedComponentsComparator() );
return connectedComponents;
}
struct PriorityComparator{
public:
PriorityComparator( QHash< AbstractCoordinatePlane*, LayoutGraphNode* > mapping )
: m_mapping( mapping )
{}
bool operator() ( AbstractCoordinatePlane *lhs, AbstractCoordinatePlane *rhs ) const
{
const LayoutGraphNode *lhsNode = m_mapping[ lhs ];
Q_ASSERT( lhsNode );
const LayoutGraphNode *rhsNode = m_mapping[ rhs ];
Q_ASSERT( rhsNode );
return lhsNode->priority < rhsNode->priority;
}
const QHash< AbstractCoordinatePlane*, LayoutGraphNode* > m_mapping;
};
void checkExistingAxes( LayoutGraphNode* node )
{
if ( node && node->diagramPlane && node->diagramPlane->diagram() )
{
AbstractCartesianDiagram *diag = qobject_cast< AbstractCartesianDiagram* >( node->diagramPlane->diagram() );
if ( diag )
{
Q_FOREACH( const CartesianAxis* axis, diag->axes() )
{
switch ( axis->position() )
{
case( CartesianAxis::Top ):
node->topAxesLayout = true;
break;
case( CartesianAxis::Bottom ):
node->bottomAxesLayout = true;
break;
case( CartesianAxis::Left ):
node->leftAxesLayout = true;
break;
case( CartesianAxis::Right ):
node->rightAxesLayout = true;
break;
}
}
}
}
}
static void mergeNodeAxisInformation( LayoutGraphNode* lhs, LayoutGraphNode* rhs )
{
lhs->topAxesLayout |= rhs->topAxesLayout;
rhs->topAxesLayout = lhs->topAxesLayout;
lhs->bottomAxesLayout |= rhs->bottomAxesLayout;
rhs->bottomAxesLayout = lhs->bottomAxesLayout;
lhs->leftAxesLayout |= rhs->leftAxesLayout;
rhs->leftAxesLayout = lhs->leftAxesLayout;
lhs->rightAxesLayout |= rhs->rightAxesLayout;
rhs->rightAxesLayout = lhs->rightAxesLayout;
}
static CoordinatePlaneList findSharingAxisDiagrams( AbstractCoordinatePlane* plane,
const CoordinatePlaneList& list,
Chart::Private::AxisType type,
QVector< CartesianAxis* >* sharedAxes )
{
if ( !plane || !plane->diagram() )
return CoordinatePlaneList();
Q_ASSERT( plane );
Q_ASSERT( plane->diagram() );
CoordinatePlaneList result;
AbstractCartesianDiagram* diagram = qobject_cast< AbstractCartesianDiagram* >( plane->diagram() );
if ( !diagram )
return CoordinatePlaneList();
QList< CartesianAxis* > axes;
Q_FOREACH( CartesianAxis* axis, diagram->axes() ) {
if ( ( type == Chart::Private::Ordinate &&
( axis->position() == CartesianAxis::Left || axis->position() == CartesianAxis::Right ) )
||
( type == Chart::Private::Abscissa &&
( axis->position() == CartesianAxis::Top || axis->position() == CartesianAxis::Bottom ) ) ) {
axes.append( axis );
}
}
Q_FOREACH( AbstractCoordinatePlane *curPlane, list )
{
AbstractCartesianDiagram* diagram =
qobject_cast< AbstractCartesianDiagram* > ( curPlane->diagram() );
if ( !diagram )
continue;
Q_FOREACH( CartesianAxis* curSearchedAxis, axes )
{
Q_FOREACH( CartesianAxis* curAxis, diagram->axes() )
{
if ( curSearchedAxis == curAxis )
{
result.append( curPlane );
if ( !sharedAxes->contains( curSearchedAxis ) )
sharedAxes->append( curSearchedAxis );
}
}
}
}
return result;
}
-/**
+/*
* this method determines the needed layout of the graph
* taking care of the sharing problematic
* its NOT allowed to have a diagram that shares
* more than one axis in the same direction
*/
QVector< LayoutGraphNode* > Chart::Private::buildPlaneLayoutGraph()
{
QHash< AbstractCoordinatePlane*, LayoutGraphNode* > planeNodeMapping;
QVector< LayoutGraphNode* > allNodes;
// create all nodes and a mapping between plane and nodes
Q_FOREACH( AbstractCoordinatePlane* curPlane, coordinatePlanes )
{
if ( curPlane->diagram() )
{
allNodes.append( new LayoutGraphNode );
allNodes[ allNodes.size() - 1 ]->diagramPlane = curPlane;
allNodes[ allNodes.size() - 1 ]->priority = allNodes.size();
checkExistingAxes( allNodes[ allNodes.size() - 1 ] );
planeNodeMapping[ curPlane ] = allNodes[ allNodes.size() - 1 ];
}
}
// build the graph connections
Q_FOREACH( LayoutGraphNode* curNode, allNodes )
{
QVector< CartesianAxis* > sharedAxes;
CoordinatePlaneList xSharedPlanes = findSharingAxisDiagrams( curNode->diagramPlane, coordinatePlanes, Abscissa, &sharedAxes );
Q_ASSERT( sharedAxes.size() < 2 );
// TODO duplicated code make a method out of it
if ( sharedAxes.size() == 1 && xSharedPlanes.size() > 1 )
{
//xSharedPlanes.removeAll( sharedAxes.first()->diagram()->coordinatePlane() );
//std::sort( xSharedPlanes.begin(), xSharedPlanes.end(), PriorityComparator( planeNodeMapping ) );
for ( int i = 0; i < xSharedPlanes.size() - 1; ++i )
{
LayoutGraphNode *tmpNode = planeNodeMapping[ xSharedPlanes[ i ] ];
Q_ASSERT( tmpNode );
LayoutGraphNode *tmpNode2 = planeNodeMapping[ xSharedPlanes[ i + 1 ] ];
Q_ASSERT( tmpNode2 );
tmpNode->bottomSuccesor = tmpNode2;
}
// if ( sharedAxes.first()->diagram() && sharedAxes.first()->diagram()->coordinatePlane() )
// {
// LayoutGraphNode *lastNode = planeNodeMapping[ xSharedPlanes.last() ];
// Q_ASSERT( lastNode );
// Q_ASSERT( sharedAxes.first()->diagram()->coordinatePlane() );
// LayoutGraphNode *ownerNode = planeNodeMapping[ sharedAxes.first()->diagram()->coordinatePlane() ];
// Q_ASSERT( ownerNode );
// lastNode->bottomSuccesor = ownerNode;
// }
//merge AxisInformation, needs a two pass run
LayoutGraphNode axisInfoNode;
for ( int count = 0; count < 2; ++count )
{
for ( int i = 0; i < xSharedPlanes.size(); ++i )
{
mergeNodeAxisInformation( &axisInfoNode, planeNodeMapping[ xSharedPlanes[ i ] ] );
}
}
}
sharedAxes.clear();
CoordinatePlaneList ySharedPlanes = findSharingAxisDiagrams( curNode->diagramPlane, coordinatePlanes, Ordinate, &sharedAxes );
Q_ASSERT( sharedAxes.size() < 2 );
if ( sharedAxes.size() == 1 && ySharedPlanes.size() > 1 )
{
//ySharedPlanes.removeAll( sharedAxes.first()->diagram()->coordinatePlane() );
//std::sort( ySharedPlanes.begin(), ySharedPlanes.end(), PriorityComparator( planeNodeMapping ) );
for ( int i = 0; i < ySharedPlanes.size() - 1; ++i )
{
LayoutGraphNode *tmpNode = planeNodeMapping[ ySharedPlanes[ i ] ];
Q_ASSERT( tmpNode );
LayoutGraphNode *tmpNode2 = planeNodeMapping[ ySharedPlanes[ i + 1 ] ];
Q_ASSERT( tmpNode2 );
tmpNode->leftSuccesor = tmpNode2;
}
// if ( sharedAxes.first()->diagram() && sharedAxes.first()->diagram()->coordinatePlane() )
// {
// LayoutGraphNode *lastNode = planeNodeMapping[ ySharedPlanes.last() ];
// Q_ASSERT( lastNode );
// Q_ASSERT( sharedAxes.first()->diagram()->coordinatePlane() );
// LayoutGraphNode *ownerNode = planeNodeMapping[ sharedAxes.first()->diagram()->coordinatePlane() ];
// Q_ASSERT( ownerNode );
// lastNode->bottomSuccesor = ownerNode;
// }
//merge AxisInformation, needs a two pass run
LayoutGraphNode axisInfoNode;
for ( int count = 0; count < 2; ++count )
{
for ( int i = 0; i < ySharedPlanes.size(); ++i )
{
mergeNodeAxisInformation( &axisInfoNode, planeNodeMapping[ ySharedPlanes[ i ] ] );
}
}
}
sharedAxes.clear();
if ( curNode->diagramPlane->referenceCoordinatePlane() )
curNode->sharedSuccesor = planeNodeMapping[ curNode->diagramPlane->referenceCoordinatePlane() ];
}
return allNodes;
}
QHash Chart::Private::buildPlaneLayoutInfos()
{
/* There are two ways in which planes can be caused to interact in
* where they are put layouting wise: The first is the reference plane. If
* such a reference plane is set, on a plane, it will use the same cell in the
* layout as that one. In addition to this, planes can share an axis. In that case
* they will be laid out in relation to each other as suggested by the position
* of the axis. If, for example Plane1 and Plane2 share an axis at position Left,
* that will result in the layout: Axis Plane1 Plane 2, vertically. If Plane1
* also happens to be Plane2's referece plane, both planes are drawn over each
* other. The reference plane concept allows two planes to share the same space
* even if neither has any axis, and in case there are shared axis, it is used
* to decided, whether the planes should be painted on top of each other or
* laid out vertically or horizontally next to each other. */
QHash axisInfos;
QHash planeInfos;
Q_FOREACH(AbstractCoordinatePlane* plane, coordinatePlanes ) {
PlaneInfo p;
// first check if we share space with another plane
p.referencePlane = plane->referenceCoordinatePlane();
planeInfos.insert( plane, p );
Q_FOREACH( AbstractDiagram* abstractDiagram, plane->diagrams() ) {
AbstractCartesianDiagram* diagram =
qobject_cast ( abstractDiagram );
if ( !diagram ) {
continue;
}
Q_FOREACH( CartesianAxis* axis, diagram->axes() ) {
if ( !axisInfos.contains( axis ) ) {
/* If this is the first time we see this axis, add it, with the
* current plane. The first plane added to the chart that has
* the axis associated with it thus "owns" it, and decides about
* layout. */
AxisInfo i;
i.plane = plane;
axisInfos.insert( axis, i );
} else {
AxisInfo i = axisInfos[axis];
if ( i.plane == plane ) {
continue; // we don't want duplicates, only shared
}
/* The user expects diagrams to be added on top, and to the right
* so that horizontally we need to move the new diagram, vertically
* the reference one. */
PlaneInfo pi = planeInfos[plane];
// plane-to-plane linking overrides linking via axes
if ( !pi.referencePlane ) {
// we're not the first plane to see this axis, mark us as a slave
pi.referencePlane = i.plane;
if ( axis->position() == CartesianAxis::Left ||
axis->position() == CartesianAxis::Right ) {
pi.horizontalOffset += 1;
}
planeInfos[plane] = pi;
pi = planeInfos[i.plane];
if ( axis->position() == CartesianAxis::Top ||
axis->position() == CartesianAxis::Bottom ) {
pi.verticalOffset += 1;
}
planeInfos[i.plane] = pi;
}
}
}
}
// Create a new grid layout for each plane that has no reference.
p = planeInfos[plane];
if ( p.referencePlane == nullptr ) {
p.gridLayout = new QGridLayout();
p.gridLayout->setContentsMargins( 0, 0, 0, 0 );
planeInfos[plane] = p;
}
}
return planeInfos;
}
void Chart::Private::slotLayoutPlanes()
{
/*TODO make sure this is really needed */
const QBoxLayout::Direction oldPlanesDirection = planesLayout ? planesLayout->direction()
: QBoxLayout::TopToBottom;
if ( planesLayout && dataAndLegendLayout )
dataAndLegendLayout->removeItem( planesLayout );
const bool hadPlanesLayout = planesLayout != nullptr;
int left, top, right, bottom;
if ( hadPlanesLayout )
planesLayout->getContentsMargins(&left, &top, &right, &bottom);
Q_FOREACH( AbstractLayoutItem* plane, planeLayoutItems ) {
plane->removeFromParentLayout();
}
//TODO they should get a correct parent, but for now it works
Q_FOREACH( AbstractLayoutItem* plane, planeLayoutItems ) {
if ( dynamic_cast< AutoSpacerLayoutItem* >( plane ) )
delete plane;
}
planeLayoutItems.clear();
delete planesLayout;
//hint: The direction is configurable by the user now, as
// we are using a QBoxLayout rather than a QVBoxLayout. (khz, 2007/04/25)
planesLayout = new QBoxLayout( oldPlanesDirection );
isPlanesLayoutDirty = true; // here we create the layouts; we need to "run" them before painting
if ( useNewLayoutSystem )
{
gridPlaneLayout = new QGridLayout;
planesLayout->addLayout( gridPlaneLayout );
if (hadPlanesLayout)
planesLayout->setContentsMargins(left, top, right, bottom);
planesLayout->setObjectName( QString::fromLatin1( "planesLayout" ) );
/* First go through all planes and all axes and figure out whether the planes
* need to coordinate. If they do, they share a grid layout, if not, each
* get their own. See buildPlaneLayoutInfos() for more details. */
QVector< LayoutGraphNode* > vals = buildPlaneLayoutGraph();
//qDebug() << Q_FUNC_INFO << "GraphNodes" << vals.size();
QVector< LayoutGraphNode* > connectedComponents = getPrioritySortedConnectedComponents( vals );
//qDebug() << Q_FUNC_INFO << "SubGraphs" << connectedComponents.size();
int row = 0;
int col = 0;
QSet< CartesianAxis* > laidOutAxes;
for ( int i = 0; i < connectedComponents.size(); ++i )
{
LayoutGraphNode *curComponent = connectedComponents[ i ];
for ( LayoutGraphNode *curRowComponent = curComponent; curRowComponent; curRowComponent = curRowComponent->bottomSuccesor )
{
col = 0;
for ( LayoutGraphNode *curColComponent = curRowComponent; curColComponent; curColComponent = curColComponent->leftSuccesor )
{
Q_ASSERT( curColComponent->diagramPlane->diagrams().size() == 1 );
Q_FOREACH( AbstractDiagram* diagram, curColComponent->diagramPlane->diagrams() )
{
const int planeRowOffset = 1;//curColComponent->topAxesLayout ? 1 : 0;
const int planeColOffset = 1;//curColComponent->leftAxesLayout ? 1 : 0;
//qDebug() << Q_FUNC_INFO << row << col << planeRowOffset << planeColOffset;
//qDebug() << Q_FUNC_INFO << row + planeRowOffset << col + planeColOffset;
planeLayoutItems << curColComponent->diagramPlane;
AbstractCartesianDiagram *cartDiag = qobject_cast< AbstractCartesianDiagram* >( diagram );
if ( cartDiag )
{
gridPlaneLayout->addItem( curColComponent->diagramPlane, row + planeRowOffset, col + planeColOffset, 2, 2 );
curColComponent->diagramPlane->setParentLayout( gridPlaneLayout );
QHBoxLayout *leftLayout = nullptr;
QHBoxLayout *rightLayout = nullptr;
QVBoxLayout *topLayout = nullptr;
QVBoxLayout *bottomLayout = nullptr;
if ( curComponent->sharedSuccesor )
{
gridPlaneLayout->addItem( curColComponent->sharedSuccesor->diagramPlane, row + planeRowOffset, col + planeColOffset, 2, 2 );
curColComponent->sharedSuccesor->diagramPlane->setParentLayout( gridPlaneLayout );
planeLayoutItems << curColComponent->sharedSuccesor->diagramPlane;
}
Q_FOREACH( CartesianAxis* axis, cartDiag->axes() )
{
if ( axis->isAbscissa() )
{
if ( curColComponent->bottomSuccesor )
continue;
}
if ( laidOutAxes.contains( axis ) )
continue;
// if ( axis->diagram() != diagram )
// continue;
switch ( axis->position() )
{
case( CartesianAxis::Top ):
if ( !topLayout )
topLayout = new QVBoxLayout;
topLayout->addItem( axis );
axis->setParentLayout( topLayout );
break;
case( CartesianAxis::Bottom ):
if ( !bottomLayout )
bottomLayout = new QVBoxLayout;
bottomLayout->addItem( axis );
axis->setParentLayout( bottomLayout );
break;
case( CartesianAxis::Left ):
if ( !leftLayout )
leftLayout = new QHBoxLayout;
leftLayout->addItem( axis );
axis->setParentLayout( leftLayout );
break;
case( CartesianAxis::Right ):
if ( !rightLayout )
{
rightLayout = new QHBoxLayout;
}
rightLayout->addItem( axis );
axis->setParentLayout( rightLayout );
break;
}
planeLayoutItems << axis;
laidOutAxes.insert( axis );
}
if ( leftLayout )
gridPlaneLayout->addLayout( leftLayout, row + planeRowOffset, col, 2, 1,
Qt::AlignRight | Qt::AlignVCenter );
if ( rightLayout )
gridPlaneLayout->addLayout( rightLayout, row, col + planeColOffset + 2, 2, 1,
Qt::AlignLeft | Qt::AlignVCenter );
if ( topLayout )
gridPlaneLayout->addLayout( topLayout, row, col + planeColOffset, 1, 2,
Qt::AlignBottom | Qt::AlignHCenter );
if ( bottomLayout )
gridPlaneLayout->addLayout( bottomLayout, row + planeRowOffset + 2,
col + planeColOffset, 1, 2, Qt::AlignTop | Qt::AlignHCenter );
}
else
{
gridPlaneLayout->addItem( curColComponent->diagramPlane, row, col, 4, 4 );
curColComponent->diagramPlane->setParentLayout( gridPlaneLayout );
}
col += planeColOffset + 2 + ( 1 );
}
}
int axisOffset = 2;//curRowComponent->topAxesLayout ? 1 : 0;
//axisOffset += curRowComponent->bottomAxesLayout ? 1 : 0;
const int rowOffset = axisOffset + 2;
row += rowOffset;
}
// if ( planesLayout->direction() == QBoxLayout::TopToBottom )
// ++row;
// else
// ++col;
}
qDeleteAll( vals );
// re-add our grid(s) to the chart's layout
if ( dataAndLegendLayout ) {
dataAndLegendLayout->addLayout( planesLayout, 1, 1 );
dataAndLegendLayout->setRowStretch( 1, 1000 );
dataAndLegendLayout->setColumnStretch( 1, 1000 );
}
slotResizePlanes();
#ifdef NEW_LAYOUT_DEBUG
for ( int i = 0; i < gridPlaneLayout->rowCount(); ++i )
{
for ( int j = 0; j < gridPlaneLayout->columnCount(); ++j )
{
if ( gridPlaneLayout->itemAtPosition( i, j ) )
qDebug() << Q_FUNC_INFO << "item at" << i << j << gridPlaneLayout->itemAtPosition( i, j )->geometry();
else
qDebug() << Q_FUNC_INFO << "item at" << i << j << "no item present";
}
}
//qDebug() << Q_FUNC_INFO << "Relayout ended";
#endif
} else {
if ( hadPlanesLayout ) {
planesLayout->setContentsMargins( left, top, right, bottom );
}
planesLayout->setContentsMargins( 0, 0, 0, 0 );
planesLayout->setSpacing( 0 );
planesLayout->setObjectName( QString::fromLatin1( "planesLayout" ) );
/* First go through all planes and all axes and figure out whether the planes
* need to coordinate. If they do, they share a grid layout, if not, each
* gets their own. See buildPlaneLayoutInfos() for more details. */
QHash planeInfos = buildPlaneLayoutInfos();
QHash axisInfos;
Q_FOREACH( AbstractCoordinatePlane* plane, coordinatePlanes ) {
Q_ASSERT( planeInfos.contains(plane) );
PlaneInfo& pi = planeInfos[ plane ];
const int column = pi.horizontalOffset;
const int row = pi.verticalOffset;
//qDebug() << "processing plane at column" << column << "and row" << row;
QGridLayout *planeLayout = pi.gridLayout;
if ( !planeLayout ) {
PlaneInfo& refPi = pi;
// if this plane is sharing an axis with another one, recursively check for the original plane and use
// the grid of that as planeLayout.
while ( !planeLayout && refPi.referencePlane ) {
refPi = planeInfos[refPi.referencePlane];
planeLayout = refPi.gridLayout;
}
Q_ASSERT_X( planeLayout,
"Chart::Private::slotLayoutPlanes()",
"Invalid reference plane. Please check that the reference plane has been added to the Chart." );
} else {
planesLayout->addLayout( planeLayout );
}
/* Put the plane in the center of the layout. If this is our own, that's
* the middle of the layout, if we are sharing, it's a cell in the center
* column of the shared grid. */
planeLayoutItems << plane;
plane->setParentLayout( planeLayout );
planeLayout->addItem( plane, row, column, 1, 1 );
//qDebug() << "Chart slotLayoutPlanes() calls planeLayout->addItem("<< row << column << ")";
planeLayout->setRowStretch( row, 2 );
planeLayout->setColumnStretch( column, 2 );
Q_FOREACH( AbstractDiagram* abstractDiagram, plane->diagrams() )
{
AbstractCartesianDiagram* diagram =
qobject_cast< AbstractCartesianDiagram* >( abstractDiagram );
if ( !diagram ) {
continue; // FIXME what about polar ?
}
if ( pi.referencePlane != nullptr )
{
pi.topAxesLayout = planeInfos[ pi.referencePlane ].topAxesLayout;
pi.bottomAxesLayout = planeInfos[ pi.referencePlane ].bottomAxesLayout;
pi.leftAxesLayout = planeInfos[ pi.referencePlane ].leftAxesLayout;
pi.rightAxesLayout = planeInfos[ pi.referencePlane ].rightAxesLayout;
}
// collect all axes of a kind into sublayouts
if ( pi.topAxesLayout == nullptr )
{
pi.topAxesLayout = new QVBoxLayout;
pi.topAxesLayout->setContentsMargins( 0, 0, 0, 0 );
pi.topAxesLayout->setObjectName( QString::fromLatin1( "topAxesLayout" ) );
}
if ( pi.bottomAxesLayout == nullptr )
{
pi.bottomAxesLayout = new QVBoxLayout;
pi.bottomAxesLayout->setContentsMargins( 0, 0, 0, 0 );
pi.bottomAxesLayout->setObjectName( QString::fromLatin1( "bottomAxesLayout" ) );
}
if ( pi.leftAxesLayout == nullptr )
{
pi.leftAxesLayout = new QHBoxLayout;
pi.leftAxesLayout->setContentsMargins( 0, 0, 0, 0 );
pi.leftAxesLayout->setObjectName( QString::fromLatin1( "leftAxesLayout" ) );
}
if ( pi.rightAxesLayout == nullptr )
{
pi.rightAxesLayout = new QHBoxLayout;
pi.rightAxesLayout->setContentsMargins( 0, 0, 0, 0 );
pi.rightAxesLayout->setObjectName( QString::fromLatin1( "rightAxesLayout" ) );
}
if ( pi.referencePlane != nullptr )
{
planeInfos[ pi.referencePlane ].topAxesLayout = pi.topAxesLayout;
planeInfos[ pi.referencePlane ].bottomAxesLayout = pi.bottomAxesLayout;
planeInfos[ pi.referencePlane ].leftAxesLayout = pi.leftAxesLayout;
planeInfos[ pi.referencePlane ].rightAxesLayout = pi.rightAxesLayout;
}
//pi.leftAxesLayout->setSizeConstraint( QLayout::SetFixedSize );
Q_FOREACH( CartesianAxis* axis, diagram->axes() ) {
if ( axisInfos.contains( axis ) ) {
continue; // already laid out this one
}
Q_ASSERT ( axis );
axis->setCachedSizeDirty();
//qDebug() << "--------------- axis added to planeLayoutItems -----------------";
planeLayoutItems << axis;
switch ( axis->position() ) {
case CartesianAxis::Top:
axis->setParentLayout( pi.topAxesLayout );
pi.topAxesLayout->addItem( axis );
break;
case CartesianAxis::Bottom:
axis->setParentLayout( pi.bottomAxesLayout );
pi.bottomAxesLayout->addItem( axis );
break;
case CartesianAxis::Left:
axis->setParentLayout( pi.leftAxesLayout );
pi.leftAxesLayout->addItem( axis );
break;
case CartesianAxis::Right:
axis->setParentLayout( pi.rightAxesLayout );
pi.rightAxesLayout->addItem( axis );
break;
default:
Q_ASSERT_X( false, "Chart::paintEvent", "unknown axis position" );
break;
};
axisInfos.insert( axis, AxisInfo() );
}
/* Put each stack of axes-layouts in the cells surrounding the
* associated plane. We are laying out in the oder the planes
* were added, and the first one gets to lay out shared axes.
* Private axes go here as well, of course. */
if ( !pi.topAxesLayout->parent() ) {
planeLayout->addLayout( pi.topAxesLayout, row - 1, column );
}
if ( !pi.bottomAxesLayout->parent() ) {
planeLayout->addLayout( pi.bottomAxesLayout, row + 1, column );
}
if ( !pi.leftAxesLayout->parent() ) {
planeLayout->addLayout( pi.leftAxesLayout, row, column - 1 );
}
if ( !pi.rightAxesLayout->parent() ) {
planeLayout->addLayout( pi.rightAxesLayout,row, column + 1 );
}
}
// use up to four auto-spacer items in the corners around the diagrams:
#define ADD_AUTO_SPACER_IF_NEEDED( \
spacerRow, spacerColumn, hLayoutIsAtTop, hLayout, vLayoutIsAtLeft, vLayout ) \
{ \
if ( hLayout || vLayout ) { \
AutoSpacerLayoutItem * spacer \
= new AutoSpacerLayoutItem( hLayoutIsAtTop, hLayout, vLayoutIsAtLeft, vLayout ); \
planeLayout->addItem( spacer, spacerRow, spacerColumn, 1, 1 ); \
spacer->setParentLayout( planeLayout ); \
planeLayoutItems << spacer; \
} \
}
if ( plane->isCornerSpacersEnabled() ) {
ADD_AUTO_SPACER_IF_NEEDED( row - 1, column - 1, false, pi.leftAxesLayout, false, pi.topAxesLayout )
ADD_AUTO_SPACER_IF_NEEDED( row + 1, column - 1, true, pi.leftAxesLayout, false, pi.bottomAxesLayout )
ADD_AUTO_SPACER_IF_NEEDED( row - 1, column + 1, false, pi.rightAxesLayout, true, pi.topAxesLayout )
ADD_AUTO_SPACER_IF_NEEDED( row + 1, column + 1, true, pi.rightAxesLayout, true, pi.bottomAxesLayout )
}
}
// re-add our grid(s) to the chart's layout
if ( dataAndLegendLayout ) {
dataAndLegendLayout->addLayout( planesLayout, 1, 1 );
dataAndLegendLayout->setRowStretch( 1, 1000 );
dataAndLegendLayout->setColumnStretch( 1, 1000 );
}
slotResizePlanes();
}
}
void Chart::Private::createLayouts()
{
// The toplevel layout provides the left and right global margins
layout = new QHBoxLayout( chart );
layout->setContentsMargins( 0, 0, 0, 0 );
layout->setObjectName( QString::fromLatin1( "Chart::Private::layout" ) );
layout->addSpacing( globalLeadingLeft );
leftOuterSpacer = layout->itemAt( layout->count() - 1 )->spacerItem();
// The vLayout provides top and bottom global margins and lays
// out headers, footers and the diagram area.
vLayout = new QVBoxLayout();
vLayout->setContentsMargins( 0, 0, 0, 0 );
vLayout->setObjectName( QString::fromLatin1( "vLayout" ) );
layout->addLayout( vLayout, 1000 );
layout->addSpacing( globalLeadingRight );
rightOuterSpacer = layout->itemAt( layout->count() - 1 )->spacerItem();
// 1. the spacing above the header area
vLayout->addSpacing( globalLeadingTop );
topOuterSpacer = vLayout->itemAt( vLayout->count() - 1 )->spacerItem();
// 2. the header area
headerLayout = new QGridLayout();
headerLayout->setContentsMargins( 0, 0, 0, 0 );
vLayout->addLayout( headerLayout );
// 3. the area containing coordinate planes, axes, and legends
dataAndLegendLayout = new QGridLayout();
dataAndLegendLayout->setContentsMargins( 0, 0, 0, 0 );
dataAndLegendLayout->setObjectName( QString::fromLatin1( "dataAndLegendLayout" ) );
vLayout->addLayout( dataAndLegendLayout, 1000 );
// 4. the footer area
footerLayout = new QGridLayout();
footerLayout->setContentsMargins( 0, 0, 0, 0 );
footerLayout->setObjectName( QString::fromLatin1( "footerLayout" ) );
vLayout->addLayout( footerLayout );
// 5. Prepare the header / footer layout cells:
// Each of the 9 header cells (the 9 footer cells)
// contain their own QVBoxLayout
// since there can be more than one header (footer) per cell.
for ( int row = 0; row < 3; ++row ) {
for ( int column = 0; column < 3; ++ column ) {
const Qt::Alignment align = s_gridAlignments[ row ][ column ];
for ( int headOrFoot = 0; headOrFoot < 2; headOrFoot++ ) {
QVBoxLayout* innerLayout = new QVBoxLayout();
innerLayout->setContentsMargins( 0, 0, 0, 0 );
innerLayout->setAlignment( align );
innerHdFtLayouts[ headOrFoot ][ row ][ column ] = innerLayout;
QGridLayout* outerLayout = headOrFoot == 0 ? headerLayout : footerLayout;
outerLayout->addLayout( innerLayout, row, column, align );
}
}
}
// 6. the spacing below the footer area
vLayout->addSpacing( globalLeadingBottom );
bottomOuterSpacer = vLayout->itemAt( vLayout->count() - 1 )->spacerItem();
// the data+axes area
dataAndLegendLayout->addLayout( planesLayout, 1, 1 );
dataAndLegendLayout->setRowStretch( 1, 1 );
dataAndLegendLayout->setColumnStretch( 1, 1 );
}
void Chart::Private::slotResizePlanes()
{
if ( !dataAndLegendLayout ) {
return;
}
if ( !overrideSize.isValid() ) {
// activate() takes the size from the layout's parent QWidget, which is not updated when overrideSize
// is set. So don't let the layout grab the wrong size in that case.
// When overrideSize *is* set, we call layout->setGeometry() in paint( QPainter*, const QRect& ),
// which also "activates" the layout in the sense that it distributes space internally.
layout->activate();
}
// Adapt diagram drawing to the new size
Q_FOREACH (AbstractCoordinatePlane* plane, coordinatePlanes ) {
plane->layoutDiagrams();
}
}
void Chart::Private::updateDirtyLayouts()
{
if ( isPlanesLayoutDirty ) {
Q_FOREACH ( AbstractCoordinatePlane* p, coordinatePlanes ) {
p->setGridNeedsRecalculate();
p->layoutPlanes();
p->layoutDiagrams();
}
}
if ( isPlanesLayoutDirty || isFloatingLegendsLayoutDirty ) {
chart->reLayoutFloatingLegends();
}
isPlanesLayoutDirty = false;
isFloatingLegendsLayoutDirty = false;
}
void Chart::Private::reapplyInternalLayouts()
{
QRect geo = layout->geometry();
invalidateLayoutTree( layout );
layout->setGeometry( geo );
slotResizePlanes();
}
void Chart::Private::paintAll( QPainter* painter )
{
updateDirtyLayouts();
QRect rect( QPoint( 0, 0 ), overrideSize.isValid() ? overrideSize : chart->size() );
//qDebug() << this<<"::paintAll() uses layout size" << currentLayoutSize;
// Paint the background (if any)
AbstractAreaBase::paintBackgroundAttributes( *painter, rect, backgroundAttributes );
// Paint the frame (if any)
AbstractAreaBase::paintFrameAttributes( *painter, rect, frameAttributes );
chart->reLayoutFloatingLegends();
Q_FOREACH( AbstractLayoutItem* planeLayoutItem, planeLayoutItems ) {
planeLayoutItem->paintAll( *painter );
}
Q_FOREACH( TextArea* textLayoutItem, textLayoutItems ) {
textLayoutItem->paintAll( *painter );
}
Q_FOREACH( Legend *legend, legends ) {
const bool hidden = legend->isHidden() && legend->testAttribute( Qt::WA_WState_ExplicitShowHide );
if ( !hidden ) {
//qDebug() << "painting legend at " << legend->geometry();
legend->paintIntoRect( *painter, legend->geometry() );
}
}
}
// ******** Chart interface implementation ***********
#define d d_func()
Chart::Chart ( QWidget* parent )
: QWidget ( parent )
, _d( new Private( this ) )
{
#if defined KDAB_EVAL
EvalDialog::checkEvalLicense( "KD Chart" );
#endif
FrameAttributes frameAttrs;
// no frame per default...
// frameAttrs.setVisible( true );
frameAttrs.setPen( QPen( Qt::black ) );
frameAttrs.setPadding( 1 );
setFrameAttributes( frameAttrs );
addCoordinatePlane( new CartesianCoordinatePlane ( this ) );
d->createLayouts();
}
Chart::~Chart()
{
delete d;
}
void Chart::setFrameAttributes( const FrameAttributes &a )
{
d->frameAttributes = a;
}
FrameAttributes Chart::frameAttributes() const
{
return d->frameAttributes;
}
void Chart::setBackgroundAttributes( const BackgroundAttributes &a )
{
d->backgroundAttributes = a;
}
BackgroundAttributes Chart::backgroundAttributes() const
{
return d->backgroundAttributes;
}
//TODO KChart 3.0; change QLayout into QBoxLayout::Direction
void Chart::setCoordinatePlaneLayout( QLayout * layout )
{
if (layout == d->planesLayout)
return;
if (d->planesLayout) {
// detach all QLayoutItem's the previous planesLayout has cause
// otherwise deleting the planesLayout would delete them too.
for(int i = d->planesLayout->count() - 1; i >= 0; --i) {
d->planesLayout->takeAt(i);
}
delete d->planesLayout;
}
d->planesLayout = qobject_cast( layout );
d->slotLayoutPlanes();
}
QLayout* Chart::coordinatePlaneLayout()
{
return d->planesLayout;
}
AbstractCoordinatePlane* Chart::coordinatePlane()
{
if ( d->coordinatePlanes.isEmpty() ) {
qWarning() << "Chart::coordinatePlane: warning: no coordinate plane defined.";
return nullptr;
} else {
return d->coordinatePlanes.first();
}
}
CoordinatePlaneList Chart::coordinatePlanes()
{
return d->coordinatePlanes;
}
void Chart::addCoordinatePlane( AbstractCoordinatePlane* plane )
{
// Append
insertCoordinatePlane( d->coordinatePlanes.count(), plane );
}
void Chart::insertCoordinatePlane( int index, AbstractCoordinatePlane* plane )
{
if ( index < 0 || index > d->coordinatePlanes.count() ) {
return;
}
connect( plane, SIGNAL(destroyedCoordinatePlane(AbstractCoordinatePlane*)),
d, SLOT(slotUnregisterDestroyedPlane(AbstractCoordinatePlane*)) );
connect( plane, SIGNAL(needUpdate()), this, SLOT(update()) );
connect( plane, SIGNAL(needRelayout()), d, SLOT(slotResizePlanes()) ) ;
connect( plane, SIGNAL(needLayoutPlanes()), d, SLOT(slotLayoutPlanes()) ) ;
connect( plane, SIGNAL(propertiesChanged()),this, SIGNAL(propertiesChanged()) );
d->coordinatePlanes.insert( index, plane );
plane->setParent( this );
d->slotLayoutPlanes();
}
void Chart::replaceCoordinatePlane( AbstractCoordinatePlane* plane,
AbstractCoordinatePlane* oldPlane_ )
{
if ( plane && oldPlane_ != plane ) {
AbstractCoordinatePlane* oldPlane = oldPlane_;
if ( d->coordinatePlanes.count() ) {
if ( ! oldPlane ) {
oldPlane = d->coordinatePlanes.first();
if ( oldPlane == plane )
return;
}
takeCoordinatePlane( oldPlane );
}
delete oldPlane;
addCoordinatePlane( plane );
}
}
void Chart::takeCoordinatePlane( AbstractCoordinatePlane* plane )
{
const int idx = d->coordinatePlanes.indexOf( plane );
if ( idx != -1 ) {
d->coordinatePlanes.takeAt( idx );
disconnect( plane, nullptr, d, nullptr );
disconnect( plane, nullptr, this, nullptr );
plane->removeFromParentLayout();
plane->setParent( nullptr );
d->mouseClickedPlanes.removeAll(plane);
}
d->slotLayoutPlanes();
// Need to emit the signal: In case somebody has connected the signal
// to her own slot for e.g. calling update() on a widget containing the chart.
emit propertiesChanged();
}
void Chart::setGlobalLeading( int left, int top, int right, int bottom )
{
setGlobalLeadingLeft( left );
setGlobalLeadingTop( top );
setGlobalLeadingRight( right );
setGlobalLeadingBottom( bottom );
}
void Chart::setGlobalLeadingLeft( int leading )
{
d->globalLeadingLeft = leading;
d->leftOuterSpacer->changeSize( leading, 0, QSizePolicy::Fixed, QSizePolicy::Minimum );
d->reapplyInternalLayouts();
}
int Chart::globalLeadingLeft() const
{
return d->globalLeadingLeft;
}
void Chart::setGlobalLeadingTop( int leading )
{
d->globalLeadingTop = leading;
d->topOuterSpacer->changeSize( 0, leading, QSizePolicy::Minimum, QSizePolicy::Fixed );
d->reapplyInternalLayouts();
}
int Chart::globalLeadingTop() const
{
return d->globalLeadingTop;
}
void Chart::setGlobalLeadingRight( int leading )
{
d->globalLeadingRight = leading;
d->rightOuterSpacer->changeSize( leading, 0, QSizePolicy::Fixed, QSizePolicy::Minimum );
d->reapplyInternalLayouts();
}
int Chart::globalLeadingRight() const
{
return d->globalLeadingRight;
}
void Chart::setGlobalLeadingBottom( int leading )
{
d->globalLeadingBottom = leading;
d->bottomOuterSpacer->changeSize( 0, leading, QSizePolicy::Minimum, QSizePolicy::Fixed );
d->reapplyInternalLayouts();
}
int Chart::globalLeadingBottom() const
{
return d->globalLeadingBottom;
}
void Chart::paint( QPainter* painter, const QRect& rect )
{
if ( rect.isEmpty() || !painter ) {
return;
}
QPaintDevice* prevDevice = GlobalMeasureScaling::paintDevice();
GlobalMeasureScaling::setPaintDevice( painter->device() );
int prevScaleFactor = PrintingParameters::scaleFactor();
PrintingParameters::setScaleFactor( qreal( painter->device()->logicalDpiX() ) / qreal( logicalDpiX() ) );
const QRect oldGeometry( geometry() );
if ( oldGeometry != rect )
setGeometry( rect );
painter->translate( rect.left(), rect.top() );
d->paintAll( painter );
// for debugging
// painter->setPen( QPen( Qt::blue, 8 ) );
// painter->drawRect( rect );
painter->translate( -rect.left(), -rect.top() );
if ( oldGeometry != rect )
setGeometry( oldGeometry );
PrintingParameters::setScaleFactor( prevScaleFactor );
GlobalMeasureScaling::setPaintDevice( prevDevice );
}
void Chart::resizeEvent ( QResizeEvent* event )
{
d->isPlanesLayoutDirty = true;
d->isFloatingLegendsLayoutDirty = true;
QWidget::resizeEvent( event );
}
void Chart::reLayoutFloatingLegends()
{
Q_FOREACH( Legend *legend, d->legends ) {
const bool hidden = legend->isHidden() && legend->testAttribute( Qt::WA_WState_ExplicitShowHide );
if ( legend->position().isFloating() && !hidden ) {
// resize the legend
const QSize legendSize( legend->sizeHint() );
legend->setGeometry( QRect( legend->geometry().topLeft(), legendSize ) );
// find the legends corner point (reference point plus any paddings)
const RelativePosition relPos( legend->floatingPosition() );
QPointF pt( relPos.calculatedPoint( size() ) );
//qDebug() << pt;
// calculate the legend's top left point
const Qt::Alignment alignTopLeft = Qt::AlignBottom | Qt::AlignLeft;
if ( (relPos.alignment() & alignTopLeft) != alignTopLeft ) {
if ( relPos.alignment() & Qt::AlignRight )
pt.rx() -= legendSize.width();
else if ( relPos.alignment() & Qt::AlignHCenter )
pt.rx() -= 0.5 * legendSize.width();
if ( relPos.alignment() & Qt::AlignBottom )
pt.ry() -= legendSize.height();
else if ( relPos.alignment() & Qt::AlignVCenter )
pt.ry() -= 0.5 * legendSize.height();
}
//qDebug() << pt << endl;
legend->move( static_cast(pt.x()), static_cast(pt.y()) );
}
}
}
void Chart::paintEvent( QPaintEvent* )
{
QPainter painter( this );
d->paintAll( &painter );
emit finishedDrawing();
}
void Chart::addHeaderFooter( HeaderFooter* hf )
{
Q_ASSERT( hf->type() == HeaderFooter::Header || hf->type() == HeaderFooter::Footer );
int row;
int column;
getRowAndColumnForPosition( hf->position().value(), &row, &column );
if ( row == -1 ) {
qWarning( "Unknown header/footer position" );
return;
}
d->headerFooters.append( hf );
d->textLayoutItems.append( hf );
connect( hf, SIGNAL(destroyedHeaderFooter(HeaderFooter*)),
d, SLOT(slotUnregisterDestroyedHeaderFooter(HeaderFooter*)) );
connect( hf, SIGNAL(positionChanged(HeaderFooter*)),
d, SLOT(slotHeaderFooterPositionChanged(HeaderFooter*)) );
// set the text attributes (why?)
TextAttributes textAttrs( hf->textAttributes() );
Measure measure( textAttrs.fontSize() );
measure.setRelativeMode( this, KChartEnums::MeasureOrientationMinimum );
measure.setValue( 20 );
textAttrs.setFontSize( measure );
hf->setTextAttributes( textAttrs );
// add it to the appropriate layout
int innerLayoutIdx = hf->type() == HeaderFooter::Header ? 0 : 1;
QVBoxLayout* headerFooterLayout = d->innerHdFtLayouts[ innerLayoutIdx ][ row ][ column ];
hf->setParentLayout( headerFooterLayout );
hf->setAlignment( s_gridAlignments[ row ][ column ] );
headerFooterLayout->addItem( hf );
d->slotResizePlanes();
}
void Chart::replaceHeaderFooter( HeaderFooter* headerFooter,
HeaderFooter* oldHeaderFooter_ )
{
if ( headerFooter && oldHeaderFooter_ != headerFooter ) {
HeaderFooter* oldHeaderFooter = oldHeaderFooter_;
if ( d->headerFooters.count() ) {
if ( ! oldHeaderFooter ) {
oldHeaderFooter = d->headerFooters.first();
if ( oldHeaderFooter == headerFooter )
return;
}
takeHeaderFooter( oldHeaderFooter );
}
delete oldHeaderFooter;
addHeaderFooter( headerFooter );
}
}
void Chart::takeHeaderFooter( HeaderFooter* headerFooter )
{
const int idx = d->headerFooters.indexOf( headerFooter );
if ( idx == -1 ) {
return;
}
disconnect( headerFooter, SIGNAL(destroyedHeaderFooter(HeaderFooter*)),
d, SLOT(slotUnregisterDestroyedHeaderFooter(HeaderFooter*)) );
d->headerFooters.takeAt( idx );
headerFooter->removeFromParentLayout();
headerFooter->setParentLayout( nullptr );
d->textLayoutItems.remove( d->textLayoutItems.indexOf( headerFooter ) );
d->slotResizePlanes();
}
void Chart::Private::slotHeaderFooterPositionChanged( HeaderFooter* hf )
{
chart->takeHeaderFooter( hf );
chart->addHeaderFooter( hf );
}
HeaderFooter* Chart::headerFooter()
{
if ( d->headerFooters.isEmpty() ) {
return nullptr;
} else {
return d->headerFooters.first();
}
}
HeaderFooterList Chart::headerFooters()
{
return d->headerFooters;
}
void Chart::Private::slotLegendPositionChanged( AbstractAreaWidget* aw )
{
Legend* legend = qobject_cast< Legend* >( aw );
Q_ASSERT( legend );
chart->takeLegend( legend );
chart->addLegendInternal( legend, false );
}
void Chart::addLegend( Legend* legend )
{
legend->show();
addLegendInternal( legend, true );
emit propertiesChanged();
}
void Chart::addLegendInternal( Legend* legend, bool setMeasures )
{
if ( !legend ) {
return;
}
KChartEnums::PositionValue pos = legend->position().value();
if ( pos == KChartEnums::PositionCenter ) {
qWarning( "Not showing legend because PositionCenter is not supported for legends." );
}
int row;
int column;
getRowAndColumnForPosition( pos, &row, &column );
if ( row < 0 && pos != KChartEnums::PositionFloating ) {
qWarning( "Not showing legend because of unknown legend position." );
return;
}
d->legends.append( legend );
legend->setParent( this );
// set text attributes (why?)
if ( setMeasures ) {
TextAttributes textAttrs( legend->textAttributes() );
Measure measure( textAttrs.fontSize() );
measure.setRelativeMode( this, KChartEnums::MeasureOrientationMinimum );
measure.setValue( 20 );
textAttrs.setFontSize( measure );
legend->setTextAttributes( textAttrs );
textAttrs = legend->titleTextAttributes();
measure.setRelativeMode( this, KChartEnums::MeasureOrientationMinimum );
measure.setValue( 24 );
textAttrs.setFontSize( measure );
legend->setTitleTextAttributes( textAttrs );
legend->setReferenceArea( this );
}
// add it to the appropriate layout
if ( pos != KChartEnums::PositionFloating ) {
legend->needSizeHint();
// in each edge and corner of the outer layout, there's a grid for the different alignments that we create
// on demand. we don't remove it when empty.
QLayoutItem* edgeItem = d->dataAndLegendLayout->itemAtPosition( row, column );
QGridLayout* alignmentsLayout = dynamic_cast< QGridLayout* >( edgeItem );
Q_ASSERT( !edgeItem || alignmentsLayout ); // if it exists, it must be a QGridLayout
if ( !alignmentsLayout ) {
alignmentsLayout = new QGridLayout;
d->dataAndLegendLayout->addLayout( alignmentsLayout, row, column );
alignmentsLayout->setContentsMargins( 0, 0, 0, 0 );
}
// in case there are several legends in the same edge or corner with the same alignment, they are stacked
// vertically using a QVBoxLayout. it is created on demand as above.
row = 1;
column = 1;
for ( int i = 0; i < 3; i++ ) {
for ( int j = 0; j < 3; j++ ) {
Qt::Alignment align = s_gridAlignments[ i ][ j ];
if ( align == legend->alignment() ) {
row = i;
column = j;
break;
}
}
}
QLayoutItem* alignmentItem = alignmentsLayout->itemAtPosition( row, column );
QVBoxLayout* sameAlignmentLayout = dynamic_cast< QVBoxLayout* >( alignmentItem );
Q_ASSERT( !alignmentItem || sameAlignmentLayout ); // if it exists, it must be a QVBoxLayout
if ( !sameAlignmentLayout ) {
sameAlignmentLayout = new QVBoxLayout;
alignmentsLayout->addLayout( sameAlignmentLayout, row, column );
sameAlignmentLayout->setContentsMargins( 0, 0, 0, 0 );
}
sameAlignmentLayout->addItem( new MyWidgetItem( legend, legend->alignment() ) );
}
connect( legend, SIGNAL(destroyedLegend(Legend*)),
d, SLOT(slotUnregisterDestroyedLegend(Legend*)) );
connect( legend, SIGNAL(positionChanged(AbstractAreaWidget*)),
d, SLOT(slotLegendPositionChanged(AbstractAreaWidget*)) );
connect( legend, SIGNAL(propertiesChanged()), this, SIGNAL(propertiesChanged()) );
d->slotResizePlanes();
}
void Chart::replaceLegend( Legend* legend, Legend* oldLegend_ )
{
if ( legend && oldLegend_ != legend ) {
Legend* oldLegend = oldLegend_;
if ( d->legends.count() ) {
if ( ! oldLegend ) {
oldLegend = d->legends.first();
if ( oldLegend == legend )
return;
}
takeLegend( oldLegend );
}
delete oldLegend;
addLegend( legend );
}
}
void Chart::takeLegend( Legend* legend )
{
const int idx = d->legends.indexOf( legend );
if ( idx == -1 ) {
return;
}
d->legends.takeAt( idx );
disconnect( legend, nullptr, d, nullptr );
disconnect( legend, nullptr, this, nullptr );
// the following removes the legend from its layout and destroys its MyWidgetItem (the link to the layout)
legend->setParent( nullptr );
d->slotResizePlanes();
emit propertiesChanged();
}
Legend* Chart::legend()
{
return d->legends.isEmpty() ? nullptr : d->legends.first();
}
LegendList Chart::legends()
{
return d->legends;
}
void Chart::mousePressEvent( QMouseEvent* event )
{
const QPoint pos = mapFromGlobal( event->globalPos() );
Q_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes ) {
if ( plane->geometry().contains( event->pos() ) && plane->diagrams().size() > 0 ) {
QMouseEvent ev( QEvent::MouseButtonPress, pos, event->globalPos(),
event->button(), event->buttons(), event->modifiers() );
plane->mousePressEvent( &ev );
d->mouseClickedPlanes.append( plane );
}
}
}
void Chart::mouseDoubleClickEvent( QMouseEvent* event )
{
const QPoint pos = mapFromGlobal( event->globalPos() );
Q_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes ) {
if ( plane->geometry().contains( event->pos() ) && plane->diagrams().size() > 0 ) {
QMouseEvent ev( QEvent::MouseButtonPress, pos, event->globalPos(),
event->button(), event->buttons(), event->modifiers() );
plane->mouseDoubleClickEvent( &ev );
}
}
}
void Chart::mouseMoveEvent( QMouseEvent* event )
{
QSet< AbstractCoordinatePlane* > eventReceivers = QSet< AbstractCoordinatePlane* >::fromList( d->mouseClickedPlanes );
Q_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes ) {
if ( plane->geometry().contains( event->pos() ) && plane->diagrams().size() > 0 ) {
eventReceivers.insert( plane );
}
}
const QPoint pos = mapFromGlobal( event->globalPos() );
Q_FOREACH( AbstractCoordinatePlane* plane, eventReceivers ) {
QMouseEvent ev( QEvent::MouseMove, pos, event->globalPos(),
event->button(), event->buttons(), event->modifiers() );
plane->mouseMoveEvent( &ev );
}
}
void Chart::mouseReleaseEvent( QMouseEvent* event )
{
QSet< AbstractCoordinatePlane* > eventReceivers = QSet< AbstractCoordinatePlane* >::fromList( d->mouseClickedPlanes );
Q_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes ) {
if ( plane->geometry().contains( event->pos() ) && plane->diagrams().size() > 0 ) {
eventReceivers.insert( plane );
}
}
const QPoint pos = mapFromGlobal( event->globalPos() );
Q_FOREACH( AbstractCoordinatePlane* plane, eventReceivers ) {
QMouseEvent ev( QEvent::MouseButtonRelease, pos, event->globalPos(),
event->button(), event->buttons(), event->modifiers() );
plane->mouseReleaseEvent( &ev );
}
d->mouseClickedPlanes.clear();
}
bool Chart::event( QEvent* event )
{
if ( event->type() == QEvent::ToolTip ) {
const QHelpEvent* const helpEvent = static_cast< QHelpEvent* >( event );
Q_FOREACH( const AbstractCoordinatePlane* const plane, d->coordinatePlanes ) {
// iterate diagrams in reverse, so that the top-most painted diagram is
// queried first for a tooltip before the diagrams behind it
const ConstAbstractDiagramList& diagrams = plane->diagrams();
for (int i = diagrams.size() - 1; i >= 0; --i) {
const AbstractDiagram* diagram = diagrams[i];
if (diagram->isHidden()) {
continue;
}
const QModelIndex index = diagram->indexAt( helpEvent->pos() );
const QVariant toolTip = index.data( Qt::ToolTipRole );
if ( toolTip.isValid() ) {
QPoint pos = mapFromGlobal( helpEvent->pos() );
QRect rect( pos - QPoint( 1, 1 ), QSize( 3, 3 ) );
QToolTip::showText( QCursor::pos(), toolTip.toString(), this, rect );
return true;
}
}
}
}
return QWidget::event( event );
}
bool Chart::useNewLayoutSystem() const
{
return d_func()->useNewLayoutSystem;
}
void Chart::setUseNewLayoutSystem( bool value )
{
if ( d_func()->useNewLayoutSystem != value )
d_func()->useNewLayoutSystem = value;
}
diff --git a/src/KGantt/kganttgraphicsview.cpp b/src/KGantt/kganttgraphicsview.cpp
index eeef037..8f2b7c0 100644
--- a/src/KGantt/kganttgraphicsview.cpp
+++ b/src/KGantt/kganttgraphicsview.cpp
@@ -1,827 +1,827 @@
/*
* Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved.
*
* This file is part of the KGantt 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 "kganttgraphicsview.h"
#include "kganttgraphicsview_p.h"
#include "kganttabstractrowcontroller.h"
#include "kganttgraphicsitem.h"
#include "kganttconstraintmodel.h"
#include "kganttdatetimetimelinedialog.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#if defined KDAB_EVAL
#include "../evaldialog/evaldialog.h"
#endif
-/*!\class KGantt::HeaderWidget
+/*\class KGantt::HeaderWidget
* \internal
*/
using namespace KGantt;
HeaderWidget::HeaderWidget( GraphicsView* parent )
: QWidget( parent ), m_offset( 0. )
{
assert( parent ); // Parent must be set
}
HeaderWidget::~HeaderWidget()
{
}
void HeaderWidget::scrollTo( int v )
{
m_offset = v;
// QWidget::scroll() wont work properly for me on Mac
//scroll( static_cast( old-v ), 0 );
update();
}
void HeaderWidget::paintEvent( QPaintEvent* ev )
{
QPainter p( this );
view()->grid()->paintHeader( &p, rect(), ev->rect(), m_offset, this );
}
bool HeaderWidget::event( QEvent* event )
{
if ( event->type() == QEvent::ToolTip ) {
DateTimeGrid* const grid = qobject_cast< DateTimeGrid* >( view()->grid() );
if ( grid ) {
QHelpEvent *e = static_cast( event );
QDateTime dt = grid->mapFromChart( view()->mapToScene( e->x(), 0 ).x() ).toDateTime();
setToolTip( dt.toString() );
}
}
return QWidget::event( event );
}
void HeaderWidget::contextMenuEvent( QContextMenuEvent* event )
{
QMenu contextMenu;
DateTimeGrid* const grid = qobject_cast< DateTimeGrid* >( view()->grid() );
QAction* actionScaleAuto = nullptr;
QAction* actionScaleMonth = nullptr;
QAction* actionScaleWeek = nullptr;
QAction* actionScaleDay = nullptr;
QAction* actionScaleHour = nullptr;
QAction* actionZoomIn = nullptr;
QAction* actionZoomOut = nullptr;
QAction* actionTimeline = nullptr;
if ( grid != nullptr )
{
QMenu* menuScale = new QMenu( tr( "Scale", "@title:menu" ), &contextMenu );
QActionGroup* scaleGroup = new QActionGroup( &contextMenu );
scaleGroup->setExclusive( true );
actionScaleAuto = new QAction( tr( "Auto", "@item:inmenu Automatic scale" ), menuScale );
actionScaleAuto->setCheckable( true );
actionScaleAuto->setChecked( grid->scale() == DateTimeGrid::ScaleAuto );
actionScaleMonth = new QAction( tr( "Month", "@item:inmenu" ), menuScale );
actionScaleMonth->setCheckable( true );
actionScaleMonth->setChecked( grid->scale() == DateTimeGrid::ScaleMonth );
actionScaleWeek = new QAction( tr( "Week", "@item:inmenu" ), menuScale );
actionScaleWeek->setCheckable( true );
actionScaleWeek->setChecked( grid->scale() == DateTimeGrid::ScaleWeek );
actionScaleDay = new QAction( tr( "Day", "@item:inmenu" ), menuScale );
actionScaleDay->setCheckable( true );
actionScaleDay->setChecked( grid->scale() == DateTimeGrid::ScaleDay );
actionScaleHour = new QAction( tr( "Hour", "@item:inmenu" ), menuScale );
actionScaleHour->setCheckable( true );
actionScaleHour->setChecked( grid->scale() == DateTimeGrid::ScaleHour );
scaleGroup->addAction( actionScaleAuto );
menuScale->addAction( actionScaleAuto );
scaleGroup->addAction( actionScaleMonth );
menuScale->addAction( actionScaleMonth );
scaleGroup->addAction( actionScaleWeek );
menuScale->addAction( actionScaleWeek );
scaleGroup->addAction( actionScaleDay );
menuScale->addAction( actionScaleDay );
scaleGroup->addAction( actionScaleHour );
menuScale->addAction( actionScaleHour );
contextMenu.addMenu( menuScale );
contextMenu.addSeparator();
actionZoomIn = new QAction( tr( "Zoom In", "@action:inmenu" ), &contextMenu );
contextMenu.addAction( actionZoomIn );
actionZoomOut = new QAction( tr( "Zoom Out", "@action:inmenu" ), &contextMenu );
contextMenu.addAction( actionZoomOut );
contextMenu.addSeparator();
actionTimeline = new QAction( tr( "Timeline...", "@action:inmenu" ), &contextMenu );
contextMenu.addAction( actionTimeline );
}
if ( contextMenu.isEmpty() )
{
event->ignore();
return;
}
const QAction* const action = contextMenu.exec( event->globalPos() );
if ( action == nullptr ) {}
else if ( action == actionScaleAuto )
{
assert( grid != nullptr );
grid->setScale( DateTimeGrid::ScaleAuto );
}
else if ( action == actionScaleMonth )
{
assert( grid != nullptr );
grid->setScale( DateTimeGrid::ScaleMonth );
}
else if ( action == actionScaleWeek )
{
assert( grid != nullptr );
grid->setScale( DateTimeGrid::ScaleWeek );
}
else if ( action == actionScaleDay )
{
assert( grid != nullptr );
grid->setScale( DateTimeGrid::ScaleDay );
}
else if ( action == actionScaleHour )
{
assert( grid != nullptr );
grid->setScale( DateTimeGrid::ScaleHour );
}
else if ( action == actionZoomIn )
{
assert( grid != nullptr );
grid->setDayWidth( grid->dayWidth() * 1.25 );
}
else if ( action == actionZoomOut )
{
assert( grid != nullptr );
// daywidth *MUST NOT* go below 1.0, it is used as an integer later on
grid->setDayWidth( qMax( 1.0, grid->dayWidth() * 0.8 ) );
}
else if ( action == actionTimeline )
{
assert( grid != nullptr );
DateTimeTimeLineDialog dlg(grid->timeLine());
dlg.exec();
}
event->accept();
}
GraphicsView::Private::Private( GraphicsView* _q )
: q( _q ), rowcontroller(nullptr), headerwidget( _q )
{
}
void GraphicsView::Private::updateHeaderGeometry()
{
q->setViewportMargins(0,rowcontroller->headerHeight(),0,0);
headerwidget.setGeometry( q->viewport()->x(),
q->viewport()->y() - rowcontroller->headerHeight(),
q->viewport()->width(),
rowcontroller->headerHeight() );
}
void GraphicsView::Private::slotGridChanged()
{
updateHeaderGeometry();
headerwidget.update();
q->updateSceneRect();
q->update();
}
void GraphicsView::Private::slotHorizontalScrollValueChanged( int val )
{
const QRectF viewRect = q->transform().mapRect( q->sceneRect() );
headerwidget.scrollTo( val-q->horizontalScrollBar()->minimum()+static_cast( viewRect.left() ) );
}
void GraphicsView::Private::slotColumnsInserted( const QModelIndex& parent, int start, int end )
{
Q_UNUSED( start );
Q_UNUSED( end );
QModelIndex idx = scene.model()->index( 0, 0, scene.summaryHandlingModel()->mapToSource( parent ) );
do {
scene.updateRow( scene.summaryHandlingModel()->mapFromSource( idx ) );
} while ( ( idx = rowcontroller->indexBelow( idx ) ) != QModelIndex() && rowcontroller->isRowVisible( idx ) );
//} while ( ( idx = d->treeview.indexBelow( idx ) ) != QModelIndex() && d->treeview.visualRect(idx).isValid() );
q->updateSceneRect();
}
void GraphicsView::Private::slotColumnsRemoved( const QModelIndex& parent, int start, int end )
{
// TODO
Q_UNUSED( start );
Q_UNUSED( end );
Q_UNUSED( parent );
q->updateScene();
}
void GraphicsView::Private::slotDataChanged( const QModelIndex& topLeft, const QModelIndex& bottomRight )
{
//qDebug() << "GraphicsView::slotDataChanged("<index( row, 0, parent ) );
}
}
void GraphicsView::Private::slotLayoutChanged()
{
//qDebug() << "slotLayoutChanged()";
q->updateScene();
}
void GraphicsView::Private::slotModelReset()
{
//qDebug() << "slotModelReset()";
q->updateScene();
}
void GraphicsView::Private::slotRowsInserted( const QModelIndex& parent, int start, int end )
{
Q_UNUSED( parent );
Q_UNUSED( start );
Q_UNUSED( end );
q->updateScene(); // TODO: This might be optimised
}
void GraphicsView::Private::removeConstraintsRecursive( QAbstractProxyModel *summaryModel, const QModelIndex& index )
{
if ( summaryModel->hasChildren( index ) ) {
//qDebug() << "removing constraints from children of"<rowCount( index ); ++row ) {
const QModelIndex child = summaryModel->index( row, index.column(), index );
removeConstraintsRecursive( summaryModel, child );
}
}
//qDebug() << "removing constraints from"<sourceModel()
QList clst = scene.constraintModel()->constraintsForIndex( summaryModel->mapToSource( index ) );
Q_FOREACH( Constraint c, clst ) {
scene.constraintModel()->removeConstraint( c );
}
}
void GraphicsView::Private::slotRowsAboutToBeRemoved( const QModelIndex& parent, int start, int end )
{
//qDebug() << "GraphicsView::Private::slotRowsAboutToBeRemoved("<columnCount( parent ); ++col ) {
const QModelIndex idx = summaryModel->index( row, col, parent );
removeConstraintsRecursive( summaryModel, idx );
scene.removeItem( idx );
}
}
}
void GraphicsView::Private::slotRowsRemoved( const QModelIndex& parent, int start, int end )
{
//qDebug() << "GraphicsView::Private::slotRowsRemoved("<updateScene();
}
void GraphicsView::Private::slotItemClicked( const QModelIndex& idx )
{
QModelIndex sidx = idx;//scene.summaryHandlingModel()->mapToSource( idx );
emit q->clicked( sidx );
if (q->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, q))
emit q->activated( sidx );
}
void GraphicsView::Private::slotItemDoubleClicked( const QModelIndex& idx )
{
QModelIndex sidx = idx;//scene.summaryHandlingModel()->mapToSource( idx );
emit q->qrealClicked( sidx );
if (!q->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, q))
emit q->activated( sidx );
}
void GraphicsView::Private::slotHeaderContextMenuRequested( const QPoint& pt )
{
emit q->headerContextMenuRequested( headerwidget.mapToGlobal( pt ) );
}
/*!\class KGantt::GraphicsView kganttgraphicsview.h KGanttGraphicsView
* \ingroup KGantt
* \brief The GraphicsView class provides a model/view implementation of a gantt chart.
*
*
*/
/*! \fn void GraphicsView::activated( const QModelIndex & index ) */
/*! \fn void GraphicsView::clicked( const QModelIndex & index ); */
/*! \fn void GraphicsView::qrealClicked( const QModelIndex & index ); */
/*! \fn void GraphicsView::entered( const QModelIndex & index ); */
/*! \fn void GraphicsView::pressed( const QModelIndex & index ); */
/*! \fn void GraphicsView::headerContextMenuRequested( const QPoint& pt )
* This signal is emitted when the header has contextMenuPolicy Qt::CustomContextMenu
* and the widget wants to show a context menu for the header. Unlike in
* QWidget::customContextMenuRequested() signal, \a pt is here in global coordinates.
*/
/*! Constructor. Creates a new KGantt::GraphicsView with parent
* \a parent.
*/
GraphicsView::GraphicsView( QWidget* parent )
: QGraphicsView( parent ), _d( new Private( this ) )
{
#if defined KDAB_EVAL
EvalDialog::checkEvalLicense( "KD Gantt" );
#endif
connect( horizontalScrollBar(), SIGNAL(valueChanged(int)),
this, SLOT(slotHorizontalScrollValueChanged(int)) );
connect( &_d->scene, SIGNAL(gridChanged()),
this, SLOT(slotGridChanged()) );
connect( &_d->scene, SIGNAL(entered(QModelIndex)),
this, SIGNAL(entered(QModelIndex)) );
connect( &_d->scene, SIGNAL(pressed(QModelIndex)),
this, SIGNAL(pressed(QModelIndex)) );
connect( &_d->scene, SIGNAL(clicked(QModelIndex)),
this, SLOT(slotItemClicked(QModelIndex)) );
connect( &_d->scene, SIGNAL(qrealClicked(QModelIndex)),
this, SLOT(slotItemDoubleClicked(QModelIndex)) );
connect( &_d->scene, SIGNAL(sceneRectChanged(QRectF)),
this, SLOT(updateSceneRect()) );
connect( &_d->headerwidget, SIGNAL(customContextMenuRequested(QPoint)),
this, SLOT(slotHeaderContextMenuRequested(QPoint)) );
setScene( &_d->scene );
// HACK!
setSummaryHandlingModel( _d->scene.summaryHandlingModel() );
// So that AbstractGrid::drawBackground() and AbstractGrid::drawForeground()
// works properly
setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
//setCacheMode( CacheBackground );
}
/*! Destroys this view. */
GraphicsView::~GraphicsView()
{
delete _d;
}
#define d d_func()
/*! Sets the model to be displayed in this view to
* \a model. The view does not take ownership of the model.
*
* To make a model work well with GraphicsView it must
* have a certain layout. Whether the model is flat or has a
* treestrucure is not important, as long as an
* AbstractRowController is provided that can navigate the
* model.
*
* GraphicsView operates per row in the model. The data is always
* taken from the _last_ item in the row. The ItemRoles used are
* Qt::DisplayRole and the roles defined in KGantt::ItemDataRole.
*
* Note: This model is not returned by \a model()
*
* \see GraphicsView::model
*/
void GraphicsView::setModel( QAbstractItemModel* model )
{
if ( d->scene.model() ) {
disconnect( d->scene.model() );
}
d->scene.setModel( model );
if (model) {
connect( model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
this, SLOT(updateSceneRect()) );
}
updateScene();
}
/*! \returns the current model displayed by this view
*
* Note: The returned model is not the model set with \a setModel()
*
* \see GraphicsView::setModel
*/
QAbstractItemModel* GraphicsView::model() const
{
return d->scene.model();
}
void GraphicsView::setSummaryHandlingModel( QAbstractProxyModel* proxyModel )
{
disconnect( d->scene.summaryHandlingModel() );
d->scene.setSummaryHandlingModel( proxyModel );
/* Connections. We have to rely on the treeview
* to receive the signals before we do(!)
*/
connect( proxyModel, SIGNAL(columnsInserted(QModelIndex,int,int)),
this, SLOT(slotColumnsInserted(QModelIndex,int,int)) );
connect( proxyModel, SIGNAL(columnsRemoved(QModelIndex,int,int)),
this, SLOT(slotColumnsRemoved(QModelIndex,int,int)) );
connect( proxyModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
this, SLOT(slotDataChanged(QModelIndex,QModelIndex)) );
connect( proxyModel, SIGNAL(layoutChanged()),
this, SLOT(slotLayoutChanged()) );
connect( proxyModel, SIGNAL(modelReset()),
this, SLOT(slotModelReset()) );
connect( proxyModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
this, SLOT(slotRowsInserted(QModelIndex,int,int)) );
connect( proxyModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int)) );
connect( proxyModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
this, SLOT(slotRowsRemoved(QModelIndex,int,int)) );
updateScene();
}
/*! Sets the constraintmodel displayed by this view.
* \see KGantt::ConstraintModel.
*/
void GraphicsView::setConstraintModel( ConstraintModel* cmodel )
{
d->scene.setConstraintModel( cmodel );
}
/*! \returns the KGantt::ConstraintModel displayed by this view.
*/
ConstraintModel* GraphicsView::constraintModel() const
{
return d->scene.constraintModel();
}
/*! \returns the KGantt::SummaryHandlingProxyModel used by this view.
*/
QAbstractProxyModel* GraphicsView::summaryHandlingModel() const
{
return d->scene.summaryHandlingModel();
}
/*! Sets the root index of the model displayed by this view.
* Similar to QAbstractItemView::setRootIndex, default is QModelIndex().
*/
void GraphicsView::setRootIndex( const QModelIndex& idx )
{
d->scene.setRootIndex( idx );
}
/*! \returns the rootindex for this view.
*/
QModelIndex GraphicsView::rootIndex() const
{
return d->scene.rootIndex();
}
/*! Sets the QItemSelectionModel used by this view to manage
* selections. Similar to QAbstractItemView::setSelectionModel
*/
void GraphicsView::setSelectionModel( QItemSelectionModel* model )
{
d->scene.setSelectionModel( model );
}
/*! \returns the QItemSelectionModel used by this view
*/
QItemSelectionModel* GraphicsView::selectionModel() const
{
return d->scene.selectionModel();
}
/*! Sets the KGantt::ItemDelegate used for rendering items on this
* view. \see ItemDelegate and QAbstractItemDelegate.
*/
void GraphicsView::setItemDelegate( ItemDelegate* delegate )
{
d->scene.setItemDelegate( delegate );
}
/*! \returns the ItemDelegate used by this view to render items
*/
ItemDelegate* GraphicsView::itemDelegate() const
{
return d->scene.itemDelegate();
}
/*! Sets the AbstractRowController used by this view. The
* AbstractRowController deals with the height and position
* of each row and with which parts of the model are
* displayed. \see AbstractRowController
*/
void GraphicsView::setRowController( AbstractRowController* rowcontroller )
{
d->rowcontroller = rowcontroller;
d->scene.setRowController( rowcontroller );
updateScene();
}
/*! \returns the AbstractRowController
* for this view. \see setRowController
*/
AbstractRowController* GraphicsView::rowController() const
{
return d->rowcontroller;
}
/*! Sets the AbstractGrid for this view. The grid is an
* object that controls how QModelIndexes are mapped
* to and from the view and how the background and header
* is rendered. \see AbstractGrid and DateTimeGrid.
*/
void GraphicsView::setGrid( AbstractGrid* grid )
{
d->scene.setGrid( grid );
d->slotGridChanged();
}
/*! \returns the AbstractGrid used by this view.
*/
AbstractGrid* GraphicsView::grid() const
{
return d->scene.grid();
}
/*! \returns the AbstractGrid used by this view.
*/
AbstractGrid* GraphicsView::takeGrid()
{
AbstractGrid *grid = d->scene.grid();
if ( grid ) {
d->scene.setGrid( nullptr) ;
}
return grid;
}
/*! Sets the view to read-only mode if \a to is true. The default is
* read/write if the model permits it.
*/
void GraphicsView::setReadOnly( bool ro )
{
d->scene.setReadOnly( ro );
}
/*!\returns true iff the view is in read-only mode
*/
bool GraphicsView::isReadOnly() const
{
return d->scene.isReadOnly();
}
/*! Sets the context menu policy for the header. The default value
* Qt::DefaultContextMenu results in a standard context menu on the header
* that allows the user to set the scale and zoom.
*
* Setting this to Qt::CustomContextMenu will cause the signal
* headerContextMenuRequested(const QPoint& pt) to be emitted instead.
*
* \see QWidget::setContextMenuPolicy( Qt::ContextMenuPolicy )
*/
void GraphicsView::setHeaderContextMenuPolicy( Qt::ContextMenuPolicy p )
{
d->headerwidget.setContextMenuPolicy( p );
}
/*! \returns the context menu policy for the header
*/
Qt::ContextMenuPolicy GraphicsView::headerContextMenuPolicy() const
{
return d->headerwidget.contextMenuPolicy();
}
/*! Adds a constraint from \a from to \a to. \a modifiers are the
* keyboard modifiers pressed by the user when the action is invoked.
*
* Override this to control how contraints are added. The default
* implementation adds a soft constraint unless the Shift key is pressed,
* in that case it adds a hard constraint. If a constraint is already
* present, it is removed and nothing is added.
*/
void GraphicsView::addConstraint( const QModelIndex& from,
const QModelIndex& to,
Qt::KeyboardModifiers modifiers )
{
if ( isReadOnly() ) return;
ConstraintModel* cmodel = constraintModel();
assert( cmodel );
Constraint c( from, to, ( modifiers&Qt::ShiftModifier )?Constraint::TypeHard:Constraint::TypeSoft );
if ( cmodel->hasConstraint( c ) ) cmodel->removeConstraint( c );
else cmodel->addConstraint( c );
}
void GraphicsView::resizeEvent( QResizeEvent* ev )
{
d->updateHeaderGeometry();
QRectF r = scene()->itemsBoundingRect();
// To scroll more to the left than the actual item start, bug #4516
r.setLeft( qMin( 0.0, r.left() ) );
// TODO: take scrollbars into account (if not always on)
// The scene should be at least the size of the viewport
QSizeF size = viewport()->size();
//TODO: why -2 below? size should be ex. frames etc?
if ( size.width() > r.width() ) {
r.setWidth( size.width() - 2 );
}
if ( size.height() > r.height() ) {
r.setHeight( size.height() - 2 );
}
const int totalh = rowController()->totalHeight();
if ( r.height() < totalh ) {
r.setHeight( totalh );
}
scene()->setSceneRect( r );
QGraphicsView::resizeEvent( ev );
}
/*!\returns The QModelIndex for the item located at
* position \a pos in the view or an invalid index
* if no item was present at that position.
*
* This is useful for for example contextmenus.
*/
QModelIndex GraphicsView::indexAt( const QPoint& pos ) const
{
QGraphicsItem* item = itemAt( pos );
if ( GraphicsItem* gitem = qgraphicsitem_cast( item ) ) {
return d->scene.summaryHandlingModel()->mapToSource( gitem->index() );
} else {
return QModelIndex();
}
}
/*! \internal */
void GraphicsView::clearItems()
{
d->scene.clearItems();
}
/*! \internal */
void GraphicsView::updateRow( const QModelIndex& idx )
{
d->scene.updateRow( d->scene.summaryHandlingModel()->mapFromSource( idx ) );
}
/*! \internal
* Adjusts the bounding rectangle of the scene.
*/
void GraphicsView::updateSceneRect()
{
/* What to do with this? We need to shrink the view to
* make collapsing items work
*/
qreal range = horizontalScrollBar()->maximum()-horizontalScrollBar()->minimum();
const qreal hscroll = horizontalScrollBar()->value()/( range>0?range:1 );
QRectF r = d->scene.itemsBoundingRect();
// To scroll more to the left than the actual item start, bug #4516
r.setTop( 0. );
r.setLeft( qMin( 0.0, r.left() ) );
r.setSize( r.size().expandedTo( viewport()->size() ) );
const int totalh = rowController()->totalHeight();
if ( r.height() < totalh ) r.setHeight( totalh );
d->scene.setSceneRect( r );
/* set scrollbar to keep the same time in view */
range = horizontalScrollBar()->maximum()-horizontalScrollBar()->minimum();
if ( range>0 ) horizontalScrollBar()->setValue( qRound( hscroll*range ) );
/* We have to update here to adjust for any rows with no
* information because they are painted with a different
* background brush
*/
d->scene.invalidate( QRectF(), QGraphicsScene::BackgroundLayer );
}
/*! \internal
* Resets the state of the view.
*/
void GraphicsView::updateScene()
{
clearItems();
if ( !model()) return;
if ( !rowController()) return;
QModelIndex idx = model()->index( 0, 0, rootIndex() );
do {
updateRow( idx );
} while ( ( idx = rowController()->indexBelow( idx ) ) != QModelIndex() && rowController()->isRowVisible(idx) );
//constraintModel()->cleanup();
//qDebug() << constraintModel();
updateSceneRect();
if ( scene() ) scene()->invalidate( QRectF(), QGraphicsScene::BackgroundLayer );
}
#if 0
TODO: For 3.0
/*! Creates a new GraphicsItem
* Re-iplement to create your own flavour of GraphicsItem
*/
GraphicsItem* GraphicsView::createItem( ItemType type ) const
{
Q_UNUSED(type)
return new GraphicsItem;
}
#endif
/*! \internal */
void GraphicsView::deleteSubtree( const QModelIndex& idx )
{
d->scene.deleteSubtree( d->scene.summaryHandlingModel()->mapFromSource( idx ) );
}
/*! Print the Gantt chart using \a printer. If \a drawRowLabels
* is true (the default), each row will have it's label printed
* on the left side. If \a drawColumnLabels is true (the
* default), each column will have it's label printed at the
* top side.
*
* This version of print() will print multiple pages.
*/
void GraphicsView::print( QPrinter* printer, bool drawRowLabels, bool drawColumnLabels )
{
d->scene.print( printer, drawRowLabels, drawColumnLabels );
}
/*! Print part of the Gantt chart from \a start to \a end using \a printer.
* If \a drawRowLabels is true (the default), each row will have it's
* label printed on the left side. If \a drawColumnLabels is true (the
* default), each column will have it's label printed at the
* top side.
*
* This version of print() will print multiple pages.
*
* To print a certain range of a chart with a DateTimeGrid, use
* qreal DateTimeGrid::mapFromDateTime( const QDateTime& dt) const
* to figure out the values for \a start and \a end.
*/
void GraphicsView::print( QPrinter* printer, qreal start, qreal end, bool drawRowLabels, bool drawColumnLabels )
{
d->scene.print( printer, start, end, drawRowLabels, drawColumnLabels );
}
/*! Render the GanttView inside the rectangle \a target using the painter \a painter.
* If \a drawRowLabels is true (the default), each row will have it's
* label printed on the left side. If \a drawColumnLabels is true (the
* default), each column will have it's label printed at the
* top side.
*/
void GraphicsView::print( QPainter* painter, const QRectF& targetRect, bool drawRowLabels, bool drawColumnLabels )
{
d->scene.print(painter, targetRect, drawRowLabels, drawColumnLabels);
}
/*! Render the GanttView inside the rectangle \a target using the painter \a painter.
* If \a drawRowLabels is true (the default), each row will have it's
* label printed on the left side. If \a drawColumnLabels is true (the
* default), each column will have it's label printed at the
* top side.
*
* To print a certain range of a chart with a DateTimeGrid, use
* qreal DateTimeGrid::mapFromDateTime( const QDateTime& dt) const
* to figure out the values for \a start and \a end.
*/
void GraphicsView::print( QPainter* painter, qreal start, qreal end,
const QRectF& targetRect, bool drawRowLabels, bool drawColumnLabels )
{
d->scene.print(painter, start, end, targetRect, drawRowLabels, drawColumnLabels);
}
#include "moc_kganttgraphicsview.cpp"