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