/**************************************************************************** ** Copyright (C) 2001-2011 Klaralvdalens Datakonsult AB. All rights reserved. ** ** This file is part of the KD Chart library. ** ** Licensees holding valid commercial KD Chart licenses may use this file in ** accordance with the KD Chart Commercial License Agreement provided with ** the Software. ** ** ** This file may be distributed and/or modified under the terms of the ** GNU General Public License version 2 and version 3 as published by the ** Free Software Foundation and appearing in the file LICENSE.GPL.txt included. ** ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ** ** Contact info@kdab.com if any conditions of this licensing are not ** clear to you. ** **********************************************************************/ #include "KDChartChart.h" #include "KDChartChart_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "KDChartCartesianCoordinatePlane.h" #include "KDChartAbstractCartesianDiagram.h" #include "KDChartHeaderFooter.h" #include "KDChartEnums.h" #include "KDChartLegend.h" #include "KDChartLayoutItems.h" #include #include #include "KDChartPainterSaver_p.h" #include "KDChartPrintingParameters.h" #if defined KDAB_EVAL #include "../evaldialog/evaldialog.h" #endif #include #define SET_ALL_MARGINS_TO_ZERO // Layout widgets even if they are not visible class MyWidgetItem : public QWidgetItem { public: explicit MyWidgetItem(QWidget *w, Qt::Alignment alignment = 0) : QWidgetItem(w) { setAlignment( alignment ); } /*reimp*/ bool isEmpty() const { QWidget* w = const_cast(this)->widget(); // legend->hide() should indeed hide the legend, // but a legend in a chart that hasn't been shown yet isn't hidden // (as can happen when using Chart::paint() without showing the chart) return w->isHidden() && w->testAttribute(Qt::WA_WState_ExplicitShowHide); } }; using namespace KDChart; void Chart::Private::slotUnregisterDestroyedLegend( Legend *l ) { legends.removeAll( l ); slotRelayout(); } void Chart::Private::slotUnregisterDestroyedHeaderFooter( HeaderFooter* hf ) { headerFooters.removeAll( hf ); hf->removeFromParentLayout(); textLayoutItems.remove( textLayoutItems.indexOf( hf ) ); slotRelayout(); } void Chart::Private::slotUnregisterDestroyedPlane( AbstractCoordinatePlane* plane ) { coordinatePlanes.removeAll( plane ); Q_FOREACH ( AbstractCoordinatePlane* p, coordinatePlanes ) { if ( p->referenceCoordinatePlane() == plane) { p->setReferenceCoordinatePlane(0); } } plane->layoutPlanes(); } Chart::Private::Private( Chart* chart_ ) : chart( chart_ ) , layout( 0 ) , vLayout( 0 ) , planesLayout( 0 ) , headerLayout( 0 ) , footerLayout( 0 ) , dataAndLegendLayout( 0 ) , globalLeadingLeft( 0 ) , globalLeadingRight( 0 ) , globalLeadingTop( 0 ) , globalLeadingBottom( 0 ) { for( int row = 0; row < 3; ++row ) { for( int column = 0; column < 3; ++column ) { dummyHeaders[ row ][ column ] = HorizontalLineLayoutItem(); dummyFooters[ row ][ column ] = HorizontalLineLayoutItem(); innerHdFtLayouts[0][row][column] = 0; innerHdFtLayouts[1][row][column] = 0; } } } Chart::Private::~Private() { removeDummyHeaderFooters(); } void Chart::Private::removeDummyHeaderFooters() { for ( int row = 0; row < 3; ++row ) { for ( int column = 0; column < 3; ++ column ) { if( innerHdFtLayouts[0][row][column] ){ innerHdFtLayouts[0][row][column]->removeItem( &(dummyHeaders[row][column]) ); innerHdFtLayouts[1][row][column]->removeItem( &(dummyFooters[row][column]) ); } } } } void Chart::Private::layoutHeadersAndFooters() { removeDummyHeaderFooters(); bool headersLineFilled[] = { false, false, false }; bool footersLineFilled[] = { false, false, false }; Q_FOREACH( HeaderFooter *hf, headerFooters ) { // for now, there are only two types of Header/Footer, // we use a pointer to the right layout, depending on the type(): int innerLayoutIdx = 0; switch( hf->type() ){ case HeaderFooter::Header: innerLayoutIdx = 0; break; case HeaderFooter::Footer: innerLayoutIdx = 1; break; default: Q_ASSERT( false ); // all types need to be handled break; }; if( hf->position() != Position::Unknown ) { int row, column; Qt::Alignment hAlign, vAlign; if( hf->position().isNorthSide() ){ row = 0; vAlign = Qt::AlignTop; } else if( hf->position().isSouthSide() ){ row = 2; vAlign = Qt::AlignBottom; } else{ row = 1; vAlign = Qt::AlignVCenter; } if( hf->position().isWestSide() ){ column = 0; hAlign = Qt::AlignLeft; } else if( hf->position().isEastSide() ){ column = 2; hAlign = Qt::AlignRight; } else{ column = 1; hAlign = Qt::AlignHCenter; } switch( hf->type() ){ case HeaderFooter::Header: if( !headersLineFilled[ row ] ) { for( int col = 0; col < 3; ++col ) innerHdFtLayouts[0][row][col]->addItem( &(dummyHeaders[ row ][ col ]) ); headersLineFilled[ row ] = true; } break; case HeaderFooter::Footer: if( !footersLineFilled[ row ] ) { for( int col = 0; col < 3; ++col ) innerHdFtLayouts[1][row][col]->addItem( &(dummyFooters[ row ][ col ]) ); footersLineFilled[ row ] = true; } break; }; textLayoutItems << hf; QVBoxLayout* headerFooterLayout = innerHdFtLayouts[innerLayoutIdx][row][column]; hf->setParentLayout( headerFooterLayout ); hf->setAlignment( hAlign | vAlign ); headerFooterLayout->addItem( hf ); } else{ qDebug( "Unknown header/footer position" ); } } } void Chart::Private::layoutLegends() { //qDebug() << "starting Chart::Private::layoutLegends()"; // To support more than one Legend, we first collect them all // in little lists: one list per grid position. // Since the dataAndLegendLayout is a 3x3 grid, we need 9 little lists. QList infos[3][3]; Q_FOREACH( Legend *legend, legends ) { legend->needSizeHint(); // we'll lay it out soon bool bOK = true; int row, column; //qDebug() << legend->position().name(); switch( legend->position().value() ) { case KDChartEnums::PositionNorthWest: row = 0; column = 0; break; case KDChartEnums::PositionNorth: row = 0; column = 1; break; case KDChartEnums::PositionNorthEast: row = 0; column = 2; break; case KDChartEnums::PositionEast: row = 1; column = 2; break; case KDChartEnums::PositionSouthEast: row = 2; column = 2; break; case KDChartEnums::PositionSouth: row = 2; column = 1; break; case KDChartEnums::PositionSouthWest: row = 2; column = 0; break; case KDChartEnums::PositionWest: row = 1; column = 0; break; case KDChartEnums::PositionCenter: qDebug( "Sorry: Legend not shown, because position Center is not supported." ); bOK = false; break; case KDChartEnums::PositionFloating: bOK = false; break; default: qDebug( "Sorry: Legend not shown, because of unknown legend position." ); bOK = false; break; } if( bOK ) infos[row][column] << legend; } // We have collected all legend information, // so we can design their layout now. for (int iR = 0; iR < 3; ++iR) { for (int iC = 0; iC < 3; ++iC) { QList& list = infos[iR][iC]; const int count = list.size(); switch( count ){ case 0: break; case 1: { Legend* legend = list.first(); dataAndLegendLayout->addItem( new MyWidgetItem(legend), iR, iC, 1, 1, legend->alignment() ); } break; default: { // We have more than one legend in the same cell // of the big dataAndLegendLayout grid. // // So we need to find out, if they are aligned the // same way: // Those legends, that are aligned the same way, will be drawn // leftbound, on top of each other, in a little VBoxLayout. // // If not al of the legends are aligned the same way, // there will be a grid with 3 cells: for left/mid/right side // (or top/mid/bottom side, resp.) legends Legend* legend = list.first(); Qt::Alignment alignment = legend->alignment(); bool haveSameAlign = true; for (int i = 1; i < count; ++i) { legend = list.at(i); if( alignment != legend->alignment() ){ haveSameAlign = false; break; } } if( haveSameAlign ){ QVBoxLayout* vLayout = new QVBoxLayout(); #if defined SET_ALL_MARGINS_TO_ZERO vLayout->setMargin(0); #endif for (int i = 0; i < count; ++i) { vLayout->addItem( new MyWidgetItem(list.at(i), Qt::AlignLeft) ); } dataAndLegendLayout->addLayout( vLayout, iR, iC, 1, 1, alignment ); }else{ QGridLayout* gridLayout = new QGridLayout(); #if defined SET_ALL_MARGINS_TO_ZERO gridLayout->setMargin(0); #endif #define ADD_VBOX_WITH_LEGENDS(row, column, align) \ { \ QVBoxLayout* innerLayout = new QVBoxLayout(); \ for (int i = 0; i < count; ++i) { \ legend = list.at(i); \ if( legend->alignment() == ( align ) ) \ innerLayout->addItem( new MyWidgetItem(legend, Qt::AlignLeft) ); \ } \ gridLayout->addLayout( innerLayout, row, column, ( align ) ); \ } ADD_VBOX_WITH_LEGENDS( 0, 0, Qt::AlignTop | Qt::AlignLeft ) ADD_VBOX_WITH_LEGENDS( 0, 1, Qt::AlignTop | Qt::AlignHCenter ) ADD_VBOX_WITH_LEGENDS( 0, 2, Qt::AlignTop | Qt::AlignRight ) ADD_VBOX_WITH_LEGENDS( 1, 0, Qt::AlignVCenter | Qt::AlignLeft ) ADD_VBOX_WITH_LEGENDS( 1, 1, Qt::AlignCenter ) ADD_VBOX_WITH_LEGENDS( 1, 2, Qt::AlignVCenter | Qt::AlignRight ) ADD_VBOX_WITH_LEGENDS( 2, 0, Qt::AlignBottom | Qt::AlignLeft ) ADD_VBOX_WITH_LEGENDS( 2, 1, Qt::AlignBottom | Qt::AlignHCenter ) ADD_VBOX_WITH_LEGENDS( 2, 2, Qt::AlignBottom | Qt::AlignRight ) dataAndLegendLayout->addLayout( gridLayout, iR, iC, 1, 1 ); } } } } } //qDebug() << "finished Chart::Private::layoutLegends()"; } QHash Chart::Private::buildPlaneLayoutInfos() { /* There are two ways in which planes can be caused to interact in * where they are put layouting wise: The first is the reference plane. If * such a reference plane is set, on a plane, it will use the same cell in the * layout as that one. In addition to this, planes can share an axis. In that case * they will be laid out in relation to each other as suggested by the position * of the axis. If, for example Plane1 and Plane2 share an axis at position Left, * that will result in the layout: Axis Plane1 Plane 2, vertically. If Plane1 * also happens to be Plane2's referece plane, both planes are drawn over each * other. The reference plane concept allows two planes to share the same space * even if neither has any axis, and in case there are shared axis, it is used * to decided, whether the planes should be painted on top of each other or * laid out vertically or horizontally next to each other. */ QHash axisInfos; QHash planeInfos; Q_FOREACH(AbstractCoordinatePlane* plane, coordinatePlanes ) { PlaneInfo p; // first check if we share space with another plane p.referencePlane = plane->referenceCoordinatePlane(); planeInfos.insert( plane, p ); Q_FOREACH( AbstractDiagram* abstractDiagram, plane->diagrams() ) { AbstractCartesianDiagram* diagram = dynamic_cast ( abstractDiagram ); if( !diagram ) continue; Q_FOREACH( CartesianAxis* axis, diagram->axes() ) { if ( !axisInfos.contains( axis ) ) { /* If this is the first time we see this axis, add it, with the * current plane. The first plane added to the chart that has * the axis associated with it thus "owns" it, and decides about * layout. */ AxisInfo i; i.plane = plane; axisInfos.insert( axis, i ); } else { AxisInfo i = axisInfos[axis]; if ( i.plane == plane ) continue; // we don't want duplicates, only shared /* The user expects diagrams to be added on top, and to the right * so that horizontally we need to move the new diagram, vertically * the reference one. */ PlaneInfo pi = planeInfos[plane]; // plane-to-plane linking overrides linking via axes if ( !pi.referencePlane ) { // we're not the first plane to see this axis, mark us as a slave pi.referencePlane = i.plane; if ( axis->position() == CartesianAxis::Left || axis->position() == CartesianAxis::Right ) pi.horizontalOffset += 1; planeInfos[plane] = pi; pi = planeInfos[i.plane]; if ( axis->position() == CartesianAxis::Top || axis->position() == CartesianAxis::Bottom ) pi.verticalOffset += 1; planeInfos[i.plane] = pi; } } } } // Create a new grid layout for each plane that has no reference. p = planeInfos[plane]; if ( p.referencePlane == 0 ) { p.gridLayout = new QGridLayout(); // TESTING(khz): set the margin of all of the layouts to Zero #if defined SET_ALL_MARGINS_TO_ZERO p.gridLayout->setMargin(0); #endif planeInfos[plane] = p; } } return planeInfos; } template static T* findOrCreateLayoutByObjectName( QLayout * parentLayout, const char* name ) { T *box = qFindChild( parentLayout, QString::fromLatin1( name ) ); if ( !box ) { box = new T(); // TESTING(khz): set the margin of all of the layouts to Zero #if defined SET_ALL_MARGINS_TO_ZERO box->setMargin(0); #endif box->setObjectName( QString::fromLatin1( name ) ); box->setSizeConstraint( QLayout::SetFixedSize ); } return box; } #if 0 static QVBoxLayout* findOrCreateVBoxLayoutByObjectName( QLayout* parentLayout, const char* name ) { return findOrCreateLayoutByObjectName( parentLayout, name ); } static QHBoxLayout* findOrCreateHBoxLayoutByObjectName( QLayout* parentLayout, const char* name ) { return findOrCreateLayoutByObjectName( parentLayout, name ); } #endif void Chart::Private::slotLayoutPlanes() { //qDebug() << "KDChart::Chart is layouting the planes"; const QBoxLayout::Direction oldPlanesDirection = planesLayout ? planesLayout->direction() : QBoxLayout::TopToBottom; if ( planesLayout && dataAndLegendLayout ) dataAndLegendLayout->removeItem( planesLayout ); const bool hadPlanesLayout = planesLayout != 0; int left, top, right, bottom; if(hadPlanesLayout) planesLayout->getContentsMargins(&left, &top, &right, &bottom); KDAB_FOREACH( KDChart::AbstractLayoutItem* plane, planeLayoutItems ) { plane->removeFromParentLayout(); } planeLayoutItems.clear(); delete planesLayout; //hint: The direction is configurable by the user now, as // we are using a QBoxLayout rather than a QVBoxLayout. (khz, 2007/04/25) planesLayout = new QBoxLayout( oldPlanesDirection ); if(hadPlanesLayout) planesLayout->setContentsMargins(left, top, right, bottom); // TESTING(khz): set the margin of all of the layouts to Zero #if defined SET_ALL_MARGINS_TO_ZERO planesLayout->setMargin(0); planesLayout->setSpacing(0); #endif planesLayout->setObjectName( QString::fromLatin1( "planesLayout" ) ); /* First go through all planes and all axes and figure out whether the planes * need to coordinate. If they do, they share a grid layout, if not, each * get their own. See buildPlaneLayoutInfos() for more details. */ QHash planeInfos = buildPlaneLayoutInfos(); QHash axisInfos; KDAB_FOREACH( AbstractCoordinatePlane* plane, coordinatePlanes ) { Q_ASSERT( planeInfos.contains(plane) ); PlaneInfo& pi = planeInfos[ plane ]; int column = pi.horizontalOffset; int row = pi.verticalOffset; //qDebug() << "processing plane at column" << column << "and row" << row; QGridLayout *planeLayout = pi.gridLayout; if(!planeLayout){ PlaneInfo& refPi = pi; // if this plane is sharing an axis with another one, recursively check for the original plane and use // the grid of that as planeLayout. while ( !planeLayout && refPi.referencePlane) { refPi = planeInfos[refPi.referencePlane]; planeLayout = refPi.gridLayout; } Q_ASSERT_X(planeLayout, "Chart::Private::slotLayoutPlanes()", "Invalid reference plane. Please Check whether the reference plane is added to the Chart or not" ); } else { planesLayout->addLayout( planeLayout ); } /* Put the plane in the center of the layout. If this is our own, that's * the middle of the layout, if we are sharing, it's a cell in the center * column of the shared grid. */ planeLayoutItems << plane; plane->setParentLayout( planeLayout ); planeLayout->addItem( plane, row, column, 1, 1, 0 ); //qDebug() << "Chart slotLayoutPlanes() calls planeLayout->addItem("<< row << column << ")"; planeLayout->setRowStretch( row, 2 ); planeLayout->setColumnStretch( column, 2 ); KDAB_FOREACH( AbstractDiagram* abstractDiagram, plane->diagrams() ) { AbstractCartesianDiagram* diagram = dynamic_cast ( abstractDiagram ); //qDebug() << "--------------- diagram ???????????????????? -----------------"; if( !diagram ) continue; // FIXME polar ? //qDebug() << "--------------- diagram ! ! ! ! ! ! ! ! ! ! -----------------"; if( pi.referencePlane != 0 ) { pi.topAxesLayout = planeInfos[ pi.referencePlane ].topAxesLayout; pi.bottomAxesLayout = planeInfos[ pi.referencePlane ].bottomAxesLayout; pi.leftAxesLayout = planeInfos[ pi.referencePlane ].leftAxesLayout; pi.rightAxesLayout = planeInfos[ pi.referencePlane ].rightAxesLayout; } // collect all axes of a kind into sublayouts if( pi.topAxesLayout == 0 ) { pi.topAxesLayout = new QVBoxLayout; #if defined SET_ALL_MARGINS_TO_ZERO pi.topAxesLayout->setMargin(0); #endif pi.topAxesLayout->setObjectName( QString::fromLatin1( "topAxesLayout" ) ); } if( pi.bottomAxesLayout == 0 ) { pi.bottomAxesLayout = new QVBoxLayout; #if defined SET_ALL_MARGINS_TO_ZERO pi.bottomAxesLayout->setMargin(0); #endif pi.bottomAxesLayout->setObjectName( QString::fromLatin1( "bottomAxesLayout" ) ); } if( pi.leftAxesLayout == 0 ) { pi.leftAxesLayout = new QHBoxLayout; #if defined SET_ALL_MARGINS_TO_ZERO pi.leftAxesLayout->setMargin(0); #endif pi.leftAxesLayout->setObjectName( QString::fromLatin1( "leftAxesLayout" ) ); } if( pi.rightAxesLayout == 0 ) { pi.rightAxesLayout = new QHBoxLayout; #if defined SET_ALL_MARGINS_TO_ZERO pi.rightAxesLayout->setMargin(0); #endif pi.rightAxesLayout->setObjectName( QString::fromLatin1( "rightAxesLayout" ) ); } if( pi.referencePlane != 0 ) { planeInfos[ pi.referencePlane ].topAxesLayout = pi.topAxesLayout; planeInfos[ pi.referencePlane ].bottomAxesLayout = pi.bottomAxesLayout; planeInfos[ pi.referencePlane ].leftAxesLayout = pi.leftAxesLayout; planeInfos[ pi.referencePlane ].rightAxesLayout = pi.rightAxesLayout; } //pi.leftAxesLayout->setSizeConstraint( QLayout::SetFixedSize ); KDAB_FOREACH( CartesianAxis* axis, diagram->axes() ) { if ( axisInfos.contains( axis ) ) continue; // already laid this one out Q_ASSERT ( axis ); axis->setCachedSizeDirty(); //qDebug() << "--------------- axis added to planeLayoutItems -----------------"; planeLayoutItems << axis; /* // Unused code trying to use a push-model: This did not work // since we can not re-layout the planes each time when // Qt layouting is calling sizeHint() connect( axis, SIGNAL( needAdjustLeftRightColumnsForOverlappingLabels( CartesianAxis*, int, int ) ), this, SLOT( slotAdjustLeftRightColumnsForOverlappingLabels( CartesianAxis*, int, int ) ) ); connect( axis, SIGNAL( needAdjustTopBottomRowsForOverlappingLabels( CartesianAxis*, int, int ) ), this, SLOT( slotAdjustTopBottomRowsForOverlappingLabels( CartesianAxis*, int, int ) ) ); */ switch ( axis->position() ) { case CartesianAxis::Top: axis->setParentLayout( pi.topAxesLayout ); pi.topAxesLayout->addItem( axis ); break; case CartesianAxis::Bottom: axis->setParentLayout( pi.bottomAxesLayout ); pi.bottomAxesLayout->addItem( axis ); break; case CartesianAxis::Left: axis->setParentLayout( pi.leftAxesLayout ); pi.leftAxesLayout->addItem( axis ); break; case CartesianAxis::Right: axis->setParentLayout( pi.rightAxesLayout ); pi.rightAxesLayout->addItem( axis ); break; default: Q_ASSERT_X( false, "Chart::paintEvent", "unknown axis position" ); break; }; axisInfos.insert( axis, AxisInfo() ); } /* Put each stack of axes-layouts in the cells surrounding the * associated plane. We are laying out in the oder the planes * were added, and the first one gets to lay out shared axes. * Private axes go here as well, of course. */ if ( !pi.topAxesLayout->parent() ) planeLayout->addLayout( pi.topAxesLayout, row - 1, column ); if ( !pi.bottomAxesLayout->parent() ) planeLayout->addLayout( pi.bottomAxesLayout, row + 1, column ); if ( !pi.leftAxesLayout->parent() ){ planeLayout->addLayout( pi.leftAxesLayout, row, column - 1); //planeLayout->setRowStretch( row, 0 ); //planeLayout->setColumnStretch( 0, 0 ); } if ( !pi.rightAxesLayout->parent() ) planeLayout->addLayout( pi.rightAxesLayout, row, column + 1); } // use up to four auto-spacer items in the corners around the diagrams: #define ADD_AUTO_SPACER_IF_NEEDED( \ spacerRow, spacerColumn, hLayoutIsAtTop, hLayout, vLayoutIsAtLeft, vLayout ) \ { \ if( hLayout || vLayout ) { \ AutoSpacerLayoutItem * spacer \ = new AutoSpacerLayoutItem( hLayoutIsAtTop, hLayout, vLayoutIsAtLeft, vLayout ); \ planeLayout->addItem( spacer, spacerRow, spacerColumn, 1, 1 ); \ spacer->setParentLayout( planeLayout ); \ planeLayoutItems << spacer; \ } \ } ADD_AUTO_SPACER_IF_NEEDED( row-1, column-1, false, pi.leftAxesLayout, false, pi.topAxesLayout ) ADD_AUTO_SPACER_IF_NEEDED( row+1, column-1, true, pi.leftAxesLayout, false, pi.bottomAxesLayout ) ADD_AUTO_SPACER_IF_NEEDED( row-1, column+1, false, pi.rightAxesLayout, true, pi.topAxesLayout ) ADD_AUTO_SPACER_IF_NEEDED( row+1, column+1, true, pi.rightAxesLayout, true, pi.bottomAxesLayout ) } // re-add our grid(s) to the chart's layout if ( dataAndLegendLayout ){ dataAndLegendLayout->addLayout( planesLayout, 1, 1 ); dataAndLegendLayout->setRowStretch( 1, 1000 ); dataAndLegendLayout->setColumnStretch( 1, 1000 ); } slotRelayout(); //qDebug() << "KDChart::Chart finished layouting the planes."; } void Chart::Private::createLayouts( QWidget* w ) { KDAB_FOREACH( KDChart::TextArea* textLayoutItem, textLayoutItems ) { textLayoutItem->removeFromParentLayout(); } textLayoutItems.clear(); KDAB_FOREACH( KDChart::AbstractArea* layoutItem, layoutItems ) { layoutItem->removeFromParentLayout(); } layoutItems.clear(); removeDummyHeaderFooters(); // layout for the planes is handled separately, so we don't want to delete it here if ( dataAndLegendLayout) { dataAndLegendLayout->removeItem( planesLayout ); planesLayout->setParent( 0 ); } // nuke the old bunch delete layout; // The HBox d->layout provides the left and right global leadings layout = new QHBoxLayout( w ); // TESTING(khz): set the margin of all of the layouts to Zero #if defined SET_ALL_MARGINS_TO_ZERO layout->setMargin(0); #endif layout->setObjectName( QString::fromLatin1( "Chart::Private::layout" ) ); layout->addSpacing( globalLeadingLeft ); // The vLayout provides top and bottom global leadings and lays // out headers/footers and the data area. vLayout = new QVBoxLayout(); // TESTING(khz): set the margin of all of the layouts to Zero #if defined SET_ALL_MARGINS_TO_ZERO vLayout->setMargin(0); #endif vLayout->setObjectName( QString::fromLatin1( "vLayout" ) ); layout->addLayout( vLayout, 1000 ); layout->addSpacing( globalLeadingRight ); // 1. the gap above the top edge of the headers area vLayout->addSpacing( globalLeadingTop ); // 2. the header(s) area headerLayout = new QGridLayout(); // TESTING(khz): set the margin of all of the layouts to Zero #if defined SET_ALL_MARGINS_TO_ZERO headerLayout->setMargin(0); #endif vLayout->addLayout( headerLayout ); // 3. the area containing coordinate plane(s), axes, legend(s) dataAndLegendLayout = new QGridLayout(); // TESTING(khz): set the margin of all of the layouts to Zero #if defined SET_ALL_MARGINS_TO_ZERO dataAndLegendLayout->setMargin(0); #endif dataAndLegendLayout->setObjectName( QString::fromLatin1( "dataAndLegendLayout" ) ); vLayout->addLayout( dataAndLegendLayout, 1000 ); // 4. the footer(s) area footerLayout = new QGridLayout(); // TESTING(khz): set the margin of all of the layouts to Zero #if defined SET_ALL_MARGINS_TO_ZERO footerLayout->setMargin(0); #endif footerLayout->setObjectName( QString::fromLatin1( "footerLayout" ) ); vLayout->addLayout( footerLayout ); // 5. Prepare the header / footer layout cells: // Each of the 9 header cells (the 9 footer cells) // contain their own QVBoxLayout // since there can be more than one header (footer) per cell. static const Qt::Alignment hdFtAlignments[3][3] = { { Qt::AlignTop | Qt::AlignLeft, Qt::AlignTop | Qt::AlignHCenter, Qt::AlignTop | Qt::AlignRight }, { Qt::AlignVCenter | Qt::AlignLeft, Qt::AlignVCenter | Qt::AlignHCenter, Qt::AlignVCenter | Qt::AlignRight }, { Qt::AlignBottom | Qt::AlignLeft, Qt::AlignBottom | Qt::AlignHCenter, Qt::AlignBottom | Qt::AlignRight } }; for ( int row = 0; row < 3; ++row ) { for ( int column = 0; column < 3; ++ column ) { QVBoxLayout* innerHdLayout = new QVBoxLayout(); QVBoxLayout* innerFtLayout = new QVBoxLayout(); innerHdFtLayouts[0][row][column] = innerHdLayout; innerHdFtLayouts[1][row][column] = innerFtLayout; #if defined SET_ALL_MARGINS_TO_ZERO innerHdLayout->setMargin(0); innerFtLayout->setMargin(0); #endif const Qt::Alignment align = hdFtAlignments[row][column]; innerHdLayout->setAlignment( align ); innerFtLayout->setAlignment( align ); headerLayout->addLayout( innerHdLayout, row, column, align ); footerLayout->addLayout( innerFtLayout, row, column, align ); } } // 6. the gap below the bottom edge of the headers area vLayout->addSpacing( globalLeadingBottom ); // the data+axes area dataAndLegendLayout->addLayout( planesLayout, 1, 1 ); dataAndLegendLayout->setRowStretch( 1, 1 ); dataAndLegendLayout->setColumnStretch( 1, 1 ); //qDebug() << "w->rect()" << w->rect(); } void Chart::Private::slotRelayout() { //qDebug() << "Chart relayouting started."; createLayouts( chart ); layoutHeadersAndFooters(); layoutLegends(); // This triggers the qlayout, see QBoxLayout::setGeometry // The geometry is not necessarily w->rect(), when using paint(), this is why // we don't call layout->activate(). const QRect geo( QRect( 0, 0, currentLayoutSize.width(), currentLayoutSize.height() ) ); if( geo.isValid() && geo != layout->geometry() ){ //qDebug() << "Chart slotRelayout() adjusting geometry to" << geo; //if( coordinatePlanes.count() ) // qDebug() << " plane geo before" << coordinatePlanes.first()->geometry(); layout->setGeometry( geo ); //if( coordinatePlanes.count() ) { // qDebug() << " plane geo after " << coordinatePlanes.first()->geometry(); //} } // Adapt diagram drawing to the new size KDAB_FOREACH (AbstractCoordinatePlane* plane, coordinatePlanes ) { plane->layoutDiagrams(); } //qDebug() << "Chart relayouting done."; } // Called when the size of the chart changes. // So in theory, we only need to adjust geometries. // But this also needs to make sure that everything is in place for the first painting. void Chart::Private::resizeLayout( const QSize& size ) { currentLayoutSize = size; //qDebug() << "Chart::resizeLayout(" << currentLayoutSize << ")"; /* // We need to make sure that the legend's layouts are populated, // so that setGeometry gets proper sizeHints from them and resizes them properly. KDAB_FOREACH( Legend *legend, legends ) { // This forceRebuild will see a wrong areaGeometry, but I don't care about geometries yet, // only about the fact that legends should have their contents populated. // -> it would be better to dissociate "building contents" and "resizing" in Legend... // legend->forceRebuild(); legend->resizeLayout( size ); } */ slotLayoutPlanes(); // includes slotRelayout //qDebug() << "Chart::resizeLayout done"; } void Chart::Private::paintAll( QPainter* painter ) { QRect rect( QPoint(0, 0), currentLayoutSize ); //qDebug() << this<<"::paintAll() uses layout size" << currentLayoutSize; // Paint the background (if any) KDChart::AbstractAreaBase::paintBackgroundAttributes( *painter, rect, backgroundAttributes ); // Paint the frame (if any) KDChart::AbstractAreaBase::paintFrameAttributes( *painter, rect, frameAttributes ); chart->reLayoutFloatingLegends(); KDAB_FOREACH( KDChart::AbstractArea* layoutItem, layoutItems ) { layoutItem->paintAll( *painter ); } KDAB_FOREACH( KDChart::AbstractLayoutItem* planeLayoutItem, planeLayoutItems ) { planeLayoutItem->paintAll( *painter ); } KDAB_FOREACH( KDChart::TextArea* textLayoutItem, textLayoutItems ) { textLayoutItem->paintAll( *painter ); } } // ******** Chart interface implementation *********** Chart::Chart ( QWidget* parent ) : QWidget ( parent ) , _d( new Private( this ) ) { #if defined KDAB_EVAL EvalDialog::checkEvalLicense( "KD Chart" ); #endif FrameAttributes frameAttrs; // no frame per default... // frameAttrs.setVisible( true ); frameAttrs.setPen( QPen( Qt::black ) ); frameAttrs.setPadding( 1 ); setFrameAttributes( frameAttrs ); addCoordinatePlane( new CartesianCoordinatePlane ( this ) ); } Chart::~Chart() { delete _d; } #define d d_func() void Chart::setFrameAttributes( const FrameAttributes &a ) { d->frameAttributes = a; } FrameAttributes Chart::frameAttributes() const { return d->frameAttributes; } void Chart::setBackgroundAttributes( const BackgroundAttributes &a ) { d->backgroundAttributes = a; } BackgroundAttributes Chart::backgroundAttributes() const { return d->backgroundAttributes; } //TODO KDChart 3.0; change QLayout into QBoxLayout::Direction void Chart::setCoordinatePlaneLayout( QLayout * layout ) { delete d->planesLayout; d->planesLayout = dynamic_cast( layout ); d->slotLayoutPlanes(); } QLayout* Chart::coordinatePlaneLayout() { return d->planesLayout; } AbstractCoordinatePlane* Chart::coordinatePlane() { if ( d->coordinatePlanes.isEmpty() ) { qWarning() << "Chart::coordinatePlane: warning: no coordinate plane defined."; return 0; } else { return d->coordinatePlanes.first(); } } CoordinatePlaneList Chart::coordinatePlanes() { return d->coordinatePlanes; } void Chart::addCoordinatePlane( AbstractCoordinatePlane* plane ) { connect( plane, SIGNAL( destroyedCoordinatePlane( AbstractCoordinatePlane* ) ), d, SLOT( slotUnregisterDestroyedPlane( AbstractCoordinatePlane* ) ) ); connect( plane, SIGNAL( needUpdate() ), this, SLOT( update() ) ); connect( plane, SIGNAL( needRelayout() ), d, SLOT( slotRelayout() ) ) ; connect( plane, SIGNAL( needLayoutPlanes() ), d, SLOT( slotLayoutPlanes() ) ) ; connect( plane, SIGNAL( propertiesChanged() ),this, SIGNAL( propertiesChanged() ) ); d->coordinatePlanes.append( plane ); plane->setParent( this ); d->slotLayoutPlanes(); } void Chart::replaceCoordinatePlane( AbstractCoordinatePlane* plane, AbstractCoordinatePlane* oldPlane_ ) { if( plane && oldPlane_ != plane ){ AbstractCoordinatePlane* oldPlane = oldPlane_; if( d->coordinatePlanes.count() ){ if( ! oldPlane ){ oldPlane = d->coordinatePlanes.first(); if( oldPlane == plane ) return; } takeCoordinatePlane( oldPlane ); } delete oldPlane; addCoordinatePlane( plane ); } } void Chart::takeCoordinatePlane( AbstractCoordinatePlane* plane ) { const int idx = d->coordinatePlanes.indexOf( plane ); if( idx != -1 ){ d->coordinatePlanes.takeAt( idx ); disconnect( plane, SIGNAL( destroyedCoordinatePlane( AbstractCoordinatePlane* ) ), d, SLOT( slotUnregisterDestroyedPlane( AbstractCoordinatePlane* ) ) ); plane->removeFromParentLayout(); plane->setParent( 0 ); d->mouseClickedPlanes.removeAll(plane); } d->slotLayoutPlanes(); // Need to emit the signal: In case somebody has connected the signal // to her own slot for e.g. calling update() on a widget containing the chart. emit propertiesChanged(); } void Chart::setGlobalLeading( int left, int top, int right, int bottom ) { setGlobalLeadingLeft( left ); setGlobalLeadingTop( top ); setGlobalLeadingRight( right ); setGlobalLeadingBottom( bottom ); d->slotRelayout(); } void Chart::setGlobalLeadingLeft( int leading ) { d->globalLeadingLeft = leading; d->slotRelayout(); } int Chart::globalLeadingLeft() const { return d->globalLeadingLeft; } void Chart::setGlobalLeadingTop( int leading ) { d->globalLeadingTop = leading; d->slotRelayout(); } int Chart::globalLeadingTop() const { return d->globalLeadingTop; } void Chart::setGlobalLeadingRight( int leading ) { d->globalLeadingRight = leading; d->slotRelayout(); } int Chart::globalLeadingRight() const { return d->globalLeadingRight; } void Chart::setGlobalLeadingBottom( int leading ) { d->globalLeadingBottom = leading; d->slotRelayout(); } int Chart::globalLeadingBottom() const { return d->globalLeadingBottom; } void Chart::paint( QPainter* painter, const QRect& target ) { if( target.isEmpty() || !painter ) return; //qDebug() << "Chart::paint( ..," << target << ")"; QPaintDevice* prevDevice = GlobalMeasureScaling::paintDevice(); GlobalMeasureScaling::setPaintDevice( painter->device() ); // Output on a widget if( dynamic_cast< QWidget* >( painter->device() ) != 0 ) { GlobalMeasureScaling::setFactors( static_cast< qreal >( target.width() ) / static_cast< qreal >( geometry().size().width() ), static_cast< qreal >( target.height() ) / static_cast< qreal >( geometry().size().height() ) ); } // Output onto a QPixmap else { PrintingParameters::setScaleFactor( static_cast< qreal >( painter->device()->logicalDpiX() ) / static_cast< qreal >( logicalDpiX() ) ); const qreal resX = static_cast< qreal >( logicalDpiX() ) / static_cast< qreal >( painter->device()->logicalDpiX() ); const qreal resY = static_cast< qreal >( logicalDpiY() ) / static_cast< qreal >( painter->device()->logicalDpiY() ); GlobalMeasureScaling::setFactors( static_cast< qreal >( target.width() ) / static_cast< qreal >( geometry().size().width() ) * resX, static_cast< qreal >( target.height() ) / static_cast< qreal >( geometry().size().height() ) * resY ); } if( target.size() != d->currentLayoutSize ){ d->resizeLayout( target.size() ); } const QPoint translation = target.topLeft(); painter->translate( translation ); d->paintAll( painter ); // for debugging: //painter->setPen(QPen(Qt::blue, 8)); //painter->drawRect(target.adjusted(12,12,-12,-12)); KDAB_FOREACH( Legend *legend, d->legends ) { const bool hidden = legend->isHidden() && legend->testAttribute(Qt::WA_WState_ExplicitShowHide); if ( !hidden ) { //qDebug() << "painting legend at " << legend->geometry(); legend->paintIntoRect( *painter, legend->geometry() ); //testing: //legend->paintIntoRect( *painter, legend->geometry().adjusted(-100,0,-100,0) ); } } painter->translate( -translation.x(), -translation.y() ); GlobalMeasureScaling::instance()->resetFactors(); PrintingParameters::resetScaleFactor(); GlobalMeasureScaling::setPaintDevice( prevDevice ); //qDebug() << "KDChart::Chart::paint() done.\n"; } void Chart::resizeEvent ( QResizeEvent * ) { d->resizeLayout( size() ); KDAB_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes ){ plane->setGridNeedsRecalculate(); } reLayoutFloatingLegends(); } void Chart::reLayoutFloatingLegends() { KDAB_FOREACH( Legend *legend, d->legends ) { const bool hidden = legend->isHidden() && legend->testAttribute(Qt::WA_WState_ExplicitShowHide); if ( legend->position().isFloating() && !hidden ){ // resize the legend const QSize legendSize( legend->sizeHint() ); legend->setGeometry( QRect( legend->geometry().topLeft(), legendSize ) ); // find the legends corner point (reference point plus any paddings) const RelativePosition relPos( legend->floatingPosition() ); QPointF pt( relPos.calculatedPoint( size() ) ); //qDebug() << pt; // calculate the legend's top left point const Qt::Alignment alignTopLeft = Qt::AlignBottom | Qt::AlignLeft; if( (relPos.alignment() & alignTopLeft) != alignTopLeft ){ if( relPos.alignment() & Qt::AlignRight ) pt.rx() -= legendSize.width(); else if( relPos.alignment() & Qt::AlignHCenter ) pt.rx() -= 0.5 * legendSize.width(); if( relPos.alignment() & Qt::AlignBottom ) pt.ry() -= legendSize.height(); else if( relPos.alignment() & Qt::AlignVCenter ) pt.ry() -= 0.5 * legendSize.height(); } //qDebug() << pt << endl; legend->move( static_cast(pt.x()), static_cast(pt.y()) ); } } } void Chart::paintEvent( QPaintEvent* ) { QPainter painter( this ); if( size() != d->currentLayoutSize ){ d->resizeLayout( size() ); reLayoutFloatingLegends(); } //FIXME(khz): Paint the background/frame too! // (can we derive Chart from AreaWidget ??) d->paintAll( &painter ); } void Chart::addHeaderFooter( HeaderFooter* headerFooter ) { d->headerFooters.append( headerFooter ); headerFooter->setParent( this ); connect( headerFooter, SIGNAL( destroyedHeaderFooter( HeaderFooter* ) ), d, SLOT( slotUnregisterDestroyedHeaderFooter( HeaderFooter* ) ) ); connect( headerFooter, SIGNAL( positionChanged( HeaderFooter* ) ), d, SLOT( slotRelayout() ) ); d->slotRelayout(); } void Chart::replaceHeaderFooter( HeaderFooter* headerFooter, HeaderFooter* oldHeaderFooter_ ) { if( headerFooter && oldHeaderFooter_ != headerFooter ){ HeaderFooter* oldHeaderFooter = oldHeaderFooter_; if( d->headerFooters.count() ){ if( ! oldHeaderFooter ){ oldHeaderFooter = d->headerFooters.first(); if( oldHeaderFooter == headerFooter ) return; } takeHeaderFooter( oldHeaderFooter ); } delete oldHeaderFooter; addHeaderFooter( headerFooter ); } } void Chart::takeHeaderFooter( HeaderFooter* headerFooter ) { const int idx = d->headerFooters.indexOf( headerFooter ); if( idx != -1 ){ d->headerFooters.takeAt( idx ); disconnect( headerFooter, SIGNAL( destroyedHeaderFooter( HeaderFooter* ) ), d, SLOT( slotUnregisterDestroyedHeaderFooter( HeaderFooter* ) ) ); headerFooter->setParent( 0 ); } d->slotRelayout(); // Need to emit the signal: In case somebody has connected the signal // to her own slot for e.g. calling update() on a widget containing the chart. emit propertiesChanged(); } HeaderFooter* Chart::headerFooter() { if( d->headerFooters.isEmpty() ) { return 0; } else { return d->headerFooters.first(); } } HeaderFooterList Chart::headerFooters() { return d->headerFooters; } void Chart::addLegend( Legend* legend ) { if( ! legend ) return; //qDebug() << "adding the legend"; d->legends.append( legend ); legend->setParent( this ); TextAttributes textAttrs( legend->textAttributes() ); KDChart::Measure measure( textAttrs.fontSize() ); measure.setRelativeMode( this, KDChartEnums::MeasureOrientationMinimum ); measure.setValue( 20 ); textAttrs.setFontSize( measure ); legend->setTextAttributes( textAttrs ); textAttrs = legend->titleTextAttributes(); measure.setRelativeMode( this, KDChartEnums::MeasureOrientationMinimum ); measure.setValue( 24 ); textAttrs.setFontSize( measure ); legend->setTitleTextAttributes( textAttrs ); legend->setReferenceArea( this ); /* future: Use relative sizes for the markers too! const uint nMA = Legend::datasetCount(); for( uint iMA = 0; iMA < nMA; ++iMA ){ MarkerAttributes ma( legend->markerAttributes( iMA ) ); ma.setMarkerSize( ... ) legend->setMarkerAttributes( iMA, ma ) } */ connect( legend, SIGNAL( destroyedLegend( Legend* ) ), d, SLOT( slotUnregisterDestroyedLegend( Legend* ) ) ); connect( legend, SIGNAL( positionChanged( AbstractAreaWidget* ) ), d, SLOT( slotLayoutPlanes() ) ); //slotRelayout() ) ); connect( legend, SIGNAL( propertiesChanged() ), this, SIGNAL( propertiesChanged() ) ); legend->setVisible( true ); d->slotRelayout(); } void Chart::replaceLegend( Legend* legend, Legend* oldLegend_ ) { if( legend && oldLegend_ != legend ){ Legend* oldLegend = oldLegend_; if( d->legends.count() ){ if( ! oldLegend ){ oldLegend = d->legends.first(); if( oldLegend == legend ) return; } takeLegend( oldLegend ); } delete oldLegend; addLegend( legend ); } } void Chart::takeLegend( Legend* legend ) { const int idx = d->legends.indexOf( legend ); if( idx != -1 ){ d->legends.takeAt( idx ); disconnect( legend, SIGNAL( destroyedLegend( Legend* ) ), d, SLOT( slotUnregisterDestroyedLegend( Legend* ) ) ); disconnect( legend, SIGNAL( positionChanged( AbstractAreaWidget* ) ), d, SLOT( slotLayoutPlanes() ) ); //slotRelayout() ) ); disconnect( legend, SIGNAL( propertiesChanged() ), this, SIGNAL( propertiesChanged() ) ); legend->setParent( 0 ); legend->setVisible( false ); } d->slotRelayout(); // Need to emit the signal: In case somebody has connected the signal // to her own slot for e.g. calling update() on a widget containing the chart. // Note: // We do this ourselves in examples/DrawIntoPainter/mainwindow.cpp emit propertiesChanged(); } Legend* Chart::legend() { if ( d->legends.isEmpty() ) { return 0; } else { return d->legends.first(); } } LegendList Chart::legends() { return d->legends; } void Chart::mousePressEvent( QMouseEvent* event ) { const QPoint pos = mapFromGlobal( event->globalPos() ); KDAB_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes ) { if ( plane->geometry().contains( event->pos() ) ) { if ( plane->diagrams().size() > 0 ) { QMouseEvent ev( QEvent::MouseButtonPress, pos, event->globalPos(), event->button(), event->buttons(), event->modifiers() ); plane->mousePressEvent( &ev ); d->mouseClickedPlanes.append( plane ); } } } } /* // Unused code trying to use a push-model: This did not work // since we can not re-layout the planes each time when // Qt layouting is calling sizeHint() void Chart::Private::slotAdjustLeftRightColumnsForOverlappingLabels( CartesianAxis* axis, int leftOverlap, int rightOverlap) { const QLayout* axisLayout = axis ? axis->parentLayout() : 0; if( (! leftOverlap && ! rightOverlap) || ! axis || ! axisLayout->parent() ) return; bool needUpdate = false; // access the planeLayout: QGridLayout* grid = qobject_cast(axisLayout->parent()); if( grid ){ // find the index of the parent layout in the planeLayout: int idx = -1; for (int i = 0; i < grid->count(); ++i) if( grid->itemAt(i) == axisLayout ) idx = i; // set the min widths of the neighboring column: if( idx > -1 ){ int row, column, rowSpan, columnSpan; grid->getItemPosition( idx, &row, &column, &rowSpan, &columnSpan ); const int leftColumn = column-1; const int rightColumn = column+columnSpan; // find the left/right axes layouts QHBoxLayout* leftAxesLayout=0; QHBoxLayout* rightAxesLayout=0; for( int i = 0; (!leftAxesLayout || !rightAxesLayout) && i < grid->count(); ++i ) { int r, c, rs, cs; grid->getItemPosition( i, &r, &c, &rs, &cs ); if( c+cs-1 == leftColumn ) leftAxesLayout = dynamic_cast(grid->itemAt(i)); if( c == rightColumn ) rightAxesLayout = dynamic_cast(grid->itemAt(i)); } if( leftAxesLayout ){ const int leftColumnMinWidth = leftOverlap; QLayoutItem* item = leftAxesLayout->count() ? dynamic_cast(leftAxesLayout->itemAt(leftAxesLayout->count()-1)) : 0; QSpacerItem* spacer = dynamic_cast(item); if( spacer ){ if( spacer->sizeHint().width() < leftColumnMinWidth ){ needUpdate = true; spacer->changeSize(leftColumnMinWidth, 1); qDebug() << "adjusted left spacer->sizeHint().width() to" << spacer->sizeHint().width(); } }else{ AbstractAxis* axis = dynamic_cast(item); if( !axis || axis->sizeHint().width() < leftColumnMinWidth ){ needUpdate = true; leftAxesLayout->insertSpacing( -1, leftColumnMinWidth ); qDebug() << "adjusted column" << leftColumn << "min width to" << leftColumnMinWidth; } } } if( rightAxesLayout ){ const int rightColumnMinWidth = rightOverlap; QLayoutItem* item = rightAxesLayout->count() ? dynamic_cast(rightAxesLayout->itemAt(0)) : 0; QSpacerItem* spacer = dynamic_cast(item); if( spacer ){ if( spacer->sizeHint().width() < rightColumnMinWidth ){ needUpdate = true; spacer->changeSize(rightColumnMinWidth, 1); qDebug() << "adjusted right spacer->sizeHint().width() to" << spacer->sizeHint().width(); } }else{ AbstractAxis* axis = dynamic_cast(item); if( !axis || axis->sizeHint().width() < rightColumnMinWidth ){ needUpdate = true; rightAxesLayout->insertSpacing( 0, rightColumnMinWidth ); qDebug() << "adjusted column" << rightColumn << "min width to" << rightColumnMinWidth; } } } } } if( needUpdate ){ ;// do something ...? } } void Chart::Private::slotAdjustTopBottomRowsForOverlappingLabels( CartesianAxis* axis, int topOverlap, int bottomOverlap) { const QLayout* axisLayout = axis ? axis->parentLayout() : 0; if( (! topOverlap && ! bottomOverlap) || ! axisLayout || ! axisLayout->parent() ) return; // access the planeLayout: QGridLayout* grid = qobject_cast(axisLayout->parent()); if( grid ){ // find the index of the parent layout in the planeLayout: int idx = -1; for (int i = 0; i < grid->count(); ++i) if( grid->itemAt(i) == axisLayout ) idx = i; // set the min widths of the neighboring column: if( idx > -1 ){ int row, column, rowSpan, columnSpan; grid->getItemPosition( idx, &row, &column, &rowSpan, &columnSpan ); const int topRow = row-1; const int bottomRow = row+rowSpan; // find the left/right axes layouts QVBoxLayout* topAxesLayout=0; QVBoxLayout* bottomAxesLayout=0; for( int i = 0; (!topAxesLayout || !bottomAxesLayout) && i < grid->count(); ++i ) { int r, c, rs, cs; grid->getItemPosition( i, &r, &c, &rs, &cs ); if( r+rs-1 == topRow ) topAxesLayout = dynamic_cast(grid->itemAt(i)); if( r == bottomRow ) bottomAxesLayout = dynamic_cast(grid->itemAt(i)); } if( topAxesLayout ){ const int topRowMinWidth = topOverlap; QLayoutItem* item = topAxesLayout->count() ? dynamic_cast(topAxesLayout->itemAt(topAxesLayout->count()-1)) : 0; QSpacerItem* spacer = dynamic_cast(item); if( spacer ){ if( spacer->sizeHint().height() < topRowMinWidth ){ spacer->changeSize(1, topRowMinWidth); qDebug() << "adjusted top spacer->sizeHint().height() to" << spacer->sizeHint().height(); } }else{ AbstractAxis* axis = dynamic_cast(item); if( !axis || axis->sizeHint().height() < topRowMinWidth ){ topAxesLayout->insertSpacing( -1, topRowMinWidth ); qDebug() << "adjusted row" << topRow << "min height to" << topRowMinWidth; } } } if( bottomAxesLayout ){ const int bottomRowMinWidth = bottomOverlap; QLayoutItem* item = bottomAxesLayout->count() ? dynamic_cast(bottomAxesLayout->itemAt(0)) : 0; QSpacerItem* spacer = dynamic_cast(item); if( spacer ){ if( spacer->sizeHint().height() < bottomRowMinWidth ){ spacer->changeSize(1, bottomRowMinWidth); qDebug() << "adjusted bottom spacer->sizeHint().height() to" << spacer->sizeHint().height(); } }else{ AbstractAxis* axis = dynamic_cast(item); if( !axis || axis->sizeHint().height() < bottomRowMinWidth ){ bottomAxesLayout->insertSpacing( 0, bottomRowMinWidth ); qDebug() << "adjusted row" << bottomRow << "min height to" << bottomRowMinWidth; } } } } } } */ void Chart::mouseDoubleClickEvent( QMouseEvent* event ) { const QPoint pos = mapFromGlobal( event->globalPos() ); KDAB_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes ) { if ( plane->geometry().contains( event->pos() ) ) { if ( plane->diagrams().size() > 0 ) { QMouseEvent ev( QEvent::MouseButtonPress, pos, event->globalPos(), event->button(), event->buttons(), event->modifiers() ); plane->mouseDoubleClickEvent( &ev ); } } } } void Chart::mouseMoveEvent( QMouseEvent* event ) { QSet< AbstractCoordinatePlane* > eventReceivers = QSet< AbstractCoordinatePlane* >::fromList( d->mouseClickedPlanes ); KDAB_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes ) { if( plane->geometry().contains( event->pos() ) ) { if( plane->diagrams().size() > 0 ) { eventReceivers.insert( plane ); } } } const QPoint pos = mapFromGlobal( event->globalPos() ); KDAB_FOREACH( AbstractCoordinatePlane* plane, eventReceivers ) { QMouseEvent ev( QEvent::MouseMove, pos, event->globalPos(), event->button(), event->buttons(), event->modifiers() ); plane->mouseMoveEvent( &ev ); } } void Chart::mouseReleaseEvent( QMouseEvent* event ) { QSet< AbstractCoordinatePlane* > eventReceivers = QSet< AbstractCoordinatePlane* >::fromList( d->mouseClickedPlanes ); KDAB_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes ) { if ( plane->geometry().contains( event->pos() ) ) { if( plane->diagrams().size() > 0 ) { eventReceivers.insert( plane ); } } } const QPoint pos = mapFromGlobal( event->globalPos() ); KDAB_FOREACH( AbstractCoordinatePlane* plane, eventReceivers ) { QMouseEvent ev( QEvent::MouseButtonRelease, pos, event->globalPos(), event->button(), event->buttons(), event->modifiers() ); plane->mouseReleaseEvent( &ev ); } d->mouseClickedPlanes.clear(); } bool Chart::event( QEvent* event ) { switch( event->type() ) { case QEvent::ToolTip: { const QHelpEvent* const helpEvent = static_cast< QHelpEvent* >( event ); KDAB_FOREACH( const AbstractCoordinatePlane* const plane, d->coordinatePlanes ) { for (int i = plane->diagrams().count() - 1; i >= 0; --i) { const QModelIndex index = plane->diagrams().at(i)->indexAt( helpEvent->pos() ); const QVariant toolTip = index.data( Qt::ToolTipRole ); if( toolTip.isValid() ) { QPoint pos = mapFromGlobal(helpEvent->pos()); QRect rect(pos-QPoint(1,1), QSize(3,3)); QToolTip::showText( QCursor::pos(), toolTip.toString(), this, rect ); return true; } } } // fall-through intended } default: return QWidget::event( event ); } }