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 }