KDChartChart.cpp

Go to the documentation of this file.
00001 /****************************************************************************
00002  ** Copyright (C) 2007 Klaralvdalens Datakonsult AB.  All rights reserved.
00003  **
00004  ** This file is part of the KD Chart library.
00005  **
00006  ** This file may be distributed and/or modified under the terms of the
00007  ** GNU General Public License version 2 as published by the Free Software
00008  ** Foundation and appearing in the file LICENSE.GPL included in the
00009  ** packaging of this file.
00010  **
00011  ** Licensees holding valid commercial KD Chart licenses may use this file in
00012  ** accordance with the KD Chart Commercial License Agreement provided with
00013  ** the Software.
00014  **
00015  ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
00016  ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
00017  **
00018  ** See http://www.kdab.net/kdchart for
00019  **   information about KDChart Commercial License Agreements.
00020  **
00021  ** Contact info@kdab.net if any conditions of this
00022  ** licensing are not clear to you.
00023  **
00024  **********************************************************************/
00025 
00026 #include "KDChartChart.h"
00027 #include "KDChartChart_p.h"
00028 
00029 #include <QList>
00030 #include <QtDebug>
00031 #include <QGridLayout>
00032 #include <QLabel>
00033 #include <QHash>
00034 #include <QToolTip>
00035 #include <QPainter>
00036 #include <QPaintEvent>
00037 #include <QLayoutItem>
00038 #include <QPushButton>
00039 #include <QApplication>
00040 #include <QEvent>
00041 
00042 #include "KDChartCartesianCoordinatePlane.h"
00043 #include "KDChartAbstractCartesianDiagram.h"
00044 #include "KDChartHeaderFooter.h"
00045 #include "KDChartEnums.h"
00046 #include "KDChartLegend.h"
00047 #include "KDChartLayoutItems.h"
00048 #include <KDChartTextAttributes.h>
00049 #include <KDChartMarkerAttributes>
00050 #include "KDChartPainterSaver_p.h"
00051 #include "KDChartPrintingParameters.h"
00052 
00053 #include <KDABLibFakes>
00054 
00055 #define SET_ALL_MARGINS_TO_ZERO
00056 
00057 // Layout widgets even if they are not visible
00058 class MyWidgetItem : public QWidgetItem
00059 {
00060 public:
00061     explicit MyWidgetItem(QWidget *w, Qt::Alignment alignment = 0)
00062         : QWidgetItem(w) {
00063         setAlignment( alignment );
00064     }
00065     /*reimp*/ bool isEmpty() const {
00066         QWidget* w = const_cast<MyWidgetItem *>(this)->widget();
00067         // legend->hide() should indeed hide the legend,
00068         // but a legend in a chart that hasn't been shown yet isn't hidden
00069         // (as can happen when using Chart::paint() without showing the chart)
00070         return w->isHidden() && w->testAttribute(Qt::WA_WState_ExplicitShowHide);
00071     }
00072 };
00073 
00074 using namespace KDChart;
00075 
00076 void Chart::Private::slotUnregisterDestroyedLegend( Legend *l )
00077 {
00078     legends.removeAll( l );
00079     slotRelayout();
00080 }
00081 
00082 void Chart::Private::slotUnregisterDestroyedHeaderFooter( HeaderFooter* hf )
00083 {
00084     headerFooters.removeAll( hf );
00085     hf->removeFromParentLayout();
00086     textLayoutItems.remove( textLayoutItems.indexOf( hf ) );
00087     slotRelayout();
00088 }
00089 
00090 void Chart::Private::slotUnregisterDestroyedPlane( AbstractCoordinatePlane* plane )
00091 {
00092     coordinatePlanes.removeAll( plane );
00093     Q_FOREACH ( AbstractCoordinatePlane* p, coordinatePlanes )
00094     {
00095         if ( p->referenceCoordinatePlane() == plane) {
00096             p->setReferenceCoordinatePlane(0);
00097         }
00098     }
00099     plane->layoutPlanes();
00100 }
00101 
00102 Chart::Private::Private( Chart* chart_ )
00103     : chart( chart_ )
00104     , layout( 0 )
00105     , vLayout( 0 )
00106     , planesLayout( 0 )
00107     , headerLayout( 0 )
00108     , footerLayout( 0 )
00109     , dataAndLegendLayout( 0 )
00110     , globalLeadingLeft( 0 )
00111     , globalLeadingRight( 0 )
00112     , globalLeadingTop( 0 )
00113     , globalLeadingBottom( 0 )
00114 {
00115     for( int row = 0; row < 3; ++row )
00116     {
00117         for( int column = 0; column < 3; ++column )
00118         {
00119             dummyHeaders[ row ][ column ] = HorizontalLineLayoutItem();
00120             dummyFooters[ row ][ column ] = HorizontalLineLayoutItem();
00121             innerHdFtLayouts[0][row][column] = 0;
00122             innerHdFtLayouts[1][row][column] = 0;
00123         }
00124     }
00125 }
00126 
00127 Chart::Private::~Private()
00128 {
00129     removeDummyHeaderFooters();
00130 }
00131 
00132 void Chart::Private::removeDummyHeaderFooters()
00133 {
00134     for ( int row = 0; row < 3; ++row )
00135     {
00136         for ( int column = 0; column < 3; ++ column )
00137         {
00138             if( innerHdFtLayouts[0][row][column] ){
00139                 innerHdFtLayouts[0][row][column]->removeItem( &(dummyHeaders[row][column]) );
00140                 innerHdFtLayouts[1][row][column]->removeItem( &(dummyFooters[row][column]) );
00141             }
00142         }
00143     }
00144 }
00145 
00146 void Chart::Private::layoutHeadersAndFooters()
00147 {
00148     removeDummyHeaderFooters();
00149 
00150     bool headersLineFilled[] = { false, false, false };
00151     bool footersLineFilled[] = { false, false, false };
00152 
00153     Q_FOREACH( HeaderFooter *hf, headerFooters ) {
00154         // for now, there are only two types of Header/Footer,
00155         // we use a pointer to the right layout, depending on the type():
00156         int innerLayoutIdx;
00157         switch( hf->type() ){
00158             case HeaderFooter::Header:
00159                 innerLayoutIdx = 0;
00160                 break;
00161             case HeaderFooter::Footer:
00162                 innerLayoutIdx = 1;
00163                 break;
00164             default:
00165                 Q_ASSERT( false ); // all types need to be handled
00166                 break;
00167         };
00168 
00169         if( hf->position() != Position::Unknown ) {
00170             int row, column;
00171             Qt::Alignment hAlign, vAlign;
00172             if( hf->position().isNorthSide() ){
00173                 row = 0;
00174                 vAlign = Qt::AlignTop;
00175             }
00176             else if( hf->position().isSouthSide() ){
00177                 row = 2;
00178                 vAlign = Qt::AlignBottom;
00179             }
00180             else{
00181                 row = 1;
00182                 vAlign = Qt::AlignVCenter;
00183             }
00184             if( hf->position().isWestSide() ){
00185                 column = 0;
00186                 hAlign = Qt::AlignLeft;
00187             }
00188             else if( hf->position().isEastSide() ){
00189                 column = 2;
00190                 hAlign = Qt::AlignRight;
00191             }
00192             else{
00193                 column = 1;
00194                 hAlign = Qt::AlignHCenter;
00195             }
00196             switch( hf->type() ){
00197                 case HeaderFooter::Header:
00198                     if( !headersLineFilled[ row ] )
00199                     {
00200                         for( int col = 0; col < 3; ++col )
00201                             innerHdFtLayouts[0][row][col]->addItem( &(dummyHeaders[ row ][ col ]) );
00202                         headersLineFilled[ row ] = true;
00203                     }
00204                     break;
00205                 case HeaderFooter::Footer:
00206                     if( !footersLineFilled[ row ] )
00207                     {
00208                         for( int col = 0; col < 3; ++col )
00209                             innerHdFtLayouts[1][row][col]->addItem( &(dummyFooters[ row ][ col ]) );
00210                         footersLineFilled[ row ] = true;
00211                     }
00212                     break;
00213             };
00214             textLayoutItems << hf;
00215             QVBoxLayout* headerFooterLayout = innerHdFtLayouts[innerLayoutIdx][row][column];
00216             hf->setParentLayout( headerFooterLayout );
00217             hf->setAlignment( hAlign | vAlign );
00218             headerFooterLayout->addItem( hf );
00219         }
00220         else{
00221             qDebug( "Unknown header/footer position" );
00222         }
00223     }
00224 }
00225 
00226 void Chart::Private::layoutLegends()
00227 {
00228     //qDebug() << "starting Chart::Private::layoutLegends()";
00229     // To support more than one Legend, we first collect them all
00230     // in little lists: one list per grid position.
00231     // Since the dataAndLegendLayout is a 3x3 grid, we need 9 little lists.
00232     QList<Legend*> infos[3][3];
00233 
00234     Q_FOREACH( Legend *legend, legends ) {
00235 
00236         legend->needSizeHint(); // we'll lay it out soon
00237 
00238         bool bOK = true;
00239         int row, column;
00240         //qDebug() << legend->position().name();
00241         switch( legend->position().value() ) {
00242             case KDChartEnums::PositionNorthWest:  row = 0;  column = 0;
00243                 break;
00244             case KDChartEnums::PositionNorth:      row = 0;  column = 1;
00245                 break;
00246             case KDChartEnums::PositionNorthEast:  row = 0;  column = 2;
00247                 break;
00248             case KDChartEnums::PositionEast:       row = 1;  column = 2;
00249                 break;
00250             case KDChartEnums::PositionSouthEast:  row = 2;  column = 2;
00251                 break;
00252             case KDChartEnums::PositionSouth:      row = 2;  column = 1;
00253                 break;
00254             case KDChartEnums::PositionSouthWest:  row = 2;  column = 0;
00255                 break;
00256             case KDChartEnums::PositionWest:       row = 1;  column = 0;
00257                 break;
00258             case KDChartEnums::PositionCenter:
00259                 qDebug( "Sorry: Legend not shown, because position Center is not supported." );
00260                 bOK = false;
00261                 break;
00262             case KDChartEnums::PositionFloating:
00263                 bOK = false;
00264                 break;
00265             default:
00266                 qDebug( "Sorry: Legend not shown, because of unknown legend position." );
00267                 bOK = false;
00268                 break;
00269         }
00270         if( bOK )
00271             infos[row][column] << legend;
00272     }
00273     // We have collected all legend information,
00274     // so we can design their layout now.
00275     for (int iR = 0; iR < 3; ++iR) {
00276         for (int iC = 0; iC < 3; ++iC) {
00277             QList<Legend*>& list = infos[iR][iC];
00278             const int count = list.size();
00279             switch( count ){
00280             case 0:
00281                 break;
00282             case 1: {
00283                     Legend* legend = list.first();
00284                     dataAndLegendLayout->addItem( new MyWidgetItem(legend),
00285                         iR, iC, 1, 1, legend->alignment() );
00286             }
00287                 break;
00288             default: {
00289                     // We have more than one legend in the same cell
00290                     // of the big dataAndLegendLayout grid.
00291                     //
00292                     // So we need to find out, if they are aligned the
00293                     // same way:
00294                     // Those legends, that are aligned the same way, will be drawn
00295                     // leftbound, on top of each other, in a little VBoxLayout.
00296                     //
00297                     // If not al of the legends are aligned the same way,
00298                     // there will be a grid with 3 cells: for left/mid/right side
00299                     // (or top/mid/bottom side, resp.) legends
00300                     Legend* legend = list.first();
00301                     Qt::Alignment alignment = legend->alignment();
00302                     bool haveSameAlign = true;
00303                     for (int i = 1; i < count; ++i) {
00304                         legend = list.at(i);
00305                         if( alignment != legend->alignment() ){
00306                             haveSameAlign = false;
00307                             break;
00308                         }
00309                     }
00310                     if( haveSameAlign ){
00311                         QVBoxLayout* vLayout = new QVBoxLayout();
00312 #if defined SET_ALL_MARGINS_TO_ZERO
00313                         vLayout->setMargin(0);
00314 #endif
00315                         for (int i = 0; i < count; ++i) {
00316                             vLayout->addItem( new MyWidgetItem(list.at(i), Qt::AlignLeft) );
00317                         }
00318                         dataAndLegendLayout->addLayout( vLayout, iR, iC, 1, 1, alignment );
00319                     }else{
00320                         QGridLayout* gridLayout = new QGridLayout();
00321 #if defined SET_ALL_MARGINS_TO_ZERO
00322                         gridLayout->setMargin(0);
00323 #endif
00324 
00325 
00326 #define ADD_VBOX_WITH_LEGENDS(row, column, align) \
00327 { \
00328     QVBoxLayout* innerLayout = new QVBoxLayout(); \
00329     for (int i = 0; i < count; ++i) { \
00330         legend = list.at(i); \
00331         if( legend->alignment() == ( align ) ) \
00332             innerLayout->addItem( new MyWidgetItem(legend, Qt::AlignLeft) ); \
00333     } \
00334     gridLayout->addLayout( innerLayout, row, column, ( align  ) ); \
00335 }
00336                         ADD_VBOX_WITH_LEGENDS( 0, 0, Qt::AlignTop     | Qt::AlignLeft )
00337                         ADD_VBOX_WITH_LEGENDS( 0, 1, Qt::AlignTop     | Qt::AlignHCenter )
00338                         ADD_VBOX_WITH_LEGENDS( 0, 2, Qt::AlignTop     | Qt::AlignRight )
00339 
00340                         ADD_VBOX_WITH_LEGENDS( 1, 0, Qt::AlignVCenter | Qt::AlignLeft )
00341                         ADD_VBOX_WITH_LEGENDS( 1, 1, Qt::AlignCenter )
00342                         ADD_VBOX_WITH_LEGENDS( 1, 2, Qt::AlignVCenter | Qt::AlignRight )
00343 
00344                         ADD_VBOX_WITH_LEGENDS( 2, 0, Qt::AlignBottom  | Qt::AlignLeft )
00345                         ADD_VBOX_WITH_LEGENDS( 2, 1, Qt::AlignBottom  | Qt::AlignHCenter )
00346                         ADD_VBOX_WITH_LEGENDS( 2, 2, Qt::AlignBottom  | Qt::AlignRight )
00347 
00348                         dataAndLegendLayout->addLayout( gridLayout, iR, iC, 1, 1 );
00349                     }
00350                 }
00351             }
00352         }
00353     }
00354     //qDebug() << "finished Chart::Private::layoutLegends()";
00355 }
00356 
00357 
00358 QHash<AbstractCoordinatePlane*, PlaneInfo> Chart::Private::buildPlaneLayoutInfos()
00359 {
00360     /* There are two ways in which planes can be caused to interact in
00361      * where they are put layouting wise: The first is the reference plane. If
00362      * such a reference plane is set, on a plane, it will use the same cell in the
00363      * layout as that one. In addition to this, planes can share an axis. In that case
00364      * they will be laid out in relation to each other as suggested by the position
00365      * of the axis. If, for example Plane1 and Plane2 share an axis at position Left,
00366      * that will result in the layout: Axis Plane1 Plane 2, vertically. If Plane1
00367      * also happens to be Plane2's referece plane, both planes are drawn over each
00368      * other. The reference plane concept allows two planes to share the same space
00369      * even if neither has any axis, and in case there are shared axis, it is used
00370      * to decided, whether the planes should be painted on top of each other or
00371      * laid out vertically or horizontally next to each other. */
00372     QHash<CartesianAxis*, AxisInfo> axisInfos;
00373     QHash<AbstractCoordinatePlane*, PlaneInfo> planeInfos;
00374     Q_FOREACH(AbstractCoordinatePlane* plane, coordinatePlanes )
00375     {
00376         PlaneInfo p;
00377         // first check if we share space with another plane
00378         p.referencePlane = plane->referenceCoordinatePlane();
00379         planeInfos.insert( plane, p );
00380 
00381         Q_FOREACH( AbstractDiagram* abstractDiagram, plane->diagrams() ) {
00382             AbstractCartesianDiagram* diagram =
00383                     dynamic_cast<AbstractCartesianDiagram*> ( abstractDiagram );
00384             if( !diagram ) continue;
00385 
00386             Q_FOREACH( CartesianAxis* axis, diagram->axes() ) {
00387                 if ( !axisInfos.contains( axis ) ) {
00388                     /* If this is the first time we see this axis, add it, with the
00389                      * current plane. The first plane added to the chart that has
00390                      * the axis associated with it thus "owns" it, and decides about
00391                      * layout. */
00392                     AxisInfo i;
00393                     i.plane = plane;
00394                     axisInfos.insert( axis, i );
00395                 } else {
00396                     AxisInfo i = axisInfos[axis];
00397                     if ( i.plane == plane ) continue; // we don't want duplicates, only shared
00398 
00399                     /* The user expects diagrams to be added on top, and to the right
00400                      * so that horizontally we need to move the new diagram, vertically
00401                      * the reference one. */
00402                     PlaneInfo pi = planeInfos[plane];
00403                     // plane-to-plane linking overrides linking via axes
00404                     if ( !pi.referencePlane ) {
00405                         // we're not the first plane to see this axis, mark us as a slave
00406                         pi.referencePlane = i.plane;
00407                         if ( axis->position() == CartesianAxis::Left
00408                             ||  axis->position() == CartesianAxis::Right )
00409                             pi.horizontalOffset += 1;
00410                         planeInfos[plane] = pi;
00411 
00412                         pi = planeInfos[i.plane];
00413                         if ( axis->position() == CartesianAxis::Top
00414                                 || axis->position() == CartesianAxis::Bottom  )
00415                             pi.verticalOffset += 1;
00416 
00417                         planeInfos[i.plane] = pi;
00418                     }
00419                 }
00420             }
00421         }
00422         // Create a new grid layout for each plane that has no reference.
00423         p = planeInfos[plane];
00424         if ( p.referencePlane == 0 ) {
00425             p.gridLayout = new QGridLayout();
00426             // TESTING(khz): set the margin of all of the layouts to Zero
00427 #if defined SET_ALL_MARGINS_TO_ZERO
00428             p.gridLayout->setMargin(0);
00429 #endif
00430             planeInfos[plane] = p;
00431         }
00432     }
00433     return planeInfos;
00434 }
00435 
00436     template <typename T>
00437 static T* findOrCreateLayoutByObjectName( QLayout * parentLayout, const char* name )
00438 {
00439     T *box = qFindChild<T*>( parentLayout, QString::fromLatin1( name ) );
00440     if ( !box ) {
00441         box = new T();
00442         // TESTING(khz): set the margin of all of the layouts to Zero
00443 #if defined SET_ALL_MARGINS_TO_ZERO
00444         box->setMargin(0);
00445 #endif
00446         box->setObjectName( QString::fromLatin1( name ) );
00447         box->setSizeConstraint( QLayout::SetFixedSize );
00448     }
00449     return box;
00450 }
00451 
00452 #if 0
00453 static QVBoxLayout* findOrCreateVBoxLayoutByObjectName( QLayout* parentLayout, const char* name )
00454 {
00455     return findOrCreateLayoutByObjectName<QVBoxLayout>( parentLayout, name );
00456 }
00457 
00458 static QHBoxLayout* findOrCreateHBoxLayoutByObjectName( QLayout* parentLayout, const char* name )
00459 {
00460     return findOrCreateLayoutByObjectName<QHBoxLayout>( parentLayout, name );
00461 }
00462 #endif
00463 
00464 void Chart::Private::slotLayoutPlanes()
00465 {
00466     //qDebug() << "KDChart::Chart is layouting the planes";
00467     const QBoxLayout::Direction oldPlanesDirection =
00468         planesLayout ? planesLayout->direction() : QBoxLayout::TopToBottom;
00469     if ( planesLayout && dataAndLegendLayout )
00470         dataAndLegendLayout->removeItem( planesLayout );
00471 
00472     KDAB_FOREACH( KDChart::AbstractLayoutItem* plane, planeLayoutItems ) {
00473         plane->removeFromParentLayout();
00474     }
00475     planeLayoutItems.clear();
00476     delete planesLayout;
00477     //hint: The direction is configurable by the user now, as
00478     //      we are using a QBoxLayout rather than a QVBoxLayout.  (khz, 2007/04/25)
00479     planesLayout = new QBoxLayout( oldPlanesDirection );
00480 
00481     // TESTING(khz): set the margin of all of the layouts to Zero
00482 #if defined SET_ALL_MARGINS_TO_ZERO
00483     planesLayout->setMargin(0);
00484     planesLayout->setSpacing(0);
00485 #endif
00486     planesLayout->setObjectName( QString::fromLatin1( "planesLayout" ) );
00487 
00488     /* First go through all planes and all axes and figure out whether the planes
00489      * need to coordinate. If they do, they share a grid layout, if not, each
00490      * get their own. See buildPlaneLayoutInfos() for more details. */
00491     QHash<AbstractCoordinatePlane*, PlaneInfo> planeInfos = buildPlaneLayoutInfos();
00492     QHash<AbstractAxis*, AxisInfo> axisInfos;
00493     KDAB_FOREACH( AbstractCoordinatePlane* plane, coordinatePlanes ) {
00494         Q_ASSERT( planeInfos.contains(plane) );
00495         PlaneInfo& pi = planeInfos[ plane ];
00496         int column = pi.horizontalOffset;
00497         int row = pi.verticalOffset;
00498         //qDebug() << "processing plane at column" << column << "and row" << row;
00499         QGridLayout *planeLayout = pi.gridLayout;
00500         if ( !planeLayout ) {
00501             // this plane is sharing an axis with another one, so use
00502             // the grid of that one as well
00503             planeLayout = planeInfos[pi.referencePlane].gridLayout;
00504         } else {
00505             planesLayout->addLayout( planeLayout );
00506         }
00507         Q_ASSERT( planeLayout );
00508         /* Put the plane in the center of the layout. If this is our own, that's
00509          * the middle of the layout, if we are sharing, it's a cell in the center
00510          * column of the shared grid. */
00511         planeLayoutItems << plane;
00512         plane->setParentLayout( planeLayout );
00513         planeLayout->addItem( plane, row, column, 1, 1, 0 );
00514         //qDebug() << "Chart slotLayoutPlanes() calls planeLayout->addItem("<< row << column << ")";
00515         planeLayout->setRowStretch(    row,    2 );
00516         planeLayout->setColumnStretch( column, 2 );
00517 
00518         KDAB_FOREACH( AbstractDiagram* abstractDiagram, plane->diagrams() )
00519         {
00520             AbstractCartesianDiagram* diagram =
00521                 dynamic_cast<AbstractCartesianDiagram*> ( abstractDiagram );
00522             //qDebug() << "--------------- diagram ???????????????????? -----------------";
00523             if( !diagram ) continue;  // FIXME polar ?
00524             //qDebug() << "--------------- diagram ! ! ! ! ! ! ! ! ! !  -----------------";
00525 
00526             if( pi.referencePlane != 0 )
00527             {
00528                 pi.topAxesLayout = planeInfos[ pi.referencePlane ].topAxesLayout;
00529                 pi.bottomAxesLayout = planeInfos[ pi.referencePlane ].bottomAxesLayout;
00530                 pi.leftAxesLayout = planeInfos[ pi.referencePlane ].leftAxesLayout;
00531                 pi.rightAxesLayout = planeInfos[ pi.referencePlane ].rightAxesLayout;
00532             }
00533 
00534             // collect all axes of a kind into sublayouts
00535             if( pi.topAxesLayout == 0 )
00536             {
00537                 pi.topAxesLayout = new QVBoxLayout;
00538 #if defined SET_ALL_MARGINS_TO_ZERO
00539                 pi.topAxesLayout->setMargin(0);
00540 #endif
00541                 pi.topAxesLayout->setObjectName( QString::fromLatin1( "topAxesLayout" ) );
00542             }
00543             if( pi.bottomAxesLayout == 0 )
00544             {
00545                 pi.bottomAxesLayout = new QVBoxLayout;
00546 #if defined SET_ALL_MARGINS_TO_ZERO
00547                 pi.bottomAxesLayout->setMargin(0);
00548 #endif
00549                 pi.bottomAxesLayout->setObjectName( QString::fromLatin1( "bottomAxesLayout" ) );
00550             }
00551             if( pi.leftAxesLayout == 0 )
00552             {
00553                 pi.leftAxesLayout = new QHBoxLayout;
00554 #if defined SET_ALL_MARGINS_TO_ZERO
00555                 pi.leftAxesLayout->setMargin(0);
00556 #endif
00557                 pi.leftAxesLayout->setObjectName( QString::fromLatin1( "leftAxesLayout" ) );
00558             }
00559             if( pi.rightAxesLayout == 0 )
00560             {
00561                 pi.rightAxesLayout = new QHBoxLayout;
00562 #if defined SET_ALL_MARGINS_TO_ZERO
00563                 pi.rightAxesLayout->setMargin(0);
00564 #endif
00565                 pi.rightAxesLayout->setObjectName( QString::fromLatin1( "rightAxesLayout" ) );
00566             }
00567 
00568             if( pi.referencePlane != 0 )
00569             {
00570                 planeInfos[ pi.referencePlane ].topAxesLayout = pi.topAxesLayout;
00571                 planeInfos[ pi.referencePlane ].bottomAxesLayout = pi.bottomAxesLayout;
00572                 planeInfos[ pi.referencePlane ].leftAxesLayout = pi.leftAxesLayout;
00573                 planeInfos[ pi.referencePlane ].rightAxesLayout = pi.rightAxesLayout;
00574             }
00575  
00576             //pi.leftAxesLayout->setSizeConstraint( QLayout::SetFixedSize );
00577 
00578             KDAB_FOREACH( CartesianAxis* axis, diagram->axes() ) {
00579                 if ( axisInfos.contains( axis ) ) continue; // already laid this one out
00580                 Q_ASSERT ( axis );
00581                 axis->setCachedSizeDirty();
00582                 //qDebug() << "--------------- axis added to planeLayoutItems  -----------------";
00583                 planeLayoutItems << axis;
00584                 /*
00585                 // Unused code trying to use a push-model: This did not work
00586                 // since we can not re-layout the planes each time when
00587                 // Qt layouting is calling sizeHint()
00588                 connect( axis, SIGNAL( needAdjustLeftRightColumnsForOverlappingLabels(
00589                 CartesianAxis*, int, int ) ),
00590                 this, SLOT( slotAdjustLeftRightColumnsForOverlappingLabels(
00591                 CartesianAxis*, int, int ) ) );
00592                 connect( axis, SIGNAL( needAdjustTopBottomRowsForOverlappingLabels(
00593                 CartesianAxis*, int, int ) ),
00594                 this, SLOT( slotAdjustTopBottomRowsForOverlappingLabels(
00595                 CartesianAxis*, int, int ) ) );
00596                 */
00597                 switch ( axis->position() )
00598                 {
00599                     case CartesianAxis::Top:
00600                         axis->setParentLayout( pi.topAxesLayout );
00601                         pi.topAxesLayout->addItem( axis );
00602                         break;
00603                     case CartesianAxis::Bottom:
00604                         axis->setParentLayout( pi.bottomAxesLayout );
00605                         pi.bottomAxesLayout->addItem( axis );
00606                         break;
00607                     case CartesianAxis::Left:
00608                         axis->setParentLayout( pi.leftAxesLayout );
00609                         pi.leftAxesLayout->addItem( axis );
00610                         break;
00611                     case CartesianAxis::Right:
00612                         axis->setParentLayout( pi.rightAxesLayout );
00613                         pi.rightAxesLayout->addItem( axis );
00614                         break;
00615                     default:
00616                         Q_ASSERT_X( false, "Chart::paintEvent",
00617                                 "unknown axis position" );
00618                         break;
00619                 };
00620                 axisInfos.insert( axis, AxisInfo() );
00621             }
00622             /* Put each stack of axes-layouts in the cells surrounding the
00623              * associated plane. We are laying out in the oder the planes
00624              * were added, and the first one gets to lay out shared axes.
00625              * Private axes go here as well, of course. */
00626             if ( !pi.topAxesLayout->parent() )
00627                 planeLayout->addLayout( pi.topAxesLayout,    row - 1, column );
00628             if ( !pi.bottomAxesLayout->parent() )
00629                 planeLayout->addLayout( pi.bottomAxesLayout, row + 1, column );
00630             if ( !pi.leftAxesLayout->parent() ){
00631                 planeLayout->addLayout( pi.leftAxesLayout,   row,     column - 1);
00632                 //planeLayout->setRowStretch(    row, 0 );
00633                 //planeLayout->setColumnStretch( 0,   0 );
00634             }
00635             if ( !pi.rightAxesLayout->parent() )
00636                 planeLayout->addLayout( pi.rightAxesLayout,  row,     column + 1);
00637         }
00638 
00639         // use up to four auto-spacer items in the corners around the diagrams:
00640 #define ADD_AUTO_SPACER_IF_NEEDED( \
00641         spacerRow, spacerColumn, hLayoutIsAtTop, hLayout, vLayoutIsAtLeft, vLayout ) \
00642         { \
00643             if( hLayout || vLayout ) { \
00644                 AutoSpacerLayoutItem * spacer \
00645                 = new AutoSpacerLayoutItem( hLayoutIsAtTop, hLayout, vLayoutIsAtLeft, vLayout ); \
00646                 planeLayout->addItem( spacer, spacerRow, spacerColumn, 1, 1 ); \
00647                 spacer->setParentLayout( planeLayout ); \
00648                 planeLayoutItems << spacer; \
00649             } \
00650         }
00651         ADD_AUTO_SPACER_IF_NEEDED( row-1, column-1, false, pi.leftAxesLayout,  false, pi.topAxesLayout )
00652             ADD_AUTO_SPACER_IF_NEEDED( row+1, column-1, true,  pi.leftAxesLayout,  false,  pi.bottomAxesLayout )
00653             ADD_AUTO_SPACER_IF_NEEDED( row-1, column+1, false, pi.rightAxesLayout, true, pi.topAxesLayout )
00654             ADD_AUTO_SPACER_IF_NEEDED( row+1, column+1, true,  pi.rightAxesLayout, true,  pi.bottomAxesLayout )
00655     }
00656     // re-add our grid(s) to the chart's layout
00657     if ( dataAndLegendLayout ){
00658         dataAndLegendLayout->addLayout( planesLayout, 1, 1 );
00659         dataAndLegendLayout->setRowStretch(    1, 1000 );
00660         dataAndLegendLayout->setColumnStretch( 1, 1000 );
00661     }
00662 
00663     slotRelayout();
00664     //qDebug() << "KDChart::Chart finished layouting the planes.";
00665 }
00666 
00667 void Chart::Private::createLayouts( QWidget* w )
00668 {
00669     KDAB_FOREACH( KDChart::TextArea* textLayoutItem, textLayoutItems ) {
00670         textLayoutItem->removeFromParentLayout();
00671     }
00672     textLayoutItems.clear();
00673 
00674     KDAB_FOREACH( KDChart::AbstractArea* layoutItem, layoutItems ) {
00675         layoutItem->removeFromParentLayout();
00676     }
00677     layoutItems.clear();
00678 
00679     removeDummyHeaderFooters();
00680 
00681     // layout for the planes is handled separately, so we don't want to delete it here
00682     if ( dataAndLegendLayout) {
00683         dataAndLegendLayout->removeItem( planesLayout );
00684         planesLayout->setParent( 0 );
00685     }
00686     // nuke the old bunch
00687     delete layout;
00688 
00689     // The HBox d->layout provides the left and right global leadings
00690     layout = new QHBoxLayout( w );
00691     // TESTING(khz): set the margin of all of the layouts to Zero
00692 #if defined SET_ALL_MARGINS_TO_ZERO
00693     layout->setMargin(0);
00694 #endif
00695     layout->setObjectName( QString::fromLatin1( "Chart::Private::layout" ) );
00696     layout->addSpacing( globalLeadingLeft );
00697 
00698     // The vLayout provides top and bottom global leadings and lays
00699     // out headers/footers and the data area.
00700     vLayout = new QVBoxLayout();
00701     // TESTING(khz): set the margin of all of the layouts to Zero
00702 #if defined SET_ALL_MARGINS_TO_ZERO
00703     vLayout->setMargin(0);
00704 #endif
00705     vLayout->setObjectName( QString::fromLatin1( "vLayout" ) );
00706     layout->addLayout( vLayout, 1000 );
00707     layout->addSpacing( globalLeadingRight );
00708 
00709 
00710 
00711     // 1. the gap above the top edge of the headers area
00712     vLayout->addSpacing( globalLeadingTop );
00713     // 2. the header(s) area
00714     headerLayout = new QGridLayout();
00715     // TESTING(khz): set the margin of all of the layouts to Zero
00716 #if defined SET_ALL_MARGINS_TO_ZERO
00717     headerLayout->setMargin(0);
00718 #endif
00719     vLayout->addLayout( headerLayout );
00720     // 3. the area containing coordinate plane(s), axes, legend(s)
00721     dataAndLegendLayout = new QGridLayout();
00722     // TESTING(khz): set the margin of all of the layouts to Zero
00723 #if defined SET_ALL_MARGINS_TO_ZERO
00724     dataAndLegendLayout->setMargin(0);
00725 #endif
00726     dataAndLegendLayout->setObjectName( QString::fromLatin1( "dataAndLegendLayout" ) );
00727     vLayout->addLayout( dataAndLegendLayout, 1000 );
00728     // 4. the footer(s) area
00729     footerLayout = new QGridLayout();
00730     // TESTING(khz): set the margin of all of the layouts to Zero
00731 #if defined SET_ALL_MARGINS_TO_ZERO
00732     footerLayout->setMargin(0);
00733 #endif
00734     footerLayout->setObjectName( QString::fromLatin1( "footerLayout" ) );
00735     vLayout->addLayout( footerLayout );
00736 
00737     // 5. Prepare the header / footer layout cells:
00738     //    Each of the 9 header cells (the 9 footer cells)
00739     //    contain their own QVBoxLayout
00740     //    since there can be more than one header (footer) per cell.
00741     static const Qt::Alignment hdFtAlignments[3][3] = {
00742         { Qt::AlignTop     | Qt::AlignLeft,  Qt::AlignTop     | Qt::AlignHCenter,  Qt::AlignTop     | Qt::AlignRight },
00743         { Qt::AlignVCenter | Qt::AlignLeft,  Qt::AlignVCenter | Qt::AlignHCenter,  Qt::AlignVCenter | Qt::AlignRight },
00744         { Qt::AlignBottom  | Qt::AlignLeft,  Qt::AlignBottom  | Qt::AlignHCenter,  Qt::AlignBottom  | Qt::AlignRight }
00745     };
00746     for ( int row = 0; row < 3; ++row )
00747     {
00748         for ( int column = 0; column < 3; ++ column )
00749         {
00750             QVBoxLayout* innerHdLayout = new QVBoxLayout();
00751             QVBoxLayout* innerFtLayout = new QVBoxLayout();
00752             innerHdFtLayouts[0][row][column] = innerHdLayout;
00753             innerHdFtLayouts[1][row][column] = innerFtLayout;
00754 #if defined SET_ALL_MARGINS_TO_ZERO
00755             innerHdLayout->setMargin(0);
00756             innerFtLayout->setMargin(0);
00757 #endif
00758             const Qt::Alignment align = hdFtAlignments[row][column];
00759             innerHdLayout->setAlignment( align );
00760             innerFtLayout->setAlignment( align );
00761             headerLayout->addLayout( innerHdLayout, row, column, align );
00762             footerLayout->addLayout( innerFtLayout, row, column, align );
00763         }
00764     }
00765 
00766     // 6. the gap below the bottom edge of the headers area
00767     vLayout->addSpacing( globalLeadingBottom );
00768 
00769     // the data+axes area
00770     dataAndLegendLayout->addLayout( planesLayout, 1, 1 );
00771     dataAndLegendLayout->setRowStretch(    1, 1 );
00772     dataAndLegendLayout->setColumnStretch( 1, 1 );
00773 
00774     //qDebug() << "w->rect()" << w->rect();
00775 }
00776 
00777 void Chart::Private::slotRelayout()
00778 {
00779     //qDebug() << "Chart relayouting started.";
00780     createLayouts( chart );
00781 
00782     layoutHeadersAndFooters();
00783     layoutLegends();
00784 
00785     // This triggers the qlayout, see QBoxLayout::setGeometry
00786     // The geometry is not necessarily w->rect(), when using paint(), this is why
00787     // we don't call layout->activate().
00788     const QRect geo( QRect( 0, 0, currentLayoutSize.width(), currentLayoutSize.height() ) );
00789     if( geo.isValid() && geo != layout->geometry() ){
00790         //qDebug() << "Chart slotRelayout() adjusting geometry to" << geo;
00791         //if( coordinatePlanes.count() )
00792         //    qDebug() << "           plane geo before" << coordinatePlanes.first()->geometry();
00793         layout->setGeometry( geo );
00794         //if( coordinatePlanes.count() ) {
00795         //    qDebug() << "           plane geo after " << coordinatePlanes.first()->geometry();
00796         //}
00797     }
00798 
00799     // Adapt diagram drawing to the new size
00800     KDAB_FOREACH (AbstractCoordinatePlane* plane, coordinatePlanes ) {
00801         plane->layoutDiagrams();
00802     }
00803     //qDebug() << "Chart relayouting done.";
00804 }
00805 
00806 // Called when the size of the chart changes.
00807 // So in theory, we only need to adjust geometries.
00808 // But this also needs to make sure that everything is in place for the first painting.
00809 void Chart::Private::resizeLayout( const QSize& size )
00810 {
00811     currentLayoutSize = size;
00812     //qDebug() << "Chart::resizeLayout(" << currentLayoutSize << ")";
00813 
00814     /*
00815     // We need to make sure that the legend's layouts are populated,
00816     // so that setGeometry gets proper sizeHints from them and resizes them properly.
00817     KDAB_FOREACH( Legend *legend, legends ) {
00818     // This forceRebuild will see a wrong areaGeometry, but I don't care about geometries yet,
00819     // only about the fact that legends should have their contents populated.
00820     // -> it would be better to dissociate "building contents" and "resizing" in Legend...
00821 
00822     //        legend->forceRebuild();
00823 
00824     legend->resizeLayout( size );
00825     }
00826     */
00827     slotLayoutPlanes(); // includes slotRelayout
00828 
00829     //qDebug() << "Chart::resizeLayout done";
00830 }
00831 
00832 
00833 void Chart::Private::paintAll( QPainter* painter )
00834 {
00835     QRect rect( QPoint(0, 0), currentLayoutSize );
00836 
00837     //qDebug() << this<<"::paintAll() uses layout size" << currentLayoutSize;
00838 
00839     // Paint the background (if any)
00840     KDChart::AbstractAreaBase::paintBackgroundAttributes(
00841             *painter, rect, backgroundAttributes );
00842     // Paint the frame (if any)
00843     KDChart::AbstractAreaBase::paintFrameAttributes(
00844             *painter, rect, frameAttributes );
00845 
00846     chart->reLayoutFloatingLegends();
00847 
00848     KDAB_FOREACH( KDChart::AbstractArea* layoutItem, layoutItems ) {
00849         layoutItem->paintAll( *painter );
00850     }
00851     KDAB_FOREACH( KDChart::AbstractLayoutItem* planeLayoutItem, planeLayoutItems ) {
00852         planeLayoutItem->paintAll( *painter );
00853     }
00854     KDAB_FOREACH( KDChart::TextArea* textLayoutItem, textLayoutItems ) {
00855         textLayoutItem->paintAll( *painter );
00856     }
00857 }
00858 
00859 // ******** Chart interface implementation ***********
00860 
00861 Chart::Chart ( QWidget* parent )
00862     : QWidget ( parent )
00863     , _d( new Private( this ) )
00864 {
00865     FrameAttributes frameAttrs;
00866 // no frame per default...
00867 //    frameAttrs.setVisible( true );
00868     frameAttrs.setPen( QPen( Qt::black ) );
00869     frameAttrs.setPadding( 1 );
00870     setFrameAttributes( frameAttrs );
00871 
00872     addCoordinatePlane( new CartesianCoordinatePlane ( this ) );
00873 }
00874 
00875 Chart::~Chart()
00876 {
00877     delete _d;
00878 }
00879 
00880 #define d d_func()
00881 
00882 void Chart::setFrameAttributes( const FrameAttributes &a )
00883 {
00884     d->frameAttributes = a;
00885 }
00886 
00887 FrameAttributes Chart::frameAttributes() const
00888 {
00889     return d->frameAttributes;
00890 }
00891 
00892 void Chart::setBackgroundAttributes( const BackgroundAttributes &a )
00893 {
00894     d->backgroundAttributes = a;
00895 }
00896 
00897 BackgroundAttributes Chart::backgroundAttributes() const
00898 {
00899     return d->backgroundAttributes;
00900 }
00901 
00902 //TODO KDChart 3.0; change QLayout into QBoxLayout::Direction
00903 void Chart::setCoordinatePlaneLayout( QLayout * layout )
00904 {
00905     delete d->planesLayout;
00906     d->planesLayout = dynamic_cast<QBoxLayout*>( layout );
00907     d->slotLayoutPlanes();
00908 }
00909 
00910 QLayout* Chart::coordinatePlaneLayout()
00911 {
00912     return d->planesLayout;
00913 }
00914 
00915 AbstractCoordinatePlane* Chart::coordinatePlane()
00916 {
00917     if ( d->coordinatePlanes.isEmpty() )
00918     {
00919         qWarning() << "Chart::coordinatePlane: warning: no coordinate plane defined.";
00920         return 0;
00921     } else {
00922         return d->coordinatePlanes.first();
00923     }
00924 }
00925 
00926 CoordinatePlaneList Chart::coordinatePlanes()
00927 {
00928     return d->coordinatePlanes;
00929 }
00930 
00931 void Chart::addCoordinatePlane( AbstractCoordinatePlane* plane )
00932 {
00933     connect( plane, SIGNAL( destroyedCoordinatePlane( AbstractCoordinatePlane* ) ),
00934              d,   SLOT( slotUnregisterDestroyedPlane( AbstractCoordinatePlane* ) ) );
00935     connect( plane, SIGNAL( needUpdate() ),       this,   SLOT( update() ) );
00936     connect( plane, SIGNAL( needRelayout() ),     d,      SLOT( slotRelayout() ) ) ;
00937     connect( plane, SIGNAL( needLayoutPlanes() ), d,      SLOT( slotLayoutPlanes() ) ) ;
00938     connect( plane, SIGNAL( propertiesChanged() ),this, SIGNAL( propertiesChanged() ) );
00939     d->coordinatePlanes.append( plane );
00940     plane->setParent( this );
00941     d->slotLayoutPlanes();
00942 }
00943 
00944 void Chart::replaceCoordinatePlane( AbstractCoordinatePlane* plane,
00945                                     AbstractCoordinatePlane* oldPlane_ )
00946 {
00947     if( plane && oldPlane_ != plane ){
00948         AbstractCoordinatePlane* oldPlane = oldPlane_;
00949         if( d->coordinatePlanes.count() ){
00950             if( ! oldPlane ){
00951                 oldPlane = d->coordinatePlanes.first();
00952                 if( oldPlane == plane )
00953                     return;
00954             }
00955             takeCoordinatePlane( oldPlane );
00956         }
00957         delete oldPlane;
00958         addCoordinatePlane( plane );
00959     }
00960 }
00961 
00962 void Chart::takeCoordinatePlane( AbstractCoordinatePlane* plane )
00963 {
00964     const int idx = d->coordinatePlanes.indexOf( plane );
00965     if( idx != -1 ){
00966         d->coordinatePlanes.takeAt( idx );
00967         disconnect( plane, SIGNAL( destroyedCoordinatePlane( AbstractCoordinatePlane* ) ),
00968                     d, SLOT( slotUnregisterDestroyedPlane( AbstractCoordinatePlane* ) ) );
00969         plane->removeFromParentLayout();
00970         plane->setParent( 0 );
00971     }
00972     d->slotLayoutPlanes();
00973     // Need to emit the signal: In case somebody has connected the signal
00974     // to her own slot for e.g. calling update() on a widget containing the chart.
00975     emit propertiesChanged();
00976 }
00977 
00978 void Chart::setGlobalLeading( int left, int top, int right, int bottom )
00979 {
00980     setGlobalLeadingLeft( left );
00981     setGlobalLeadingTop( top );
00982     setGlobalLeadingRight( right );
00983     setGlobalLeadingBottom( bottom );
00984     d->slotRelayout();
00985 }
00986 
00987 void Chart::setGlobalLeadingLeft( int leading )
00988 {
00989     d->globalLeadingLeft = leading;
00990     d->slotRelayout();
00991 }
00992 
00993 int Chart::globalLeadingLeft() const
00994 {
00995     return d->globalLeadingLeft;
00996 }
00997 
00998 void Chart::setGlobalLeadingTop( int leading )
00999 {
01000     d->globalLeadingTop = leading;
01001     d->slotRelayout();
01002 }
01003 
01004 int Chart::globalLeadingTop() const
01005 {
01006     return d->globalLeadingTop;
01007 }
01008 
01009 void Chart::setGlobalLeadingRight( int leading )
01010 {
01011     d->globalLeadingRight = leading;
01012     d->slotRelayout();
01013 }
01014 
01015 int Chart::globalLeadingRight() const
01016 {
01017     return d->globalLeadingRight;
01018 }
01019 
01020 void Chart::setGlobalLeadingBottom( int leading )
01021 {
01022     d->globalLeadingBottom = leading;
01023     d->slotRelayout();
01024 }
01025 
01026 int Chart::globalLeadingBottom() const
01027 {
01028     return d->globalLeadingBottom;
01029 }
01030 
01031 void Chart::paint( QPainter* painter, const QRect& target )
01032 {
01033     if( target.isEmpty() || !painter ) return;
01034     //qDebug() << "Chart::paint( ..," << target << ")";
01035 
01036     QPaintDevice* prevDevice = GlobalMeasureScaling::paintDevice();
01037     GlobalMeasureScaling::setPaintDevice( painter->device() );
01038 
01039     // Output on a widget
01040     if( dynamic_cast< QWidget* >( painter->device() ) != 0 )
01041     {
01042         GlobalMeasureScaling::setFactors(
01043                 static_cast< qreal >( target.width() ) /
01044                 static_cast< qreal >( geometry().size().width() ),
01045                 static_cast< qreal >( target.height() ) /
01046                 static_cast< qreal >( geometry().size().height() ) );
01047     }
01048     // Output onto a QPixmap 
01049     else
01050     {
01051         PrintingParameters::setScaleFactor( static_cast< qreal >( painter->device()->logicalDpiX() ) / static_cast< qreal >( logicalDpiX() ) );
01052 
01053         const qreal resX = static_cast< qreal >( logicalDpiX() ) / static_cast< qreal >( painter->device()->logicalDpiX() );
01054         const qreal resY = static_cast< qreal >( logicalDpiY() ) / static_cast< qreal >( painter->device()->logicalDpiY() );
01055 
01056         GlobalMeasureScaling::setFactors(
01057                 static_cast< qreal >( target.width() ) /
01058                 static_cast< qreal >( geometry().size().width() ) * resX,
01059                 static_cast< qreal >( target.height() ) /
01060                 static_cast< qreal >( geometry().size().height() ) * resY );
01061     }
01062 
01063 
01064     if( target.size() != d->currentLayoutSize ){
01065         d->resizeLayout( target.size() );
01066     }
01067     const QPoint translation = target.topLeft();
01068     painter->translate( translation );
01069 
01070     d->paintAll( painter );
01071 
01072     // for debugging:
01073     //painter->setPen(QPen(Qt::blue, 8));
01074     //painter->drawRect(target.adjusted(12,12,-12,-12));
01075 
01076     KDAB_FOREACH( Legend *legend, d->legends ) {
01077         const bool hidden = legend->isHidden() && legend->testAttribute(Qt::WA_WState_ExplicitShowHide);
01078         if ( !hidden ) {
01079             //qDebug() << "painting legend at " << legend->geometry();
01080             legend->paintIntoRect( *painter, legend->geometry() );
01081             //testing:
01082             //legend->paintIntoRect( *painter, legend->geometry().adjusted(-100,0,-100,0) );
01083         }
01084     }
01085 
01086     painter->translate( -translation.x(), -translation.y() );
01087 
01088     GlobalMeasureScaling::instance()->resetFactors();
01089     PrintingParameters::resetScaleFactor();
01090     GlobalMeasureScaling::setPaintDevice( prevDevice );
01091 
01092     //qDebug() << "KDChart::Chart::paint() done.\n";
01093 }
01094 
01095 void Chart::resizeEvent ( QResizeEvent * )
01096 {
01097     d->resizeLayout( size() );
01098     KDAB_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes ){
01099         plane->setGridNeedsRecalculate();
01100     }
01101     reLayoutFloatingLegends();
01102 }
01103 
01104 
01105 void Chart::reLayoutFloatingLegends()
01106 {
01107     KDAB_FOREACH( Legend *legend, d->legends ) {
01108         const bool hidden = legend->isHidden() && legend->testAttribute(Qt::WA_WState_ExplicitShowHide);
01109         if ( legend->position().isFloating() && !hidden ){
01110             // resize the legend
01111             const QSize legendSize( legend->sizeHint() );
01112             legend->setGeometry( QRect( legend->geometry().topLeft(), legendSize ) );
01113             // find the legends corner point (reference point plus any paddings)
01114             const RelativePosition relPos( legend->floatingPosition() );
01115             QPointF pt( relPos.calculatedPoint( size() ) );
01116             //qDebug() << pt;
01117             // calculate the legend's top left point
01118             const Qt::Alignment alignTopLeft = Qt::AlignBottom | Qt::AlignLeft;
01119             if( (relPos.alignment() & alignTopLeft) != alignTopLeft ){
01120                 if( relPos.alignment() & Qt::AlignRight )
01121                     pt.rx() -= legendSize.width();
01122                 else if( relPos.alignment() & Qt::AlignHCenter )
01123                     pt.rx() -= 0.5 * legendSize.width();
01124 
01125                 if( relPos.alignment() & Qt::AlignBottom )
01126                     pt.ry() -= legendSize.height();
01127                 else if( relPos.alignment() & Qt::AlignVCenter )
01128                     pt.ry() -= 0.5 * legendSize.height();
01129             }
01130             //qDebug() << pt << endl;
01131             legend->move( static_cast<int>(pt.x()), static_cast<int>(pt.y()) );
01132         }
01133     }
01134 }
01135 
01136 
01137 void Chart::paintEvent( QPaintEvent* )
01138 {
01139     QPainter painter( this );
01140 
01141     if( size() != d->currentLayoutSize ){
01142         d->resizeLayout( size() );
01143         reLayoutFloatingLegends();
01144     }
01145 
01146     //FIXME(khz): Paint the background/frame too!
01147     //            (can we derive Chart from AreaWidget ??)
01148     d->paintAll( &painter );
01149 }
01150 
01151 void Chart::addHeaderFooter( HeaderFooter* headerFooter )
01152 {
01153     d->headerFooters.append( headerFooter );
01154     headerFooter->setParent( this );
01155     connect( headerFooter, SIGNAL( destroyedHeaderFooter( HeaderFooter* ) ),
01156              d, SLOT( slotUnregisterDestroyedHeaderFooter( HeaderFooter* ) ) );
01157     connect( headerFooter, SIGNAL( positionChanged( HeaderFooter* ) ),
01158              d, SLOT( slotRelayout() ) );
01159     d->slotRelayout();
01160 }
01161 
01162 void Chart::replaceHeaderFooter( HeaderFooter* headerFooter,
01163                                  HeaderFooter* oldHeaderFooter_ )
01164 {
01165     if( headerFooter && oldHeaderFooter_ != headerFooter ){
01166         HeaderFooter* oldHeaderFooter = oldHeaderFooter_;
01167         if( d->headerFooters.count() ){
01168             if( ! oldHeaderFooter ){
01169                 oldHeaderFooter =  d->headerFooters.first();
01170                 if( oldHeaderFooter == headerFooter )
01171                     return;
01172             }
01173             takeHeaderFooter( oldHeaderFooter );
01174         }
01175         delete oldHeaderFooter;
01176         addHeaderFooter( headerFooter );
01177     }
01178 }
01179 
01180 void Chart::takeHeaderFooter( HeaderFooter* headerFooter )
01181 {
01182     const int idx = d->headerFooters.indexOf( headerFooter );
01183     if( idx != -1 ){
01184         d->headerFooters.takeAt( idx );
01185         disconnect( headerFooter, SIGNAL( destroyedHeaderFooter( HeaderFooter* ) ),
01186                     d, SLOT( slotUnregisterDestroyedHeaderFooter( HeaderFooter* ) ) );
01187         headerFooter->setParent( 0 );
01188     }
01189     d->slotRelayout();
01190     // Need to emit the signal: In case somebody has connected the signal
01191     // to her own slot for e.g. calling update() on a widget containing the chart.
01192     emit propertiesChanged();
01193 }
01194 
01195 HeaderFooter* Chart::headerFooter()
01196 {
01197     if( d->headerFooters.isEmpty() ) {
01198         return 0;
01199     } else {
01200         return d->headerFooters.first();
01201     }
01202 }
01203 
01204 HeaderFooterList Chart::headerFooters()
01205 {
01206     return d->headerFooters;
01207 }
01208 
01209 void Chart::addLegend( Legend* legend )
01210 {
01211     if( ! legend ) return;
01212 
01213     //qDebug() << "adding the legend";
01214     d->legends.append( legend );
01215     legend->setParent( this );
01216 
01217     TextAttributes textAttrs( legend->textAttributes() );
01218 
01219     KDChart::Measure measure( textAttrs.fontSize() );
01220     measure.setRelativeMode( this, KDChartEnums::MeasureOrientationMinimum );
01221     measure.setValue( 20 );
01222     textAttrs.setFontSize( measure );
01223     legend->setTextAttributes( textAttrs );
01224 
01225     textAttrs = legend->titleTextAttributes();
01226     measure.setRelativeMode( this, KDChartEnums::MeasureOrientationMinimum );
01227     measure.setValue( 24 );
01228     textAttrs.setFontSize( measure );
01229 
01230     legend->setTitleTextAttributes( textAttrs );
01231 
01232     legend->setReferenceArea( this );
01233 
01234 /*
01235     future: Use relative sizes for the markers too!
01236 
01237     const uint nMA = Legend::datasetCount();
01238     for( uint iMA = 0; iMA < nMA; ++iMA ){
01239         MarkerAttributes ma( legend->markerAttributes( iMA ) );
01240         ma.setMarkerSize( ... )
01241         legend->setMarkerAttributes( iMA, ma )
01242     }
01243 */
01244 
01245     connect( legend, SIGNAL( destroyedLegend( Legend* ) ),
01246              d, SLOT( slotUnregisterDestroyedLegend( Legend* ) ) );
01247     connect( legend, SIGNAL( positionChanged( AbstractAreaWidget* ) ),
01248              d, SLOT( slotLayoutPlanes() ) ); //slotRelayout() ) );
01249     connect( legend, SIGNAL( propertiesChanged() ),
01250              this,   SIGNAL( propertiesChanged() ) );
01251     legend->setVisible( true );
01252     d->slotRelayout();
01253 }
01254 
01255 
01256 void Chart::replaceLegend( Legend* legend, Legend* oldLegend_ )
01257 {
01258     if( legend && oldLegend_ != legend ){
01259         Legend* oldLegend = oldLegend_;
01260         if( d->legends.count() ){
01261             if( ! oldLegend ){
01262                 oldLegend = d->legends.first();
01263                 if( oldLegend == legend )
01264                     return;
01265             }
01266             takeLegend( oldLegend );
01267         }
01268         delete oldLegend;
01269         addLegend( legend );
01270     }
01271 }
01272 
01273 void Chart::takeLegend( Legend* legend )
01274 {
01275     const int idx = d->legends.indexOf( legend );
01276     if( idx != -1 ){
01277         d->legends.takeAt( idx );
01278         disconnect( legend, SIGNAL( destroyedLegend( Legend* ) ),
01279                     d, SLOT( slotUnregisterDestroyedLegend( Legend* ) ) );
01280         disconnect( legend, SIGNAL( positionChanged( AbstractAreaWidget* ) ),
01281                     d, SLOT( slotLayoutPlanes() ) ); //slotRelayout() ) );
01282         disconnect( legend, SIGNAL( propertiesChanged() ),
01283                     this,   SIGNAL( propertiesChanged() ) );
01284         legend->setParent( 0 );
01285         legend->setVisible( false );
01286     }
01287     d->slotRelayout();
01288 
01289     // Need to emit the signal: In case somebody has connected the signal
01290     // to her own slot for e.g. calling update() on a widget containing the chart.
01291     // Note:
01292     // We do this ourselves in examples/DrawIntoPainter/mainwindow.cpp
01293     emit propertiesChanged();
01294 }
01295 
01296 Legend* Chart::legend()
01297 {
01298     if ( d->legends.isEmpty() )
01299     {
01300         return 0;
01301     } else {
01302         return d->legends.first();
01303     }
01304 }
01305 
01306 LegendList Chart::legends()
01307 {
01308     return d->legends;
01309 }
01310 
01311 
01312 void Chart::mousePressEvent( QMouseEvent* event )
01313 {
01314     const QPoint pos = mapFromGlobal( event->globalPos() );
01315 
01316     KDAB_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes )
01317     {
01318         if ( plane->geometry().contains( event->pos() ) )
01319         {
01320             if ( plane->diagrams().size() > 0 )
01321             {
01322                 QMouseEvent ev( QEvent::MouseButtonPress, pos, event->globalPos(),
01323                                 event->button(), event->buttons(),
01324                                 event->modifiers() );
01325 
01326                 plane->mousePressEvent( &ev );
01327                 d->mouseClickedPlanes.append( plane );
01328            }
01329        }
01330     }
01331 }
01332 
01333 /*
01334 // Unused code trying to use a push-model: This did not work
01335 // since we can not re-layout the planes each time when
01336 // Qt layouting is calling sizeHint()
01337 void Chart::Private::slotAdjustLeftRightColumnsForOverlappingLabels(
01338         CartesianAxis* axis, int leftOverlap, int rightOverlap)
01339 {
01340     const QLayout* axisLayout = axis ? axis->parentLayout() : 0;
01341 
01342     if( (! leftOverlap && ! rightOverlap) || ! axis || ! axisLayout->parent() )
01343         return;
01344 
01345     bool needUpdate = false;
01346     // access the planeLayout:
01347     QGridLayout* grid = qobject_cast<QGridLayout*>(axisLayout->parent());
01348     if( grid ){
01349         // find the index of the parent layout in the planeLayout:
01350         int idx = -1;
01351         for (int i = 0; i < grid->count(); ++i)
01352             if( grid->itemAt(i) == axisLayout )
01353                 idx = i;
01354         // set the min widths of the neighboring column:
01355         if( idx > -1 ){
01356             int row, column, rowSpan, columnSpan;
01357             grid->getItemPosition( idx, &row, &column, &rowSpan, &columnSpan );
01358             const int leftColumn = column-1;
01359             const int rightColumn = column+columnSpan;
01360             // find the left/right axes layouts
01361             QHBoxLayout* leftAxesLayout=0;
01362             QHBoxLayout* rightAxesLayout=0;
01363             for( int i = 0;
01364                  (!leftAxesLayout || !rightAxesLayout) && i < grid->count();
01365                  ++i )
01366             {
01367                 int r, c, rs, cs;
01368                 grid->getItemPosition( i, &r, &c, &rs, &cs );
01369                 if( c+cs-1 == leftColumn )
01370                     leftAxesLayout = dynamic_cast<QHBoxLayout*>(grid->itemAt(i));
01371                 if( c == rightColumn )
01372                     rightAxesLayout = dynamic_cast<QHBoxLayout*>(grid->itemAt(i));
01373             }
01374             if( leftAxesLayout ){
01375                 const int leftColumnMinWidth = leftOverlap;
01376                 QLayoutItem* item = leftAxesLayout->count()
01377                         ? dynamic_cast<QLayoutItem*>(leftAxesLayout->itemAt(leftAxesLayout->count()-1))
01378                     : 0;
01379                 QSpacerItem* spacer = dynamic_cast<QSpacerItem*>(item);
01380                 if( spacer ){
01381                     if( spacer->sizeHint().width() < leftColumnMinWidth ){
01382                         needUpdate = true;
01383                         spacer->changeSize(leftColumnMinWidth, 1);
01384                         qDebug() << "adjusted left spacer->sizeHint().width() to" << spacer->sizeHint().width();
01385                     }
01386                 }else{
01387                     AbstractAxis* axis = dynamic_cast<AbstractAxis*>(item);
01388                     if( !axis || axis->sizeHint().width() < leftColumnMinWidth ){
01389                         needUpdate = true;
01390                         leftAxesLayout->insertSpacing( -1, leftColumnMinWidth );
01391                         qDebug() << "adjusted column" << leftColumn << "min width to" << leftColumnMinWidth;
01392                     }
01393                 }
01394             }
01395             if( rightAxesLayout ){
01396                 const int rightColumnMinWidth = rightOverlap;
01397                 QLayoutItem* item = rightAxesLayout->count()
01398                         ? dynamic_cast<QLayoutItem*>(rightAxesLayout->itemAt(0))
01399                     : 0;
01400                 QSpacerItem* spacer = dynamic_cast<QSpacerItem*>(item);
01401                 if( spacer ){
01402                     if( spacer->sizeHint().width() < rightColumnMinWidth ){
01403                         needUpdate = true;
01404                         spacer->changeSize(rightColumnMinWidth, 1);
01405                         qDebug() << "adjusted right spacer->sizeHint().width() to" << spacer->sizeHint().width();
01406                     }
01407                 }else{
01408                     AbstractAxis* axis = dynamic_cast<AbstractAxis*>(item);
01409                     if( !axis || axis->sizeHint().width() < rightColumnMinWidth ){
01410                         needUpdate = true;
01411                         rightAxesLayout->insertSpacing( 0, rightColumnMinWidth );
01412                         qDebug() << "adjusted column" << rightColumn << "min width to" << rightColumnMinWidth;
01413                     }
01414                 }
01415             }
01416         }
01417     }
01418     if( needUpdate ){
01419         ;// do something ...?
01420     }
01421 }
01422 
01423 
01424 void Chart::Private::slotAdjustTopBottomRowsForOverlappingLabels(
01425         CartesianAxis* axis, int topOverlap, int bottomOverlap)
01426 {
01427     const QLayout* axisLayout = axis ? axis->parentLayout() : 0;
01428 
01429     if( (! topOverlap && ! bottomOverlap) || ! axisLayout || ! axisLayout->parent() )
01430         return;
01431 
01432     // access the planeLayout:
01433     QGridLayout* grid = qobject_cast<QGridLayout*>(axisLayout->parent());
01434     if( grid ){
01435             // find the index of the parent layout in the planeLayout:
01436         int idx = -1;
01437         for (int i = 0; i < grid->count(); ++i)
01438             if( grid->itemAt(i) == axisLayout )
01439                 idx = i;
01440             // set the min widths of the neighboring column:
01441         if( idx > -1 ){
01442             int row, column, rowSpan, columnSpan;
01443             grid->getItemPosition( idx, &row, &column, &rowSpan, &columnSpan );
01444             const int topRow = row-1;
01445             const int bottomRow = row+rowSpan;
01446                 // find the left/right axes layouts
01447             QVBoxLayout* topAxesLayout=0;
01448             QVBoxLayout* bottomAxesLayout=0;
01449             for( int i = 0;
01450                  (!topAxesLayout || !bottomAxesLayout) && i < grid->count();
01451                  ++i )
01452             {
01453                 int r, c, rs, cs;
01454                 grid->getItemPosition( i, &r, &c, &rs, &cs );
01455                 if( r+rs-1 == topRow )
01456                     topAxesLayout = dynamic_cast<QVBoxLayout*>(grid->itemAt(i));
01457                 if( r == bottomRow )
01458                     bottomAxesLayout = dynamic_cast<QVBoxLayout*>(grid->itemAt(i));
01459             }
01460             if( topAxesLayout ){
01461                 const int topRowMinWidth = topOverlap;
01462                 QLayoutItem* item = topAxesLayout->count()
01463                         ? dynamic_cast<QLayoutItem*>(topAxesLayout->itemAt(topAxesLayout->count()-1))
01464                     : 0;
01465                 QSpacerItem* spacer = dynamic_cast<QSpacerItem*>(item);
01466                 if( spacer ){
01467                     if( spacer->sizeHint().height() < topRowMinWidth ){
01468                         spacer->changeSize(1, topRowMinWidth);
01469                         qDebug() << "adjusted top spacer->sizeHint().height() to" << spacer->sizeHint().height();
01470                     }
01471                 }else{
01472                     AbstractAxis* axis = dynamic_cast<AbstractAxis*>(item);
01473                     if( !axis || axis->sizeHint().height() < topRowMinWidth ){
01474                         topAxesLayout->insertSpacing( -1, topRowMinWidth );
01475                         qDebug() << "adjusted row" << topRow << "min height to" << topRowMinWidth;
01476                     }
01477                 }
01478             }
01479             if( bottomAxesLayout ){
01480                 const int bottomRowMinWidth = bottomOverlap;
01481                 QLayoutItem* item = bottomAxesLayout->count()
01482                         ? dynamic_cast<QLayoutItem*>(bottomAxesLayout->itemAt(0))
01483                     : 0;
01484                 QSpacerItem* spacer = dynamic_cast<QSpacerItem*>(item);
01485                 if( spacer ){
01486                     if( spacer->sizeHint().height() < bottomRowMinWidth ){
01487                         spacer->changeSize(1, bottomRowMinWidth);
01488                         qDebug() << "adjusted bottom spacer->sizeHint().height() to" << spacer->sizeHint().height();
01489                     }
01490                 }else{
01491                     AbstractAxis* axis = dynamic_cast<AbstractAxis*>(item);
01492                     if( !axis || axis->sizeHint().height() < bottomRowMinWidth ){
01493                         bottomAxesLayout->insertSpacing( 0, bottomRowMinWidth );
01494                         qDebug() << "adjusted row" << bottomRow << "min height to" << bottomRowMinWidth;
01495                     }
01496                 }
01497             }
01498         }
01499     }
01500 }
01501 */
01502 
01503 void Chart::mouseDoubleClickEvent( QMouseEvent* event )
01504 {
01505     const QPoint pos = mapFromGlobal( event->globalPos() );
01506 
01507     KDAB_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes )
01508     {
01509         if ( plane->geometry().contains( event->pos() ) )
01510         {
01511             if ( plane->diagrams().size() > 0 )
01512             {
01513                 QMouseEvent ev( QEvent::MouseButtonPress, pos, event->globalPos(),
01514                                 event->button(), event->buttons(),
01515                                 event->modifiers() );
01516                 plane->mouseDoubleClickEvent( &ev );
01517             }
01518         }
01519     }
01520 }
01521 
01522 void Chart::mouseMoveEvent( QMouseEvent* event )
01523 {
01524     QSet< AbstractCoordinatePlane* > eventReceivers = QSet< AbstractCoordinatePlane* >::fromList( d->mouseClickedPlanes );
01525 
01526     KDAB_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes )
01527     {
01528         if( plane->geometry().contains( event->pos() ) )
01529         {
01530             if( plane->diagrams().size() > 0 )
01531             {
01532                 eventReceivers.insert( plane );
01533             }
01534         }
01535     }
01536 
01537     const QPoint pos = mapFromGlobal( event->globalPos() );
01538 
01539     KDAB_FOREACH( AbstractCoordinatePlane* plane, eventReceivers )
01540     {
01541         QMouseEvent ev( QEvent::MouseMove, pos, event->globalPos(),
01542                          event->button(), event->buttons(),
01543                          event->modifiers() );
01544         plane->mouseMoveEvent( &ev );
01545     }
01546 }
01547 
01548 void Chart::mouseReleaseEvent( QMouseEvent* event )
01549 {
01550     QSet< AbstractCoordinatePlane* > eventReceivers = QSet< AbstractCoordinatePlane* >::fromList( d->mouseClickedPlanes );
01551 
01552     KDAB_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes )
01553     {
01554         if ( plane->geometry().contains( event->pos() ) )
01555         {
01556             if( plane->diagrams().size() > 0 )
01557             {
01558                 eventReceivers.insert( plane );
01559             }
01560         }
01561     }
01562 
01563     const QPoint pos = mapFromGlobal( event->globalPos() );
01564 
01565     KDAB_FOREACH( AbstractCoordinatePlane* plane, eventReceivers )
01566     {
01567         QMouseEvent ev( QEvent::MouseButtonRelease, pos, event->globalPos(),
01568                          event->button(), event->buttons(),
01569                          event->modifiers() );
01570         plane->mouseReleaseEvent( &ev );
01571     }
01572 
01573     d->mouseClickedPlanes.clear();
01574 }
01575 
01576 bool Chart::event( QEvent* event )
01577 {
01578     switch( event->type() )
01579     {
01580     case QEvent::ToolTip:
01581     {
01582         const QHelpEvent* const helpEvent = static_cast< QHelpEvent* >( event );
01583         KDAB_FOREACH( const AbstractCoordinatePlane* const plane, d->coordinatePlanes )
01584         {
01585             KDAB_FOREACH( const AbstractDiagram* const diag, plane->diagrams() )
01586             {
01587                 const QModelIndex index = diag->indexAt( helpEvent->pos() );
01588                 const QVariant toolTip = index.data( Qt::ToolTipRole );
01589                 if( toolTip.isValid() )
01590                 {
01591                     QToolTip::showText( helpEvent->globalPos(), toolTip.toString() );
01592                     return true;
01593                 }
01594             }
01595         }
01596         // fall-through intended
01597     }
01598     default:
01599         return QWidget::event( event );
01600     }
01601 }

Generated on Thu Mar 4 23:19:10 2010 for KD Chart 2 by  doxygen 1.5.4