KDChartChart.cpp

Go to the documentation of this file.
00001 /****************************************************************************
00002 ** Copyright (C) 2001-2011 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.txt 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 
00510         if(!planeLayout){
00511             PlaneInfo& refPi = pi;
00512             // if this plane is sharing an axis with another one, recursively check for the original plane and use
00513             // the grid of that as planeLayout.
00514             while ( !planeLayout && refPi.referencePlane) {
00515                 refPi = planeInfos[refPi.referencePlane];
00516                 planeLayout = refPi.gridLayout;
00517             }
00518             Q_ASSERT_X(planeLayout,
00519                        "Chart::Private::slotLayoutPlanes()",
00520                        "Invalid reference plane. Please Check whether the reference plane is added to the Chart or not" );
00521         } else {
00522              planesLayout->addLayout( planeLayout );
00523         }
00524 
00525         /* Put the plane in the center of the layout. If this is our own, that's
00526          * the middle of the layout, if we are sharing, it's a cell in the center
00527          * column of the shared grid. */
00528         planeLayoutItems << plane;
00529         plane->setParentLayout( planeLayout );
00530         planeLayout->addItem( plane, row, column, 1, 1, 0 );
00531         //qDebug() << "Chart slotLayoutPlanes() calls planeLayout->addItem("<< row << column << ")";
00532         planeLayout->setRowStretch(    row,    2 );
00533         planeLayout->setColumnStretch( column, 2 );
00534         KDAB_FOREACH( AbstractDiagram* abstractDiagram, plane->diagrams() )
00535         {
00536             AbstractCartesianDiagram* diagram =
00537                 dynamic_cast<AbstractCartesianDiagram*> ( abstractDiagram );
00538             //qDebug() << "--------------- diagram ???????????????????? -----------------";
00539             if( !diagram ) continue;  // FIXME polar ?
00540             //qDebug() << "--------------- diagram ! ! ! ! ! ! ! ! ! !  -----------------";
00541 
00542             if( pi.referencePlane != 0 )
00543             {
00544                 pi.topAxesLayout = planeInfos[ pi.referencePlane ].topAxesLayout;
00545                 pi.bottomAxesLayout = planeInfos[ pi.referencePlane ].bottomAxesLayout;
00546                 pi.leftAxesLayout = planeInfos[ pi.referencePlane ].leftAxesLayout;
00547                 pi.rightAxesLayout = planeInfos[ pi.referencePlane ].rightAxesLayout;
00548             }
00549 
00550             // collect all axes of a kind into sublayouts
00551             if( pi.topAxesLayout == 0 )
00552             {
00553                 pi.topAxesLayout = new QVBoxLayout;
00554 #if defined SET_ALL_MARGINS_TO_ZERO
00555                 pi.topAxesLayout->setMargin(0);
00556 #endif
00557                 pi.topAxesLayout->setObjectName( QString::fromLatin1( "topAxesLayout" ) );
00558             }
00559             if( pi.bottomAxesLayout == 0 )
00560             {
00561                 pi.bottomAxesLayout = new QVBoxLayout;
00562 #if defined SET_ALL_MARGINS_TO_ZERO
00563                 pi.bottomAxesLayout->setMargin(0);
00564 #endif
00565                 pi.bottomAxesLayout->setObjectName( QString::fromLatin1( "bottomAxesLayout" ) );
00566             }
00567             if( pi.leftAxesLayout == 0 )
00568             {
00569                 pi.leftAxesLayout = new QHBoxLayout;
00570 #if defined SET_ALL_MARGINS_TO_ZERO
00571                 pi.leftAxesLayout->setMargin(0);
00572 #endif
00573                 pi.leftAxesLayout->setObjectName( QString::fromLatin1( "leftAxesLayout" ) );
00574             }
00575             if( pi.rightAxesLayout == 0 )
00576             {
00577                 pi.rightAxesLayout = new QHBoxLayout;
00578 #if defined SET_ALL_MARGINS_TO_ZERO
00579                 pi.rightAxesLayout->setMargin(0);
00580 #endif
00581                 pi.rightAxesLayout->setObjectName( QString::fromLatin1( "rightAxesLayout" ) );
00582             }
00583 
00584             if( pi.referencePlane != 0 )
00585             {
00586                 planeInfos[ pi.referencePlane ].topAxesLayout = pi.topAxesLayout;
00587                 planeInfos[ pi.referencePlane ].bottomAxesLayout = pi.bottomAxesLayout;
00588                 planeInfos[ pi.referencePlane ].leftAxesLayout = pi.leftAxesLayout;
00589                 planeInfos[ pi.referencePlane ].rightAxesLayout = pi.rightAxesLayout;
00590             }
00591  
00592             //pi.leftAxesLayout->setSizeConstraint( QLayout::SetFixedSize );
00593             KDAB_FOREACH( CartesianAxis* axis, diagram->axes() ) {
00594                 if ( axisInfos.contains( axis ) ) continue; // already laid this one out
00595                 Q_ASSERT ( axis );
00596                 axis->setCachedSizeDirty();
00597                 //qDebug() << "--------------- axis added to planeLayoutItems  -----------------";
00598                 planeLayoutItems << axis;
00599                 /*
00600                 // Unused code trying to use a push-model: This did not work
00601                 // since we can not re-layout the planes each time when
00602                 // Qt layouting is calling sizeHint()
00603                 connect( axis, SIGNAL( needAdjustLeftRightColumnsForOverlappingLabels(
00604                 CartesianAxis*, int, int ) ),
00605                 this, SLOT( slotAdjustLeftRightColumnsForOverlappingLabels(
00606                 CartesianAxis*, int, int ) ) );
00607                 connect( axis, SIGNAL( needAdjustTopBottomRowsForOverlappingLabels(
00608                 CartesianAxis*, int, int ) ),
00609                 this, SLOT( slotAdjustTopBottomRowsForOverlappingLabels(
00610                 CartesianAxis*, int, int ) ) );
00611                 */
00612                 switch ( axis->position() )
00613                 {
00614                     case CartesianAxis::Top:
00615                         axis->setParentLayout( pi.topAxesLayout );
00616                         pi.topAxesLayout->addItem( axis );
00617                         break;
00618                     case CartesianAxis::Bottom:
00619                         axis->setParentLayout( pi.bottomAxesLayout );
00620                         pi.bottomAxesLayout->addItem( axis );
00621                         break;
00622                     case CartesianAxis::Left:
00623                         axis->setParentLayout( pi.leftAxesLayout );
00624                         pi.leftAxesLayout->addItem( axis );
00625                         break;
00626                     case CartesianAxis::Right:
00627                         axis->setParentLayout( pi.rightAxesLayout );
00628                         pi.rightAxesLayout->addItem( axis );
00629                         break;
00630                     default:
00631                         Q_ASSERT_X( false, "Chart::paintEvent",
00632                                 "unknown axis position" );
00633                         break;
00634                 };
00635                 axisInfos.insert( axis, AxisInfo() );
00636             }
00637             /* Put each stack of axes-layouts in the cells surrounding the
00638              * associated plane. We are laying out in the oder the planes
00639              * were added, and the first one gets to lay out shared axes.
00640              * Private axes go here as well, of course. */
00641             if ( !pi.topAxesLayout->parent() )
00642                 planeLayout->addLayout( pi.topAxesLayout,    row - 1, column );
00643             if ( !pi.bottomAxesLayout->parent() )
00644                 planeLayout->addLayout( pi.bottomAxesLayout, row + 1, column );
00645             if ( !pi.leftAxesLayout->parent() ){
00646                 planeLayout->addLayout( pi.leftAxesLayout,   row,     column - 1);
00647                 //planeLayout->setRowStretch(    row, 0 );
00648                 //planeLayout->setColumnStretch( 0,   0 );
00649             }
00650             if ( !pi.rightAxesLayout->parent() )
00651                 planeLayout->addLayout( pi.rightAxesLayout,  row,     column + 1);
00652         }
00653 
00654         // use up to four auto-spacer items in the corners around the diagrams:
00655 #define ADD_AUTO_SPACER_IF_NEEDED( \
00656         spacerRow, spacerColumn, hLayoutIsAtTop, hLayout, vLayoutIsAtLeft, vLayout ) \
00657         { \
00658             if( hLayout || vLayout ) { \
00659                 AutoSpacerLayoutItem * spacer \
00660                 = new AutoSpacerLayoutItem( hLayoutIsAtTop, hLayout, vLayoutIsAtLeft, vLayout ); \
00661                 planeLayout->addItem( spacer, spacerRow, spacerColumn, 1, 1 ); \
00662                 spacer->setParentLayout( planeLayout ); \
00663                 planeLayoutItems << spacer; \
00664             } \
00665         }
00666         ADD_AUTO_SPACER_IF_NEEDED( row-1, column-1, false, pi.leftAxesLayout,  false, pi.topAxesLayout )
00667             ADD_AUTO_SPACER_IF_NEEDED( row+1, column-1, true,  pi.leftAxesLayout,  false,  pi.bottomAxesLayout )
00668             ADD_AUTO_SPACER_IF_NEEDED( row-1, column+1, false, pi.rightAxesLayout, true, pi.topAxesLayout )
00669             ADD_AUTO_SPACER_IF_NEEDED( row+1, column+1, true,  pi.rightAxesLayout, true,  pi.bottomAxesLayout )
00670     }
00671     // re-add our grid(s) to the chart's layout
00672     if ( dataAndLegendLayout ){
00673         dataAndLegendLayout->addLayout( planesLayout, 1, 1 );
00674         dataAndLegendLayout->setRowStretch(    1, 1000 );
00675         dataAndLegendLayout->setColumnStretch( 1, 1000 );
00676     }
00677 
00678     slotRelayout();
00679     //qDebug() << "KDChart::Chart finished layouting the planes.";
00680 }
00681 
00682 void Chart::Private::createLayouts( QWidget* w )
00683 {
00684     KDAB_FOREACH( KDChart::TextArea* textLayoutItem, textLayoutItems ) {
00685         textLayoutItem->removeFromParentLayout();
00686     }
00687     textLayoutItems.clear();
00688 
00689     KDAB_FOREACH( KDChart::AbstractArea* layoutItem, layoutItems ) {
00690         layoutItem->removeFromParentLayout();
00691     }
00692     layoutItems.clear();
00693 
00694     removeDummyHeaderFooters();
00695 
00696     // layout for the planes is handled separately, so we don't want to delete it here
00697     if ( dataAndLegendLayout) {
00698         dataAndLegendLayout->removeItem( planesLayout );
00699         planesLayout->setParent( 0 );
00700     }
00701     // nuke the old bunch
00702     delete layout;
00703 
00704     // The HBox d->layout provides the left and right global leadings
00705     layout = new QHBoxLayout( w );
00706     // TESTING(khz): set the margin of all of the layouts to Zero
00707 #if defined SET_ALL_MARGINS_TO_ZERO
00708     layout->setMargin(0);
00709 #endif
00710     layout->setObjectName( QString::fromLatin1( "Chart::Private::layout" ) );
00711     layout->addSpacing( globalLeadingLeft );
00712 
00713     // The vLayout provides top and bottom global leadings and lays
00714     // out headers/footers and the data area.
00715     vLayout = new QVBoxLayout();
00716     // TESTING(khz): set the margin of all of the layouts to Zero
00717 #if defined SET_ALL_MARGINS_TO_ZERO
00718     vLayout->setMargin(0);
00719 #endif
00720     vLayout->setObjectName( QString::fromLatin1( "vLayout" ) );
00721     layout->addLayout( vLayout, 1000 );
00722     layout->addSpacing( globalLeadingRight );
00723 
00724 
00725 
00726     // 1. the gap above the top edge of the headers area
00727     vLayout->addSpacing( globalLeadingTop );
00728     // 2. the header(s) area
00729     headerLayout = new QGridLayout();
00730     // TESTING(khz): set the margin of all of the layouts to Zero
00731 #if defined SET_ALL_MARGINS_TO_ZERO
00732     headerLayout->setMargin(0);
00733 #endif
00734     vLayout->addLayout( headerLayout );
00735     // 3. the area containing coordinate plane(s), axes, legend(s)
00736     dataAndLegendLayout = new QGridLayout();
00737     // TESTING(khz): set the margin of all of the layouts to Zero
00738 #if defined SET_ALL_MARGINS_TO_ZERO
00739     dataAndLegendLayout->setMargin(0);
00740 #endif
00741     dataAndLegendLayout->setObjectName( QString::fromLatin1( "dataAndLegendLayout" ) );
00742     vLayout->addLayout( dataAndLegendLayout, 1000 );
00743     // 4. the footer(s) area
00744     footerLayout = new QGridLayout();
00745     // TESTING(khz): set the margin of all of the layouts to Zero
00746 #if defined SET_ALL_MARGINS_TO_ZERO
00747     footerLayout->setMargin(0);
00748 #endif
00749     footerLayout->setObjectName( QString::fromLatin1( "footerLayout" ) );
00750     vLayout->addLayout( footerLayout );
00751 
00752     // 5. Prepare the header / footer layout cells:
00753     //    Each of the 9 header cells (the 9 footer cells)
00754     //    contain their own QVBoxLayout
00755     //    since there can be more than one header (footer) per cell.
00756     static const Qt::Alignment hdFtAlignments[3][3] = {
00757         { Qt::AlignTop     | Qt::AlignLeft,  Qt::AlignTop     | Qt::AlignHCenter,  Qt::AlignTop     | Qt::AlignRight },
00758         { Qt::AlignVCenter | Qt::AlignLeft,  Qt::AlignVCenter | Qt::AlignHCenter,  Qt::AlignVCenter | Qt::AlignRight },
00759         { Qt::AlignBottom  | Qt::AlignLeft,  Qt::AlignBottom  | Qt::AlignHCenter,  Qt::AlignBottom  | Qt::AlignRight }
00760     };
00761     for ( int row = 0; row < 3; ++row )
00762     {
00763         for ( int column = 0; column < 3; ++ column )
00764         {
00765             QVBoxLayout* innerHdLayout = new QVBoxLayout();
00766             QVBoxLayout* innerFtLayout = new QVBoxLayout();
00767             innerHdFtLayouts[0][row][column] = innerHdLayout;
00768             innerHdFtLayouts[1][row][column] = innerFtLayout;
00769 #if defined SET_ALL_MARGINS_TO_ZERO
00770             innerHdLayout->setMargin(0);
00771             innerFtLayout->setMargin(0);
00772 #endif
00773             const Qt::Alignment align = hdFtAlignments[row][column];
00774             innerHdLayout->setAlignment( align );
00775             innerFtLayout->setAlignment( align );
00776             headerLayout->addLayout( innerHdLayout, row, column, align );
00777             footerLayout->addLayout( innerFtLayout, row, column, align );
00778         }
00779     }
00780 
00781     // 6. the gap below the bottom edge of the headers area
00782     vLayout->addSpacing( globalLeadingBottom );
00783 
00784     // the data+axes area
00785     dataAndLegendLayout->addLayout( planesLayout, 1, 1 );
00786     dataAndLegendLayout->setRowStretch(    1, 1 );
00787     dataAndLegendLayout->setColumnStretch( 1, 1 );
00788 
00789     //qDebug() << "w->rect()" << w->rect();
00790 }
00791 
00792 void Chart::Private::slotRelayout()
00793 {
00794     //qDebug() << "Chart relayouting started.";
00795     createLayouts( chart );
00796 
00797     layoutHeadersAndFooters();
00798     layoutLegends();
00799 
00800     // This triggers the qlayout, see QBoxLayout::setGeometry
00801     // The geometry is not necessarily w->rect(), when using paint(), this is why
00802     // we don't call layout->activate().
00803     const QRect geo( QRect( 0, 0, currentLayoutSize.width(), currentLayoutSize.height() ) );
00804     if( geo.isValid() && geo != layout->geometry() ){
00805         //qDebug() << "Chart slotRelayout() adjusting geometry to" << geo;
00806         //if( coordinatePlanes.count() )
00807         //    qDebug() << "           plane geo before" << coordinatePlanes.first()->geometry();
00808         layout->setGeometry( geo );
00809         //if( coordinatePlanes.count() ) {
00810         //    qDebug() << "           plane geo after " << coordinatePlanes.first()->geometry();
00811         //}
00812     }
00813 
00814     // Adapt diagram drawing to the new size
00815     KDAB_FOREACH (AbstractCoordinatePlane* plane, coordinatePlanes ) {
00816         plane->layoutDiagrams();
00817     }
00818     //qDebug() << "Chart relayouting done.";
00819 }
00820 
00821 // Called when the size of the chart changes.
00822 // So in theory, we only need to adjust geometries.
00823 // But this also needs to make sure that everything is in place for the first painting.
00824 void Chart::Private::resizeLayout( const QSize& size )
00825 {
00826     currentLayoutSize = size;
00827     //qDebug() << "Chart::resizeLayout(" << currentLayoutSize << ")";
00828 
00829     /*
00830     // We need to make sure that the legend's layouts are populated,
00831     // so that setGeometry gets proper sizeHints from them and resizes them properly.
00832     KDAB_FOREACH( Legend *legend, legends ) {
00833     // This forceRebuild will see a wrong areaGeometry, but I don't care about geometries yet,
00834     // only about the fact that legends should have their contents populated.
00835     // -> it would be better to dissociate "building contents" and "resizing" in Legend...
00836 
00837     //        legend->forceRebuild();
00838 
00839     legend->resizeLayout( size );
00840     }
00841     */
00842     slotLayoutPlanes(); // includes slotRelayout
00843 
00844     //qDebug() << "Chart::resizeLayout done";
00845 }
00846 
00847 
00848 void Chart::Private::paintAll( QPainter* painter )
00849 {
00850     QRect rect( QPoint(0, 0), currentLayoutSize );
00851 
00852     //qDebug() << this<<"::paintAll() uses layout size" << currentLayoutSize;
00853 
00854     // Paint the background (if any)
00855     KDChart::AbstractAreaBase::paintBackgroundAttributes(
00856             *painter, rect, backgroundAttributes );
00857     // Paint the frame (if any)
00858     KDChart::AbstractAreaBase::paintFrameAttributes(
00859             *painter, rect, frameAttributes );
00860 
00861     chart->reLayoutFloatingLegends();
00862 
00863     KDAB_FOREACH( KDChart::AbstractArea* layoutItem, layoutItems ) {
00864         layoutItem->paintAll( *painter );
00865     }
00866     KDAB_FOREACH( KDChart::AbstractLayoutItem* planeLayoutItem, planeLayoutItems ) {
00867         planeLayoutItem->paintAll( *painter );
00868     }
00869     KDAB_FOREACH( KDChart::TextArea* textLayoutItem, textLayoutItems ) {
00870         textLayoutItem->paintAll( *painter );
00871     }
00872 }
00873 
00874 // ******** Chart interface implementation ***********
00875 
00876 Chart::Chart ( QWidget* parent )
00877     : QWidget ( parent )
00878     , _d( new Private( this ) )
00879 {
00880 #if defined KDAB_EVAL
00881     EvalDialog::checkEvalLicense( "KD Chart" );
00882 #endif
00883 
00884     FrameAttributes frameAttrs;
00885 // no frame per default...
00886 //    frameAttrs.setVisible( true );
00887     frameAttrs.setPen( QPen( Qt::black ) );
00888     frameAttrs.setPadding( 1 );
00889     setFrameAttributes( frameAttrs );
00890 
00891     addCoordinatePlane( new CartesianCoordinatePlane ( this ) );
00892 }
00893 
00894 Chart::~Chart()
00895 {
00896     delete _d;
00897 }
00898 
00899 #define d d_func()
00900 
00901 void Chart::setFrameAttributes( const FrameAttributes &a )
00902 {
00903     d->frameAttributes = a;
00904 }
00905 
00906 FrameAttributes Chart::frameAttributes() const
00907 {
00908     return d->frameAttributes;
00909 }
00910 
00911 void Chart::setBackgroundAttributes( const BackgroundAttributes &a )
00912 {
00913     d->backgroundAttributes = a;
00914 }
00915 
00916 BackgroundAttributes Chart::backgroundAttributes() const
00917 {
00918     return d->backgroundAttributes;
00919 }
00920 
00921 //TODO KDChart 3.0; change QLayout into QBoxLayout::Direction
00922 void Chart::setCoordinatePlaneLayout( QLayout * layout )
00923 {
00924     delete d->planesLayout;
00925     d->planesLayout = dynamic_cast<QBoxLayout*>( layout );
00926     d->slotLayoutPlanes();
00927 }
00928 
00929 QLayout* Chart::coordinatePlaneLayout()
00930 {
00931     return d->planesLayout;
00932 }
00933 
00934 AbstractCoordinatePlane* Chart::coordinatePlane()
00935 {
00936     if ( d->coordinatePlanes.isEmpty() )
00937     {
00938         qWarning() << "Chart::coordinatePlane: warning: no coordinate plane defined.";
00939         return 0;
00940     } else {
00941         return d->coordinatePlanes.first();
00942     }
00943 }
00944 
00945 CoordinatePlaneList Chart::coordinatePlanes()
00946 {
00947     return d->coordinatePlanes;
00948 }
00949 
00950 void Chart::addCoordinatePlane( AbstractCoordinatePlane* plane )
00951 {
00952     connect( plane, SIGNAL( destroyedCoordinatePlane( AbstractCoordinatePlane* ) ),
00953              d,   SLOT( slotUnregisterDestroyedPlane( AbstractCoordinatePlane* ) ) );
00954     connect( plane, SIGNAL( needUpdate() ),       this,   SLOT( update() ) );
00955     connect( plane, SIGNAL( needRelayout() ),     d,      SLOT( slotRelayout() ) ) ;
00956     connect( plane, SIGNAL( needLayoutPlanes() ), d,      SLOT( slotLayoutPlanes() ) ) ;
00957     connect( plane, SIGNAL( propertiesChanged() ),this, SIGNAL( propertiesChanged() ) );
00958     d->coordinatePlanes.append( plane );
00959     plane->setParent( this );
00960     d->slotLayoutPlanes();
00961 }
00962 
00963 void Chart::replaceCoordinatePlane( AbstractCoordinatePlane* plane,
00964                                     AbstractCoordinatePlane* oldPlane_ )
00965 {
00966     if( plane && oldPlane_ != plane ){
00967         AbstractCoordinatePlane* oldPlane = oldPlane_;
00968         if( d->coordinatePlanes.count() ){
00969             if( ! oldPlane ){
00970                 oldPlane = d->coordinatePlanes.first();
00971                 if( oldPlane == plane )
00972                     return;
00973             }
00974             takeCoordinatePlane( oldPlane );
00975         }
00976         delete oldPlane;
00977         addCoordinatePlane( plane );
00978     }
00979 }
00980 
00981 void Chart::takeCoordinatePlane( AbstractCoordinatePlane* plane )
00982 {
00983     const int idx = d->coordinatePlanes.indexOf( plane );
00984     if( idx != -1 ){
00985         d->coordinatePlanes.takeAt( idx );
00986         disconnect( plane, SIGNAL( destroyedCoordinatePlane( AbstractCoordinatePlane* ) ),
00987                     d, SLOT( slotUnregisterDestroyedPlane( AbstractCoordinatePlane* ) ) );
00988         plane->removeFromParentLayout();
00989         plane->setParent( 0 );
00990         d->mouseClickedPlanes.removeAll(plane);
00991     }
00992     d->slotLayoutPlanes();
00993     // Need to emit the signal: In case somebody has connected the signal
00994     // to her own slot for e.g. calling update() on a widget containing the chart.
00995     emit propertiesChanged();
00996 }
00997 
00998 void Chart::setGlobalLeading( int left, int top, int right, int bottom )
00999 {
01000     setGlobalLeadingLeft( left );
01001     setGlobalLeadingTop( top );
01002     setGlobalLeadingRight( right );
01003     setGlobalLeadingBottom( bottom );
01004     d->slotRelayout();
01005 }
01006 
01007 void Chart::setGlobalLeadingLeft( int leading )
01008 {
01009     d->globalLeadingLeft = leading;
01010     d->slotRelayout();
01011 }
01012 
01013 int Chart::globalLeadingLeft() const
01014 {
01015     return d->globalLeadingLeft;
01016 }
01017 
01018 void Chart::setGlobalLeadingTop( int leading )
01019 {
01020     d->globalLeadingTop = leading;
01021     d->slotRelayout();
01022 }
01023 
01024 int Chart::globalLeadingTop() const
01025 {
01026     return d->globalLeadingTop;
01027 }
01028 
01029 void Chart::setGlobalLeadingRight( int leading )
01030 {
01031     d->globalLeadingRight = leading;
01032     d->slotRelayout();
01033 }
01034 
01035 int Chart::globalLeadingRight() const
01036 {
01037     return d->globalLeadingRight;
01038 }
01039 
01040 void Chart::setGlobalLeadingBottom( int leading )
01041 {
01042     d->globalLeadingBottom = leading;
01043     d->slotRelayout();
01044 }
01045 
01046 int Chart::globalLeadingBottom() const
01047 {
01048     return d->globalLeadingBottom;
01049 }
01050 
01051 void Chart::paint( QPainter* painter, const QRect& target )
01052 {
01053     if( target.isEmpty() || !painter ) return;
01054     //qDebug() << "Chart::paint( ..," << target << ")";
01055 
01056     QPaintDevice* prevDevice = GlobalMeasureScaling::paintDevice();
01057     GlobalMeasureScaling::setPaintDevice( painter->device() );
01058 
01059     // Output on a widget
01060     if( dynamic_cast< QWidget* >( painter->device() ) != 0 )
01061     {
01062         GlobalMeasureScaling::setFactors(
01063                 static_cast< qreal >( target.width() ) /
01064                 static_cast< qreal >( geometry().size().width() ),
01065                 static_cast< qreal >( target.height() ) /
01066                 static_cast< qreal >( geometry().size().height() ) );
01067     }
01068     // Output onto a QPixmap 
01069     else
01070     {
01071         PrintingParameters::setScaleFactor( static_cast< qreal >( painter->device()->logicalDpiX() ) / static_cast< qreal >( logicalDpiX() ) );
01072 
01073         const qreal resX = static_cast< qreal >( logicalDpiX() ) / static_cast< qreal >( painter->device()->logicalDpiX() );
01074         const qreal resY = static_cast< qreal >( logicalDpiY() ) / static_cast< qreal >( painter->device()->logicalDpiY() );
01075 
01076         GlobalMeasureScaling::setFactors(
01077                 static_cast< qreal >( target.width() ) /
01078                 static_cast< qreal >( geometry().size().width() ) * resX,
01079                 static_cast< qreal >( target.height() ) /
01080                 static_cast< qreal >( geometry().size().height() ) * resY );
01081     }
01082 
01083 
01084     if( target.size() != d->currentLayoutSize ){
01085         d->resizeLayout( target.size() );
01086     }
01087     const QPoint translation = target.topLeft();
01088     painter->translate( translation );
01089 
01090     d->paintAll( painter );
01091 
01092     // for debugging:
01093     //painter->setPen(QPen(Qt::blue, 8));
01094     //painter->drawRect(target.adjusted(12,12,-12,-12));
01095 
01096     KDAB_FOREACH( Legend *legend, d->legends ) {
01097         const bool hidden = legend->isHidden() && legend->testAttribute(Qt::WA_WState_ExplicitShowHide);
01098         if ( !hidden ) {
01099             //qDebug() << "painting legend at " << legend->geometry();
01100             legend->paintIntoRect( *painter, legend->geometry() );
01101             //testing:
01102             //legend->paintIntoRect( *painter, legend->geometry().adjusted(-100,0,-100,0) );
01103         }
01104     }
01105 
01106     painter->translate( -translation.x(), -translation.y() );
01107 
01108     GlobalMeasureScaling::instance()->resetFactors();
01109     PrintingParameters::resetScaleFactor();
01110     GlobalMeasureScaling::setPaintDevice( prevDevice );
01111 
01112     //qDebug() << "KDChart::Chart::paint() done.\n";
01113 }
01114 
01115 void Chart::resizeEvent ( QResizeEvent * )
01116 {
01117     d->resizeLayout( size() );
01118     KDAB_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes ){
01119         plane->setGridNeedsRecalculate();
01120     }
01121     reLayoutFloatingLegends();
01122 }
01123 
01124 
01125 void Chart::reLayoutFloatingLegends()
01126 {
01127     KDAB_FOREACH( Legend *legend, d->legends ) {
01128         const bool hidden = legend->isHidden() && legend->testAttribute(Qt::WA_WState_ExplicitShowHide);
01129         if ( legend->position().isFloating() && !hidden ){
01130             // resize the legend
01131             const QSize legendSize( legend->sizeHint() );
01132             legend->setGeometry( QRect( legend->geometry().topLeft(), legendSize ) );
01133             // find the legends corner point (reference point plus any paddings)
01134             const RelativePosition relPos( legend->floatingPosition() );
01135             QPointF pt( relPos.calculatedPoint( size() ) );
01136             //qDebug() << pt;
01137             // calculate the legend's top left point
01138             const Qt::Alignment alignTopLeft = Qt::AlignBottom | Qt::AlignLeft;
01139             if( (relPos.alignment() & alignTopLeft) != alignTopLeft ){
01140                 if( relPos.alignment() & Qt::AlignRight )
01141                     pt.rx() -= legendSize.width();
01142                 else if( relPos.alignment() & Qt::AlignHCenter )
01143                     pt.rx() -= 0.5 * legendSize.width();
01144 
01145                 if( relPos.alignment() & Qt::AlignBottom )
01146                     pt.ry() -= legendSize.height();
01147                 else if( relPos.alignment() & Qt::AlignVCenter )
01148                     pt.ry() -= 0.5 * legendSize.height();
01149             }
01150             //qDebug() << pt << endl;
01151             legend->move( static_cast<int>(pt.x()), static_cast<int>(pt.y()) );
01152         }
01153     }
01154 }
01155 
01156 
01157 void Chart::paintEvent( QPaintEvent* )
01158 {
01159     QPainter painter( this );
01160 
01161     if( size() != d->currentLayoutSize ){
01162         d->resizeLayout( size() );
01163         reLayoutFloatingLegends();
01164     }
01165 
01166     //FIXME(khz): Paint the background/frame too!
01167     //            (can we derive Chart from AreaWidget ??)
01168     d->paintAll( &painter );
01169 }
01170 
01171 void Chart::addHeaderFooter( HeaderFooter* headerFooter )
01172 {
01173     d->headerFooters.append( headerFooter );
01174     headerFooter->setParent( this );
01175     connect( headerFooter, SIGNAL( destroyedHeaderFooter( HeaderFooter* ) ),
01176              d, SLOT( slotUnregisterDestroyedHeaderFooter( HeaderFooter* ) ) );
01177     connect( headerFooter, SIGNAL( positionChanged( HeaderFooter* ) ),
01178              d, SLOT( slotRelayout() ) );
01179     d->slotRelayout();
01180 }
01181 
01182 void Chart::replaceHeaderFooter( HeaderFooter* headerFooter,
01183                                  HeaderFooter* oldHeaderFooter_ )
01184 {
01185     if( headerFooter && oldHeaderFooter_ != headerFooter ){
01186         HeaderFooter* oldHeaderFooter = oldHeaderFooter_;
01187         if( d->headerFooters.count() ){
01188             if( ! oldHeaderFooter ){
01189                 oldHeaderFooter =  d->headerFooters.first();
01190                 if( oldHeaderFooter == headerFooter )
01191                     return;
01192             }
01193             takeHeaderFooter( oldHeaderFooter );
01194         }
01195         delete oldHeaderFooter;
01196         addHeaderFooter( headerFooter );
01197     }
01198 }
01199 
01200 void Chart::takeHeaderFooter( HeaderFooter* headerFooter )
01201 {
01202     const int idx = d->headerFooters.indexOf( headerFooter );
01203     if( idx != -1 ){
01204         d->headerFooters.takeAt( idx );
01205         disconnect( headerFooter, SIGNAL( destroyedHeaderFooter( HeaderFooter* ) ),
01206                     d, SLOT( slotUnregisterDestroyedHeaderFooter( HeaderFooter* ) ) );
01207         headerFooter->setParent( 0 );
01208     }
01209     d->slotRelayout();
01210     // Need to emit the signal: In case somebody has connected the signal
01211     // to her own slot for e.g. calling update() on a widget containing the chart.
01212     emit propertiesChanged();
01213 }
01214 
01215 HeaderFooter* Chart::headerFooter()
01216 {
01217     if( d->headerFooters.isEmpty() ) {
01218         return 0;
01219     } else {
01220         return d->headerFooters.first();
01221     }
01222 }
01223 
01224 HeaderFooterList Chart::headerFooters()
01225 {
01226     return d->headerFooters;
01227 }
01228 
01229 void Chart::addLegend( Legend* legend )
01230 {
01231     if( ! legend ) return;
01232 
01233     //qDebug() << "adding the legend";
01234     d->legends.append( legend );
01235     legend->setParent( this );
01236 
01237     TextAttributes textAttrs( legend->textAttributes() );
01238 
01239     KDChart::Measure measure( textAttrs.fontSize() );
01240     measure.setRelativeMode( this, KDChartEnums::MeasureOrientationMinimum );
01241     measure.setValue( 20 );
01242     textAttrs.setFontSize( measure );
01243     legend->setTextAttributes( textAttrs );
01244 
01245     textAttrs = legend->titleTextAttributes();
01246     measure.setRelativeMode( this, KDChartEnums::MeasureOrientationMinimum );
01247     measure.setValue( 24 );
01248     textAttrs.setFontSize( measure );
01249 
01250     legend->setTitleTextAttributes( textAttrs );
01251 
01252     legend->setReferenceArea( this );
01253 
01254 /*
01255     future: Use relative sizes for the markers too!
01256 
01257     const uint nMA = Legend::datasetCount();
01258     for( uint iMA = 0; iMA < nMA; ++iMA ){
01259         MarkerAttributes ma( legend->markerAttributes( iMA ) );
01260         ma.setMarkerSize( ... )
01261         legend->setMarkerAttributes( iMA, ma )
01262     }
01263 */
01264 
01265     connect( legend, SIGNAL( destroyedLegend( Legend* ) ),
01266              d, SLOT( slotUnregisterDestroyedLegend( Legend* ) ) );
01267     connect( legend, SIGNAL( positionChanged( AbstractAreaWidget* ) ),
01268              d, SLOT( slotLayoutPlanes() ) ); //slotRelayout() ) );
01269     connect( legend, SIGNAL( propertiesChanged() ),
01270              this,   SIGNAL( propertiesChanged() ) );
01271     legend->setVisible( true );
01272     d->slotRelayout();
01273 }
01274 
01275 
01276 void Chart::replaceLegend( Legend* legend, Legend* oldLegend_ )
01277 {
01278     if( legend && oldLegend_ != legend ){
01279         Legend* oldLegend = oldLegend_;
01280         if( d->legends.count() ){
01281             if( ! oldLegend ){
01282                 oldLegend = d->legends.first();
01283                 if( oldLegend == legend )
01284                     return;
01285             }
01286             takeLegend( oldLegend );
01287         }
01288         delete oldLegend;
01289         addLegend( legend );
01290     }
01291 }
01292 
01293 void Chart::takeLegend( Legend* legend )
01294 {
01295     const int idx = d->legends.indexOf( legend );
01296     if( idx != -1 ){
01297         d->legends.takeAt( idx );
01298         disconnect( legend, SIGNAL( destroyedLegend( Legend* ) ),
01299                     d, SLOT( slotUnregisterDestroyedLegend( Legend* ) ) );
01300         disconnect( legend, SIGNAL( positionChanged( AbstractAreaWidget* ) ),
01301                     d, SLOT( slotLayoutPlanes() ) ); //slotRelayout() ) );
01302         disconnect( legend, SIGNAL( propertiesChanged() ),
01303                     this,   SIGNAL( propertiesChanged() ) );
01304         legend->setParent( 0 );
01305         legend->setVisible( false );
01306     }
01307     d->slotRelayout();
01308 
01309     // Need to emit the signal: In case somebody has connected the signal
01310     // to her own slot for e.g. calling update() on a widget containing the chart.
01311     // Note:
01312     // We do this ourselves in examples/DrawIntoPainter/mainwindow.cpp
01313     emit propertiesChanged();
01314 }
01315 
01316 Legend* Chart::legend()
01317 {
01318     if ( d->legends.isEmpty() )
01319     {
01320         return 0;
01321     } else {
01322         return d->legends.first();
01323     }
01324 }
01325 
01326 LegendList Chart::legends()
01327 {
01328     return d->legends;
01329 }
01330 
01331 
01332 void Chart::mousePressEvent( QMouseEvent* event )
01333 {
01334     const QPoint pos = mapFromGlobal( event->globalPos() );
01335 
01336     KDAB_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes )
01337     {
01338         if ( plane->geometry().contains( event->pos() ) )
01339         {
01340             if ( plane->diagrams().size() > 0 )
01341             {
01342                 QMouseEvent ev( QEvent::MouseButtonPress, pos, event->globalPos(),
01343                                 event->button(), event->buttons(),
01344                                 event->modifiers() );
01345 
01346                 plane->mousePressEvent( &ev );
01347                 d->mouseClickedPlanes.append( plane );
01348            }
01349        }
01350     }
01351 }
01352 
01353 /*
01354 // Unused code trying to use a push-model: This did not work
01355 // since we can not re-layout the planes each time when
01356 // Qt layouting is calling sizeHint()
01357 void Chart::Private::slotAdjustLeftRightColumnsForOverlappingLabels(
01358         CartesianAxis* axis, int leftOverlap, int rightOverlap)
01359 {
01360     const QLayout* axisLayout = axis ? axis->parentLayout() : 0;
01361 
01362     if( (! leftOverlap && ! rightOverlap) || ! axis || ! axisLayout->parent() )
01363         return;
01364 
01365     bool needUpdate = false;
01366     // access the planeLayout:
01367     QGridLayout* grid = qobject_cast<QGridLayout*>(axisLayout->parent());
01368     if( grid ){
01369         // find the index of the parent layout in the planeLayout:
01370         int idx = -1;
01371         for (int i = 0; i < grid->count(); ++i)
01372             if( grid->itemAt(i) == axisLayout )
01373                 idx = i;
01374         // set the min widths of the neighboring column:
01375         if( idx > -1 ){
01376             int row, column, rowSpan, columnSpan;
01377             grid->getItemPosition( idx, &row, &column, &rowSpan, &columnSpan );
01378             const int leftColumn = column-1;
01379             const int rightColumn = column+columnSpan;
01380             // find the left/right axes layouts
01381             QHBoxLayout* leftAxesLayout=0;
01382             QHBoxLayout* rightAxesLayout=0;
01383             for( int i = 0;
01384                  (!leftAxesLayout || !rightAxesLayout) && i < grid->count();
01385                  ++i )
01386             {
01387                 int r, c, rs, cs;
01388                 grid->getItemPosition( i, &r, &c, &rs, &cs );
01389                 if( c+cs-1 == leftColumn )
01390                     leftAxesLayout = dynamic_cast<QHBoxLayout*>(grid->itemAt(i));
01391                 if( c == rightColumn )
01392                     rightAxesLayout = dynamic_cast<QHBoxLayout*>(grid->itemAt(i));
01393             }
01394             if( leftAxesLayout ){
01395                 const int leftColumnMinWidth = leftOverlap;
01396                 QLayoutItem* item = leftAxesLayout->count()
01397                         ? dynamic_cast<QLayoutItem*>(leftAxesLayout->itemAt(leftAxesLayout->count()-1))
01398                     : 0;
01399                 QSpacerItem* spacer = dynamic_cast<QSpacerItem*>(item);
01400                 if( spacer ){
01401                     if( spacer->sizeHint().width() < leftColumnMinWidth ){
01402                         needUpdate = true;
01403                         spacer->changeSize(leftColumnMinWidth, 1);
01404                         qDebug() << "adjusted left spacer->sizeHint().width() to" << spacer->sizeHint().width();
01405                     }
01406                 }else{
01407                     AbstractAxis* axis = dynamic_cast<AbstractAxis*>(item);
01408                     if( !axis || axis->sizeHint().width() < leftColumnMinWidth ){
01409                         needUpdate = true;
01410                         leftAxesLayout->insertSpacing( -1, leftColumnMinWidth );
01411                         qDebug() << "adjusted column" << leftColumn << "min width to" << leftColumnMinWidth;
01412                     }
01413                 }
01414             }
01415             if( rightAxesLayout ){
01416                 const int rightColumnMinWidth = rightOverlap;
01417                 QLayoutItem* item = rightAxesLayout->count()
01418                         ? dynamic_cast<QLayoutItem*>(rightAxesLayout->itemAt(0))
01419                     : 0;
01420                 QSpacerItem* spacer = dynamic_cast<QSpacerItem*>(item);
01421                 if( spacer ){
01422                     if( spacer->sizeHint().width() < rightColumnMinWidth ){
01423                         needUpdate = true;
01424                         spacer->changeSize(rightColumnMinWidth, 1);
01425                         qDebug() << "adjusted right spacer->sizeHint().width() to" << spacer->sizeHint().width();
01426                     }
01427                 }else{
01428                     AbstractAxis* axis = dynamic_cast<AbstractAxis*>(item);
01429                     if( !axis || axis->sizeHint().width() < rightColumnMinWidth ){
01430                         needUpdate = true;
01431                         rightAxesLayout->insertSpacing( 0, rightColumnMinWidth );
01432                         qDebug() << "adjusted column" << rightColumn << "min width to" << rightColumnMinWidth;
01433                     }
01434                 }
01435             }
01436         }
01437     }
01438     if( needUpdate ){
01439         ;// do something ...?
01440     }
01441 }
01442 
01443 
01444 void Chart::Private::slotAdjustTopBottomRowsForOverlappingLabels(
01445         CartesianAxis* axis, int topOverlap, int bottomOverlap)
01446 {
01447     const QLayout* axisLayout = axis ? axis->parentLayout() : 0;
01448 
01449     if( (! topOverlap && ! bottomOverlap) || ! axisLayout || ! axisLayout->parent() )
01450         return;
01451 
01452     // access the planeLayout:
01453     QGridLayout* grid = qobject_cast<QGridLayout*>(axisLayout->parent());
01454     if( grid ){
01455             // find the index of the parent layout in the planeLayout:
01456         int idx = -1;
01457         for (int i = 0; i < grid->count(); ++i)
01458             if( grid->itemAt(i) == axisLayout )
01459                 idx = i;
01460             // set the min widths of the neighboring column:
01461         if( idx > -1 ){
01462             int row, column, rowSpan, columnSpan;
01463             grid->getItemPosition( idx, &row, &column, &rowSpan, &columnSpan );
01464             const int topRow = row-1;
01465             const int bottomRow = row+rowSpan;
01466                 // find the left/right axes layouts
01467             QVBoxLayout* topAxesLayout=0;
01468             QVBoxLayout* bottomAxesLayout=0;
01469             for( int i = 0;
01470                  (!topAxesLayout || !bottomAxesLayout) && i < grid->count();
01471                  ++i )
01472             {
01473                 int r, c, rs, cs;
01474                 grid->getItemPosition( i, &r, &c, &rs, &cs );
01475                 if( r+rs-1 == topRow )
01476                     topAxesLayout = dynamic_cast<QVBoxLayout*>(grid->itemAt(i));
01477                 if( r == bottomRow )
01478                     bottomAxesLayout = dynamic_cast<QVBoxLayout*>(grid->itemAt(i));
01479             }
01480             if( topAxesLayout ){
01481                 const int topRowMinWidth = topOverlap;
01482                 QLayoutItem* item = topAxesLayout->count()
01483                         ? dynamic_cast<QLayoutItem*>(topAxesLayout->itemAt(topAxesLayout->count()-1))
01484                     : 0;
01485                 QSpacerItem* spacer = dynamic_cast<QSpacerItem*>(item);
01486                 if( spacer ){
01487                     if( spacer->sizeHint().height() < topRowMinWidth ){
01488                         spacer->changeSize(1, topRowMinWidth);
01489                         qDebug() << "adjusted top spacer->sizeHint().height() to" << spacer->sizeHint().height();
01490                     }
01491                 }else{
01492                     AbstractAxis* axis = dynamic_cast<AbstractAxis*>(item);
01493                     if( !axis || axis->sizeHint().height() < topRowMinWidth ){
01494                         topAxesLayout->insertSpacing( -1, topRowMinWidth );
01495                         qDebug() << "adjusted row" << topRow << "min height to" << topRowMinWidth;
01496                     }
01497                 }
01498             }
01499             if( bottomAxesLayout ){
01500                 const int bottomRowMinWidth = bottomOverlap;
01501                 QLayoutItem* item = bottomAxesLayout->count()
01502                         ? dynamic_cast<QLayoutItem*>(bottomAxesLayout->itemAt(0))
01503                     : 0;
01504                 QSpacerItem* spacer = dynamic_cast<QSpacerItem*>(item);
01505                 if( spacer ){
01506                     if( spacer->sizeHint().height() < bottomRowMinWidth ){
01507                         spacer->changeSize(1, bottomRowMinWidth);
01508                         qDebug() << "adjusted bottom spacer->sizeHint().height() to" << spacer->sizeHint().height();
01509                     }
01510                 }else{
01511                     AbstractAxis* axis = dynamic_cast<AbstractAxis*>(item);
01512                     if( !axis || axis->sizeHint().height() < bottomRowMinWidth ){
01513                         bottomAxesLayout->insertSpacing( 0, bottomRowMinWidth );
01514                         qDebug() << "adjusted row" << bottomRow << "min height to" << bottomRowMinWidth;
01515                     }
01516                 }
01517             }
01518         }
01519     }
01520 }
01521 */
01522 
01523 void Chart::mouseDoubleClickEvent( QMouseEvent* event )
01524 {
01525     const QPoint pos = mapFromGlobal( event->globalPos() );
01526 
01527     KDAB_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes )
01528     {
01529         if ( plane->geometry().contains( event->pos() ) )
01530         {
01531             if ( plane->diagrams().size() > 0 )
01532             {
01533                 QMouseEvent ev( QEvent::MouseButtonPress, pos, event->globalPos(),
01534                                 event->button(), event->buttons(),
01535                                 event->modifiers() );
01536                 plane->mouseDoubleClickEvent( &ev );
01537             }
01538         }
01539     }
01540 }
01541 
01542 void Chart::mouseMoveEvent( QMouseEvent* event )
01543 {
01544     QSet< AbstractCoordinatePlane* > eventReceivers = QSet< AbstractCoordinatePlane* >::fromList( d->mouseClickedPlanes );
01545 
01546     KDAB_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes )
01547     {
01548         if( plane->geometry().contains( event->pos() ) )
01549         {
01550             if( plane->diagrams().size() > 0 )
01551             {
01552                 eventReceivers.insert( plane );
01553             }
01554         }
01555     }
01556 
01557     const QPoint pos = mapFromGlobal( event->globalPos() );
01558 
01559     KDAB_FOREACH( AbstractCoordinatePlane* plane, eventReceivers )
01560     {
01561         QMouseEvent ev( QEvent::MouseMove, pos, event->globalPos(),
01562                          event->button(), event->buttons(),
01563                          event->modifiers() );
01564         plane->mouseMoveEvent( &ev );
01565     }
01566 }
01567 
01568 void Chart::mouseReleaseEvent( QMouseEvent* event )
01569 {
01570     QSet< AbstractCoordinatePlane* > eventReceivers = QSet< AbstractCoordinatePlane* >::fromList( d->mouseClickedPlanes );
01571 
01572     KDAB_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes )
01573     {
01574         if ( plane->geometry().contains( event->pos() ) )
01575         {
01576             if( plane->diagrams().size() > 0 )
01577             {
01578                 eventReceivers.insert( plane );
01579             }
01580         }
01581     }
01582 
01583     const QPoint pos = mapFromGlobal( event->globalPos() );
01584 
01585     KDAB_FOREACH( AbstractCoordinatePlane* plane, eventReceivers )
01586     {
01587         QMouseEvent ev( QEvent::MouseButtonRelease, pos, event->globalPos(),
01588                          event->button(), event->buttons(),
01589                          event->modifiers() );
01590         plane->mouseReleaseEvent( &ev );
01591     }
01592 
01593     d->mouseClickedPlanes.clear();
01594 }
01595 
01596 bool Chart::event( QEvent* event )
01597 {
01598     switch( event->type() )
01599     {
01600     case QEvent::ToolTip:
01601     {
01602         const QHelpEvent* const helpEvent = static_cast< QHelpEvent* >( event );
01603         KDAB_FOREACH( const AbstractCoordinatePlane* const plane, d->coordinatePlanes )
01604         {
01605             for (int i = plane->diagrams().count() - 1; i >= 0; --i) {
01606                 const QModelIndex index = plane->diagrams().at(i)->indexAt( helpEvent->pos() );
01607                 const QVariant toolTip = index.data( Qt::ToolTipRole );
01608                 if( toolTip.isValid() )
01609                 {
01610                     QPoint pos = mapFromGlobal(helpEvent->pos());
01611                     QRect rect(pos-QPoint(1,1), QSize(3,3));
01612                     QToolTip::showText( QCursor::pos(), toolTip.toString(), this, rect );
01613                     return true;
01614                 }
01615             }
01616         }
01617         // fall-through intended
01618     }
01619     default:
01620         return QWidget::event( event );
01621     }
01622 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Defines

Klarälvdalens Datakonsult AB (KDAB)
Qt-related services and products
http://www.kdab.com/
http://www.kdab.com/products/kd-chart/