KDChartChart.cpp

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