KD Chart 2 [rev.2.4]

KDChartChart.cpp

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