File indexing completed on 2024-05-12 04:20:32
0001 /* 0002 * SPDX-FileCopyrightText: 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. 0003 * 0004 * This file is part of the KD Chart library. 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "KChartChart.h" 0010 #include "KChartChart_p.h" 0011 0012 #include <QList> 0013 #include <QtDebug> 0014 #include <QGridLayout> 0015 #include <QLabel> 0016 #include <QHash> 0017 #include <QToolTip> 0018 #include <QPainter> 0019 #include <QPaintEvent> 0020 #include <QLayoutItem> 0021 #include <QPushButton> 0022 #include <QApplication> 0023 #include <QEvent> 0024 0025 #include "KChartCartesianCoordinatePlane.h" 0026 #include "KChartAbstractCartesianDiagram.h" 0027 #include "KChartHeaderFooter.h" 0028 #include "KChartEnums.h" 0029 #include "KChartLegend.h" 0030 #include "KChartLayoutItems.h" 0031 #include <KChartTextAttributes.h> 0032 #include <KChartMarkerAttributes.h> 0033 #include "KChartPainterSaver_p.h" 0034 #include "KChartPrintingParameters.h" 0035 0036 #include <algorithm> 0037 0038 #if defined KDAB_EVAL 0039 #include "../evaldialog/evaldialog.h" 0040 #endif 0041 0042 #if 0 0043 // dumpLayoutTree dumps a QLayout tree in a hopefully easy to read format to stderr - feel free to 0044 // use, improve and extend; it is very useful for looking at any layout problem. 0045 0046 #include <typeinfo> 0047 0048 // this is this different from both QRect::isEmpty() and QRect::isNull() for "wrong" QRects, 0049 // i.e. those where topLeft() is actually below and / or right of bottomRight(). 0050 static bool isZeroArea(const QRect &r) 0051 { 0052 return !r.width() || !r.height(); 0053 } 0054 0055 static QString lineProlog(int nestingDepth, int lineno) 0056 { 0057 QString numbering(QString::number(lineno).rightJustified(5).append(QChar::fromAscii(':'))); 0058 QString indent(nestingDepth * 4, QLatin1Char(' ')); 0059 return numbering + indent; 0060 } 0061 0062 static void dumpLayoutTreeRecurse(QLayout *l, int *counter, int depth) 0063 { 0064 const QLatin1String colorOn(isZeroArea(l->geometry()) ? "\033[0m" : "\033[32m"); 0065 const QLatin1String colorOff("\033[0m"); 0066 0067 QString prolog = lineProlog(depth, *counter); 0068 (*counter)++; 0069 0070 qDebug() << colorOn + prolog << l->metaObject()->className() << l->geometry() 0071 << "hint" << l->sizeHint() 0072 << l->hasHeightForWidth() << "min" << l->minimumSize() 0073 << "max" << l->maximumSize() 0074 << l->expandingDirections() << l->alignment() 0075 << colorOff; 0076 for (int i = 0; i < l->count(); i++) { 0077 QLayoutItem *child = l->itemAt(i); 0078 if (QLayout *childL = child->layout()) { 0079 dumpLayoutTreeRecurse(childL, counter, depth + 1); 0080 } else { 0081 // The isZeroArea check culls usually largely useless output - you might want to remove it in 0082 // some debugging situations. Add a boolean parameter to this and dumpLayoutTree() if you do. 0083 if (!isZeroArea(child->geometry())) { 0084 prolog = lineProlog(depth + 1, *counter); 0085 (*counter)++; 0086 qDebug() << colorOn + prolog << typeid(*child).name() << child->geometry() 0087 << "hint" << child->sizeHint() 0088 << child->hasHeightForWidth() << "min" << child->minimumSize() 0089 << "max" << child->maximumSize() 0090 << child->expandingDirections() << child->alignment() 0091 << colorOff; 0092 } 0093 } 0094 } 0095 } 0096 0097 static void dumpLayoutTree(QLayout *l) 0098 { 0099 int counter = 0; 0100 dumpLayoutTreeRecurse(l, &counter, 0); 0101 } 0102 #endif 0103 0104 static const Qt::Alignment s_gridAlignments[ 3 ][ 3 ] = { // [ row ][ column ] 0105 { Qt::AlignTop | Qt::AlignLeft, Qt::AlignTop | Qt::AlignHCenter, Qt::AlignTop | Qt::AlignRight }, 0106 { Qt::AlignVCenter | Qt::AlignLeft, Qt::AlignVCenter | Qt::AlignHCenter, Qt::AlignVCenter | Qt::AlignRight }, 0107 { Qt::AlignBottom | Qt::AlignLeft, Qt::AlignBottom | Qt::AlignHCenter, Qt::AlignBottom | Qt::AlignRight } 0108 }; 0109 0110 static void getRowAndColumnForPosition(KChartEnums::PositionValue pos, int* row, int* column) 0111 { 0112 switch ( pos ) { 0113 case KChartEnums::PositionNorthWest: *row = 0; *column = 0; 0114 break; 0115 case KChartEnums::PositionNorth: *row = 0; *column = 1; 0116 break; 0117 case KChartEnums::PositionNorthEast: *row = 0; *column = 2; 0118 break; 0119 case KChartEnums::PositionEast: *row = 1; *column = 2; 0120 break; 0121 case KChartEnums::PositionSouthEast: *row = 2; *column = 2; 0122 break; 0123 case KChartEnums::PositionSouth: *row = 2; *column = 1; 0124 break; 0125 case KChartEnums::PositionSouthWest: *row = 2; *column = 0; 0126 break; 0127 case KChartEnums::PositionWest: *row = 1; *column = 0; 0128 break; 0129 case KChartEnums::PositionCenter: *row = 1; *column = 1; 0130 break; 0131 default: *row = -1; *column = -1; 0132 break; 0133 } 0134 } 0135 0136 using namespace KChart; 0137 0138 // Layout widgets even if they are not visible (that's why isEmpty() is overridden) - at least that 0139 // was the original reason... 0140 class MyWidgetItem : public QWidgetItem 0141 { 0142 public: 0143 explicit MyWidgetItem(QWidget *w, Qt::Alignment alignment = Qt::Alignment()) 0144 : QWidgetItem( w ) 0145 { 0146 setAlignment( alignment ); 0147 } 0148 0149 // All of the methods are reimplemented from QWidgetItem, and work around some oddity in QLayout and / or 0150 // KD Chart - I forgot the details between writing this code as an experiment and committing it, very 0151 // sorry about that! 0152 // Feel free to comment out any of them and then try the line-breaking feature in horizontal legends in 0153 // the Legends/Advanced example. It will not work well in various ways - won't get enough space and look 0154 // very broken, will inhibit resizing the window etc. 0155 0156 QSize sizeHint() const override 0157 { 0158 QWidget* w = const_cast< MyWidgetItem * >( this )->widget(); 0159 return w->sizeHint(); 0160 } 0161 0162 QSize minimumSize() const override 0163 { 0164 QWidget* w = const_cast< MyWidgetItem * >( this )->widget(); 0165 return w->minimumSize(); 0166 } 0167 0168 QSize maximumSize() const override 0169 { 0170 // Not just passing on w->maximumSize() fixes that the size policy of Legend is disregarded, making 0171 // Legend take all available space, which makes both the Legend internal layout and the overall 0172 // layout of chart + legend look bad. QWidget::maximumSize() is not a virtual method, it's a 0173 // property, so "overriding" that one would be even uglier, and prevent user-set property 0174 // values from doing anything. 0175 QWidget* w = const_cast< MyWidgetItem * >( this )->widget(); 0176 QSize ret = w->maximumSize(); 0177 const QSize hint = w->sizeHint(); 0178 const QSizePolicy::Policy hPolicy = w->sizePolicy().horizontalPolicy(); 0179 if (hPolicy == QSizePolicy::Fixed || hPolicy == QSizePolicy::Maximum) { 0180 ret.rwidth() = hint.width(); 0181 } 0182 const QSizePolicy::Policy vPolicy = w->sizePolicy().verticalPolicy(); 0183 if (vPolicy == QSizePolicy::Fixed || vPolicy == QSizePolicy::Maximum) { 0184 ret.rheight() = hint.height(); 0185 } 0186 return ret; 0187 } 0188 0189 Qt::Orientations expandingDirections() const override 0190 { 0191 QWidget* w = const_cast< MyWidgetItem * >( this )->widget(); 0192 if ( isEmpty() ) { 0193 return Qt::Orientations(); 0194 } 0195 Qt::Orientations e = w->sizePolicy().expandingDirections(); 0196 return e; 0197 } 0198 0199 void setGeometry(const QRect &g) override 0200 { 0201 QWidget* w = const_cast< MyWidgetItem * >( this )->widget(); 0202 w->setGeometry(g); 0203 } 0204 0205 QRect geometry() const override 0206 { 0207 QWidget* w = const_cast< MyWidgetItem * >( this )->widget(); 0208 return w->geometry(); 0209 } 0210 0211 bool hasHeightForWidth() const override 0212 { 0213 QWidget* w = const_cast< MyWidgetItem * >( this )->widget(); 0214 bool ret = !isEmpty() && 0215 qobject_cast< Legend* >( w )->hasHeightForWidth(); 0216 return ret; 0217 } 0218 0219 int heightForWidth( int width ) const override 0220 { 0221 QWidget* w = const_cast< MyWidgetItem * >( this )->widget(); 0222 int ret = w->heightForWidth( width ); 0223 return ret; 0224 } 0225 0226 bool isEmpty() const override { 0227 QWidget* w = const_cast< MyWidgetItem * >( this )->widget(); 0228 // legend->hide() should indeed hide the legend, 0229 // but a legend in a chart that hasn't been shown yet isn't hidden 0230 // (as can happen when using Chart::paint() without showing the chart) 0231 return w->isHidden() && w->testAttribute( Qt::WA_WState_ExplicitShowHide ); 0232 } 0233 }; 0234 0235 // When "abusing" QLayouts to lay out items with different geometry from the backing QWidgets, 0236 // some manual work is required to correctly update all the sublayouts. 0237 // This is because all the convenient ways to deal with QLayouts assume QWidgets somewhere. 0238 // What this does is somewhat similar to QLayout::activate(), but it never refers to the parent 0239 // QWidget which has the wrong geometry. 0240 static void invalidateLayoutTree( QLayoutItem *item ) 0241 { 0242 QLayout *layout = item->layout(); 0243 if ( layout ) { 0244 const int count = layout->count(); 0245 for ( int i = 0; i < count; i++ ) { 0246 invalidateLayoutTree( layout->itemAt( i ) ); 0247 } 0248 } 0249 item->invalidate(); 0250 } 0251 0252 void Chart::Private::slotUnregisterDestroyedLegend( Legend *l ) 0253 { 0254 chart->takeLegend( l ); 0255 } 0256 0257 void Chart::Private::slotUnregisterDestroyedHeaderFooter( HeaderFooter* hf ) 0258 { 0259 chart->takeHeaderFooter( hf ); 0260 } 0261 0262 void Chart::Private::slotUnregisterDestroyedPlane( AbstractCoordinatePlane* plane ) 0263 { 0264 coordinatePlanes.removeAll( plane ); 0265 for ( AbstractCoordinatePlane* p : qAsConst(coordinatePlanes) ) { 0266 if ( p->referenceCoordinatePlane() == plane) { 0267 p->setReferenceCoordinatePlane( nullptr ); 0268 } 0269 } 0270 plane->layoutPlanes(); 0271 } 0272 0273 Chart::Private::Private( Chart* chart_ ) 0274 : chart( chart_ ) 0275 , useNewLayoutSystem( false ) 0276 , layout(nullptr) 0277 , vLayout(nullptr) 0278 , planesLayout(nullptr) 0279 , headerLayout(nullptr) 0280 , footerLayout(nullptr) 0281 , dataAndLegendLayout(nullptr) 0282 , leftOuterSpacer(nullptr) 0283 , rightOuterSpacer(nullptr) 0284 , topOuterSpacer(nullptr) 0285 , bottomOuterSpacer(nullptr) 0286 , isFloatingLegendsLayoutDirty( true ) 0287 , isPlanesLayoutDirty( true ) 0288 , globalLeadingLeft(0) 0289 , globalLeadingRight(0) 0290 , globalLeadingTop(0) 0291 , globalLeadingBottom(0) 0292 { 0293 for ( int row = 0; row < 3; ++row ) { 0294 for ( int column = 0; column < 3; ++column ) { 0295 for ( int i = 0; i < 2; i++ ) { 0296 innerHdFtLayouts[ i ][ row ][ column ] = nullptr; 0297 } 0298 } 0299 } 0300 } 0301 0302 Chart::Private::~Private() 0303 { 0304 } 0305 0306 enum VisitorState{ Visited, Unknown }; 0307 struct ConnectedComponentsComparator{ 0308 bool operator()( const LayoutGraphNode *lhs, const LayoutGraphNode *rhs ) const 0309 { 0310 return lhs->priority < rhs->priority; 0311 } 0312 }; 0313 0314 static QVector< LayoutGraphNode* > getPrioritySortedConnectedComponents( QVector< LayoutGraphNode* > &nodeList ) 0315 { 0316 QVector< LayoutGraphNode* >connectedComponents; 0317 QHash< LayoutGraphNode*, VisitorState > visitedComponents; 0318 for ( LayoutGraphNode* node : nodeList ) 0319 visitedComponents[ node ] = Unknown; 0320 for ( int i = 0; i < nodeList.size(); ++i ) 0321 { 0322 LayoutGraphNode *curNode = nodeList[ i ]; 0323 LayoutGraphNode *representativeNode = curNode; 0324 if ( visitedComponents[ curNode ] != Visited ) 0325 { 0326 QStack< LayoutGraphNode* > stack; 0327 stack.push( curNode ); 0328 while ( !stack.isEmpty() ) 0329 { 0330 curNode = stack.pop(); 0331 Q_ASSERT( visitedComponents[ curNode ] != Visited ); 0332 visitedComponents[ curNode ] = Visited; 0333 if ( curNode->bottomSuccesor && visitedComponents[ curNode->bottomSuccesor ] != Visited ) 0334 stack.push( curNode->bottomSuccesor ); 0335 if ( curNode->leftSuccesor && visitedComponents[ curNode->leftSuccesor ] != Visited ) 0336 stack.push( curNode->leftSuccesor ); 0337 if ( curNode->sharedSuccesor && visitedComponents[ curNode->sharedSuccesor ] != Visited ) 0338 stack.push( curNode->sharedSuccesor ); 0339 if ( curNode->priority < representativeNode->priority ) 0340 representativeNode = curNode; 0341 } 0342 connectedComponents.append( representativeNode ); 0343 } 0344 } 0345 std::sort( connectedComponents.begin(), connectedComponents.end(), ConnectedComponentsComparator() ); 0346 return connectedComponents; 0347 } 0348 0349 struct PriorityComparator{ 0350 public: 0351 PriorityComparator( QHash< AbstractCoordinatePlane*, LayoutGraphNode* > mapping ) 0352 : m_mapping( mapping ) 0353 {} 0354 bool operator() ( AbstractCoordinatePlane *lhs, AbstractCoordinatePlane *rhs ) const 0355 { 0356 const LayoutGraphNode *lhsNode = m_mapping[ lhs ]; 0357 Q_ASSERT( lhsNode ); 0358 const LayoutGraphNode *rhsNode = m_mapping[ rhs ]; 0359 Q_ASSERT( rhsNode ); 0360 return lhsNode->priority < rhsNode->priority; 0361 } 0362 0363 const QHash< AbstractCoordinatePlane*, LayoutGraphNode* > m_mapping; 0364 }; 0365 0366 void checkExistingAxes( LayoutGraphNode* node ) 0367 { 0368 if ( node && node->diagramPlane && node->diagramPlane->diagram() ) 0369 { 0370 AbstractCartesianDiagram *diag = qobject_cast< AbstractCartesianDiagram* >( node->diagramPlane->diagram() ); 0371 if ( diag ) 0372 { 0373 const CartesianAxisList axes = diag->axes(); 0374 for ( const CartesianAxis* axis : axes ) 0375 { 0376 switch ( axis->position() ) 0377 { 0378 case( CartesianAxis::Top ): 0379 node->topAxesLayout = true; 0380 break; 0381 case( CartesianAxis::Bottom ): 0382 node->bottomAxesLayout = true; 0383 break; 0384 case( CartesianAxis::Left ): 0385 node->leftAxesLayout = true; 0386 break; 0387 case( CartesianAxis::Right ): 0388 node->rightAxesLayout = true; 0389 break; 0390 } 0391 } 0392 } 0393 } 0394 } 0395 0396 static void mergeNodeAxisInformation( LayoutGraphNode* lhs, LayoutGraphNode* rhs ) 0397 { 0398 lhs->topAxesLayout |= rhs->topAxesLayout; 0399 rhs->topAxesLayout = lhs->topAxesLayout; 0400 0401 lhs->bottomAxesLayout |= rhs->bottomAxesLayout; 0402 rhs->bottomAxesLayout = lhs->bottomAxesLayout; 0403 0404 lhs->leftAxesLayout |= rhs->leftAxesLayout; 0405 rhs->leftAxesLayout = lhs->leftAxesLayout; 0406 0407 lhs->rightAxesLayout |= rhs->rightAxesLayout; 0408 rhs->rightAxesLayout = lhs->rightAxesLayout; 0409 } 0410 0411 static CoordinatePlaneList findSharingAxisDiagrams( AbstractCoordinatePlane* plane, 0412 const CoordinatePlaneList& list, 0413 Chart::Private::AxisType type, 0414 QVector< CartesianAxis* >* sharedAxes ) 0415 { 0416 if ( !plane || !plane->diagram() ) 0417 return CoordinatePlaneList(); 0418 Q_ASSERT( plane ); 0419 Q_ASSERT( plane->diagram() ); 0420 CoordinatePlaneList result; 0421 AbstractCartesianDiagram* diagram = qobject_cast< AbstractCartesianDiagram* >( plane->diagram() ); 0422 if ( !diagram ) 0423 return CoordinatePlaneList(); 0424 0425 QList< CartesianAxis* > axes; 0426 for ( CartesianAxis* axis : diagram->axes() ) { 0427 if ( ( type == Chart::Private::Ordinate && 0428 ( axis->position() == CartesianAxis::Left || axis->position() == CartesianAxis::Right ) ) 0429 || 0430 ( type == Chart::Private::Abscissa && 0431 ( axis->position() == CartesianAxis::Top || axis->position() == CartesianAxis::Bottom ) ) ) { 0432 axes.append( axis ); 0433 } 0434 } 0435 for( AbstractCoordinatePlane *curPlane : list ) 0436 { 0437 AbstractCartesianDiagram* diagram = 0438 qobject_cast< AbstractCartesianDiagram* > ( curPlane->diagram() ); 0439 if ( !diagram ) 0440 continue; 0441 for ( CartesianAxis* curSearchedAxis : qAsConst(axes) ) 0442 { 0443 for( CartesianAxis* curAxis : diagram->axes() ) 0444 { 0445 if ( curSearchedAxis == curAxis ) 0446 { 0447 result.append( curPlane ); 0448 if ( !sharedAxes->contains( curSearchedAxis ) ) 0449 sharedAxes->append( curSearchedAxis ); 0450 } 0451 } 0452 } 0453 } 0454 0455 return result; 0456 } 0457 0458 /* 0459 * this method determines the needed layout of the graph 0460 * taking care of the sharing problematic 0461 * its NOT allowed to have a diagram that shares 0462 * more than one axis in the same direction 0463 */ 0464 QVector< LayoutGraphNode* > Chart::Private::buildPlaneLayoutGraph() 0465 { 0466 QHash< AbstractCoordinatePlane*, LayoutGraphNode* > planeNodeMapping; 0467 QVector< LayoutGraphNode* > allNodes; 0468 // create all nodes and a mapping between plane and nodes 0469 for ( AbstractCoordinatePlane* curPlane : qAsConst(coordinatePlanes) ) 0470 { 0471 if ( curPlane->diagram() ) 0472 { 0473 allNodes.append( new LayoutGraphNode ); 0474 allNodes[ allNodes.size() - 1 ]->diagramPlane = curPlane; 0475 allNodes[ allNodes.size() - 1 ]->priority = allNodes.size(); 0476 checkExistingAxes( allNodes[ allNodes.size() - 1 ] ); 0477 planeNodeMapping[ curPlane ] = allNodes[ allNodes.size() - 1 ]; 0478 } 0479 } 0480 // build the graph connections 0481 for ( LayoutGraphNode* curNode : qAsConst(allNodes) ) 0482 { 0483 QVector< CartesianAxis* > sharedAxes; 0484 CoordinatePlaneList xSharedPlanes = findSharingAxisDiagrams( curNode->diagramPlane, coordinatePlanes, Abscissa, &sharedAxes ); 0485 Q_ASSERT( sharedAxes.size() < 2 ); 0486 // TODO duplicated code make a method out of it 0487 if ( sharedAxes.size() == 1 && xSharedPlanes.size() > 1 ) 0488 { 0489 //xSharedPlanes.removeAll( sharedAxes.first()->diagram()->coordinatePlane() ); 0490 //std::sort( xSharedPlanes.begin(), xSharedPlanes.end(), PriorityComparator( planeNodeMapping ) ); 0491 for ( int i = 0; i < xSharedPlanes.size() - 1; ++i ) 0492 { 0493 LayoutGraphNode *tmpNode = planeNodeMapping[ xSharedPlanes[ i ] ]; 0494 Q_ASSERT( tmpNode ); 0495 LayoutGraphNode *tmpNode2 = planeNodeMapping[ xSharedPlanes[ i + 1 ] ]; 0496 Q_ASSERT( tmpNode2 ); 0497 tmpNode->bottomSuccesor = tmpNode2; 0498 } 0499 // if ( sharedAxes.first()->diagram() && sharedAxes.first()->diagram()->coordinatePlane() ) 0500 // { 0501 // LayoutGraphNode *lastNode = planeNodeMapping[ xSharedPlanes.last() ]; 0502 // Q_ASSERT( lastNode ); 0503 // Q_ASSERT( sharedAxes.first()->diagram()->coordinatePlane() ); 0504 // LayoutGraphNode *ownerNode = planeNodeMapping[ sharedAxes.first()->diagram()->coordinatePlane() ]; 0505 // Q_ASSERT( ownerNode ); 0506 // lastNode->bottomSuccesor = ownerNode; 0507 // } 0508 //merge AxisInformation, needs a two pass run 0509 LayoutGraphNode axisInfoNode; 0510 for ( int count = 0; count < 2; ++count ) 0511 { 0512 for ( int i = 0; i < xSharedPlanes.size(); ++i ) 0513 { 0514 mergeNodeAxisInformation( &axisInfoNode, planeNodeMapping[ xSharedPlanes[ i ] ] ); 0515 } 0516 } 0517 } 0518 sharedAxes.clear(); 0519 CoordinatePlaneList ySharedPlanes = findSharingAxisDiagrams( curNode->diagramPlane, coordinatePlanes, Ordinate, &sharedAxes ); 0520 Q_ASSERT( sharedAxes.size() < 2 ); 0521 if ( sharedAxes.size() == 1 && ySharedPlanes.size() > 1 ) 0522 { 0523 //ySharedPlanes.removeAll( sharedAxes.first()->diagram()->coordinatePlane() ); 0524 //std::sort( ySharedPlanes.begin(), ySharedPlanes.end(), PriorityComparator( planeNodeMapping ) ); 0525 for ( int i = 0; i < ySharedPlanes.size() - 1; ++i ) 0526 { 0527 LayoutGraphNode *tmpNode = planeNodeMapping[ ySharedPlanes[ i ] ]; 0528 Q_ASSERT( tmpNode ); 0529 LayoutGraphNode *tmpNode2 = planeNodeMapping[ ySharedPlanes[ i + 1 ] ]; 0530 Q_ASSERT( tmpNode2 ); 0531 tmpNode->leftSuccesor = tmpNode2; 0532 } 0533 // if ( sharedAxes.first()->diagram() && sharedAxes.first()->diagram()->coordinatePlane() ) 0534 // { 0535 // LayoutGraphNode *lastNode = planeNodeMapping[ ySharedPlanes.last() ]; 0536 // Q_ASSERT( lastNode ); 0537 // Q_ASSERT( sharedAxes.first()->diagram()->coordinatePlane() ); 0538 // LayoutGraphNode *ownerNode = planeNodeMapping[ sharedAxes.first()->diagram()->coordinatePlane() ]; 0539 // Q_ASSERT( ownerNode ); 0540 // lastNode->bottomSuccesor = ownerNode; 0541 // } 0542 //merge AxisInformation, needs a two pass run 0543 LayoutGraphNode axisInfoNode; 0544 for ( int count = 0; count < 2; ++count ) 0545 { 0546 for ( int i = 0; i < ySharedPlanes.size(); ++i ) 0547 { 0548 mergeNodeAxisInformation( &axisInfoNode, planeNodeMapping[ ySharedPlanes[ i ] ] ); 0549 } 0550 } 0551 } 0552 sharedAxes.clear(); 0553 if ( curNode->diagramPlane->referenceCoordinatePlane() ) 0554 curNode->sharedSuccesor = planeNodeMapping[ curNode->diagramPlane->referenceCoordinatePlane() ]; 0555 } 0556 0557 return allNodes; 0558 } 0559 0560 QHash<AbstractCoordinatePlane*, PlaneInfo> Chart::Private::buildPlaneLayoutInfos() 0561 { 0562 /* There are two ways in which planes can be caused to interact in 0563 * where they are put layouting wise: The first is the reference plane. If 0564 * such a reference plane is set, on a plane, it will use the same cell in the 0565 * layout as that one. In addition to this, planes can share an axis. In that case 0566 * they will be laid out in relation to each other as suggested by the position 0567 * of the axis. If, for example Plane1 and Plane2 share an axis at position Left, 0568 * that will result in the layout: Axis Plane1 Plane 2, vertically. If Plane1 0569 * also happens to be Plane2's reference plane, both planes are drawn over each 0570 * other. The reference plane concept allows two planes to share the same space 0571 * even if neither has any axis, and in case there are shared axis, it is used 0572 * to decided, whether the planes should be painted on top of each other or 0573 * laid out vertically or horizontally next to each other. */ 0574 QHash<CartesianAxis*, AxisInfo> axisInfos; 0575 QHash<AbstractCoordinatePlane*, PlaneInfo> planeInfos; 0576 for (AbstractCoordinatePlane* plane : qAsConst(coordinatePlanes) ) { 0577 PlaneInfo p; 0578 // first check if we share space with another plane 0579 p.referencePlane = plane->referenceCoordinatePlane(); 0580 planeInfos.insert( plane, p ); 0581 0582 0583 const auto diagrams = plane->diagrams(); 0584 for ( AbstractDiagram* abstractDiagram : diagrams ) { 0585 AbstractCartesianDiagram* diagram = 0586 qobject_cast<AbstractCartesianDiagram*> ( abstractDiagram ); 0587 if ( !diagram ) { 0588 continue; 0589 } 0590 0591 const CartesianAxisList axes = diagram->axes(); 0592 for ( CartesianAxis* axis : axes ) { 0593 if ( !axisInfos.contains( axis ) ) { 0594 /* If this is the first time we see this axis, add it, with the 0595 * current plane. The first plane added to the chart that has 0596 * the axis associated with it thus "owns" it, and decides about 0597 * layout. */ 0598 AxisInfo i; 0599 i.plane = plane; 0600 axisInfos.insert( axis, i ); 0601 } else { 0602 AxisInfo i = axisInfos[axis]; 0603 if ( i.plane == plane ) { 0604 continue; // we don't want duplicates, only shared 0605 } 0606 0607 /* The user expects diagrams to be added on top, and to the right 0608 * so that horizontally we need to move the new diagram, vertically 0609 * the reference one. */ 0610 PlaneInfo pi = planeInfos[plane]; 0611 // plane-to-plane linking overrides linking via axes 0612 if ( !pi.referencePlane ) { 0613 // we're not the first plane to see this axis, mark us as a slave 0614 pi.referencePlane = i.plane; 0615 if ( axis->position() == CartesianAxis::Left || 0616 axis->position() == CartesianAxis::Right ) { 0617 pi.horizontalOffset += 1; 0618 } 0619 planeInfos[plane] = pi; 0620 0621 pi = planeInfos[i.plane]; 0622 if ( axis->position() == CartesianAxis::Top || 0623 axis->position() == CartesianAxis::Bottom ) { 0624 pi.verticalOffset += 1; 0625 } 0626 0627 planeInfos[i.plane] = pi; 0628 } 0629 } 0630 } 0631 } 0632 // Create a new grid layout for each plane that has no reference. 0633 p = planeInfos[plane]; 0634 if ( p.referencePlane == nullptr ) { 0635 p.gridLayout = new QGridLayout(); 0636 p.gridLayout->setContentsMargins( 0, 0, 0, 0 ); 0637 planeInfos[plane] = p; 0638 } 0639 } 0640 return planeInfos; 0641 } 0642 0643 void Chart::Private::slotLayoutPlanes() 0644 { 0645 /*TODO make sure this is really needed */ 0646 const QBoxLayout::Direction oldPlanesDirection = planesLayout ? planesLayout->direction() 0647 : QBoxLayout::TopToBottom; 0648 if ( planesLayout && dataAndLegendLayout ) 0649 dataAndLegendLayout->removeItem( planesLayout ); 0650 0651 const bool hadPlanesLayout = planesLayout != nullptr; 0652 int left, top, right, bottom; 0653 if ( hadPlanesLayout ) 0654 planesLayout->getContentsMargins(&left, &top, &right, &bottom); 0655 0656 for ( AbstractLayoutItem* plane : qAsConst(planeLayoutItems) ) { 0657 plane->removeFromParentLayout(); 0658 } 0659 //TODO they should get a correct parent, but for now it works 0660 for ( AbstractLayoutItem* plane : qAsConst(planeLayoutItems) ) { 0661 if ( dynamic_cast< AutoSpacerLayoutItem* >( plane ) ) 0662 delete plane; 0663 } 0664 0665 planeLayoutItems.clear(); 0666 delete planesLayout; 0667 //hint: The direction is configurable by the user now, as 0668 // we are using a QBoxLayout rather than a QVBoxLayout. (khz, 2007/04/25) 0669 planesLayout = new QBoxLayout( oldPlanesDirection ); 0670 0671 isPlanesLayoutDirty = true; // here we create the layouts; we need to "run" them before painting 0672 0673 if ( useNewLayoutSystem ) 0674 { 0675 gridPlaneLayout = new QGridLayout; 0676 planesLayout->addLayout( gridPlaneLayout ); 0677 0678 if (hadPlanesLayout) 0679 planesLayout->setContentsMargins(left, top, right, bottom); 0680 planesLayout->setObjectName( QString::fromLatin1( "planesLayout" ) ); 0681 0682 /* First go through all planes and all axes and figure out whether the planes 0683 * need to coordinate. If they do, they share a grid layout, if not, each 0684 * get their own. See buildPlaneLayoutInfos() for more details. */ 0685 0686 QVector< LayoutGraphNode* > vals = buildPlaneLayoutGraph(); 0687 //qDebug() << Q_FUNC_INFO << "GraphNodes" << vals.size(); 0688 QVector< LayoutGraphNode* > connectedComponents = getPrioritySortedConnectedComponents( vals ); 0689 //qDebug() << Q_FUNC_INFO << "SubGraphs" << connectedComponents.size(); 0690 int row = 0; 0691 int col = 0; 0692 QSet< CartesianAxis* > laidOutAxes; 0693 for ( int i = 0; i < connectedComponents.size(); ++i ) 0694 { 0695 LayoutGraphNode *curComponent = connectedComponents[ i ]; 0696 for ( LayoutGraphNode *curRowComponent = curComponent; curRowComponent; curRowComponent = curRowComponent->bottomSuccesor ) 0697 { 0698 col = 0; 0699 for ( LayoutGraphNode *curColComponent = curRowComponent; curColComponent; curColComponent = curColComponent->leftSuccesor ) 0700 { 0701 Q_ASSERT( curColComponent->diagramPlane->diagrams().size() == 1 ); 0702 const auto diags{curColComponent->diagramPlane->diagrams()}; 0703 for ( AbstractDiagram* diagram : diags ) 0704 { 0705 const int planeRowOffset = 1;//curColComponent->topAxesLayout ? 1 : 0; 0706 const int planeColOffset = 1;//curColComponent->leftAxesLayout ? 1 : 0; 0707 //qDebug() << Q_FUNC_INFO << row << col << planeRowOffset << planeColOffset; 0708 0709 //qDebug() << Q_FUNC_INFO << row + planeRowOffset << col + planeColOffset; 0710 planeLayoutItems << curColComponent->diagramPlane; 0711 AbstractCartesianDiagram *cartDiag = qobject_cast< AbstractCartesianDiagram* >( diagram ); 0712 if ( cartDiag ) 0713 { 0714 gridPlaneLayout->addItem( curColComponent->diagramPlane, row + planeRowOffset, col + planeColOffset, 2, 2 ); 0715 curColComponent->diagramPlane->setParentLayout( gridPlaneLayout ); 0716 QHBoxLayout *leftLayout = nullptr; 0717 QHBoxLayout *rightLayout = nullptr; 0718 QVBoxLayout *topLayout = nullptr; 0719 QVBoxLayout *bottomLayout = nullptr; 0720 if ( curComponent->sharedSuccesor ) 0721 { 0722 gridPlaneLayout->addItem( curColComponent->sharedSuccesor->diagramPlane, row + planeRowOffset, col + planeColOffset, 2, 2 ); 0723 curColComponent->sharedSuccesor->diagramPlane->setParentLayout( gridPlaneLayout ); 0724 planeLayoutItems << curColComponent->sharedSuccesor->diagramPlane; 0725 } 0726 const auto axes = cartDiag->axes(); 0727 for ( CartesianAxis* axis : axes ) { 0728 if ( axis->isAbscissa() ) 0729 { 0730 if ( curColComponent->bottomSuccesor ) 0731 continue; 0732 } 0733 if ( laidOutAxes.contains( axis ) ) 0734 continue; 0735 // if ( axis->diagram() != diagram ) 0736 // continue; 0737 switch ( axis->position() ) 0738 { 0739 case( CartesianAxis::Top ): 0740 if ( !topLayout ) 0741 topLayout = new QVBoxLayout; 0742 topLayout->addItem( axis ); 0743 axis->setParentLayout( topLayout ); 0744 break; 0745 case( CartesianAxis::Bottom ): 0746 if ( !bottomLayout ) 0747 bottomLayout = new QVBoxLayout; 0748 bottomLayout->addItem( axis ); 0749 axis->setParentLayout( bottomLayout ); 0750 break; 0751 case( CartesianAxis::Left ): 0752 if ( !leftLayout ) 0753 leftLayout = new QHBoxLayout; 0754 leftLayout->addItem( axis ); 0755 axis->setParentLayout( leftLayout ); 0756 break; 0757 case( CartesianAxis::Right ): 0758 if ( !rightLayout ) 0759 { 0760 rightLayout = new QHBoxLayout; 0761 } 0762 rightLayout->addItem( axis ); 0763 axis->setParentLayout( rightLayout ); 0764 break; 0765 } 0766 planeLayoutItems << axis; 0767 laidOutAxes.insert( axis ); 0768 } 0769 if ( leftLayout ) 0770 gridPlaneLayout->addLayout( leftLayout, row + planeRowOffset, col, 2, 1, 0771 Qt::AlignRight | Qt::AlignVCenter ); 0772 if ( rightLayout ) 0773 gridPlaneLayout->addLayout( rightLayout, row, col + planeColOffset + 2, 2, 1, 0774 Qt::AlignLeft | Qt::AlignVCenter ); 0775 if ( topLayout ) 0776 gridPlaneLayout->addLayout( topLayout, row, col + planeColOffset, 1, 2, 0777 Qt::AlignBottom | Qt::AlignHCenter ); 0778 if ( bottomLayout ) 0779 gridPlaneLayout->addLayout( bottomLayout, row + planeRowOffset + 2, 0780 col + planeColOffset, 1, 2, Qt::AlignTop | Qt::AlignHCenter ); 0781 } 0782 else 0783 { 0784 gridPlaneLayout->addItem( curColComponent->diagramPlane, row, col, 4, 4 ); 0785 curColComponent->diagramPlane->setParentLayout( gridPlaneLayout ); 0786 } 0787 col += planeColOffset + 2 + ( 1 ); 0788 } 0789 } 0790 int axisOffset = 2;//curRowComponent->topAxesLayout ? 1 : 0; 0791 //axisOffset += curRowComponent->bottomAxesLayout ? 1 : 0; 0792 const int rowOffset = axisOffset + 2; 0793 row += rowOffset; 0794 } 0795 0796 // if ( planesLayout->direction() == QBoxLayout::TopToBottom ) 0797 // ++row; 0798 // else 0799 // ++col; 0800 } 0801 0802 qDeleteAll( vals ); 0803 // re-add our grid(s) to the chart's layout 0804 if ( dataAndLegendLayout ) { 0805 dataAndLegendLayout->addLayout( planesLayout, 1, 1 ); 0806 dataAndLegendLayout->setRowStretch( 1, 1000 ); 0807 dataAndLegendLayout->setColumnStretch( 1, 1000 ); 0808 } 0809 slotResizePlanes(); 0810 #ifdef NEW_LAYOUT_DEBUG 0811 for ( int i = 0; i < gridPlaneLayout->rowCount(); ++i ) 0812 { 0813 for ( int j = 0; j < gridPlaneLayout->columnCount(); ++j ) 0814 { 0815 if ( gridPlaneLayout->itemAtPosition( i, j ) ) 0816 qDebug() << Q_FUNC_INFO << "item at" << i << j << gridPlaneLayout->itemAtPosition( i, j )->geometry(); 0817 else 0818 qDebug() << Q_FUNC_INFO << "item at" << i << j << "no item present"; 0819 } 0820 } 0821 //qDebug() << Q_FUNC_INFO << "Relayout ended"; 0822 #endif 0823 } else { 0824 if ( hadPlanesLayout ) { 0825 planesLayout->setContentsMargins( left, top, right, bottom ); 0826 } 0827 0828 planesLayout->setContentsMargins( 0, 0, 0, 0 ); 0829 planesLayout->setSpacing( 0 ); 0830 planesLayout->setObjectName( QString::fromLatin1( "planesLayout" ) ); 0831 0832 /* First go through all planes and all axes and figure out whether the planes 0833 * need to coordinate. If they do, they share a grid layout, if not, each 0834 * gets their own. See buildPlaneLayoutInfos() for more details. */ 0835 QHash<AbstractCoordinatePlane*, PlaneInfo> planeInfos = buildPlaneLayoutInfos(); 0836 QHash<AbstractAxis*, AxisInfo> axisInfos; 0837 for ( AbstractCoordinatePlane* plane : qAsConst(coordinatePlanes) ) { 0838 Q_ASSERT( planeInfos.contains(plane) ); 0839 PlaneInfo& pi = planeInfos[ plane ]; 0840 const int column = pi.horizontalOffset; 0841 const int row = pi.verticalOffset; 0842 //qDebug() << "processing plane at column" << column << "and row" << row; 0843 QGridLayout *planeLayout = pi.gridLayout; 0844 0845 if ( !planeLayout ) { 0846 PlaneInfo& refPi = pi; 0847 // if this plane is sharing an axis with another one, recursively check for the original plane and use 0848 // the grid of that as planeLayout. 0849 while ( !planeLayout && refPi.referencePlane ) { 0850 refPi = planeInfos[refPi.referencePlane]; 0851 planeLayout = refPi.gridLayout; 0852 } 0853 Q_ASSERT_X( planeLayout, 0854 "Chart::Private::slotLayoutPlanes()", 0855 "Invalid reference plane. Please check that the reference plane has been added to the Chart." ); 0856 } else { 0857 planesLayout->addLayout( planeLayout ); 0858 } 0859 0860 /* Put the plane in the center of the layout. If this is our own, that's 0861 * the middle of the layout, if we are sharing, it's a cell in the center 0862 * column of the shared grid. */ 0863 planeLayoutItems << plane; 0864 plane->setParentLayout( planeLayout ); 0865 planeLayout->addItem( plane, row, column, 1, 1 ); 0866 //qDebug() << "Chart slotLayoutPlanes() calls planeLayout->addItem("<< row << column << ")"; 0867 planeLayout->setRowStretch( row, 2 ); 0868 planeLayout->setColumnStretch( column, 2 ); 0869 0870 const auto diagrams = plane->diagrams(); 0871 for ( AbstractDiagram* abstractDiagram : diagrams ) 0872 { 0873 AbstractCartesianDiagram* diagram = 0874 qobject_cast< AbstractCartesianDiagram* >( abstractDiagram ); 0875 if ( !diagram ) { 0876 continue; // FIXME what about polar ? 0877 } 0878 0879 if ( pi.referencePlane != nullptr ) 0880 { 0881 pi.topAxesLayout = planeInfos[ pi.referencePlane ].topAxesLayout; 0882 pi.bottomAxesLayout = planeInfos[ pi.referencePlane ].bottomAxesLayout; 0883 pi.leftAxesLayout = planeInfos[ pi.referencePlane ].leftAxesLayout; 0884 pi.rightAxesLayout = planeInfos[ pi.referencePlane ].rightAxesLayout; 0885 } 0886 0887 // collect all axes of a kind into sublayouts 0888 if ( pi.topAxesLayout == nullptr ) 0889 { 0890 pi.topAxesLayout = new QVBoxLayout; 0891 pi.topAxesLayout->setContentsMargins( 0, 0, 0, 0 ); 0892 pi.topAxesLayout->setObjectName( QString::fromLatin1( "topAxesLayout" ) ); 0893 } 0894 if ( pi.bottomAxesLayout == nullptr ) 0895 { 0896 pi.bottomAxesLayout = new QVBoxLayout; 0897 pi.bottomAxesLayout->setContentsMargins( 0, 0, 0, 0 ); 0898 pi.bottomAxesLayout->setObjectName( QString::fromLatin1( "bottomAxesLayout" ) ); 0899 } 0900 if ( pi.leftAxesLayout == nullptr ) 0901 { 0902 pi.leftAxesLayout = new QHBoxLayout; 0903 pi.leftAxesLayout->setContentsMargins( 0, 0, 0, 0 ); 0904 pi.leftAxesLayout->setObjectName( QString::fromLatin1( "leftAxesLayout" ) ); 0905 } 0906 if ( pi.rightAxesLayout == nullptr ) 0907 { 0908 pi.rightAxesLayout = new QHBoxLayout; 0909 pi.rightAxesLayout->setContentsMargins( 0, 0, 0, 0 ); 0910 pi.rightAxesLayout->setObjectName( QString::fromLatin1( "rightAxesLayout" ) ); 0911 } 0912 0913 if ( pi.referencePlane != nullptr ) 0914 { 0915 planeInfos[ pi.referencePlane ].topAxesLayout = pi.topAxesLayout; 0916 planeInfos[ pi.referencePlane ].bottomAxesLayout = pi.bottomAxesLayout; 0917 planeInfos[ pi.referencePlane ].leftAxesLayout = pi.leftAxesLayout; 0918 planeInfos[ pi.referencePlane ].rightAxesLayout = pi.rightAxesLayout; 0919 } 0920 0921 //pi.leftAxesLayout->setSizeConstraint( QLayout::SetFixedSize ); 0922 const CartesianAxisList axes = diagram->axes(); 0923 for ( CartesianAxis* axis : axes ) { 0924 if ( axisInfos.contains( axis ) ) { 0925 continue; // already laid out this one 0926 } 0927 Q_ASSERT ( axis ); 0928 axis->setCachedSizeDirty(); 0929 //qDebug() << "--------------- axis added to planeLayoutItems -----------------"; 0930 planeLayoutItems << axis; 0931 0932 switch ( axis->position() ) { 0933 case CartesianAxis::Top: 0934 axis->setParentLayout( pi.topAxesLayout ); 0935 pi.topAxesLayout->addItem( axis ); 0936 break; 0937 case CartesianAxis::Bottom: 0938 axis->setParentLayout( pi.bottomAxesLayout ); 0939 pi.bottomAxesLayout->addItem( axis ); 0940 break; 0941 case CartesianAxis::Left: 0942 axis->setParentLayout( pi.leftAxesLayout ); 0943 pi.leftAxesLayout->addItem( axis ); 0944 break; 0945 case CartesianAxis::Right: 0946 axis->setParentLayout( pi.rightAxesLayout ); 0947 pi.rightAxesLayout->addItem( axis ); 0948 break; 0949 default: 0950 Q_ASSERT_X( false, "Chart::paintEvent", "unknown axis position" ); 0951 break; 0952 }; 0953 axisInfos.insert( axis, AxisInfo() ); 0954 } 0955 /* Put each stack of axes-layouts in the cells surrounding the 0956 * associated plane. We are laying out in the oder the planes 0957 * were added, and the first one gets to lay out shared axes. 0958 * Private axes go here as well, of course. */ 0959 0960 if ( !pi.topAxesLayout->parent() ) { 0961 planeLayout->addLayout( pi.topAxesLayout, row - 1, column ); 0962 } 0963 if ( !pi.bottomAxesLayout->parent() ) { 0964 planeLayout->addLayout( pi.bottomAxesLayout, row + 1, column ); 0965 } 0966 if ( !pi.leftAxesLayout->parent() ) { 0967 planeLayout->addLayout( pi.leftAxesLayout, row, column - 1 ); 0968 } 0969 if ( !pi.rightAxesLayout->parent() ) { 0970 planeLayout->addLayout( pi.rightAxesLayout,row, column + 1 ); 0971 } 0972 } 0973 0974 // use up to four auto-spacer items in the corners around the diagrams: 0975 #define ADD_AUTO_SPACER_IF_NEEDED( \ 0976 spacerRow, spacerColumn, hLayoutIsAtTop, hLayout, vLayoutIsAtLeft, vLayout ) \ 0977 { \ 0978 if ( hLayout || vLayout ) { \ 0979 AutoSpacerLayoutItem * spacer \ 0980 = new AutoSpacerLayoutItem( hLayoutIsAtTop, hLayout, vLayoutIsAtLeft, vLayout ); \ 0981 planeLayout->addItem( spacer, spacerRow, spacerColumn, 1, 1 ); \ 0982 spacer->setParentLayout( planeLayout ); \ 0983 planeLayoutItems << spacer; \ 0984 } \ 0985 } 0986 0987 if ( plane->isCornerSpacersEnabled() ) { 0988 ADD_AUTO_SPACER_IF_NEEDED( row - 1, column - 1, false, pi.leftAxesLayout, false, pi.topAxesLayout ) 0989 ADD_AUTO_SPACER_IF_NEEDED( row + 1, column - 1, true, pi.leftAxesLayout, false, pi.bottomAxesLayout ) 0990 ADD_AUTO_SPACER_IF_NEEDED( row - 1, column + 1, false, pi.rightAxesLayout, true, pi.topAxesLayout ) 0991 ADD_AUTO_SPACER_IF_NEEDED( row + 1, column + 1, true, pi.rightAxesLayout, true, pi.bottomAxesLayout ) 0992 } 0993 } 0994 // re-add our grid(s) to the chart's layout 0995 if ( dataAndLegendLayout ) { 0996 dataAndLegendLayout->addLayout( planesLayout, 1, 1 ); 0997 dataAndLegendLayout->setRowStretch( 1, 1000 ); 0998 dataAndLegendLayout->setColumnStretch( 1, 1000 ); 0999 } 1000 1001 slotResizePlanes(); 1002 } 1003 } 1004 1005 void Chart::Private::createLayouts() 1006 { 1007 // The toplevel layout provides the left and right global margins 1008 layout = new QHBoxLayout( chart ); 1009 layout->setContentsMargins( 0, 0, 0, 0 ); 1010 layout->setObjectName( QString::fromLatin1( "Chart::Private::layout" ) ); 1011 layout->addSpacing( globalLeadingLeft ); 1012 leftOuterSpacer = layout->itemAt( layout->count() - 1 )->spacerItem(); 1013 1014 // The vLayout provides top and bottom global margins and lays 1015 // out headers, footers and the diagram area. 1016 vLayout = new QVBoxLayout(); 1017 vLayout->setContentsMargins( 0, 0, 0, 0 ); 1018 vLayout->setObjectName( QString::fromLatin1( "vLayout" ) ); 1019 1020 layout->addLayout( vLayout, 1000 ); 1021 layout->addSpacing( globalLeadingRight ); 1022 rightOuterSpacer = layout->itemAt( layout->count() - 1 )->spacerItem(); 1023 1024 // 1. the spacing above the header area 1025 vLayout->addSpacing( globalLeadingTop ); 1026 topOuterSpacer = vLayout->itemAt( vLayout->count() - 1 )->spacerItem(); 1027 // 2. the header area 1028 headerLayout = new QGridLayout(); 1029 headerLayout->setContentsMargins( 0, 0, 0, 0 ); 1030 vLayout->addLayout( headerLayout ); 1031 // 3. the area containing coordinate planes, axes, and legends 1032 dataAndLegendLayout = new QGridLayout(); 1033 dataAndLegendLayout->setContentsMargins( 0, 0, 0, 0 ); 1034 dataAndLegendLayout->setObjectName( QString::fromLatin1( "dataAndLegendLayout" ) ); 1035 vLayout->addLayout( dataAndLegendLayout, 1000 ); 1036 // 4. the footer area 1037 footerLayout = new QGridLayout(); 1038 footerLayout->setContentsMargins( 0, 0, 0, 0 ); 1039 footerLayout->setObjectName( QString::fromLatin1( "footerLayout" ) ); 1040 vLayout->addLayout( footerLayout ); 1041 1042 // 5. Prepare the header / footer layout cells: 1043 // Each of the 9 header cells (the 9 footer cells) 1044 // contain their own QVBoxLayout 1045 // since there can be more than one header (footer) per cell. 1046 for ( int row = 0; row < 3; ++row ) { 1047 for ( int column = 0; column < 3; ++ column ) { 1048 const Qt::Alignment align = s_gridAlignments[ row ][ column ]; 1049 for ( int headOrFoot = 0; headOrFoot < 2; headOrFoot++ ) { 1050 QVBoxLayout* innerLayout = new QVBoxLayout(); 1051 innerLayout->setContentsMargins( 0, 0, 0, 0 ); 1052 innerLayout->setAlignment( align ); 1053 innerHdFtLayouts[ headOrFoot ][ row ][ column ] = innerLayout; 1054 1055 QGridLayout* outerLayout = headOrFoot == 0 ? headerLayout : footerLayout; 1056 outerLayout->addLayout( innerLayout, row, column, align ); 1057 } 1058 } 1059 } 1060 1061 // 6. the spacing below the footer area 1062 vLayout->addSpacing( globalLeadingBottom ); 1063 bottomOuterSpacer = vLayout->itemAt( vLayout->count() - 1 )->spacerItem(); 1064 1065 // the data+axes area 1066 dataAndLegendLayout->addLayout( planesLayout, 1, 1 ); 1067 dataAndLegendLayout->setRowStretch( 1, 1 ); 1068 dataAndLegendLayout->setColumnStretch( 1, 1 ); 1069 } 1070 1071 void Chart::Private::slotResizePlanes() 1072 { 1073 if ( !dataAndLegendLayout ) { 1074 return; 1075 } 1076 if ( !overrideSize.isValid() ) { 1077 // activate() takes the size from the layout's parent QWidget, which is not updated when overrideSize 1078 // is set. So don't let the layout grab the wrong size in that case. 1079 // When overrideSize *is* set, we call layout->setGeometry() in paint( QPainter*, const QRect& ), 1080 // which also "activates" the layout in the sense that it distributes space internally. 1081 layout->activate(); 1082 } 1083 // Adapt diagram drawing to the new size 1084 for ( AbstractCoordinatePlane* plane : qAsConst(coordinatePlanes) ) { 1085 plane->layoutDiagrams(); 1086 } 1087 } 1088 1089 void Chart::Private::updateDirtyLayouts() 1090 { 1091 if ( isPlanesLayoutDirty ) { 1092 for ( AbstractCoordinatePlane* p : qAsConst(coordinatePlanes) ) { 1093 p->setGridNeedsRecalculate(); 1094 p->layoutPlanes(); 1095 p->layoutDiagrams(); 1096 } 1097 } 1098 if ( isPlanesLayoutDirty || isFloatingLegendsLayoutDirty ) { 1099 chart->reLayoutFloatingLegends(); 1100 } 1101 isPlanesLayoutDirty = false; 1102 isFloatingLegendsLayoutDirty = false; 1103 } 1104 1105 void Chart::Private::reapplyInternalLayouts() 1106 { 1107 QRect geo = layout->geometry(); 1108 1109 invalidateLayoutTree( layout ); 1110 layout->setGeometry( geo ); 1111 slotResizePlanes(); 1112 } 1113 1114 void Chart::Private::paintAll( QPainter* painter ) 1115 { 1116 updateDirtyLayouts(); 1117 1118 QRect rect( QPoint( 0, 0 ), overrideSize.isValid() ? overrideSize : chart->size() ); 1119 1120 //qDebug() << this<<"::paintAll() uses layout size" << currentLayoutSize; 1121 1122 // Paint the background (if any) 1123 AbstractAreaBase::paintBackgroundAttributes( *painter, rect, backgroundAttributes ); 1124 // Paint the frame (if any) 1125 AbstractAreaBase::paintFrameAttributes( *painter, rect, frameAttributes ); 1126 1127 chart->reLayoutFloatingLegends(); 1128 1129 for( AbstractLayoutItem* planeLayoutItem : qAsConst(planeLayoutItems) ) { 1130 planeLayoutItem->paintAll( *painter ); 1131 } 1132 for( TextArea* textLayoutItem : qAsConst(textLayoutItems) ) { 1133 textLayoutItem->paintAll( *painter ); 1134 } 1135 for ( Legend *legend : qAsConst(legends) ) { 1136 const bool hidden = legend->isHidden() && legend->testAttribute( Qt::WA_WState_ExplicitShowHide ); 1137 if ( !hidden ) { 1138 //qDebug() << "painting legend at " << legend->geometry(); 1139 legend->paintIntoRect( *painter, legend->geometry() ); 1140 } 1141 } 1142 } 1143 1144 // ******** Chart interface implementation *********** 1145 1146 #define d d_func() 1147 1148 Chart::Chart ( QWidget* parent ) 1149 : QWidget ( parent ) 1150 , _d( new Private( this ) ) 1151 { 1152 #if defined KDAB_EVAL 1153 EvalDialog::checkEvalLicense( "KD Chart" ); 1154 #endif 1155 1156 FrameAttributes frameAttrs; 1157 // no frame per default... 1158 // frameAttrs.setVisible( true ); 1159 frameAttrs.setPen( QPen( Qt::black ) ); 1160 frameAttrs.setPadding( 1 ); 1161 setFrameAttributes( frameAttrs ); 1162 1163 addCoordinatePlane( new CartesianCoordinatePlane ( this ) ); 1164 1165 d->createLayouts(); 1166 } 1167 1168 Chart::~Chart() 1169 { 1170 // early disconnect the legend, otherwise destruction of a diagram might trigger an 1171 // update of the legend properties during destruction of the chart, leading to the 1172 // following assert in Qt6: 1173 // qobjectdefs_impl.h:119: ASSERT failure in KChart::Chart: 1174 // "Called object is not of the correct type (class destructor may have already run)" 1175 for (auto legend : d->legends) { 1176 disconnect(legend, nullptr, this, nullptr); 1177 } 1178 1179 delete d; 1180 } 1181 1182 void Chart::setFrameAttributes( const FrameAttributes &a ) 1183 { 1184 d->frameAttributes = a; 1185 } 1186 1187 FrameAttributes Chart::frameAttributes() const 1188 { 1189 return d->frameAttributes; 1190 } 1191 1192 void Chart::setBackgroundAttributes( const BackgroundAttributes &a ) 1193 { 1194 d->backgroundAttributes = a; 1195 } 1196 1197 BackgroundAttributes Chart::backgroundAttributes() const 1198 { 1199 return d->backgroundAttributes; 1200 } 1201 1202 //TODO KChart 3.0; change QLayout into QBoxLayout::Direction 1203 void Chart::setCoordinatePlaneLayout( QLayout * layout ) 1204 { 1205 if (layout == d->planesLayout) 1206 return; 1207 if (d->planesLayout) { 1208 // detach all QLayoutItem's the previous planesLayout has cause 1209 // otherwise deleting the planesLayout would delete them too. 1210 for(int i = d->planesLayout->count() - 1; i >= 0; --i) { 1211 d->planesLayout->takeAt(i); 1212 } 1213 delete d->planesLayout; 1214 } 1215 d->planesLayout = qobject_cast<QBoxLayout*>( layout ); 1216 d->slotLayoutPlanes(); 1217 } 1218 1219 QLayout* Chart::coordinatePlaneLayout() 1220 { 1221 return d->planesLayout; 1222 } 1223 1224 AbstractCoordinatePlane* Chart::coordinatePlane() 1225 { 1226 if ( d->coordinatePlanes.isEmpty() ) { 1227 qWarning() << "Chart::coordinatePlane: warning: no coordinate plane defined."; 1228 return nullptr; 1229 } else { 1230 return d->coordinatePlanes.first(); 1231 } 1232 } 1233 1234 CoordinatePlaneList Chart::coordinatePlanes() 1235 { 1236 return d->coordinatePlanes; 1237 } 1238 1239 void Chart::addCoordinatePlane( AbstractCoordinatePlane* plane ) 1240 { 1241 // Append 1242 insertCoordinatePlane( d->coordinatePlanes.count(), plane ); 1243 } 1244 1245 void Chart::insertCoordinatePlane( int index, AbstractCoordinatePlane* plane ) 1246 { 1247 if ( index < 0 || index > d->coordinatePlanes.count() ) { 1248 return; 1249 } 1250 1251 connect( plane, &AbstractCoordinatePlane::destroyedCoordinatePlane, 1252 d, &Private::slotUnregisterDestroyedPlane ); 1253 connect( plane, &AbstractCoordinatePlane::needUpdate, this, QOverload<>::of(&Chart::update) ); 1254 connect( plane, &AbstractCoordinatePlane::needRelayout, d, &Private::slotResizePlanes ) ; 1255 connect( plane, &AbstractCoordinatePlane::needLayoutPlanes, d, &Private::slotLayoutPlanes ) ; 1256 connect( plane, &AbstractCoordinatePlane::propertiesChanged, this, &Chart::propertiesChanged ); 1257 d->coordinatePlanes.insert( index, plane ); 1258 plane->setParent( this ); 1259 d->slotLayoutPlanes(); 1260 } 1261 1262 void Chart::replaceCoordinatePlane( AbstractCoordinatePlane* plane, 1263 AbstractCoordinatePlane* oldPlane_ ) 1264 { 1265 if ( plane && oldPlane_ != plane ) { 1266 AbstractCoordinatePlane* oldPlane = oldPlane_; 1267 if ( d->coordinatePlanes.count() ) { 1268 if ( ! oldPlane ) { 1269 oldPlane = d->coordinatePlanes.first(); 1270 if ( oldPlane == plane ) 1271 return; 1272 } 1273 takeCoordinatePlane( oldPlane ); 1274 } 1275 delete oldPlane; 1276 addCoordinatePlane( plane ); 1277 } 1278 } 1279 1280 void Chart::takeCoordinatePlane( AbstractCoordinatePlane* plane ) 1281 { 1282 const int idx = d->coordinatePlanes.indexOf( plane ); 1283 if ( idx != -1 ) { 1284 d->coordinatePlanes.takeAt( idx ); 1285 disconnect( plane, nullptr, d, nullptr ); 1286 disconnect( plane, nullptr, this, nullptr ); 1287 plane->removeFromParentLayout(); 1288 plane->setParent( nullptr ); 1289 d->mouseClickedPlanes.removeAll(plane); 1290 } 1291 d->slotLayoutPlanes(); 1292 // Need to emit the signal: In case somebody has connected the signal 1293 // to her own slot for e.g. calling update() on a widget containing the chart. 1294 Q_EMIT propertiesChanged(); 1295 } 1296 1297 void Chart::setGlobalLeading( int left, int top, int right, int bottom ) 1298 { 1299 setGlobalLeadingLeft( left ); 1300 setGlobalLeadingTop( top ); 1301 setGlobalLeadingRight( right ); 1302 setGlobalLeadingBottom( bottom ); 1303 } 1304 1305 void Chart::setGlobalLeadingLeft( int leading ) 1306 { 1307 d->globalLeadingLeft = leading; 1308 d->leftOuterSpacer->changeSize( leading, 0, QSizePolicy::Fixed, QSizePolicy::Minimum ); 1309 d->reapplyInternalLayouts(); 1310 } 1311 1312 int Chart::globalLeadingLeft() const 1313 { 1314 return d->globalLeadingLeft; 1315 } 1316 1317 void Chart::setGlobalLeadingTop( int leading ) 1318 { 1319 d->globalLeadingTop = leading; 1320 d->topOuterSpacer->changeSize( 0, leading, QSizePolicy::Minimum, QSizePolicy::Fixed ); 1321 d->reapplyInternalLayouts(); 1322 } 1323 1324 int Chart::globalLeadingTop() const 1325 { 1326 return d->globalLeadingTop; 1327 } 1328 1329 void Chart::setGlobalLeadingRight( int leading ) 1330 { 1331 d->globalLeadingRight = leading; 1332 d->rightOuterSpacer->changeSize( leading, 0, QSizePolicy::Fixed, QSizePolicy::Minimum ); 1333 d->reapplyInternalLayouts(); 1334 } 1335 1336 int Chart::globalLeadingRight() const 1337 { 1338 return d->globalLeadingRight; 1339 } 1340 1341 void Chart::setGlobalLeadingBottom( int leading ) 1342 { 1343 d->globalLeadingBottom = leading; 1344 d->bottomOuterSpacer->changeSize( 0, leading, QSizePolicy::Minimum, QSizePolicy::Fixed ); 1345 d->reapplyInternalLayouts(); 1346 } 1347 1348 int Chart::globalLeadingBottom() const 1349 { 1350 return d->globalLeadingBottom; 1351 } 1352 1353 void Chart::paint( QPainter* painter, const QRect& rect ) 1354 { 1355 if ( rect.isEmpty() || !painter ) { 1356 return; 1357 } 1358 1359 QPaintDevice* prevDevice = GlobalMeasureScaling::paintDevice(); 1360 GlobalMeasureScaling::setPaintDevice( painter->device() ); 1361 int prevScaleFactor = PrintingParameters::scaleFactor(); 1362 1363 PrintingParameters::setScaleFactor( qreal( painter->device()->logicalDpiX() ) / qreal( logicalDpiX() ) ); 1364 1365 const QRect oldGeometry( geometry() ); 1366 if ( oldGeometry != rect ) { 1367 setGeometry( rect ); 1368 d->isPlanesLayoutDirty = true; 1369 d->isFloatingLegendsLayoutDirty = true; 1370 } 1371 painter->translate( rect.left(), rect.top() ); 1372 d->paintAll( painter ); 1373 1374 // for debugging 1375 // painter->setPen( QPen( Qt::blue, 8 ) ); 1376 // painter->drawRect( rect ); 1377 1378 painter->translate( -rect.left(), -rect.top() ); 1379 if ( oldGeometry != rect ) { 1380 setGeometry( oldGeometry ); 1381 d->isPlanesLayoutDirty = true; 1382 d->isFloatingLegendsLayoutDirty = true; 1383 } 1384 1385 PrintingParameters::setScaleFactor( prevScaleFactor ); 1386 GlobalMeasureScaling::setPaintDevice( prevDevice ); 1387 } 1388 1389 void Chart::resizeEvent ( QResizeEvent* event ) 1390 { 1391 d->isPlanesLayoutDirty = true; 1392 d->isFloatingLegendsLayoutDirty = true; 1393 QWidget::resizeEvent( event ); 1394 } 1395 1396 void Chart::reLayoutFloatingLegends() 1397 { 1398 for( Legend *legend : qAsConst(d->legends) ) { 1399 const bool hidden = legend->isHidden() && legend->testAttribute( Qt::WA_WState_ExplicitShowHide ); 1400 if ( legend->position().isFloating() && !hidden ) { 1401 // resize the legend 1402 const QSize legendSize( legend->sizeHint() ); 1403 legend->setGeometry( QRect( legend->geometry().topLeft(), legendSize ) ); 1404 // find the legends corner point (reference point plus any paddings) 1405 const RelativePosition relPos( legend->floatingPosition() ); 1406 QPointF pt( relPos.calculatedPoint( size() ) ); 1407 //qDebug() << pt; 1408 // calculate the legend's top left point 1409 const Qt::Alignment alignTopLeft = Qt::AlignBottom | Qt::AlignLeft; 1410 if ( (relPos.alignment() & alignTopLeft) != alignTopLeft ) { 1411 if ( relPos.alignment() & Qt::AlignRight ) 1412 pt.rx() -= legendSize.width(); 1413 else if ( relPos.alignment() & Qt::AlignHCenter ) 1414 pt.rx() -= 0.5 * legendSize.width(); 1415 1416 if ( relPos.alignment() & Qt::AlignBottom ) 1417 pt.ry() -= legendSize.height(); 1418 else if ( relPos.alignment() & Qt::AlignVCenter ) 1419 pt.ry() -= 0.5 * legendSize.height(); 1420 } 1421 //qDebug() << pt << endl; 1422 legend->move( static_cast<int>(pt.x()), static_cast<int>(pt.y()) ); 1423 } 1424 } 1425 } 1426 1427 1428 void Chart::paintEvent( QPaintEvent* ) 1429 { 1430 QPainter painter( this ); 1431 d->paintAll( &painter ); 1432 Q_EMIT finishedDrawing(); 1433 } 1434 1435 void Chart::addHeaderFooter( HeaderFooter* hf ) 1436 { 1437 Q_ASSERT( hf->type() == HeaderFooter::Header || hf->type() == HeaderFooter::Footer ); 1438 int row; 1439 int column; 1440 getRowAndColumnForPosition( hf->position().value(), &row, &column ); 1441 if ( row == -1 ) { 1442 qWarning( "Unknown header/footer position" ); 1443 return; 1444 } 1445 1446 d->headerFooters.append( hf ); 1447 d->textLayoutItems.append( hf ); 1448 connect( hf, &HeaderFooter::destroyedHeaderFooter, 1449 d, &Private::slotUnregisterDestroyedHeaderFooter ); 1450 connect( hf, &HeaderFooter::positionChanged, 1451 d, &Private::slotHeaderFooterPositionChanged ); 1452 1453 // set the text attributes (why?) 1454 1455 TextAttributes textAttrs( hf->textAttributes() ); 1456 Measure measure( textAttrs.fontSize() ); 1457 measure.setRelativeMode( this, KChartEnums::MeasureOrientationMinimum ); 1458 measure.setValue( 20 ); 1459 textAttrs.setFontSize( measure ); 1460 hf->setTextAttributes( textAttrs ); 1461 1462 // add it to the appropriate layout 1463 1464 int innerLayoutIdx = hf->type() == HeaderFooter::Header ? 0 : 1; 1465 QVBoxLayout* headerFooterLayout = d->innerHdFtLayouts[ innerLayoutIdx ][ row ][ column ]; 1466 1467 hf->setParentLayout( headerFooterLayout ); 1468 hf->setAlignment( s_gridAlignments[ row ][ column ] ); 1469 headerFooterLayout->addItem( hf ); 1470 1471 d->slotResizePlanes(); 1472 } 1473 1474 void Chart::replaceHeaderFooter( HeaderFooter* headerFooter, 1475 HeaderFooter* oldHeaderFooter_ ) 1476 { 1477 if ( headerFooter && oldHeaderFooter_ != headerFooter ) { 1478 HeaderFooter* oldHeaderFooter = oldHeaderFooter_; 1479 if ( d->headerFooters.count() ) { 1480 if ( ! oldHeaderFooter ) { 1481 oldHeaderFooter = d->headerFooters.first(); 1482 if ( oldHeaderFooter == headerFooter ) 1483 return; 1484 } 1485 takeHeaderFooter( oldHeaderFooter ); 1486 } 1487 delete oldHeaderFooter; 1488 addHeaderFooter( headerFooter ); 1489 } 1490 } 1491 1492 void Chart::takeHeaderFooter( HeaderFooter* headerFooter ) 1493 { 1494 const int idx = d->headerFooters.indexOf( headerFooter ); 1495 if ( idx == -1 ) { 1496 return; 1497 } 1498 disconnect( headerFooter, &HeaderFooter::destroyedHeaderFooter, 1499 d, &Private::slotUnregisterDestroyedHeaderFooter ); 1500 1501 d->headerFooters.takeAt( idx ); 1502 headerFooter->removeFromParentLayout(); 1503 headerFooter->setParentLayout( nullptr ); 1504 d->textLayoutItems.remove( d->textLayoutItems.indexOf( headerFooter ) ); 1505 1506 d->slotResizePlanes(); 1507 } 1508 1509 void Chart::Private::slotHeaderFooterPositionChanged( HeaderFooter* hf ) 1510 { 1511 chart->takeHeaderFooter( hf ); 1512 chart->addHeaderFooter( hf ); 1513 } 1514 1515 HeaderFooter* Chart::headerFooter() 1516 { 1517 if ( d->headerFooters.isEmpty() ) { 1518 return nullptr; 1519 } else { 1520 return d->headerFooters.first(); 1521 } 1522 } 1523 1524 HeaderFooterList Chart::headerFooters() 1525 { 1526 return d->headerFooters; 1527 } 1528 1529 void Chart::Private::slotLegendPositionChanged( AbstractAreaWidget* aw ) 1530 { 1531 Legend* legend = qobject_cast< Legend* >( aw ); 1532 Q_ASSERT( legend ); 1533 chart->takeLegend( legend ); 1534 chart->addLegendInternal( legend, false ); 1535 } 1536 1537 void Chart::addLegend( Legend* legend ) 1538 { 1539 legend->show(); 1540 addLegendInternal( legend, true ); 1541 Q_EMIT propertiesChanged(); 1542 } 1543 1544 void Chart::addLegendInternal( Legend* legend, bool setMeasures ) 1545 { 1546 if ( !legend ) { 1547 return; 1548 } 1549 1550 KChartEnums::PositionValue pos = legend->position().value(); 1551 if ( pos == KChartEnums::PositionCenter ) { 1552 qWarning( "Not showing legend because PositionCenter is not supported for legends." ); 1553 } 1554 1555 int row; 1556 int column; 1557 getRowAndColumnForPosition( pos, &row, &column ); 1558 if ( row < 0 && pos != KChartEnums::PositionFloating ) { 1559 qWarning( "Not showing legend because of unknown legend position." ); 1560 return; 1561 } 1562 1563 d->legends.append( legend ); 1564 legend->setParent( this ); 1565 1566 // set text attributes (why?) 1567 1568 if ( setMeasures ) { 1569 TextAttributes textAttrs( legend->textAttributes() ); 1570 Measure measure( textAttrs.fontSize() ); 1571 measure.setRelativeMode( this, KChartEnums::MeasureOrientationMinimum ); 1572 measure.setValue( 20 ); 1573 textAttrs.setFontSize( measure ); 1574 legend->setTextAttributes( textAttrs ); 1575 1576 textAttrs = legend->titleTextAttributes(); 1577 measure.setRelativeMode( this, KChartEnums::MeasureOrientationMinimum ); 1578 measure.setValue( 24 ); 1579 textAttrs.setFontSize( measure ); 1580 1581 legend->setTitleTextAttributes( textAttrs ); 1582 legend->setReferenceArea( this ); 1583 } 1584 1585 // add it to the appropriate layout 1586 1587 if ( pos != KChartEnums::PositionFloating ) { 1588 legend->needSizeHint(); 1589 1590 // in each edge and corner of the outer layout, there's a grid for the different alignments that we create 1591 // on demand. we don't remove it when empty. 1592 1593 QLayoutItem* edgeItem = d->dataAndLegendLayout->itemAtPosition( row, column ); 1594 QGridLayout* alignmentsLayout = dynamic_cast< QGridLayout* >( edgeItem ); 1595 Q_ASSERT( !edgeItem || alignmentsLayout ); // if it exists, it must be a QGridLayout 1596 if ( !alignmentsLayout ) { 1597 alignmentsLayout = new QGridLayout; 1598 d->dataAndLegendLayout->addLayout( alignmentsLayout, row, column ); 1599 alignmentsLayout->setContentsMargins( 0, 0, 0, 0 ); 1600 } 1601 1602 // in case there are several legends in the same edge or corner with the same alignment, they are stacked 1603 // vertically using a QVBoxLayout. it is created on demand as above. 1604 1605 row = 1; 1606 column = 1; 1607 for ( int i = 0; i < 3; i++ ) { 1608 for ( int j = 0; j < 3; j++ ) { 1609 Qt::Alignment align = s_gridAlignments[ i ][ j ]; 1610 if ( align == legend->alignment() ) { 1611 row = i; 1612 column = j; 1613 break; 1614 } 1615 } 1616 } 1617 1618 QLayoutItem* alignmentItem = alignmentsLayout->itemAtPosition( row, column ); 1619 QVBoxLayout* sameAlignmentLayout = dynamic_cast< QVBoxLayout* >( alignmentItem ); 1620 Q_ASSERT( !alignmentItem || sameAlignmentLayout ); // if it exists, it must be a QVBoxLayout 1621 if ( !sameAlignmentLayout ) { 1622 sameAlignmentLayout = new QVBoxLayout; 1623 alignmentsLayout->addLayout( sameAlignmentLayout, row, column ); 1624 sameAlignmentLayout->setContentsMargins( 0, 0, 0, 0 ); 1625 } 1626 1627 sameAlignmentLayout->addItem( new MyWidgetItem( legend, legend->alignment() ) ); 1628 } 1629 1630 connect( legend, &Legend::destroyedLegend, 1631 d, &Private::slotUnregisterDestroyedLegend ); 1632 connect( legend, &Legend::positionChanged, 1633 d, &Private::slotLegendPositionChanged ); 1634 connect( legend, &Legend::propertiesChanged, this, &Chart::propertiesChanged ); 1635 1636 d->slotResizePlanes(); 1637 } 1638 1639 void Chart::replaceLegend( Legend* legend, Legend* oldLegend_ ) 1640 { 1641 if ( legend && oldLegend_ != legend ) { 1642 Legend* oldLegend = oldLegend_; 1643 if ( d->legends.count() ) { 1644 if ( ! oldLegend ) { 1645 oldLegend = d->legends.first(); 1646 if ( oldLegend == legend ) 1647 return; 1648 } 1649 takeLegend( oldLegend ); 1650 } 1651 delete oldLegend; 1652 addLegend( legend ); 1653 } 1654 } 1655 1656 void Chart::takeLegend( Legend* legend ) 1657 { 1658 const int idx = d->legends.indexOf( legend ); 1659 if ( idx == -1 ) { 1660 return; 1661 } 1662 1663 d->legends.takeAt( idx ); 1664 disconnect( legend, nullptr, d, nullptr ); 1665 disconnect( legend, nullptr, this, nullptr ); 1666 // the following removes the legend from its layout and destroys its MyWidgetItem (the link to the layout) 1667 legend->setParent( nullptr ); 1668 1669 d->slotResizePlanes(); 1670 Q_EMIT propertiesChanged(); 1671 } 1672 1673 Legend* Chart::legend() 1674 { 1675 return d->legends.isEmpty() ? nullptr : d->legends.first(); 1676 } 1677 1678 LegendList Chart::legends() 1679 { 1680 return d->legends; 1681 } 1682 1683 void Chart::mousePressEvent( QMouseEvent* event ) 1684 { 1685 const QPoint pos = mapFromGlobal( event->globalPos() ); 1686 1687 for( AbstractCoordinatePlane* plane : qAsConst(d->coordinatePlanes) ) { 1688 if ( plane->geometry().contains( event->pos() ) && plane->diagrams().size() > 0 ) { 1689 QMouseEvent ev( QEvent::MouseButtonPress, pos, event->globalPos(), 1690 event->button(), event->buttons(), event->modifiers() ); 1691 plane->mousePressEvent( &ev ); 1692 d->mouseClickedPlanes.append( plane ); 1693 } 1694 } 1695 } 1696 1697 void Chart::mouseDoubleClickEvent( QMouseEvent* event ) 1698 { 1699 const QPoint pos = mapFromGlobal( event->globalPos() ); 1700 1701 for( AbstractCoordinatePlane* plane : qAsConst(d->coordinatePlanes) ) { 1702 if ( plane->geometry().contains( event->pos() ) && plane->diagrams().size() > 0 ) { 1703 QMouseEvent ev( QEvent::MouseButtonPress, pos, event->globalPos(), 1704 event->button(), event->buttons(), event->modifiers() ); 1705 plane->mouseDoubleClickEvent( &ev ); 1706 } 1707 } 1708 } 1709 1710 void Chart::mouseMoveEvent( QMouseEvent* event ) 1711 { 1712 auto eventReceivers = QSet<AbstractCoordinatePlane*>(d->mouseClickedPlanes.cbegin(), d->mouseClickedPlanes.cend()); 1713 1714 for( AbstractCoordinatePlane* plane : qAsConst(d->coordinatePlanes) ) { 1715 if ( plane->geometry().contains( event->pos() ) && plane->diagrams().size() > 0 ) { 1716 eventReceivers.insert( plane ); 1717 } 1718 } 1719 1720 const QPoint pos = mapFromGlobal( event->globalPos() ); 1721 1722 for( AbstractCoordinatePlane* plane : qAsConst(eventReceivers) ) { 1723 QMouseEvent ev( QEvent::MouseMove, pos, event->globalPos(), 1724 event->button(), event->buttons(), event->modifiers() ); 1725 plane->mouseMoveEvent( &ev ); 1726 } 1727 } 1728 1729 void Chart::mouseReleaseEvent( QMouseEvent* event ) 1730 { 1731 auto eventReceivers = QSet<AbstractCoordinatePlane*>(d->mouseClickedPlanes.cbegin(), d->mouseClickedPlanes.cend()); 1732 1733 for ( AbstractCoordinatePlane* plane : qAsConst(d->coordinatePlanes) ) { 1734 if ( plane->geometry().contains( event->pos() ) && plane->diagrams().size() > 0 ) { 1735 eventReceivers.insert( plane ); 1736 } 1737 } 1738 1739 const QPoint pos = mapFromGlobal( event->globalPos() ); 1740 1741 for( AbstractCoordinatePlane* plane : qAsConst(eventReceivers) ) { 1742 QMouseEvent ev( QEvent::MouseButtonRelease, pos, event->globalPos(), 1743 event->button(), event->buttons(), event->modifiers() ); 1744 plane->mouseReleaseEvent( &ev ); 1745 } 1746 1747 d->mouseClickedPlanes.clear(); 1748 } 1749 1750 bool Chart::event( QEvent* event ) 1751 { 1752 if ( event->type() == QEvent::ToolTip ) { 1753 const QHelpEvent* const helpEvent = static_cast< QHelpEvent* >( event ); 1754 for( const AbstractCoordinatePlane* const plane : qAsConst(d->coordinatePlanes) ) { 1755 // iterate diagrams in reverse, so that the top-most painted diagram is 1756 // queried first for a tooltip before the diagrams behind it 1757 const ConstAbstractDiagramList& diagrams = plane->diagrams(); 1758 for (int i = diagrams.size() - 1; i >= 0; --i) { 1759 const AbstractDiagram* diagram = diagrams[i]; 1760 if (diagram->isHidden()) { 1761 continue; 1762 } 1763 const QModelIndex index = diagram->indexAt( helpEvent->pos() ); 1764 const QVariant toolTip = index.data( Qt::ToolTipRole ); 1765 if ( toolTip.isValid() ) { 1766 QPoint pos = mapFromGlobal( helpEvent->pos() ); 1767 QRect rect( pos - QPoint( 1, 1 ), QSize( 3, 3 ) ); 1768 QToolTip::showText( QCursor::pos(), toolTip.toString(), this, rect ); 1769 return true; 1770 } 1771 } 1772 } 1773 } 1774 return QWidget::event( event ); 1775 } 1776 1777 bool Chart::useNewLayoutSystem() const 1778 { 1779 return d_func()->useNewLayoutSystem; 1780 } 1781 void Chart::setUseNewLayoutSystem( bool value ) 1782 { 1783 if ( d_func()->useNewLayoutSystem != value ) 1784 d_func()->useNewLayoutSystem = value; 1785 }