KD Chart 2 [rev.2.4]
|
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 }