KissCount/lib/libkdchart/src/KDChartChart.cpp

1625 lines
60 KiB
C++
Raw Normal View History

2012-01-28 15:54:17 +01:00
/****************************************************************************
** Copyright (C) 2001-2011 Klaralvdalens Datakonsult AB. All rights reserved.
**
** This file is part of the KD Chart library.
**
** Licensees holding valid commercial KD Chart licenses may use this file in
** accordance with the KD Chart Commercial License Agreement provided with
** the Software.
**
**
** This file may be distributed and/or modified under the terms of the
** GNU General Public License version 2 and version 3 as published by the
** Free Software Foundation and appearing in the file LICENSE.GPL.txt included.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** Contact info@kdab.com if any conditions of this licensing are not
** clear to you.
**
**********************************************************************/
#include "KDChartChart.h"
#include "KDChartChart_p.h"
#include <QList>
#include <QtDebug>
#include <QGridLayout>
#include <QLabel>
#include <QHash>
#include <QToolTip>
#include <QPainter>
#include <QPaintEvent>
#include <QLayoutItem>
#include <QPushButton>
#include <QApplication>
#include <QEvent>
#include "KDChartCartesianCoordinatePlane.h"
#include "KDChartAbstractCartesianDiagram.h"
#include "KDChartHeaderFooter.h"
#include "KDChartEnums.h"
#include "KDChartLegend.h"
#include "KDChartLayoutItems.h"
#include <KDChartTextAttributes.h>
#include <KDChartMarkerAttributes>
#include "KDChartPainterSaver_p.h"
#include "KDChartPrintingParameters.h"
#if defined KDAB_EVAL
#include "../evaldialog/evaldialog.h"
#endif
#include <KDABLibFakes>
#define SET_ALL_MARGINS_TO_ZERO
// Layout widgets even if they are not visible
class MyWidgetItem : public QWidgetItem
{
public:
explicit MyWidgetItem(QWidget *w, Qt::Alignment alignment = 0)
: QWidgetItem(w) {
setAlignment( alignment );
}
/*reimp*/ bool isEmpty() const {
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);
}
};
using namespace KDChart;
void Chart::Private::slotUnregisterDestroyedLegend( Legend *l )
{
legends.removeAll( l );
slotRelayout();
}
void Chart::Private::slotUnregisterDestroyedHeaderFooter( HeaderFooter* hf )
{
headerFooters.removeAll( hf );
hf->removeFromParentLayout();
textLayoutItems.remove( textLayoutItems.indexOf( hf ) );
slotRelayout();
}
void Chart::Private::slotUnregisterDestroyedPlane( AbstractCoordinatePlane* plane )
{
coordinatePlanes.removeAll( plane );
Q_FOREACH ( AbstractCoordinatePlane* p, coordinatePlanes )
{
if ( p->referenceCoordinatePlane() == plane) {
p->setReferenceCoordinatePlane(0);
}
}
plane->layoutPlanes();
}
Chart::Private::Private( Chart* chart_ )
: chart( chart_ )
, layout( 0 )
, vLayout( 0 )
, planesLayout( 0 )
, headerLayout( 0 )
, footerLayout( 0 )
, dataAndLegendLayout( 0 )
, globalLeadingLeft( 0 )
, globalLeadingRight( 0 )
, globalLeadingTop( 0 )
, globalLeadingBottom( 0 )
{
for( int row = 0; row < 3; ++row )
{
for( int column = 0; column < 3; ++column )
{
dummyHeaders[ row ][ column ] = HorizontalLineLayoutItem();
dummyFooters[ row ][ column ] = HorizontalLineLayoutItem();
innerHdFtLayouts[0][row][column] = 0;
innerHdFtLayouts[1][row][column] = 0;
}
}
}
Chart::Private::~Private()
{
removeDummyHeaderFooters();
}
void Chart::Private::removeDummyHeaderFooters()
{
for ( int row = 0; row < 3; ++row )
{
for ( int column = 0; column < 3; ++ column )
{
if( innerHdFtLayouts[0][row][column] ){
innerHdFtLayouts[0][row][column]->removeItem( &(dummyHeaders[row][column]) );
innerHdFtLayouts[1][row][column]->removeItem( &(dummyFooters[row][column]) );
}
}
}
}
void Chart::Private::layoutHeadersAndFooters()
{
removeDummyHeaderFooters();
bool headersLineFilled[] = { false, false, false };
bool footersLineFilled[] = { false, false, false };
Q_FOREACH( HeaderFooter *hf, headerFooters ) {
// for now, there are only two types of Header/Footer,
// we use a pointer to the right layout, depending on the type():
int innerLayoutIdx = 0;
switch( hf->type() ){
case HeaderFooter::Header:
innerLayoutIdx = 0;
break;
case HeaderFooter::Footer:
innerLayoutIdx = 1;
break;
default:
Q_ASSERT( false ); // all types need to be handled
break;
};
if( hf->position() != Position::Unknown ) {
int row, column;
Qt::Alignment hAlign, vAlign;
if( hf->position().isNorthSide() ){
row = 0;
vAlign = Qt::AlignTop;
}
else if( hf->position().isSouthSide() ){
row = 2;
vAlign = Qt::AlignBottom;
}
else{
row = 1;
vAlign = Qt::AlignVCenter;
}
if( hf->position().isWestSide() ){
column = 0;
hAlign = Qt::AlignLeft;
}
else if( hf->position().isEastSide() ){
column = 2;
hAlign = Qt::AlignRight;
}
else{
column = 1;
hAlign = Qt::AlignHCenter;
}
switch( hf->type() ){
case HeaderFooter::Header:
if( !headersLineFilled[ row ] )
{
for( int col = 0; col < 3; ++col )
innerHdFtLayouts[0][row][col]->addItem( &(dummyHeaders[ row ][ col ]) );
headersLineFilled[ row ] = true;
}
break;
case HeaderFooter::Footer:
if( !footersLineFilled[ row ] )
{
for( int col = 0; col < 3; ++col )
innerHdFtLayouts[1][row][col]->addItem( &(dummyFooters[ row ][ col ]) );
footersLineFilled[ row ] = true;
}
break;
};
textLayoutItems << hf;
QVBoxLayout* headerFooterLayout = innerHdFtLayouts[innerLayoutIdx][row][column];
hf->setParentLayout( headerFooterLayout );
hf->setAlignment( hAlign | vAlign );
headerFooterLayout->addItem( hf );
}
else{
qDebug( "Unknown header/footer position" );
}
}
}
void Chart::Private::layoutLegends()
{
//qDebug() << "starting Chart::Private::layoutLegends()";
// To support more than one Legend, we first collect them all
// in little lists: one list per grid position.
// Since the dataAndLegendLayout is a 3x3 grid, we need 9 little lists.
QList<Legend*> infos[3][3];
Q_FOREACH( Legend *legend, legends ) {
legend->needSizeHint(); // we'll lay it out soon
bool bOK = true;
int row, column;
//qDebug() << legend->position().name();
switch( legend->position().value() ) {
case KDChartEnums::PositionNorthWest: row = 0; column = 0;
break;
case KDChartEnums::PositionNorth: row = 0; column = 1;
break;
case KDChartEnums::PositionNorthEast: row = 0; column = 2;
break;
case KDChartEnums::PositionEast: row = 1; column = 2;
break;
case KDChartEnums::PositionSouthEast: row = 2; column = 2;
break;
case KDChartEnums::PositionSouth: row = 2; column = 1;
break;
case KDChartEnums::PositionSouthWest: row = 2; column = 0;
break;
case KDChartEnums::PositionWest: row = 1; column = 0;
break;
case KDChartEnums::PositionCenter:
qDebug( "Sorry: Legend not shown, because position Center is not supported." );
bOK = false;
break;
case KDChartEnums::PositionFloating:
bOK = false;
break;
default:
qDebug( "Sorry: Legend not shown, because of unknown legend position." );
bOK = false;
break;
}
if( bOK )
infos[row][column] << legend;
}
// We have collected all legend information,
// so we can design their layout now.
for (int iR = 0; iR < 3; ++iR) {
for (int iC = 0; iC < 3; ++iC) {
QList<Legend*>& list = infos[iR][iC];
const int count = list.size();
switch( count ){
case 0:
break;
case 1: {
Legend* legend = list.first();
dataAndLegendLayout->addItem( new MyWidgetItem(legend),
iR, iC, 1, 1, legend->alignment() );
}
break;
default: {
// We have more than one legend in the same cell
// of the big dataAndLegendLayout grid.
//
// So we need to find out, if they are aligned the
// same way:
// Those legends, that are aligned the same way, will be drawn
// leftbound, on top of each other, in a little VBoxLayout.
//
// If not al of the legends are aligned the same way,
// there will be a grid with 3 cells: for left/mid/right side
// (or top/mid/bottom side, resp.) legends
Legend* legend = list.first();
Qt::Alignment alignment = legend->alignment();
bool haveSameAlign = true;
for (int i = 1; i < count; ++i) {
legend = list.at(i);
if( alignment != legend->alignment() ){
haveSameAlign = false;
break;
}
}
if( haveSameAlign ){
QVBoxLayout* vLayout = new QVBoxLayout();
#if defined SET_ALL_MARGINS_TO_ZERO
vLayout->setMargin(0);
#endif
for (int i = 0; i < count; ++i) {
vLayout->addItem( new MyWidgetItem(list.at(i), Qt::AlignLeft) );
}
dataAndLegendLayout->addLayout( vLayout, iR, iC, 1, 1, alignment );
}else{
QGridLayout* gridLayout = new QGridLayout();
#if defined SET_ALL_MARGINS_TO_ZERO
gridLayout->setMargin(0);
#endif
#define ADD_VBOX_WITH_LEGENDS(row, column, align) \
{ \
QVBoxLayout* innerLayout = new QVBoxLayout(); \
for (int i = 0; i < count; ++i) { \
legend = list.at(i); \
if( legend->alignment() == ( align ) ) \
innerLayout->addItem( new MyWidgetItem(legend, Qt::AlignLeft) ); \
} \
gridLayout->addLayout( innerLayout, row, column, ( align ) ); \
}
ADD_VBOX_WITH_LEGENDS( 0, 0, Qt::AlignTop | Qt::AlignLeft )
ADD_VBOX_WITH_LEGENDS( 0, 1, Qt::AlignTop | Qt::AlignHCenter )
ADD_VBOX_WITH_LEGENDS( 0, 2, Qt::AlignTop | Qt::AlignRight )
ADD_VBOX_WITH_LEGENDS( 1, 0, Qt::AlignVCenter | Qt::AlignLeft )
ADD_VBOX_WITH_LEGENDS( 1, 1, Qt::AlignCenter )
ADD_VBOX_WITH_LEGENDS( 1, 2, Qt::AlignVCenter | Qt::AlignRight )
ADD_VBOX_WITH_LEGENDS( 2, 0, Qt::AlignBottom | Qt::AlignLeft )
ADD_VBOX_WITH_LEGENDS( 2, 1, Qt::AlignBottom | Qt::AlignHCenter )
ADD_VBOX_WITH_LEGENDS( 2, 2, Qt::AlignBottom | Qt::AlignRight )
dataAndLegendLayout->addLayout( gridLayout, iR, iC, 1, 1 );
}
}
}
}
}
//qDebug() << "finished Chart::Private::layoutLegends()";
}
QHash<AbstractCoordinatePlane*, PlaneInfo> 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<CartesianAxis*, AxisInfo> axisInfos;
QHash<AbstractCoordinatePlane*, PlaneInfo> 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 =
dynamic_cast<AbstractCartesianDiagram*> ( 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 == 0 ) {
p.gridLayout = new QGridLayout();
// TESTING(khz): set the margin of all of the layouts to Zero
#if defined SET_ALL_MARGINS_TO_ZERO
p.gridLayout->setMargin(0);
#endif
planeInfos[plane] = p;
}
}
return planeInfos;
}
template <typename T>
static T* findOrCreateLayoutByObjectName( QLayout * parentLayout, const char* name )
{
T *box = qFindChild<T*>( parentLayout, QString::fromLatin1( name ) );
if ( !box ) {
box = new T();
// TESTING(khz): set the margin of all of the layouts to Zero
#if defined SET_ALL_MARGINS_TO_ZERO
box->setMargin(0);
#endif
box->setObjectName( QString::fromLatin1( name ) );
box->setSizeConstraint( QLayout::SetFixedSize );
}
return box;
}
#if 0
static QVBoxLayout* findOrCreateVBoxLayoutByObjectName( QLayout* parentLayout, const char* name )
{
return findOrCreateLayoutByObjectName<QVBoxLayout>( parentLayout, name );
}
static QHBoxLayout* findOrCreateHBoxLayoutByObjectName( QLayout* parentLayout, const char* name )
{
return findOrCreateLayoutByObjectName<QHBoxLayout>( parentLayout, name );
}
#endif
void Chart::Private::slotLayoutPlanes()
{
//qDebug() << "KDChart::Chart is layouting the planes";
const QBoxLayout::Direction oldPlanesDirection =
planesLayout ? planesLayout->direction() : QBoxLayout::TopToBottom;
if ( planesLayout && dataAndLegendLayout )
dataAndLegendLayout->removeItem( planesLayout );
const bool hadPlanesLayout = planesLayout != 0;
int left, top, right, bottom;
if(hadPlanesLayout)
planesLayout->getContentsMargins(&left, &top, &right, &bottom);
KDAB_FOREACH( KDChart::AbstractLayoutItem* plane, planeLayoutItems ) {
plane->removeFromParentLayout();
}
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 );
if(hadPlanesLayout)
planesLayout->setContentsMargins(left, top, right, bottom);
// TESTING(khz): set the margin of all of the layouts to Zero
#if defined SET_ALL_MARGINS_TO_ZERO
planesLayout->setMargin(0);
planesLayout->setSpacing(0);
#endif
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. */
QHash<AbstractCoordinatePlane*, PlaneInfo> planeInfos = buildPlaneLayoutInfos();
QHash<AbstractAxis*, AxisInfo> axisInfos;
KDAB_FOREACH( AbstractCoordinatePlane* plane, coordinatePlanes ) {
Q_ASSERT( planeInfos.contains(plane) );
PlaneInfo& pi = planeInfos[ plane ];
int column = pi.horizontalOffset;
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 whether the reference plane is added to the Chart or not" );
} 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, 0 );
//qDebug() << "Chart slotLayoutPlanes() calls planeLayout->addItem("<< row << column << ")";
planeLayout->setRowStretch( row, 2 );
planeLayout->setColumnStretch( column, 2 );
KDAB_FOREACH( AbstractDiagram* abstractDiagram, plane->diagrams() )
{
AbstractCartesianDiagram* diagram =
dynamic_cast<AbstractCartesianDiagram*> ( abstractDiagram );
//qDebug() << "--------------- diagram ???????????????????? -----------------";
if( !diagram ) continue; // FIXME polar ?
//qDebug() << "--------------- diagram ! ! ! ! ! ! ! ! ! ! -----------------";
if( pi.referencePlane != 0 )
{
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 == 0 )
{
pi.topAxesLayout = new QVBoxLayout;
#if defined SET_ALL_MARGINS_TO_ZERO
pi.topAxesLayout->setMargin(0);
#endif
pi.topAxesLayout->setObjectName( QString::fromLatin1( "topAxesLayout" ) );
}
if( pi.bottomAxesLayout == 0 )
{
pi.bottomAxesLayout = new QVBoxLayout;
#if defined SET_ALL_MARGINS_TO_ZERO
pi.bottomAxesLayout->setMargin(0);
#endif
pi.bottomAxesLayout->setObjectName( QString::fromLatin1( "bottomAxesLayout" ) );
}
if( pi.leftAxesLayout == 0 )
{
pi.leftAxesLayout = new QHBoxLayout;
#if defined SET_ALL_MARGINS_TO_ZERO
pi.leftAxesLayout->setMargin(0);
#endif
pi.leftAxesLayout->setObjectName( QString::fromLatin1( "leftAxesLayout" ) );
}
if( pi.rightAxesLayout == 0 )
{
pi.rightAxesLayout = new QHBoxLayout;
#if defined SET_ALL_MARGINS_TO_ZERO
pi.rightAxesLayout->setMargin(0);
#endif
pi.rightAxesLayout->setObjectName( QString::fromLatin1( "rightAxesLayout" ) );
}
if( pi.referencePlane != 0 )
{
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 );
KDAB_FOREACH( CartesianAxis* axis, diagram->axes() ) {
if ( axisInfos.contains( axis ) ) continue; // already laid this one out
Q_ASSERT ( axis );
axis->setCachedSizeDirty();
//qDebug() << "--------------- axis added to planeLayoutItems -----------------";
planeLayoutItems << axis;
/*
// Unused code trying to use a push-model: This did not work
// since we can not re-layout the planes each time when
// Qt layouting is calling sizeHint()
connect( axis, SIGNAL( needAdjustLeftRightColumnsForOverlappingLabels(
CartesianAxis*, int, int ) ),
this, SLOT( slotAdjustLeftRightColumnsForOverlappingLabels(
CartesianAxis*, int, int ) ) );
connect( axis, SIGNAL( needAdjustTopBottomRowsForOverlappingLabels(
CartesianAxis*, int, int ) ),
this, SLOT( slotAdjustTopBottomRowsForOverlappingLabels(
CartesianAxis*, int, int ) ) );
*/
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);
//planeLayout->setRowStretch( row, 0 );
//planeLayout->setColumnStretch( 0, 0 );
}
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; \
} \
}
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 );
}
slotRelayout();
//qDebug() << "KDChart::Chart finished layouting the planes.";
}
void Chart::Private::createLayouts( QWidget* w )
{
KDAB_FOREACH( KDChart::TextArea* textLayoutItem, textLayoutItems ) {
textLayoutItem->removeFromParentLayout();
}
textLayoutItems.clear();
KDAB_FOREACH( KDChart::AbstractArea* layoutItem, layoutItems ) {
layoutItem->removeFromParentLayout();
}
layoutItems.clear();
removeDummyHeaderFooters();
// layout for the planes is handled separately, so we don't want to delete it here
if ( dataAndLegendLayout) {
dataAndLegendLayout->removeItem( planesLayout );
planesLayout->setParent( 0 );
}
// nuke the old bunch
delete layout;
// The HBox d->layout provides the left and right global leadings
layout = new QHBoxLayout( w );
// TESTING(khz): set the margin of all of the layouts to Zero
#if defined SET_ALL_MARGINS_TO_ZERO
layout->setMargin(0);
#endif
layout->setObjectName( QString::fromLatin1( "Chart::Private::layout" ) );
layout->addSpacing( globalLeadingLeft );
// The vLayout provides top and bottom global leadings and lays
// out headers/footers and the data area.
vLayout = new QVBoxLayout();
// TESTING(khz): set the margin of all of the layouts to Zero
#if defined SET_ALL_MARGINS_TO_ZERO
vLayout->setMargin(0);
#endif
vLayout->setObjectName( QString::fromLatin1( "vLayout" ) );
layout->addLayout( vLayout, 1000 );
layout->addSpacing( globalLeadingRight );
// 1. the gap above the top edge of the headers area
vLayout->addSpacing( globalLeadingTop );
// 2. the header(s) area
headerLayout = new QGridLayout();
// TESTING(khz): set the margin of all of the layouts to Zero
#if defined SET_ALL_MARGINS_TO_ZERO
headerLayout->setMargin(0);
#endif
vLayout->addLayout( headerLayout );
// 3. the area containing coordinate plane(s), axes, legend(s)
dataAndLegendLayout = new QGridLayout();
// TESTING(khz): set the margin of all of the layouts to Zero
#if defined SET_ALL_MARGINS_TO_ZERO
dataAndLegendLayout->setMargin(0);
#endif
dataAndLegendLayout->setObjectName( QString::fromLatin1( "dataAndLegendLayout" ) );
vLayout->addLayout( dataAndLegendLayout, 1000 );
// 4. the footer(s) area
footerLayout = new QGridLayout();
// TESTING(khz): set the margin of all of the layouts to Zero
#if defined SET_ALL_MARGINS_TO_ZERO
footerLayout->setMargin(0);
#endif
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.
static const Qt::Alignment hdFtAlignments[3][3] = {
{ 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 }
};
for ( int row = 0; row < 3; ++row )
{
for ( int column = 0; column < 3; ++ column )
{
QVBoxLayout* innerHdLayout = new QVBoxLayout();
QVBoxLayout* innerFtLayout = new QVBoxLayout();
innerHdFtLayouts[0][row][column] = innerHdLayout;
innerHdFtLayouts[1][row][column] = innerFtLayout;
#if defined SET_ALL_MARGINS_TO_ZERO
innerHdLayout->setMargin(0);
innerFtLayout->setMargin(0);
#endif
const Qt::Alignment align = hdFtAlignments[row][column];
innerHdLayout->setAlignment( align );
innerFtLayout->setAlignment( align );
headerLayout->addLayout( innerHdLayout, row, column, align );
footerLayout->addLayout( innerFtLayout, row, column, align );
}
}
// 6. the gap below the bottom edge of the headers area
vLayout->addSpacing( globalLeadingBottom );
// the data+axes area
dataAndLegendLayout->addLayout( planesLayout, 1, 1 );
dataAndLegendLayout->setRowStretch( 1, 1 );
dataAndLegendLayout->setColumnStretch( 1, 1 );
//qDebug() << "w->rect()" << w->rect();
}
void Chart::Private::slotRelayout()
{
//qDebug() << "Chart relayouting started.";
createLayouts( chart );
layoutHeadersAndFooters();
layoutLegends();
// This triggers the qlayout, see QBoxLayout::setGeometry
// The geometry is not necessarily w->rect(), when using paint(), this is why
// we don't call layout->activate().
const QRect geo( QRect( 0, 0, currentLayoutSize.width(), currentLayoutSize.height() ) );
if( geo.isValid() && geo != layout->geometry() ){
//qDebug() << "Chart slotRelayout() adjusting geometry to" << geo;
//if( coordinatePlanes.count() )
// qDebug() << " plane geo before" << coordinatePlanes.first()->geometry();
layout->setGeometry( geo );
//if( coordinatePlanes.count() ) {
// qDebug() << " plane geo after " << coordinatePlanes.first()->geometry();
//}
}
// Adapt diagram drawing to the new size
KDAB_FOREACH (AbstractCoordinatePlane* plane, coordinatePlanes ) {
plane->layoutDiagrams();
}
//qDebug() << "Chart relayouting done.";
}
// Called when the size of the chart changes.
// So in theory, we only need to adjust geometries.
// But this also needs to make sure that everything is in place for the first painting.
void Chart::Private::resizeLayout( const QSize& size )
{
currentLayoutSize = size;
//qDebug() << "Chart::resizeLayout(" << currentLayoutSize << ")";
/*
// We need to make sure that the legend's layouts are populated,
// so that setGeometry gets proper sizeHints from them and resizes them properly.
KDAB_FOREACH( Legend *legend, legends ) {
// This forceRebuild will see a wrong areaGeometry, but I don't care about geometries yet,
// only about the fact that legends should have their contents populated.
// -> it would be better to dissociate "building contents" and "resizing" in Legend...
// legend->forceRebuild();
legend->resizeLayout( size );
}
*/
slotLayoutPlanes(); // includes slotRelayout
//qDebug() << "Chart::resizeLayout done";
}
void Chart::Private::paintAll( QPainter* painter )
{
QRect rect( QPoint(0, 0), currentLayoutSize );
//qDebug() << this<<"::paintAll() uses layout size" << currentLayoutSize;
// Paint the background (if any)
KDChart::AbstractAreaBase::paintBackgroundAttributes(
*painter, rect, backgroundAttributes );
// Paint the frame (if any)
KDChart::AbstractAreaBase::paintFrameAttributes(
*painter, rect, frameAttributes );
chart->reLayoutFloatingLegends();
KDAB_FOREACH( KDChart::AbstractArea* layoutItem, layoutItems ) {
layoutItem->paintAll( *painter );
}
KDAB_FOREACH( KDChart::AbstractLayoutItem* planeLayoutItem, planeLayoutItems ) {
planeLayoutItem->paintAll( *painter );
}
KDAB_FOREACH( KDChart::TextArea* textLayoutItem, textLayoutItems ) {
textLayoutItem->paintAll( *painter );
}
}
// ******** Chart interface implementation ***********
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 ) );
}
Chart::~Chart()
{
delete _d;
}
#define d d_func()
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 KDChart 3.0; change QLayout into QBoxLayout::Direction
void Chart::setCoordinatePlaneLayout( QLayout * layout )
{
delete d->planesLayout;
d->planesLayout = dynamic_cast<QBoxLayout*>( 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 0;
} else {
return d->coordinatePlanes.first();
}
}
CoordinatePlaneList Chart::coordinatePlanes()
{
return d->coordinatePlanes;
}
void Chart::addCoordinatePlane( AbstractCoordinatePlane* plane )
{
connect( plane, SIGNAL( destroyedCoordinatePlane( AbstractCoordinatePlane* ) ),
d, SLOT( slotUnregisterDestroyedPlane( AbstractCoordinatePlane* ) ) );
connect( plane, SIGNAL( needUpdate() ), this, SLOT( update() ) );
connect( plane, SIGNAL( needRelayout() ), d, SLOT( slotRelayout() ) ) ;
connect( plane, SIGNAL( needLayoutPlanes() ), d, SLOT( slotLayoutPlanes() ) ) ;
connect( plane, SIGNAL( propertiesChanged() ),this, SIGNAL( propertiesChanged() ) );
d->coordinatePlanes.append( 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, SIGNAL( destroyedCoordinatePlane( AbstractCoordinatePlane* ) ),
d, SLOT( slotUnregisterDestroyedPlane( AbstractCoordinatePlane* ) ) );
plane->removeFromParentLayout();
plane->setParent( 0 );
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 );
d->slotRelayout();
}
void Chart::setGlobalLeadingLeft( int leading )
{
d->globalLeadingLeft = leading;
d->slotRelayout();
}
int Chart::globalLeadingLeft() const
{
return d->globalLeadingLeft;
}
void Chart::setGlobalLeadingTop( int leading )
{
d->globalLeadingTop = leading;
d->slotRelayout();
}
int Chart::globalLeadingTop() const
{
return d->globalLeadingTop;
}
void Chart::setGlobalLeadingRight( int leading )
{
d->globalLeadingRight = leading;
d->slotRelayout();
}
int Chart::globalLeadingRight() const
{
return d->globalLeadingRight;
}
void Chart::setGlobalLeadingBottom( int leading )
{
d->globalLeadingBottom = leading;
d->slotRelayout();
}
int Chart::globalLeadingBottom() const
{
return d->globalLeadingBottom;
}
void Chart::paint( QPainter* painter, const QRect& target )
{
if( target.isEmpty() || !painter ) return;
//qDebug() << "Chart::paint( ..," << target << ")";
QPaintDevice* prevDevice = GlobalMeasureScaling::paintDevice();
GlobalMeasureScaling::setPaintDevice( painter->device() );
// Output on a widget
if( dynamic_cast< QWidget* >( painter->device() ) != 0 )
{
GlobalMeasureScaling::setFactors(
static_cast< qreal >( target.width() ) /
static_cast< qreal >( geometry().size().width() ),
static_cast< qreal >( target.height() ) /
static_cast< qreal >( geometry().size().height() ) );
}
// Output onto a QPixmap
else
{
PrintingParameters::setScaleFactor( static_cast< qreal >( painter->device()->logicalDpiX() ) / static_cast< qreal >( logicalDpiX() ) );
const qreal resX = static_cast< qreal >( logicalDpiX() ) / static_cast< qreal >( painter->device()->logicalDpiX() );
const qreal resY = static_cast< qreal >( logicalDpiY() ) / static_cast< qreal >( painter->device()->logicalDpiY() );
GlobalMeasureScaling::setFactors(
static_cast< qreal >( target.width() ) /
static_cast< qreal >( geometry().size().width() ) * resX,
static_cast< qreal >( target.height() ) /
static_cast< qreal >( geometry().size().height() ) * resY );
}
if( target.size() != d->currentLayoutSize ){
d->resizeLayout( target.size() );
}
const QPoint translation = target.topLeft();
painter->translate( translation );
d->paintAll( painter );
// for debugging:
//painter->setPen(QPen(Qt::blue, 8));
//painter->drawRect(target.adjusted(12,12,-12,-12));
KDAB_FOREACH( Legend *legend, d->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() );
//testing:
//legend->paintIntoRect( *painter, legend->geometry().adjusted(-100,0,-100,0) );
}
}
painter->translate( -translation.x(), -translation.y() );
GlobalMeasureScaling::instance()->resetFactors();
PrintingParameters::resetScaleFactor();
GlobalMeasureScaling::setPaintDevice( prevDevice );
//qDebug() << "KDChart::Chart::paint() done.\n";
}
void Chart::resizeEvent ( QResizeEvent * )
{
d->resizeLayout( size() );
KDAB_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes ){
plane->setGridNeedsRecalculate();
}
reLayoutFloatingLegends();
}
void Chart::reLayoutFloatingLegends()
{
KDAB_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<int>(pt.x()), static_cast<int>(pt.y()) );
}
}
}
void Chart::paintEvent( QPaintEvent* )
{
QPainter painter( this );
if( size() != d->currentLayoutSize ){
d->resizeLayout( size() );
reLayoutFloatingLegends();
}
//FIXME(khz): Paint the background/frame too!
// (can we derive Chart from AreaWidget ??)
d->paintAll( &painter );
}
void Chart::addHeaderFooter( HeaderFooter* headerFooter )
{
d->headerFooters.append( headerFooter );
headerFooter->setParent( this );
connect( headerFooter, SIGNAL( destroyedHeaderFooter( HeaderFooter* ) ),
d, SLOT( slotUnregisterDestroyedHeaderFooter( HeaderFooter* ) ) );
connect( headerFooter, SIGNAL( positionChanged( HeaderFooter* ) ),
d, SLOT( slotRelayout() ) );
d->slotRelayout();
}
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 ){
d->headerFooters.takeAt( idx );
disconnect( headerFooter, SIGNAL( destroyedHeaderFooter( HeaderFooter* ) ),
d, SLOT( slotUnregisterDestroyedHeaderFooter( HeaderFooter* ) ) );
headerFooter->setParent( 0 );
}
d->slotRelayout();
// 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();
}
HeaderFooter* Chart::headerFooter()
{
if( d->headerFooters.isEmpty() ) {
return 0;
} else {
return d->headerFooters.first();
}
}
HeaderFooterList Chart::headerFooters()
{
return d->headerFooters;
}
void Chart::addLegend( Legend* legend )
{
if( ! legend ) return;
//qDebug() << "adding the legend";
d->legends.append( legend );
legend->setParent( this );
TextAttributes textAttrs( legend->textAttributes() );
KDChart::Measure measure( textAttrs.fontSize() );
measure.setRelativeMode( this, KDChartEnums::MeasureOrientationMinimum );
measure.setValue( 20 );
textAttrs.setFontSize( measure );
legend->setTextAttributes( textAttrs );
textAttrs = legend->titleTextAttributes();
measure.setRelativeMode( this, KDChartEnums::MeasureOrientationMinimum );
measure.setValue( 24 );
textAttrs.setFontSize( measure );
legend->setTitleTextAttributes( textAttrs );
legend->setReferenceArea( this );
/*
future: Use relative sizes for the markers too!
const uint nMA = Legend::datasetCount();
for( uint iMA = 0; iMA < nMA; ++iMA ){
MarkerAttributes ma( legend->markerAttributes( iMA ) );
ma.setMarkerSize( ... )
legend->setMarkerAttributes( iMA, ma )
}
*/
connect( legend, SIGNAL( destroyedLegend( Legend* ) ),
d, SLOT( slotUnregisterDestroyedLegend( Legend* ) ) );
connect( legend, SIGNAL( positionChanged( AbstractAreaWidget* ) ),
d, SLOT( slotLayoutPlanes() ) ); //slotRelayout() ) );
connect( legend, SIGNAL( propertiesChanged() ),
this, SIGNAL( propertiesChanged() ) );
legend->setVisible( true );
d->slotRelayout();
}
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 ){
d->legends.takeAt( idx );
disconnect( legend, SIGNAL( destroyedLegend( Legend* ) ),
d, SLOT( slotUnregisterDestroyedLegend( Legend* ) ) );
disconnect( legend, SIGNAL( positionChanged( AbstractAreaWidget* ) ),
d, SLOT( slotLayoutPlanes() ) ); //slotRelayout() ) );
disconnect( legend, SIGNAL( propertiesChanged() ),
this, SIGNAL( propertiesChanged() ) );
legend->setParent( 0 );
legend->setVisible( false );
}
d->slotRelayout();
// 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.
// Note:
// We do this ourselves in examples/DrawIntoPainter/mainwindow.cpp
emit propertiesChanged();
}
Legend* Chart::legend()
{
if ( d->legends.isEmpty() )
{
return 0;
} else {
return d->legends.first();
}
}
LegendList Chart::legends()
{
return d->legends;
}
void Chart::mousePressEvent( QMouseEvent* event )
{
const QPoint pos = mapFromGlobal( event->globalPos() );
KDAB_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes )
{
if ( plane->geometry().contains( event->pos() ) )
{
if ( plane->diagrams().size() > 0 )
{
QMouseEvent ev( QEvent::MouseButtonPress, pos, event->globalPos(),
event->button(), event->buttons(),
event->modifiers() );
plane->mousePressEvent( &ev );
d->mouseClickedPlanes.append( plane );
}
}
}
}
/*
// Unused code trying to use a push-model: This did not work
// since we can not re-layout the planes each time when
// Qt layouting is calling sizeHint()
void Chart::Private::slotAdjustLeftRightColumnsForOverlappingLabels(
CartesianAxis* axis, int leftOverlap, int rightOverlap)
{
const QLayout* axisLayout = axis ? axis->parentLayout() : 0;
if( (! leftOverlap && ! rightOverlap) || ! axis || ! axisLayout->parent() )
return;
bool needUpdate = false;
// access the planeLayout:
QGridLayout* grid = qobject_cast<QGridLayout*>(axisLayout->parent());
if( grid ){
// find the index of the parent layout in the planeLayout:
int idx = -1;
for (int i = 0; i < grid->count(); ++i)
if( grid->itemAt(i) == axisLayout )
idx = i;
// set the min widths of the neighboring column:
if( idx > -1 ){
int row, column, rowSpan, columnSpan;
grid->getItemPosition( idx, &row, &column, &rowSpan, &columnSpan );
const int leftColumn = column-1;
const int rightColumn = column+columnSpan;
// find the left/right axes layouts
QHBoxLayout* leftAxesLayout=0;
QHBoxLayout* rightAxesLayout=0;
for( int i = 0;
(!leftAxesLayout || !rightAxesLayout) && i < grid->count();
++i )
{
int r, c, rs, cs;
grid->getItemPosition( i, &r, &c, &rs, &cs );
if( c+cs-1 == leftColumn )
leftAxesLayout = dynamic_cast<QHBoxLayout*>(grid->itemAt(i));
if( c == rightColumn )
rightAxesLayout = dynamic_cast<QHBoxLayout*>(grid->itemAt(i));
}
if( leftAxesLayout ){
const int leftColumnMinWidth = leftOverlap;
QLayoutItem* item = leftAxesLayout->count()
? dynamic_cast<QLayoutItem*>(leftAxesLayout->itemAt(leftAxesLayout->count()-1))
: 0;
QSpacerItem* spacer = dynamic_cast<QSpacerItem*>(item);
if( spacer ){
if( spacer->sizeHint().width() < leftColumnMinWidth ){
needUpdate = true;
spacer->changeSize(leftColumnMinWidth, 1);
qDebug() << "adjusted left spacer->sizeHint().width() to" << spacer->sizeHint().width();
}
}else{
AbstractAxis* axis = dynamic_cast<AbstractAxis*>(item);
if( !axis || axis->sizeHint().width() < leftColumnMinWidth ){
needUpdate = true;
leftAxesLayout->insertSpacing( -1, leftColumnMinWidth );
qDebug() << "adjusted column" << leftColumn << "min width to" << leftColumnMinWidth;
}
}
}
if( rightAxesLayout ){
const int rightColumnMinWidth = rightOverlap;
QLayoutItem* item = rightAxesLayout->count()
? dynamic_cast<QLayoutItem*>(rightAxesLayout->itemAt(0))
: 0;
QSpacerItem* spacer = dynamic_cast<QSpacerItem*>(item);
if( spacer ){
if( spacer->sizeHint().width() < rightColumnMinWidth ){
needUpdate = true;
spacer->changeSize(rightColumnMinWidth, 1);
qDebug() << "adjusted right spacer->sizeHint().width() to" << spacer->sizeHint().width();
}
}else{
AbstractAxis* axis = dynamic_cast<AbstractAxis*>(item);
if( !axis || axis->sizeHint().width() < rightColumnMinWidth ){
needUpdate = true;
rightAxesLayout->insertSpacing( 0, rightColumnMinWidth );
qDebug() << "adjusted column" << rightColumn << "min width to" << rightColumnMinWidth;
}
}
}
}
}
if( needUpdate ){
;// do something ...?
}
}
void Chart::Private::slotAdjustTopBottomRowsForOverlappingLabels(
CartesianAxis* axis, int topOverlap, int bottomOverlap)
{
const QLayout* axisLayout = axis ? axis->parentLayout() : 0;
if( (! topOverlap && ! bottomOverlap) || ! axisLayout || ! axisLayout->parent() )
return;
// access the planeLayout:
QGridLayout* grid = qobject_cast<QGridLayout*>(axisLayout->parent());
if( grid ){
// find the index of the parent layout in the planeLayout:
int idx = -1;
for (int i = 0; i < grid->count(); ++i)
if( grid->itemAt(i) == axisLayout )
idx = i;
// set the min widths of the neighboring column:
if( idx > -1 ){
int row, column, rowSpan, columnSpan;
grid->getItemPosition( idx, &row, &column, &rowSpan, &columnSpan );
const int topRow = row-1;
const int bottomRow = row+rowSpan;
// find the left/right axes layouts
QVBoxLayout* topAxesLayout=0;
QVBoxLayout* bottomAxesLayout=0;
for( int i = 0;
(!topAxesLayout || !bottomAxesLayout) && i < grid->count();
++i )
{
int r, c, rs, cs;
grid->getItemPosition( i, &r, &c, &rs, &cs );
if( r+rs-1 == topRow )
topAxesLayout = dynamic_cast<QVBoxLayout*>(grid->itemAt(i));
if( r == bottomRow )
bottomAxesLayout = dynamic_cast<QVBoxLayout*>(grid->itemAt(i));
}
if( topAxesLayout ){
const int topRowMinWidth = topOverlap;
QLayoutItem* item = topAxesLayout->count()
? dynamic_cast<QLayoutItem*>(topAxesLayout->itemAt(topAxesLayout->count()-1))
: 0;
QSpacerItem* spacer = dynamic_cast<QSpacerItem*>(item);
if( spacer ){
if( spacer->sizeHint().height() < topRowMinWidth ){
spacer->changeSize(1, topRowMinWidth);
qDebug() << "adjusted top spacer->sizeHint().height() to" << spacer->sizeHint().height();
}
}else{
AbstractAxis* axis = dynamic_cast<AbstractAxis*>(item);
if( !axis || axis->sizeHint().height() < topRowMinWidth ){
topAxesLayout->insertSpacing( -1, topRowMinWidth );
qDebug() << "adjusted row" << topRow << "min height to" << topRowMinWidth;
}
}
}
if( bottomAxesLayout ){
const int bottomRowMinWidth = bottomOverlap;
QLayoutItem* item = bottomAxesLayout->count()
? dynamic_cast<QLayoutItem*>(bottomAxesLayout->itemAt(0))
: 0;
QSpacerItem* spacer = dynamic_cast<QSpacerItem*>(item);
if( spacer ){
if( spacer->sizeHint().height() < bottomRowMinWidth ){
spacer->changeSize(1, bottomRowMinWidth);
qDebug() << "adjusted bottom spacer->sizeHint().height() to" << spacer->sizeHint().height();
}
}else{
AbstractAxis* axis = dynamic_cast<AbstractAxis*>(item);
if( !axis || axis->sizeHint().height() < bottomRowMinWidth ){
bottomAxesLayout->insertSpacing( 0, bottomRowMinWidth );
qDebug() << "adjusted row" << bottomRow << "min height to" << bottomRowMinWidth;
}
}
}
}
}
}
*/
void Chart::mouseDoubleClickEvent( QMouseEvent* event )
{
const QPoint pos = mapFromGlobal( event->globalPos() );
KDAB_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes )
{
if ( plane->geometry().contains( event->pos() ) )
{
if ( 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 );
KDAB_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes )
{
if( plane->geometry().contains( event->pos() ) )
{
if( plane->diagrams().size() > 0 )
{
eventReceivers.insert( plane );
}
}
}
const QPoint pos = mapFromGlobal( event->globalPos() );
KDAB_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 );
KDAB_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes )
{
if ( plane->geometry().contains( event->pos() ) )
{
if( plane->diagrams().size() > 0 )
{
eventReceivers.insert( plane );
}
}
}
const QPoint pos = mapFromGlobal( event->globalPos() );
KDAB_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 )
{
switch( event->type() )
{
case QEvent::ToolTip:
{
const QHelpEvent* const helpEvent = static_cast< QHelpEvent* >( event );
KDAB_FOREACH( const AbstractCoordinatePlane* const plane, d->coordinatePlanes )
{
for (int i = plane->diagrams().count() - 1; i >= 0; --i) {
const QModelIndex index = plane->diagrams().at(i)->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;
}
}
}
// fall-through intended
}
default:
return QWidget::event( event );
}
}