1 /*
2  * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB.  All rights reserved.
3  *
4  * This file is part of the KD Chart library.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of
9  * the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
18  */
19 
20 #include "KChartChart.h"
21 #include "KChartChart_p.h"
22 
23 #include <QList>
24 #include <QtDebug>
25 #include <QGridLayout>
26 #include <QLabel>
27 #include <QHash>
28 #include <QToolTip>
29 #include <QPainter>
30 #include <QPaintEvent>
31 #include <QLayoutItem>
32 #include <QPushButton>
33 #include <QApplication>
34 #include <QEvent>
35 
36 #include "KChartCartesianCoordinatePlane.h"
37 #include "KChartAbstractCartesianDiagram.h"
38 #include "KChartHeaderFooter.h"
39 #include "KChartEnums.h"
40 #include "KChartLegend.h"
41 #include "KChartLayoutItems.h"
42 #include <KChartTextAttributes.h>
43 #include <KChartMarkerAttributes.h>
44 #include "KChartPainterSaver_p.h"
45 #include "KChartPrintingParameters.h"
46 
47 #include <algorithm>
48 
49 #if defined KDAB_EVAL
50 #include "../evaldialog/evaldialog.h"
51 #endif
52 
53 #if 0
54 // dumpLayoutTree dumps a QLayout tree in a hopefully easy to read format to stderr - feel free to
55 // use, improve and extend; it is very useful for looking at any layout problem.
56 
57 #include <typeinfo>
58 
59 // this is this different from both QRect::isEmpty() and QRect::isNull() for "wrong" QRects,
60 // i.e. those where topLeft() is actually below and / or right of bottomRight().
61 static bool isZeroArea(const QRect &r)
62 {
63     return !r.width() || !r.height();
64 }
65 
66 static QString lineProlog(int nestingDepth, int lineno)
67 {
68     QString numbering(QString::number(lineno).rightJustified(5).append(QChar::fromAscii(':')));
69     QString indent(nestingDepth * 4, QLatin1Char(' '));
70     return numbering + indent;
71 }
72 
73 static void dumpLayoutTreeRecurse(QLayout *l, int *counter, int depth)
74 {
75     const QLatin1String colorOn(isZeroArea(l->geometry()) ? "\033[0m" : "\033[32m");
76     const QLatin1String colorOff("\033[0m");
77 
78     QString prolog = lineProlog(depth, *counter);
79     (*counter)++;
80 
81     qDebug() << colorOn + prolog << l->metaObject()->className() << l->geometry()
82              << "hint" << l->sizeHint()
83              << l->hasHeightForWidth() << "min" << l->minimumSize()
84              << "max" << l->maximumSize()
85              << l->expandingDirections() << l->alignment()
86              << colorOff;
87     for (int i = 0; i < l->count(); i++) {
88         QLayoutItem *child = l->itemAt(i);
89         if (QLayout *childL = child->layout()) {
90             dumpLayoutTreeRecurse(childL, counter, depth + 1);
91         } else {
92             // The isZeroArea check culls usually largely useless output - you might want to remove it in
93             // some debugging situations. Add a boolean parameter to this and dumpLayoutTree() if you do.
94             if (!isZeroArea(child->geometry())) {
95                 prolog = lineProlog(depth + 1, *counter);
96                 (*counter)++;
97                 qDebug() << colorOn + prolog << typeid(*child).name() << child->geometry()
98                          << "hint" << child->sizeHint()
99                          << child->hasHeightForWidth() << "min" << child->minimumSize()
100                          << "max" << child->maximumSize()
101                          << child->expandingDirections() << child->alignment()
102                          << colorOff;
103             }
104         }
105     }
106 }
107 
108 static void dumpLayoutTree(QLayout *l)
109 {
110     int counter = 0;
111     dumpLayoutTreeRecurse(l, &counter, 0);
112 }
113 #endif
114 
115 static const Qt::Alignment s_gridAlignments[ 3 ][ 3 ] = { // [ row ][ column ]
116     { Qt::AlignTop | Qt::AlignLeft,     Qt::AlignTop | Qt::AlignHCenter,     Qt::AlignTop | Qt::AlignRight },
117     { Qt::AlignVCenter | Qt::AlignLeft, Qt::AlignVCenter | Qt::AlignHCenter, Qt::AlignVCenter | Qt::AlignRight },
118     { Qt::AlignBottom | Qt::AlignLeft,  Qt::AlignBottom | Qt::AlignHCenter,  Qt::AlignBottom | Qt::AlignRight }
119 };
120 
getRowAndColumnForPosition(KChartEnums::PositionValue pos,int * row,int * column)121 static void getRowAndColumnForPosition(KChartEnums::PositionValue pos, int* row, int* column)
122 {
123     switch ( pos ) {
124     case KChartEnums::PositionNorthWest:  *row = 0;  *column = 0;
125         break;
126     case KChartEnums::PositionNorth:      *row = 0;  *column = 1;
127         break;
128     case KChartEnums::PositionNorthEast:  *row = 0;  *column = 2;
129         break;
130     case KChartEnums::PositionEast:       *row = 1;  *column = 2;
131         break;
132     case KChartEnums::PositionSouthEast:  *row = 2;  *column = 2;
133         break;
134     case KChartEnums::PositionSouth:      *row = 2;  *column = 1;
135         break;
136     case KChartEnums::PositionSouthWest:  *row = 2;  *column = 0;
137         break;
138     case KChartEnums::PositionWest:       *row = 1;  *column = 0;
139         break;
140     case KChartEnums::PositionCenter:     *row = 1;  *column = 1;
141         break;
142     default:                               *row = -1; *column = -1;
143         break;
144     }
145 }
146 
147 using namespace KChart;
148 
149 // Layout widgets even if they are not visible (that's why isEmpty() is overridden) - at least that
150 // was the original reason...
151 class MyWidgetItem : public QWidgetItem
152 {
153 public:
MyWidgetItem(QWidget * w,Qt::Alignment alignment=Qt::Alignment ())154     explicit MyWidgetItem(QWidget *w, Qt::Alignment alignment = Qt::Alignment())
155         : QWidgetItem( w )
156     {
157         setAlignment( alignment );
158     }
159 
160     // All of the methods are reimplemented from QWidgetItem, and work around some oddity in QLayout and / or
161     // KD Chart - I forgot the details between writing this code as an experiment and committing it, very
162     // sorry about that!
163     // Feel free to comment out any of them and then try the line-breaking feature in horizontal legends in
164     // the Legends/Advanced example. It will not work well in various ways - won't get enough space and look
165     // very broken, will inhibit resizing the window etc.
166 
sizeHint() const167     QSize sizeHint() const override
168     {
169         QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
170         return w->sizeHint();
171     }
172 
minimumSize() const173     QSize minimumSize() const override
174     {
175         QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
176         return w->minimumSize();
177     }
178 
maximumSize() const179     QSize maximumSize() const override
180     {
181         // Not just passing on w->maximumSize() fixes that the size policy of Legend is disregarded, making
182         // Legend take all available space, which makes both the Legend internal layout and the overall
183         // layout of chart + legend look bad. QWidget::maximumSize() is not a virtual method, it's a
184         // property, so "overriding" that one would be even uglier, and prevent user-set property
185         // values from doing anything.
186         QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
187         QSize ret = w->maximumSize();
188         const QSize hint = w->sizeHint();
189         const QSizePolicy::Policy hPolicy = w->sizePolicy().horizontalPolicy();
190         if (hPolicy == QSizePolicy::Fixed || hPolicy == QSizePolicy::Maximum) {
191             ret.rwidth() = hint.width();
192         }
193         const QSizePolicy::Policy vPolicy = w->sizePolicy().verticalPolicy();
194         if (vPolicy == QSizePolicy::Fixed || vPolicy == QSizePolicy::Maximum) {
195             ret.rheight() = hint.height();
196         }
197         return ret;
198     }
199 
expandingDirections() const200     Qt::Orientations expandingDirections() const override
201     {
202         QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
203         if ( isEmpty() ) {
204             return Qt::Orientations();
205         }
206         Qt::Orientations e = w->sizePolicy().expandingDirections();
207         return e;
208     }
209 
setGeometry(const QRect & g)210     void setGeometry(const QRect &g) override
211     {
212         QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
213         w->setGeometry(g);
214     }
215 
geometry() const216     QRect geometry() const override
217     {
218         QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
219         return w->geometry();
220     }
221 
hasHeightForWidth() const222     bool hasHeightForWidth() const override
223     {
224         QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
225         bool ret = !isEmpty() &&
226         qobject_cast< Legend* >( w )->hasHeightForWidth();
227         return ret;
228     }
229 
heightForWidth(int width) const230     int heightForWidth( int width ) const override
231     {
232         QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
233         int ret = w->heightForWidth( width );
234         return ret;
235     }
236 
isEmpty() const237     bool isEmpty() const override {
238         QWidget* w = const_cast< MyWidgetItem * >( this )->widget();
239         // legend->hide() should indeed hide the legend,
240         // but a legend in a chart that hasn't been shown yet isn't hidden
241         // (as can happen when using Chart::paint() without showing the chart)
242         return w->isHidden() && w->testAttribute( Qt::WA_WState_ExplicitShowHide );
243     }
244 };
245 
246 // When "abusing" QLayouts to lay out items with different geometry from the backing QWidgets,
247 // some manual work is required to correctly update all the sublayouts.
248 // This is because all the convenient ways to deal with QLayouts assume QWidgets somewhere.
249 // What this does is somewhat similar to QLayout::activate(), but it never refers to the parent
250 // QWidget which has the wrong geometry.
invalidateLayoutTree(QLayoutItem * item)251 static void invalidateLayoutTree( QLayoutItem *item )
252 {
253     QLayout *layout = item->layout();
254     if ( layout ) {
255         const int count = layout->count();
256         for ( int i = 0; i < count; i++ ) {
257             invalidateLayoutTree( layout->itemAt( i ) );
258         }
259     }
260     item->invalidate();
261 }
262 
slotUnregisterDestroyedLegend(Legend * l)263 void Chart::Private::slotUnregisterDestroyedLegend( Legend *l )
264 {
265     chart->takeLegend( l );
266 }
267 
slotUnregisterDestroyedHeaderFooter(HeaderFooter * hf)268 void Chart::Private::slotUnregisterDestroyedHeaderFooter( HeaderFooter* hf )
269 {
270     chart->takeHeaderFooter( hf );
271 }
272 
slotUnregisterDestroyedPlane(AbstractCoordinatePlane * plane)273 void Chart::Private::slotUnregisterDestroyedPlane( AbstractCoordinatePlane* plane )
274 {
275     coordinatePlanes.removeAll( plane );
276     Q_FOREACH ( AbstractCoordinatePlane* p, coordinatePlanes ) {
277         if ( p->referenceCoordinatePlane() == plane) {
278             p->setReferenceCoordinatePlane( nullptr );
279         }
280     }
281     plane->layoutPlanes();
282 }
283 
Private(Chart * chart_)284 Chart::Private::Private( Chart* chart_ )
285     : chart( chart_ )
286     , useNewLayoutSystem( false )
287     , layout(nullptr)
288     , vLayout(nullptr)
289     , planesLayout(nullptr)
290     , headerLayout(nullptr)
291     , footerLayout(nullptr)
292     , dataAndLegendLayout(nullptr)
293     , leftOuterSpacer(nullptr)
294     , rightOuterSpacer(nullptr)
295     , topOuterSpacer(nullptr)
296     , bottomOuterSpacer(nullptr)
297     , isFloatingLegendsLayoutDirty( true )
298     , isPlanesLayoutDirty( true )
299     , globalLeadingLeft(0)
300     , globalLeadingRight(0)
301     , globalLeadingTop(0)
302     , globalLeadingBottom(0)
303 {
304     for ( int row = 0; row < 3; ++row ) {
305         for ( int column = 0; column < 3; ++column ) {
306             for ( int i = 0; i < 2; i++ ) {
307                 innerHdFtLayouts[ i ][ row ][ column ] = nullptr;
308             }
309         }
310     }
311 }
312 
~Private()313 Chart::Private::~Private()
314 {
315 }
316 
317 enum VisitorState{ Visited, Unknown };
318 struct ConnectedComponentsComparator{
operator ()ConnectedComponentsComparator319     bool operator()( const LayoutGraphNode *lhs, const LayoutGraphNode *rhs ) const
320     {
321         return lhs->priority < rhs->priority;
322     }
323 };
324 
getPrioritySortedConnectedComponents(QVector<LayoutGraphNode * > & nodeList)325 static QVector< LayoutGraphNode* > getPrioritySortedConnectedComponents( QVector< LayoutGraphNode* > &nodeList )
326 {
327     QVector< LayoutGraphNode* >connectedComponents;
328     QHash< LayoutGraphNode*, VisitorState > visitedComponents;
329     Q_FOREACH ( LayoutGraphNode* node, nodeList )
330         visitedComponents[ node ] = Unknown;
331     for ( int i = 0; i < nodeList.size(); ++i )
332     {
333         LayoutGraphNode *curNode = nodeList[ i ];
334         LayoutGraphNode *representativeNode = curNode;
335         if ( visitedComponents[ curNode ] != Visited )
336         {
337             QStack< LayoutGraphNode* > stack;
338             stack.push( curNode );
339             while ( !stack.isEmpty() )
340             {
341                 curNode = stack.pop();
342                 Q_ASSERT( visitedComponents[ curNode ] != Visited );
343                 visitedComponents[ curNode ] = Visited;
344                 if ( curNode->bottomSuccesor && visitedComponents[ curNode->bottomSuccesor ] != Visited )
345                     stack.push( curNode->bottomSuccesor );
346                 if ( curNode->leftSuccesor && visitedComponents[ curNode->leftSuccesor ] != Visited )
347                     stack.push( curNode->leftSuccesor );
348                 if ( curNode->sharedSuccesor && visitedComponents[ curNode->sharedSuccesor ] != Visited )
349                     stack.push( curNode->sharedSuccesor );
350                 if ( curNode->priority < representativeNode->priority )
351                     representativeNode = curNode;
352             }
353             connectedComponents.append( representativeNode );
354         }
355     }
356     std::sort( connectedComponents.begin(), connectedComponents.end(), ConnectedComponentsComparator() );
357     return connectedComponents;
358 }
359 
360 struct PriorityComparator{
361 public:
PriorityComparatorPriorityComparator362     PriorityComparator( QHash< AbstractCoordinatePlane*, LayoutGraphNode* > mapping )
363         : m_mapping( mapping )
364     {}
operator ()PriorityComparator365     bool operator() ( AbstractCoordinatePlane *lhs, AbstractCoordinatePlane *rhs ) const
366     {
367         const LayoutGraphNode *lhsNode = m_mapping[ lhs ];
368         Q_ASSERT( lhsNode );
369         const LayoutGraphNode *rhsNode = m_mapping[ rhs ];
370         Q_ASSERT( rhsNode );
371         return lhsNode->priority < rhsNode->priority;
372     }
373 
374     const QHash< AbstractCoordinatePlane*, LayoutGraphNode* > m_mapping;
375 };
376 
checkExistingAxes(LayoutGraphNode * node)377 void checkExistingAxes( LayoutGraphNode* node )
378 {
379     if ( node && node->diagramPlane && node->diagramPlane->diagram() )
380     {
381         AbstractCartesianDiagram *diag = qobject_cast< AbstractCartesianDiagram* >( node->diagramPlane->diagram() );
382         if ( diag )
383         {
384             Q_FOREACH( const CartesianAxis* axis, diag->axes() )
385             {
386                 switch ( axis->position() )
387                 {
388                 case( CartesianAxis::Top ):
389                     node->topAxesLayout = true;
390                     break;
391                 case( CartesianAxis::Bottom ):
392                     node->bottomAxesLayout = true;
393                     break;
394                 case( CartesianAxis::Left ):
395                     node->leftAxesLayout = true;
396                     break;
397                 case( CartesianAxis::Right ):
398                     node->rightAxesLayout = true;
399                     break;
400                 }
401             }
402         }
403     }
404 }
405 
mergeNodeAxisInformation(LayoutGraphNode * lhs,LayoutGraphNode * rhs)406 static void mergeNodeAxisInformation( LayoutGraphNode* lhs, LayoutGraphNode* rhs )
407 {
408     lhs->topAxesLayout |= rhs->topAxesLayout;
409     rhs->topAxesLayout = lhs->topAxesLayout;
410 
411     lhs->bottomAxesLayout |= rhs->bottomAxesLayout;
412     rhs->bottomAxesLayout = lhs->bottomAxesLayout;
413 
414     lhs->leftAxesLayout |= rhs->leftAxesLayout;
415     rhs->leftAxesLayout = lhs->leftAxesLayout;
416 
417     lhs->rightAxesLayout |= rhs->rightAxesLayout;
418     rhs->rightAxesLayout = lhs->rightAxesLayout;
419 }
420 
findSharingAxisDiagrams(AbstractCoordinatePlane * plane,const CoordinatePlaneList & list,Chart::Private::AxisType type,QVector<CartesianAxis * > * sharedAxes)421 static CoordinatePlaneList findSharingAxisDiagrams( AbstractCoordinatePlane* plane,
422                                                     const CoordinatePlaneList& list,
423                                                     Chart::Private::AxisType type,
424                                                     QVector< CartesianAxis* >* sharedAxes )
425 {
426     if ( !plane || !plane->diagram() )
427         return CoordinatePlaneList();
428     Q_ASSERT( plane );
429     Q_ASSERT( plane->diagram() );
430     CoordinatePlaneList result;
431     AbstractCartesianDiagram* diagram = qobject_cast< AbstractCartesianDiagram* >( plane->diagram() );
432     if ( !diagram )
433         return CoordinatePlaneList();
434 
435     QList< CartesianAxis* > axes;
436     Q_FOREACH( CartesianAxis* axis, diagram->axes() ) {
437         if ( ( type == Chart::Private::Ordinate &&
438               ( axis->position() == CartesianAxis::Left || axis->position() == CartesianAxis::Right ) )
439             ||
440              ( type == Chart::Private::Abscissa &&
441               ( axis->position() == CartesianAxis::Top || axis->position() == CartesianAxis::Bottom ) ) ) {
442             axes.append( axis );
443         }
444     }
445     Q_FOREACH( AbstractCoordinatePlane *curPlane, list )
446     {
447         AbstractCartesianDiagram* diagram =
448                 qobject_cast< AbstractCartesianDiagram* > ( curPlane->diagram() );
449         if ( !diagram )
450             continue;
451         Q_FOREACH( CartesianAxis* curSearchedAxis, axes )
452         {
453             Q_FOREACH( CartesianAxis* curAxis, diagram->axes() )
454             {
455                 if ( curSearchedAxis == curAxis )
456                 {
457                     result.append( curPlane );
458                     if ( !sharedAxes->contains( curSearchedAxis ) )
459                         sharedAxes->append( curSearchedAxis );
460                 }
461             }
462         }
463     }
464 
465     return result;
466 }
467 
468 /*
469   * this method determines the needed layout of the graph
470   * taking care of the sharing problematic
471   * its NOT allowed to have a diagram that shares
472   * more than one axis in the same direction
473   */
buildPlaneLayoutGraph()474 QVector< LayoutGraphNode* > Chart::Private::buildPlaneLayoutGraph()
475 {
476     QHash< AbstractCoordinatePlane*, LayoutGraphNode* > planeNodeMapping;
477     QVector< LayoutGraphNode* > allNodes;
478     // create all nodes and a mapping between plane and nodes
479     Q_FOREACH( AbstractCoordinatePlane* curPlane, coordinatePlanes )
480     {
481         if ( curPlane->diagram() )
482         {
483             allNodes.append( new LayoutGraphNode );
484             allNodes[ allNodes.size() - 1 ]->diagramPlane = curPlane;
485             allNodes[ allNodes.size() - 1 ]->priority = allNodes.size();
486             checkExistingAxes( allNodes[ allNodes.size() - 1 ] );
487             planeNodeMapping[ curPlane ] = allNodes[ allNodes.size() - 1 ];
488         }
489     }
490     // build the graph connections
491     Q_FOREACH( LayoutGraphNode* curNode, allNodes )
492     {
493         QVector< CartesianAxis* > sharedAxes;
494         CoordinatePlaneList xSharedPlanes = findSharingAxisDiagrams( curNode->diagramPlane, coordinatePlanes, Abscissa, &sharedAxes );
495         Q_ASSERT( sharedAxes.size() < 2 );
496         // TODO duplicated code make a method out of it
497         if ( sharedAxes.size() == 1 && xSharedPlanes.size() > 1 )
498         {
499             //xSharedPlanes.removeAll( sharedAxes.first()->diagram()->coordinatePlane() );
500             //std::sort( xSharedPlanes.begin(), xSharedPlanes.end(), PriorityComparator( planeNodeMapping ) );
501             for ( int i = 0; i < xSharedPlanes.size() - 1; ++i )
502             {
503                 LayoutGraphNode *tmpNode = planeNodeMapping[ xSharedPlanes[ i ] ];
504                 Q_ASSERT( tmpNode );
505                 LayoutGraphNode *tmpNode2 = planeNodeMapping[ xSharedPlanes[ i + 1 ] ];
506                 Q_ASSERT( tmpNode2 );
507                 tmpNode->bottomSuccesor = tmpNode2;
508             }
509 //            if ( sharedAxes.first()->diagram() &&  sharedAxes.first()->diagram()->coordinatePlane() )
510 //            {
511 //                LayoutGraphNode *lastNode = planeNodeMapping[ xSharedPlanes.last() ];
512 //                Q_ASSERT( lastNode );
513 //                Q_ASSERT( sharedAxes.first()->diagram()->coordinatePlane() );
514 //                LayoutGraphNode *ownerNode = planeNodeMapping[ sharedAxes.first()->diagram()->coordinatePlane() ];
515 //                Q_ASSERT( ownerNode );
516 //                lastNode->bottomSuccesor = ownerNode;
517 //            }
518             //merge AxisInformation, needs a two pass run
519             LayoutGraphNode axisInfoNode;
520             for ( int count = 0; count < 2; ++count )
521             {
522                 for ( int i = 0; i < xSharedPlanes.size(); ++i )
523                 {
524                     mergeNodeAxisInformation( &axisInfoNode, planeNodeMapping[ xSharedPlanes[ i ] ] );
525                 }
526             }
527         }
528         sharedAxes.clear();
529         CoordinatePlaneList ySharedPlanes = findSharingAxisDiagrams( curNode->diagramPlane, coordinatePlanes, Ordinate, &sharedAxes );
530         Q_ASSERT( sharedAxes.size() < 2 );
531         if ( sharedAxes.size() == 1 && ySharedPlanes.size() > 1 )
532         {
533             //ySharedPlanes.removeAll( sharedAxes.first()->diagram()->coordinatePlane() );
534             //std::sort( ySharedPlanes.begin(), ySharedPlanes.end(), PriorityComparator( planeNodeMapping ) );
535             for ( int i = 0; i < ySharedPlanes.size() - 1; ++i )
536             {
537                 LayoutGraphNode *tmpNode = planeNodeMapping[ ySharedPlanes[ i ] ];
538                 Q_ASSERT( tmpNode );
539                 LayoutGraphNode *tmpNode2 = planeNodeMapping[ ySharedPlanes[ i + 1 ] ];
540                 Q_ASSERT( tmpNode2 );
541                 tmpNode->leftSuccesor = tmpNode2;
542             }
543 //            if ( sharedAxes.first()->diagram() &&  sharedAxes.first()->diagram()->coordinatePlane() )
544 //            {
545 //                LayoutGraphNode *lastNode = planeNodeMapping[ ySharedPlanes.last() ];
546 //                Q_ASSERT( lastNode );
547 //                Q_ASSERT( sharedAxes.first()->diagram()->coordinatePlane() );
548 //                LayoutGraphNode *ownerNode = planeNodeMapping[ sharedAxes.first()->diagram()->coordinatePlane() ];
549 //                Q_ASSERT( ownerNode );
550 //                lastNode->bottomSuccesor = ownerNode;
551 //            }
552             //merge AxisInformation, needs a two pass run
553             LayoutGraphNode axisInfoNode;
554             for ( int count = 0; count < 2; ++count )
555             {
556                 for ( int i = 0; i < ySharedPlanes.size(); ++i )
557                 {
558                     mergeNodeAxisInformation( &axisInfoNode, planeNodeMapping[ ySharedPlanes[ i ] ] );
559                 }
560             }
561         }
562         sharedAxes.clear();
563         if ( curNode->diagramPlane->referenceCoordinatePlane() )
564             curNode->sharedSuccesor = planeNodeMapping[ curNode->diagramPlane->referenceCoordinatePlane() ];
565     }
566 
567     return allNodes;
568 }
569 
buildPlaneLayoutInfos()570 QHash<AbstractCoordinatePlane*, PlaneInfo> Chart::Private::buildPlaneLayoutInfos()
571 {
572     /* There are two ways in which planes can be caused to interact in
573      * where they are put layouting wise: The first is the reference plane. If
574      * such a reference plane is set, on a plane, it will use the same cell in the
575      * layout as that one. In addition to this, planes can share an axis. In that case
576      * they will be laid out in relation to each other as suggested by the position
577      * of the axis. If, for example Plane1 and Plane2 share an axis at position Left,
578      * that will result in the layout: Axis Plane1 Plane 2, vertically. If Plane1
579      * also happens to be Plane2's referece plane, both planes are drawn over each
580      * other. The reference plane concept allows two planes to share the same space
581      * even if neither has any axis, and in case there are shared axis, it is used
582      * to decided, whether the planes should be painted on top of each other or
583      * laid out vertically or horizontally next to each other. */
584     QHash<CartesianAxis*, AxisInfo> axisInfos;
585     QHash<AbstractCoordinatePlane*, PlaneInfo> planeInfos;
586     Q_FOREACH(AbstractCoordinatePlane* plane, coordinatePlanes ) {
587         PlaneInfo p;
588         // first check if we share space with another plane
589         p.referencePlane = plane->referenceCoordinatePlane();
590         planeInfos.insert( plane, p );
591 
592         Q_FOREACH( AbstractDiagram* abstractDiagram, plane->diagrams() ) {
593             AbstractCartesianDiagram* diagram =
594                     qobject_cast<AbstractCartesianDiagram*> ( abstractDiagram );
595             if ( !diagram ) {
596                 continue;
597             }
598 
599             Q_FOREACH( CartesianAxis* axis, diagram->axes() ) {
600                 if ( !axisInfos.contains( axis ) ) {
601                     /* If this is the first time we see this axis, add it, with the
602                      * current plane. The first plane added to the chart that has
603                      * the axis associated with it thus "owns" it, and decides about
604                      * layout. */
605                     AxisInfo i;
606                     i.plane = plane;
607                     axisInfos.insert( axis, i );
608                 } else {
609                     AxisInfo i = axisInfos[axis];
610                     if ( i.plane == plane ) {
611                         continue; // we don't want duplicates, only shared
612                     }
613 
614                     /* The user expects diagrams to be added on top, and to the right
615                      * so that horizontally we need to move the new diagram, vertically
616                      * the reference one. */
617                     PlaneInfo pi = planeInfos[plane];
618                     // plane-to-plane linking overrides linking via axes
619                     if ( !pi.referencePlane ) {
620                         // we're not the first plane to see this axis, mark us as a slave
621                         pi.referencePlane = i.plane;
622                         if ( axis->position() == CartesianAxis::Left ||
623                              axis->position() == CartesianAxis::Right ) {
624                             pi.horizontalOffset += 1;
625                         }
626                         planeInfos[plane] = pi;
627 
628                         pi = planeInfos[i.plane];
629                         if ( axis->position() == CartesianAxis::Top ||
630                              axis->position() == CartesianAxis::Bottom )  {
631                             pi.verticalOffset += 1;
632                         }
633 
634                         planeInfos[i.plane] = pi;
635                     }
636                 }
637             }
638         }
639         // Create a new grid layout for each plane that has no reference.
640         p = planeInfos[plane];
641         if ( p.referencePlane == nullptr ) {
642             p.gridLayout = new QGridLayout();
643             p.gridLayout->setContentsMargins( 0, 0, 0, 0 );
644             planeInfos[plane] = p;
645         }
646     }
647     return planeInfos;
648 }
649 
slotLayoutPlanes()650 void Chart::Private::slotLayoutPlanes()
651 {
652     /*TODO make sure this is really needed */
653     const QBoxLayout::Direction oldPlanesDirection = planesLayout ? planesLayout->direction()
654                                                                   : QBoxLayout::TopToBottom;
655     if ( planesLayout && dataAndLegendLayout )
656         dataAndLegendLayout->removeItem( planesLayout );
657 
658     const bool hadPlanesLayout = planesLayout != nullptr;
659     int left, top, right, bottom;
660     if ( hadPlanesLayout )
661         planesLayout->getContentsMargins(&left, &top, &right, &bottom);
662 
663     Q_FOREACH( AbstractLayoutItem* plane, planeLayoutItems ) {
664         plane->removeFromParentLayout();
665     }
666     //TODO they should get a correct parent, but for now it works
667     Q_FOREACH( AbstractLayoutItem* plane, planeLayoutItems ) {
668         if ( dynamic_cast< AutoSpacerLayoutItem* >( plane ) )
669             delete plane;
670     }
671 
672     planeLayoutItems.clear();
673     delete planesLayout;
674     //hint: The direction is configurable by the user now, as
675     //      we are using a QBoxLayout rather than a QVBoxLayout.  (khz, 2007/04/25)
676     planesLayout = new QBoxLayout( oldPlanesDirection );
677 
678     isPlanesLayoutDirty = true; // here we create the layouts; we need to "run" them before painting
679 
680     if ( useNewLayoutSystem )
681     {
682         gridPlaneLayout = new QGridLayout;
683         planesLayout->addLayout( gridPlaneLayout );
684 
685         if (hadPlanesLayout)
686             planesLayout->setContentsMargins(left, top, right, bottom);
687         planesLayout->setObjectName( QString::fromLatin1( "planesLayout" ) );
688 
689         /* First go through all planes and all axes and figure out whether the planes
690          * need to coordinate. If they do, they share a grid layout, if not, each
691          * get their own. See buildPlaneLayoutInfos() for more details. */
692 
693         QVector< LayoutGraphNode* > vals = buildPlaneLayoutGraph();
694         //qDebug() << Q_FUNC_INFO << "GraphNodes" << vals.size();
695         QVector< LayoutGraphNode* > connectedComponents = getPrioritySortedConnectedComponents( vals );
696         //qDebug() << Q_FUNC_INFO << "SubGraphs" << connectedComponents.size();
697         int row = 0;
698         int col = 0;
699         QSet< CartesianAxis* > laidOutAxes;
700         for ( int i = 0; i < connectedComponents.size(); ++i )
701         {
702             LayoutGraphNode *curComponent = connectedComponents[ i ];
703             for ( LayoutGraphNode *curRowComponent = curComponent; curRowComponent; curRowComponent = curRowComponent->bottomSuccesor )
704             {
705                 col = 0;
706                 for ( LayoutGraphNode *curColComponent = curRowComponent; curColComponent; curColComponent = curColComponent->leftSuccesor )
707                 {
708                     Q_ASSERT( curColComponent->diagramPlane->diagrams().size() == 1 );
709                     Q_FOREACH( AbstractDiagram* diagram, curColComponent->diagramPlane->diagrams() )
710                     {
711                         const int planeRowOffset = 1;//curColComponent->topAxesLayout ? 1 : 0;
712                         const int planeColOffset = 1;//curColComponent->leftAxesLayout ? 1 : 0;
713                         //qDebug() << Q_FUNC_INFO << row << col << planeRowOffset << planeColOffset;
714 
715                         //qDebug() << Q_FUNC_INFO << row + planeRowOffset << col + planeColOffset;
716                         planeLayoutItems << curColComponent->diagramPlane;
717                         AbstractCartesianDiagram *cartDiag = qobject_cast< AbstractCartesianDiagram* >( diagram );
718                         if ( cartDiag )
719                         {
720                             gridPlaneLayout->addItem( curColComponent->diagramPlane, row + planeRowOffset, col + planeColOffset, 2, 2 );
721                             curColComponent->diagramPlane->setParentLayout( gridPlaneLayout );
722                             QHBoxLayout *leftLayout = nullptr;
723                             QHBoxLayout *rightLayout = nullptr;
724                             QVBoxLayout *topLayout = nullptr;
725                             QVBoxLayout *bottomLayout = nullptr;
726                             if ( curComponent->sharedSuccesor )
727                             {
728                                 gridPlaneLayout->addItem( curColComponent->sharedSuccesor->diagramPlane, row + planeRowOffset, col + planeColOffset, 2, 2 );
729                                 curColComponent->sharedSuccesor->diagramPlane->setParentLayout( gridPlaneLayout );
730                                 planeLayoutItems << curColComponent->sharedSuccesor->diagramPlane;
731                             }
732                             Q_FOREACH( CartesianAxis* axis, cartDiag->axes() )
733                             {
734                                 if ( axis->isAbscissa() )
735                                 {
736                                     if ( curColComponent->bottomSuccesor )
737                                         continue;
738                                 }
739                                 if ( laidOutAxes.contains( axis ) )
740                                     continue;
741         //                        if ( axis->diagram() != diagram )
742         //                            continue;
743                                 switch ( axis->position() )
744                                 {
745                                 case( CartesianAxis::Top ):
746                                     if ( !topLayout )
747                                         topLayout = new QVBoxLayout;
748                                     topLayout->addItem( axis );
749                                     axis->setParentLayout( topLayout );
750                                     break;
751                                 case( CartesianAxis::Bottom ):
752                                     if ( !bottomLayout )
753                                         bottomLayout = new QVBoxLayout;
754                                     bottomLayout->addItem( axis );
755                                     axis->setParentLayout( bottomLayout );
756                                     break;
757                                 case( CartesianAxis::Left ):
758                                     if ( !leftLayout )
759                                         leftLayout = new QHBoxLayout;
760                                     leftLayout->addItem( axis );
761                                     axis->setParentLayout( leftLayout );
762                                     break;
763                                 case( CartesianAxis::Right ):
764                                     if ( !rightLayout )
765                                     {
766                                         rightLayout = new QHBoxLayout;
767                                     }
768                                     rightLayout->addItem( axis );
769                                     axis->setParentLayout( rightLayout );
770                                     break;
771                                 }
772                                 planeLayoutItems << axis;
773                                 laidOutAxes.insert( axis );
774                             }
775                             if ( leftLayout )
776                                 gridPlaneLayout->addLayout( leftLayout, row + planeRowOffset, col, 2, 1,
777                                                             Qt::AlignRight | Qt::AlignVCenter );
778                             if ( rightLayout )
779                                 gridPlaneLayout->addLayout( rightLayout, row, col + planeColOffset + 2, 2, 1,
780                                                             Qt::AlignLeft | Qt::AlignVCenter );
781                             if ( topLayout )
782                                 gridPlaneLayout->addLayout( topLayout, row, col + planeColOffset, 1, 2,
783                                                             Qt::AlignBottom | Qt::AlignHCenter );
784                             if ( bottomLayout )
785                                 gridPlaneLayout->addLayout( bottomLayout, row + planeRowOffset + 2,
786                                                             col + planeColOffset, 1, 2, Qt::AlignTop | Qt::AlignHCenter );
787                         }
788                         else
789                         {
790                             gridPlaneLayout->addItem( curColComponent->diagramPlane, row, col, 4, 4 );
791                             curColComponent->diagramPlane->setParentLayout( gridPlaneLayout );
792                         }
793                         col += planeColOffset + 2 + ( 1 );
794                     }
795                 }
796                 int axisOffset = 2;//curRowComponent->topAxesLayout ? 1 : 0;
797                 //axisOffset += curRowComponent->bottomAxesLayout ? 1 : 0;
798                 const int rowOffset = axisOffset + 2;
799                 row += rowOffset;
800             }
801 
802     //        if ( planesLayout->direction() == QBoxLayout::TopToBottom )
803     //            ++row;
804     //        else
805     //            ++col;
806         }
807 
808         qDeleteAll( vals );
809         // re-add our grid(s) to the chart's layout
810         if ( dataAndLegendLayout ) {
811             dataAndLegendLayout->addLayout( planesLayout, 1, 1 );
812             dataAndLegendLayout->setRowStretch( 1, 1000 );
813             dataAndLegendLayout->setColumnStretch( 1, 1000 );
814         }
815         slotResizePlanes();
816 #ifdef NEW_LAYOUT_DEBUG
817         for ( int i = 0; i < gridPlaneLayout->rowCount(); ++i )
818         {
819             for ( int j = 0; j < gridPlaneLayout->columnCount(); ++j )
820             {
821                 if ( gridPlaneLayout->itemAtPosition( i, j ) )
822                     qDebug() << Q_FUNC_INFO << "item at" << i << j << gridPlaneLayout->itemAtPosition( i, j )->geometry();
823                 else
824                     qDebug() << Q_FUNC_INFO << "item at" << i << j << "no item present";
825             }
826         }
827         //qDebug() << Q_FUNC_INFO << "Relayout ended";
828 #endif
829     } else {
830         if ( hadPlanesLayout ) {
831             planesLayout->setContentsMargins( left, top, right, bottom );
832         }
833 
834         planesLayout->setContentsMargins( 0, 0, 0, 0 );
835         planesLayout->setSpacing( 0 );
836         planesLayout->setObjectName( QString::fromLatin1( "planesLayout" ) );
837 
838         /* First go through all planes and all axes and figure out whether the planes
839          * need to coordinate. If they do, they share a grid layout, if not, each
840          * gets their own. See buildPlaneLayoutInfos() for more details. */
841         QHash<AbstractCoordinatePlane*, PlaneInfo> planeInfos = buildPlaneLayoutInfos();
842         QHash<AbstractAxis*, AxisInfo> axisInfos;
843         Q_FOREACH( AbstractCoordinatePlane* plane, coordinatePlanes ) {
844             Q_ASSERT( planeInfos.contains(plane) );
845             PlaneInfo& pi = planeInfos[ plane ];
846             const int column = pi.horizontalOffset;
847             const int row = pi.verticalOffset;
848             //qDebug() << "processing plane at column" << column << "and row" << row;
849             QGridLayout *planeLayout = pi.gridLayout;
850 
851             if ( !planeLayout ) {
852                 PlaneInfo& refPi = pi;
853                 // if this plane is sharing an axis with another one, recursively check for the original plane and use
854                 // the grid of that as planeLayout.
855                 while ( !planeLayout && refPi.referencePlane ) {
856                     refPi = planeInfos[refPi.referencePlane];
857                     planeLayout = refPi.gridLayout;
858                 }
859                 Q_ASSERT_X( planeLayout,
860                             "Chart::Private::slotLayoutPlanes()",
861                             "Invalid reference plane. Please check that the reference plane has been added to the Chart." );
862             } else {
863                 planesLayout->addLayout( planeLayout );
864             }
865 
866             /* Put the plane in the center of the layout. If this is our own, that's
867              * the middle of the layout, if we are sharing, it's a cell in the center
868              * column of the shared grid. */
869             planeLayoutItems << plane;
870             plane->setParentLayout( planeLayout );
871             planeLayout->addItem( plane, row, column, 1, 1 );
872             //qDebug() << "Chart slotLayoutPlanes() calls planeLayout->addItem("<< row << column << ")";
873             planeLayout->setRowStretch( row, 2 );
874             planeLayout->setColumnStretch( column, 2 );
875             Q_FOREACH( AbstractDiagram* abstractDiagram, plane->diagrams() )
876             {
877                 AbstractCartesianDiagram* diagram =
878                     qobject_cast< AbstractCartesianDiagram* >( abstractDiagram );
879                 if ( !diagram ) {
880                     continue;  // FIXME what about polar ?
881                 }
882 
883                 if ( pi.referencePlane != nullptr )
884                 {
885                     pi.topAxesLayout = planeInfos[ pi.referencePlane ].topAxesLayout;
886                     pi.bottomAxesLayout = planeInfos[ pi.referencePlane ].bottomAxesLayout;
887                     pi.leftAxesLayout = planeInfos[ pi.referencePlane ].leftAxesLayout;
888                     pi.rightAxesLayout = planeInfos[ pi.referencePlane ].rightAxesLayout;
889                 }
890 
891                 // collect all axes of a kind into sublayouts
892                 if ( pi.topAxesLayout == nullptr )
893                 {
894                     pi.topAxesLayout = new QVBoxLayout;
895                     pi.topAxesLayout->setContentsMargins( 0, 0, 0, 0 );
896                     pi.topAxesLayout->setObjectName( QString::fromLatin1( "topAxesLayout" ) );
897                 }
898                 if ( pi.bottomAxesLayout == nullptr )
899                 {
900                     pi.bottomAxesLayout = new QVBoxLayout;
901                     pi.bottomAxesLayout->setContentsMargins( 0, 0, 0, 0 );
902                     pi.bottomAxesLayout->setObjectName( QString::fromLatin1( "bottomAxesLayout" ) );
903                 }
904                 if ( pi.leftAxesLayout == nullptr )
905                 {
906                     pi.leftAxesLayout = new QHBoxLayout;
907                     pi.leftAxesLayout->setContentsMargins( 0, 0, 0, 0 );
908                     pi.leftAxesLayout->setObjectName( QString::fromLatin1( "leftAxesLayout" ) );
909                 }
910                 if ( pi.rightAxesLayout == nullptr )
911                 {
912                     pi.rightAxesLayout = new QHBoxLayout;
913                     pi.rightAxesLayout->setContentsMargins( 0, 0, 0, 0 );
914                     pi.rightAxesLayout->setObjectName( QString::fromLatin1( "rightAxesLayout" ) );
915                 }
916 
917                 if ( pi.referencePlane != nullptr )
918                 {
919                     planeInfos[ pi.referencePlane ].topAxesLayout = pi.topAxesLayout;
920                     planeInfos[ pi.referencePlane ].bottomAxesLayout = pi.bottomAxesLayout;
921                     planeInfos[ pi.referencePlane ].leftAxesLayout = pi.leftAxesLayout;
922                     planeInfos[ pi.referencePlane ].rightAxesLayout = pi.rightAxesLayout;
923                 }
924 
925                 //pi.leftAxesLayout->setSizeConstraint( QLayout::SetFixedSize );
926                 Q_FOREACH( CartesianAxis* axis, diagram->axes() ) {
927                     if ( axisInfos.contains( axis ) ) {
928                         continue; // already laid out this one
929                     }
930                     Q_ASSERT ( axis );
931                     axis->setCachedSizeDirty();
932                     //qDebug() << "--------------- axis added to planeLayoutItems  -----------------";
933                     planeLayoutItems << axis;
934 
935                     switch ( axis->position() ) {
936                     case CartesianAxis::Top:
937                         axis->setParentLayout( pi.topAxesLayout );
938                         pi.topAxesLayout->addItem( axis );
939                         break;
940                     case CartesianAxis::Bottom:
941                         axis->setParentLayout( pi.bottomAxesLayout );
942                         pi.bottomAxesLayout->addItem( axis );
943                         break;
944                     case CartesianAxis::Left:
945                         axis->setParentLayout( pi.leftAxesLayout );
946                         pi.leftAxesLayout->addItem( axis );
947                         break;
948                     case CartesianAxis::Right:
949                         axis->setParentLayout( pi.rightAxesLayout );
950                         pi.rightAxesLayout->addItem( axis );
951                         break;
952                     default:
953                         Q_ASSERT_X( false, "Chart::paintEvent", "unknown axis position" );
954                         break;
955                     };
956                     axisInfos.insert( axis, AxisInfo() );
957                 }
958                 /* Put each stack of axes-layouts in the cells surrounding the
959                  * associated plane. We are laying out in the oder the planes
960                  * were added, and the first one gets to lay out shared axes.
961                  * Private axes go here as well, of course. */
962 
963                 if ( !pi.topAxesLayout->parent() ) {
964                     planeLayout->addLayout( pi.topAxesLayout, row - 1, column );
965                 }
966                 if ( !pi.bottomAxesLayout->parent() ) {
967                     planeLayout->addLayout( pi.bottomAxesLayout, row + 1, column );
968                 }
969                 if ( !pi.leftAxesLayout->parent() ) {
970                     planeLayout->addLayout( pi.leftAxesLayout, row, column - 1 );
971                 }
972                 if ( !pi.rightAxesLayout->parent() ) {
973                     planeLayout->addLayout( pi.rightAxesLayout,row, column + 1 );
974                 }
975             }
976 
977             // use up to four auto-spacer items in the corners around the diagrams:
978     #define ADD_AUTO_SPACER_IF_NEEDED( \
979             spacerRow, spacerColumn, hLayoutIsAtTop, hLayout, vLayoutIsAtLeft, vLayout ) \
980             { \
981                 if ( hLayout || vLayout ) { \
982                     AutoSpacerLayoutItem * spacer \
983                     = new AutoSpacerLayoutItem( hLayoutIsAtTop, hLayout, vLayoutIsAtLeft, vLayout ); \
984                     planeLayout->addItem( spacer, spacerRow, spacerColumn, 1, 1 ); \
985                     spacer->setParentLayout( planeLayout ); \
986                     planeLayoutItems << spacer; \
987                 } \
988             }
989 
990             if ( plane->isCornerSpacersEnabled() ) {
991                 ADD_AUTO_SPACER_IF_NEEDED( row - 1, column - 1, false, pi.leftAxesLayout,  false, pi.topAxesLayout )
992                 ADD_AUTO_SPACER_IF_NEEDED( row + 1, column - 1, true,  pi.leftAxesLayout,  false,  pi.bottomAxesLayout )
993                 ADD_AUTO_SPACER_IF_NEEDED( row - 1, column + 1, false, pi.rightAxesLayout, true, pi.topAxesLayout )
994                 ADD_AUTO_SPACER_IF_NEEDED( row + 1, column + 1, true,  pi.rightAxesLayout, true,  pi.bottomAxesLayout )
995             }
996         }
997         // re-add our grid(s) to the chart's layout
998         if ( dataAndLegendLayout ) {
999             dataAndLegendLayout->addLayout( planesLayout, 1, 1 );
1000             dataAndLegendLayout->setRowStretch( 1, 1000 );
1001             dataAndLegendLayout->setColumnStretch( 1, 1000 );
1002         }
1003 
1004         slotResizePlanes();
1005     }
1006 }
1007 
createLayouts()1008 void Chart::Private::createLayouts()
1009 {
1010     // The toplevel layout provides the left and right global margins
1011     layout = new QHBoxLayout( chart );
1012     layout->setContentsMargins( 0, 0, 0, 0 );
1013     layout->setObjectName( QString::fromLatin1( "Chart::Private::layout" ) );
1014     layout->addSpacing( globalLeadingLeft );
1015     leftOuterSpacer = layout->itemAt( layout->count() - 1 )->spacerItem();
1016 
1017     // The vLayout provides top and bottom global margins and lays
1018     // out headers, footers and the diagram area.
1019     vLayout = new QVBoxLayout();
1020     vLayout->setContentsMargins( 0, 0, 0, 0 );
1021     vLayout->setObjectName( QString::fromLatin1( "vLayout" ) );
1022 
1023     layout->addLayout( vLayout, 1000 );
1024     layout->addSpacing( globalLeadingRight );
1025     rightOuterSpacer = layout->itemAt( layout->count() - 1 )->spacerItem();
1026 
1027     // 1. the spacing above the header area
1028     vLayout->addSpacing( globalLeadingTop );
1029     topOuterSpacer = vLayout->itemAt( vLayout->count() - 1 )->spacerItem();
1030     // 2. the header area
1031     headerLayout = new QGridLayout();
1032     headerLayout->setContentsMargins( 0, 0, 0, 0 );
1033     vLayout->addLayout( headerLayout );
1034     // 3. the area containing coordinate planes, axes, and legends
1035     dataAndLegendLayout = new QGridLayout();
1036     dataAndLegendLayout->setContentsMargins( 0, 0, 0, 0 );
1037     dataAndLegendLayout->setObjectName( QString::fromLatin1( "dataAndLegendLayout" ) );
1038     vLayout->addLayout( dataAndLegendLayout, 1000 );
1039     // 4. the footer area
1040     footerLayout = new QGridLayout();
1041     footerLayout->setContentsMargins( 0, 0, 0, 0 );
1042     footerLayout->setObjectName( QString::fromLatin1( "footerLayout" ) );
1043     vLayout->addLayout( footerLayout );
1044 
1045     // 5. Prepare the header / footer layout cells:
1046     //    Each of the 9 header cells (the 9 footer cells)
1047     //    contain their own QVBoxLayout
1048     //    since there can be more than one header (footer) per cell.
1049     for ( int row = 0; row < 3; ++row ) {
1050         for ( int column = 0; column < 3; ++ column ) {
1051             const Qt::Alignment align = s_gridAlignments[ row ][ column ];
1052             for ( int headOrFoot = 0; headOrFoot < 2; headOrFoot++ ) {
1053                 QVBoxLayout* innerLayout = new QVBoxLayout();
1054                 innerLayout->setContentsMargins( 0, 0, 0, 0 );
1055                 innerLayout->setAlignment( align );
1056                 innerHdFtLayouts[ headOrFoot ][ row ][ column ] = innerLayout;
1057 
1058                 QGridLayout* outerLayout = headOrFoot == 0 ? headerLayout : footerLayout;
1059                 outerLayout->addLayout( innerLayout, row, column, align );
1060             }
1061         }
1062     }
1063 
1064     // 6. the spacing below the footer area
1065     vLayout->addSpacing( globalLeadingBottom );
1066     bottomOuterSpacer = vLayout->itemAt( vLayout->count() - 1 )->spacerItem();
1067 
1068     // the data+axes area
1069     dataAndLegendLayout->addLayout( planesLayout, 1, 1 );
1070     dataAndLegendLayout->setRowStretch( 1, 1 );
1071     dataAndLegendLayout->setColumnStretch( 1, 1 );
1072 }
1073 
slotResizePlanes()1074 void Chart::Private::slotResizePlanes()
1075 {
1076     if ( !dataAndLegendLayout ) {
1077         return;
1078     }
1079     if ( !overrideSize.isValid() ) {
1080         // activate() takes the size from the layout's parent QWidget, which is not updated when overrideSize
1081         // is set. So don't let the layout grab the wrong size in that case.
1082         // When overrideSize *is* set, we call layout->setGeometry() in paint( QPainter*, const QRect& ),
1083         // which also "activates" the layout in the sense that it distributes space internally.
1084         layout->activate();
1085     }
1086     // Adapt diagram drawing to the new size
1087     Q_FOREACH (AbstractCoordinatePlane* plane, coordinatePlanes ) {
1088         plane->layoutDiagrams();
1089     }
1090 }
1091 
updateDirtyLayouts()1092 void Chart::Private::updateDirtyLayouts()
1093 {
1094     if ( isPlanesLayoutDirty ) {
1095         Q_FOREACH ( AbstractCoordinatePlane* p, coordinatePlanes ) {
1096             p->setGridNeedsRecalculate();
1097             p->layoutPlanes();
1098             p->layoutDiagrams();
1099         }
1100     }
1101     if ( isPlanesLayoutDirty || isFloatingLegendsLayoutDirty ) {
1102         chart->reLayoutFloatingLegends();
1103     }
1104     isPlanesLayoutDirty = false;
1105     isFloatingLegendsLayoutDirty = false;
1106 }
1107 
reapplyInternalLayouts()1108 void Chart::Private::reapplyInternalLayouts()
1109 {
1110     QRect geo = layout->geometry();
1111 
1112     invalidateLayoutTree( layout );
1113     layout->setGeometry( geo );
1114     slotResizePlanes();
1115 }
1116 
paintAll(QPainter * painter)1117 void Chart::Private::paintAll( QPainter* painter )
1118 {
1119     updateDirtyLayouts();
1120 
1121     QRect rect( QPoint( 0, 0 ), overrideSize.isValid() ? overrideSize : chart->size() );
1122 
1123     //qDebug() << this<<"::paintAll() uses layout size" << currentLayoutSize;
1124 
1125     // Paint the background (if any)
1126     AbstractAreaBase::paintBackgroundAttributes( *painter, rect, backgroundAttributes );
1127     // Paint the frame (if any)
1128     AbstractAreaBase::paintFrameAttributes( *painter, rect, frameAttributes );
1129 
1130     chart->reLayoutFloatingLegends();
1131 
1132     Q_FOREACH( AbstractLayoutItem* planeLayoutItem, planeLayoutItems ) {
1133         planeLayoutItem->paintAll( *painter );
1134     }
1135     Q_FOREACH( TextArea* textLayoutItem, textLayoutItems ) {
1136         textLayoutItem->paintAll( *painter );
1137     }
1138     Q_FOREACH( Legend *legend, legends ) {
1139         const bool hidden = legend->isHidden() && legend->testAttribute( Qt::WA_WState_ExplicitShowHide );
1140         if ( !hidden ) {
1141             //qDebug() << "painting legend at " << legend->geometry();
1142             legend->paintIntoRect( *painter, legend->geometry() );
1143         }
1144     }
1145 }
1146 
1147 // ******** Chart interface implementation ***********
1148 
1149 #define d d_func()
1150 
Chart(QWidget * parent)1151 Chart::Chart ( QWidget* parent )
1152     : QWidget ( parent )
1153     , _d( new Private( this ) )
1154 {
1155 #if defined KDAB_EVAL
1156     EvalDialog::checkEvalLicense( "KD Chart" );
1157 #endif
1158 
1159     FrameAttributes frameAttrs;
1160 // no frame per default...
1161 //    frameAttrs.setVisible( true );
1162     frameAttrs.setPen( QPen( Qt::black ) );
1163     frameAttrs.setPadding( 1 );
1164     setFrameAttributes( frameAttrs );
1165 
1166     addCoordinatePlane( new CartesianCoordinatePlane ( this ) );
1167 
1168     d->createLayouts();
1169 }
1170 
~Chart()1171 Chart::~Chart()
1172 {
1173     delete d;
1174 }
1175 
setFrameAttributes(const FrameAttributes & a)1176 void Chart::setFrameAttributes( const FrameAttributes &a )
1177 {
1178     d->frameAttributes = a;
1179 }
1180 
frameAttributes() const1181 FrameAttributes Chart::frameAttributes() const
1182 {
1183     return d->frameAttributes;
1184 }
1185 
setBackgroundAttributes(const BackgroundAttributes & a)1186 void Chart::setBackgroundAttributes( const BackgroundAttributes &a )
1187 {
1188     d->backgroundAttributes = a;
1189 }
1190 
backgroundAttributes() const1191 BackgroundAttributes Chart::backgroundAttributes() const
1192 {
1193     return d->backgroundAttributes;
1194 }
1195 
1196 //TODO KChart 3.0; change QLayout into QBoxLayout::Direction
setCoordinatePlaneLayout(QLayout * layout)1197 void Chart::setCoordinatePlaneLayout( QLayout * layout )
1198 {
1199     if (layout == d->planesLayout)
1200         return;
1201     if (d->planesLayout) {
1202         // detach all QLayoutItem's the previous planesLayout has cause
1203         // otherwise deleting the planesLayout would delete them too.
1204         for(int i = d->planesLayout->count() - 1; i >= 0; --i) {
1205             d->planesLayout->takeAt(i);
1206         }
1207         delete d->planesLayout;
1208     }
1209     d->planesLayout = qobject_cast<QBoxLayout*>( layout );
1210     d->slotLayoutPlanes();
1211 }
1212 
coordinatePlaneLayout()1213 QLayout* Chart::coordinatePlaneLayout()
1214 {
1215     return d->planesLayout;
1216 }
1217 
coordinatePlane()1218 AbstractCoordinatePlane* Chart::coordinatePlane()
1219 {
1220     if ( d->coordinatePlanes.isEmpty() ) {
1221         qWarning() << "Chart::coordinatePlane: warning: no coordinate plane defined.";
1222         return nullptr;
1223     } else {
1224         return d->coordinatePlanes.first();
1225     }
1226 }
1227 
coordinatePlanes()1228 CoordinatePlaneList Chart::coordinatePlanes()
1229 {
1230     return d->coordinatePlanes;
1231 }
1232 
addCoordinatePlane(AbstractCoordinatePlane * plane)1233 void Chart::addCoordinatePlane( AbstractCoordinatePlane* plane )
1234 {
1235     // Append
1236     insertCoordinatePlane( d->coordinatePlanes.count(), plane );
1237 }
1238 
insertCoordinatePlane(int index,AbstractCoordinatePlane * plane)1239 void Chart::insertCoordinatePlane( int index, AbstractCoordinatePlane* plane )
1240 {
1241     if ( index < 0 || index > d->coordinatePlanes.count() ) {
1242         return;
1243     }
1244 
1245     connect( plane, SIGNAL(destroyedCoordinatePlane(AbstractCoordinatePlane*)),
1246              d,   SLOT(slotUnregisterDestroyedPlane(AbstractCoordinatePlane*)) );
1247     connect( plane, SIGNAL(needUpdate()),       this,   SLOT(update()) );
1248     connect( plane, SIGNAL(needRelayout()),     d,      SLOT(slotResizePlanes()) ) ;
1249     connect( plane, SIGNAL(needLayoutPlanes()), d,      SLOT(slotLayoutPlanes()) ) ;
1250     connect( plane, SIGNAL(propertiesChanged()),this, SIGNAL(propertiesChanged()) );
1251     d->coordinatePlanes.insert( index, plane );
1252     plane->setParent( this );
1253     d->slotLayoutPlanes();
1254 }
1255 
replaceCoordinatePlane(AbstractCoordinatePlane * plane,AbstractCoordinatePlane * oldPlane_)1256 void Chart::replaceCoordinatePlane( AbstractCoordinatePlane* plane,
1257                                     AbstractCoordinatePlane* oldPlane_ )
1258 {
1259     if ( plane && oldPlane_ != plane ) {
1260         AbstractCoordinatePlane* oldPlane = oldPlane_;
1261         if ( d->coordinatePlanes.count() ) {
1262             if ( ! oldPlane ) {
1263                 oldPlane = d->coordinatePlanes.first();
1264                 if ( oldPlane == plane )
1265                     return;
1266             }
1267             takeCoordinatePlane( oldPlane );
1268         }
1269         delete oldPlane;
1270         addCoordinatePlane( plane );
1271     }
1272 }
1273 
takeCoordinatePlane(AbstractCoordinatePlane * plane)1274 void Chart::takeCoordinatePlane( AbstractCoordinatePlane* plane )
1275 {
1276     const int idx = d->coordinatePlanes.indexOf( plane );
1277     if ( idx != -1 ) {
1278         d->coordinatePlanes.takeAt( idx );
1279         disconnect( plane, nullptr, d, nullptr );
1280         disconnect( plane, nullptr, this, nullptr );
1281         plane->removeFromParentLayout();
1282         plane->setParent( nullptr );
1283         d->mouseClickedPlanes.removeAll(plane);
1284     }
1285     d->slotLayoutPlanes();
1286     // Need to Q_EMIT the signal: In case somebody has connected the signal
1287     // to her own slot for e.g. calling update() on a widget containing the chart.
1288     Q_EMIT propertiesChanged();
1289 }
1290 
setGlobalLeading(int left,int top,int right,int bottom)1291 void Chart::setGlobalLeading( int left, int top, int right, int bottom )
1292 {
1293     setGlobalLeadingLeft( left );
1294     setGlobalLeadingTop( top );
1295     setGlobalLeadingRight( right );
1296     setGlobalLeadingBottom( bottom );
1297 }
1298 
setGlobalLeadingLeft(int leading)1299 void Chart::setGlobalLeadingLeft( int leading )
1300 {
1301     d->globalLeadingLeft = leading;
1302     d->leftOuterSpacer->changeSize( leading, 0, QSizePolicy::Fixed, QSizePolicy::Minimum );
1303     d->reapplyInternalLayouts();
1304 }
1305 
globalLeadingLeft() const1306 int Chart::globalLeadingLeft() const
1307 {
1308     return d->globalLeadingLeft;
1309 }
1310 
setGlobalLeadingTop(int leading)1311 void Chart::setGlobalLeadingTop( int leading )
1312 {
1313     d->globalLeadingTop = leading;
1314     d->topOuterSpacer->changeSize( 0, leading, QSizePolicy::Minimum, QSizePolicy::Fixed );
1315     d->reapplyInternalLayouts();
1316 }
1317 
globalLeadingTop() const1318 int Chart::globalLeadingTop() const
1319 {
1320     return d->globalLeadingTop;
1321 }
1322 
setGlobalLeadingRight(int leading)1323 void Chart::setGlobalLeadingRight( int leading )
1324 {
1325     d->globalLeadingRight = leading;
1326     d->rightOuterSpacer->changeSize( leading, 0, QSizePolicy::Fixed, QSizePolicy::Minimum );
1327     d->reapplyInternalLayouts();
1328 }
1329 
globalLeadingRight() const1330 int Chart::globalLeadingRight() const
1331 {
1332     return d->globalLeadingRight;
1333 }
1334 
setGlobalLeadingBottom(int leading)1335 void Chart::setGlobalLeadingBottom( int leading )
1336 {
1337     d->globalLeadingBottom = leading;
1338     d->bottomOuterSpacer->changeSize( 0, leading, QSizePolicy::Minimum, QSizePolicy::Fixed );
1339     d->reapplyInternalLayouts();
1340 }
1341 
globalLeadingBottom() const1342 int Chart::globalLeadingBottom() const
1343 {
1344     return d->globalLeadingBottom;
1345 }
1346 
paint(QPainter * painter,const QRect & rect)1347 void Chart::paint( QPainter* painter, const QRect& rect )
1348 {
1349     if ( rect.isEmpty() || !painter ) {
1350         return;
1351     }
1352 
1353     QPaintDevice* prevDevice = GlobalMeasureScaling::paintDevice();
1354     GlobalMeasureScaling::setPaintDevice( painter->device() );
1355     int prevScaleFactor = PrintingParameters::scaleFactor();
1356 
1357     PrintingParameters::setScaleFactor( qreal( painter->device()->logicalDpiX() ) / qreal( logicalDpiX() ) );
1358 
1359     const QRect oldGeometry( geometry() );
1360     if ( oldGeometry != rect ) {
1361         setGeometry( rect );
1362         d->isPlanesLayoutDirty = true;
1363         d->isFloatingLegendsLayoutDirty = true;
1364     }
1365     painter->translate( rect.left(), rect.top() );
1366     d->paintAll( painter );
1367 
1368     // for debugging
1369     // painter->setPen( QPen( Qt::blue, 8 ) );
1370     // painter->drawRect( rect );
1371 
1372     painter->translate( -rect.left(), -rect.top() );
1373     if ( oldGeometry != rect ) {
1374         setGeometry( oldGeometry );
1375         d->isPlanesLayoutDirty = true;
1376         d->isFloatingLegendsLayoutDirty = true;
1377     }
1378 
1379     PrintingParameters::setScaleFactor( prevScaleFactor );
1380     GlobalMeasureScaling::setPaintDevice( prevDevice );
1381 }
1382 
resizeEvent(QResizeEvent * event)1383 void Chart::resizeEvent ( QResizeEvent* event )
1384 {
1385     d->isPlanesLayoutDirty = true;
1386     d->isFloatingLegendsLayoutDirty = true;
1387     QWidget::resizeEvent( event );
1388 }
1389 
reLayoutFloatingLegends()1390 void Chart::reLayoutFloatingLegends()
1391 {
1392     Q_FOREACH( Legend *legend, d->legends ) {
1393         const bool hidden = legend->isHidden() && legend->testAttribute( Qt::WA_WState_ExplicitShowHide );
1394         if ( legend->position().isFloating() && !hidden ) {
1395             // resize the legend
1396             const QSize legendSize( legend->sizeHint() );
1397             legend->setGeometry( QRect( legend->geometry().topLeft(), legendSize ) );
1398             // find the legends corner point (reference point plus any paddings)
1399             const RelativePosition relPos( legend->floatingPosition() );
1400             QPointF pt( relPos.calculatedPoint( size() ) );
1401             //qDebug() << pt;
1402             // calculate the legend's top left point
1403             const Qt::Alignment alignTopLeft = Qt::AlignBottom | Qt::AlignLeft;
1404             if ( (relPos.alignment() & alignTopLeft) != alignTopLeft ) {
1405                 if ( relPos.alignment() & Qt::AlignRight )
1406                     pt.rx() -= legendSize.width();
1407                 else if ( relPos.alignment() & Qt::AlignHCenter )
1408                     pt.rx() -= 0.5 * legendSize.width();
1409 
1410                 if ( relPos.alignment() & Qt::AlignBottom )
1411                     pt.ry() -= legendSize.height();
1412                 else if ( relPos.alignment() & Qt::AlignVCenter )
1413                     pt.ry() -= 0.5 * legendSize.height();
1414             }
1415             //qDebug() << pt << endl;
1416             legend->move( static_cast<int>(pt.x()), static_cast<int>(pt.y()) );
1417         }
1418     }
1419 }
1420 
1421 
paintEvent(QPaintEvent *)1422 void Chart::paintEvent( QPaintEvent* )
1423 {
1424     QPainter painter( this );
1425     d->paintAll( &painter );
1426     Q_EMIT finishedDrawing();
1427 }
1428 
addHeaderFooter(HeaderFooter * hf)1429 void Chart::addHeaderFooter( HeaderFooter* hf )
1430 {
1431     Q_ASSERT( hf->type() == HeaderFooter::Header || hf->type() == HeaderFooter::Footer );
1432     int row;
1433     int column;
1434     getRowAndColumnForPosition( hf->position().value(), &row, &column );
1435     if ( row == -1 ) {
1436         qWarning( "Unknown header/footer position" );
1437         return;
1438     }
1439 
1440     d->headerFooters.append( hf );
1441     d->textLayoutItems.append( hf );
1442     connect( hf, SIGNAL(destroyedHeaderFooter(HeaderFooter*)),
1443              d, SLOT(slotUnregisterDestroyedHeaderFooter(HeaderFooter*)) );
1444     connect( hf, SIGNAL(positionChanged(HeaderFooter*)),
1445              d, SLOT(slotHeaderFooterPositionChanged(HeaderFooter*)) );
1446 
1447     // set the text attributes (why?)
1448 
1449     TextAttributes textAttrs( hf->textAttributes() );
1450     Measure measure( textAttrs.fontSize() );
1451     measure.setRelativeMode( this, KChartEnums::MeasureOrientationMinimum );
1452     measure.setValue( 20 );
1453     textAttrs.setFontSize( measure );
1454     hf->setTextAttributes( textAttrs );
1455 
1456     // add it to the appropriate layout
1457 
1458     int innerLayoutIdx = hf->type() == HeaderFooter::Header ? 0 : 1;
1459     QVBoxLayout* headerFooterLayout = d->innerHdFtLayouts[ innerLayoutIdx ][ row ][ column ];
1460 
1461     hf->setParentLayout( headerFooterLayout );
1462     hf->setAlignment( s_gridAlignments[ row ][ column ] );
1463     headerFooterLayout->addItem( hf );
1464 
1465     d->slotResizePlanes();
1466 }
1467 
replaceHeaderFooter(HeaderFooter * headerFooter,HeaderFooter * oldHeaderFooter_)1468 void Chart::replaceHeaderFooter( HeaderFooter* headerFooter,
1469                                  HeaderFooter* oldHeaderFooter_ )
1470 {
1471     if ( headerFooter && oldHeaderFooter_ != headerFooter ) {
1472         HeaderFooter* oldHeaderFooter = oldHeaderFooter_;
1473         if ( d->headerFooters.count() ) {
1474             if ( ! oldHeaderFooter ) {
1475                 oldHeaderFooter = d->headerFooters.first();
1476                 if ( oldHeaderFooter == headerFooter )
1477                     return;
1478             }
1479             takeHeaderFooter( oldHeaderFooter );
1480         }
1481         delete oldHeaderFooter;
1482         addHeaderFooter( headerFooter );
1483     }
1484 }
1485 
takeHeaderFooter(HeaderFooter * headerFooter)1486 void Chart::takeHeaderFooter( HeaderFooter* headerFooter )
1487 {
1488     const int idx = d->headerFooters.indexOf( headerFooter );
1489     if ( idx == -1 ) {
1490         return;
1491     }
1492     disconnect( headerFooter, SIGNAL(destroyedHeaderFooter(HeaderFooter*)),
1493                 d, SLOT(slotUnregisterDestroyedHeaderFooter(HeaderFooter*)) );
1494 
1495     d->headerFooters.takeAt( idx );
1496     headerFooter->removeFromParentLayout();
1497     headerFooter->setParentLayout( nullptr );
1498     d->textLayoutItems.remove( d->textLayoutItems.indexOf( headerFooter ) );
1499 
1500     d->slotResizePlanes();
1501 }
1502 
slotHeaderFooterPositionChanged(HeaderFooter * hf)1503 void Chart::Private::slotHeaderFooterPositionChanged( HeaderFooter* hf )
1504 {
1505     chart->takeHeaderFooter( hf );
1506     chart->addHeaderFooter( hf );
1507 }
1508 
headerFooter()1509 HeaderFooter* Chart::headerFooter()
1510 {
1511     if ( d->headerFooters.isEmpty() ) {
1512         return nullptr;
1513     } else {
1514         return d->headerFooters.first();
1515     }
1516 }
1517 
headerFooters()1518 HeaderFooterList Chart::headerFooters()
1519 {
1520     return d->headerFooters;
1521 }
1522 
slotLegendPositionChanged(AbstractAreaWidget * aw)1523 void Chart::Private::slotLegendPositionChanged( AbstractAreaWidget* aw )
1524 {
1525     Legend* legend = qobject_cast< Legend* >( aw );
1526     Q_ASSERT( legend );
1527     chart->takeLegend( legend );
1528     chart->addLegendInternal( legend, false );
1529 }
1530 
addLegend(Legend * legend)1531 void Chart::addLegend( Legend* legend )
1532 {
1533     legend->show();
1534     addLegendInternal( legend, true );
1535     Q_EMIT propertiesChanged();
1536 }
1537 
addLegendInternal(Legend * legend,bool setMeasures)1538 void Chart::addLegendInternal( Legend* legend, bool setMeasures )
1539 {
1540     if ( !legend ) {
1541         return;
1542     }
1543 
1544     KChartEnums::PositionValue pos = legend->position().value();
1545     if ( pos == KChartEnums::PositionCenter ) {
1546        qWarning( "Not showing legend because PositionCenter is not supported for legends." );
1547     }
1548 
1549     int row;
1550     int column;
1551     getRowAndColumnForPosition( pos, &row, &column );
1552     if ( row < 0 && pos != KChartEnums::PositionFloating ) {
1553         qWarning( "Not showing legend because of unknown legend position." );
1554         return;
1555     }
1556 
1557     d->legends.append( legend );
1558     legend->setParent( this );
1559 
1560     // set text attributes (why?)
1561 
1562     if ( setMeasures ) {
1563         TextAttributes textAttrs( legend->textAttributes() );
1564         Measure measure( textAttrs.fontSize() );
1565         measure.setRelativeMode( this, KChartEnums::MeasureOrientationMinimum );
1566         measure.setValue( 20 );
1567         textAttrs.setFontSize( measure );
1568         legend->setTextAttributes( textAttrs );
1569 
1570         textAttrs = legend->titleTextAttributes();
1571         measure.setRelativeMode( this, KChartEnums::MeasureOrientationMinimum );
1572         measure.setValue( 24 );
1573         textAttrs.setFontSize( measure );
1574 
1575         legend->setTitleTextAttributes( textAttrs );
1576         legend->setReferenceArea( this );
1577     }
1578 
1579     // add it to the appropriate layout
1580 
1581     if ( pos != KChartEnums::PositionFloating ) {
1582         legend->needSizeHint();
1583 
1584         // in each edge and corner of the outer layout, there's a grid for the different alignments that we create
1585         // on demand. we don't remove it when empty.
1586 
1587         QLayoutItem* edgeItem = d->dataAndLegendLayout->itemAtPosition( row, column );
1588         QGridLayout* alignmentsLayout = dynamic_cast< QGridLayout* >( edgeItem );
1589         Q_ASSERT( !edgeItem || alignmentsLayout ); // if it exists, it must be a QGridLayout
1590         if ( !alignmentsLayout ) {
1591             alignmentsLayout = new QGridLayout;
1592             d->dataAndLegendLayout->addLayout( alignmentsLayout, row, column );
1593             alignmentsLayout->setContentsMargins( 0, 0, 0, 0 );
1594         }
1595 
1596         // in case there are several legends in the same edge or corner with the same alignment, they are stacked
1597         // vertically using a QVBoxLayout. it is created on demand as above.
1598 
1599         row = 1;
1600         column = 1;
1601         for ( int i = 0; i < 3; i++ ) {
1602             for ( int j = 0; j < 3; j++ ) {
1603                 Qt::Alignment align = s_gridAlignments[ i ][ j ];
1604                 if ( align == legend->alignment() ) {
1605                     row = i;
1606                     column = j;
1607                     break;
1608                 }
1609             }
1610         }
1611 
1612         QLayoutItem* alignmentItem = alignmentsLayout->itemAtPosition( row, column );
1613         QVBoxLayout* sameAlignmentLayout = dynamic_cast< QVBoxLayout* >( alignmentItem );
1614         Q_ASSERT( !alignmentItem || sameAlignmentLayout ); // if it exists, it must be a QVBoxLayout
1615         if ( !sameAlignmentLayout ) {
1616             sameAlignmentLayout = new QVBoxLayout;
1617             alignmentsLayout->addLayout( sameAlignmentLayout, row, column );
1618             sameAlignmentLayout->setContentsMargins( 0, 0, 0, 0 );
1619         }
1620 
1621         sameAlignmentLayout->addItem( new MyWidgetItem( legend, legend->alignment() ) );
1622     }
1623 
1624     connect( legend, SIGNAL(destroyedLegend(Legend*)),
1625              d, SLOT(slotUnregisterDestroyedLegend(Legend*)) );
1626     connect( legend, SIGNAL(positionChanged(AbstractAreaWidget*)),
1627              d, SLOT(slotLegendPositionChanged(AbstractAreaWidget*)) );
1628     connect( legend, SIGNAL(propertiesChanged()), this, SIGNAL(propertiesChanged()) );
1629 
1630     d->slotResizePlanes();
1631 }
1632 
replaceLegend(Legend * legend,Legend * oldLegend_)1633 void Chart::replaceLegend( Legend* legend, Legend* oldLegend_ )
1634 {
1635     if ( legend && oldLegend_ != legend ) {
1636         Legend* oldLegend = oldLegend_;
1637         if ( d->legends.count() ) {
1638             if ( ! oldLegend ) {
1639                 oldLegend = d->legends.first();
1640                 if ( oldLegend == legend )
1641                     return;
1642             }
1643             takeLegend( oldLegend );
1644         }
1645         delete oldLegend;
1646         addLegend( legend );
1647     }
1648 }
1649 
takeLegend(Legend * legend)1650 void Chart::takeLegend( Legend* legend )
1651 {
1652     const int idx = d->legends.indexOf( legend );
1653     if ( idx == -1 ) {
1654         return;
1655     }
1656 
1657     d->legends.takeAt( idx );
1658     disconnect( legend, nullptr, d, nullptr );
1659     disconnect( legend, nullptr, this, nullptr );
1660     // the following removes the legend from its layout and destroys its MyWidgetItem (the link to the layout)
1661     legend->setParent( nullptr );
1662 
1663     d->slotResizePlanes();
1664     Q_EMIT propertiesChanged();
1665 }
1666 
legend()1667 Legend* Chart::legend()
1668 {
1669     return d->legends.isEmpty() ? nullptr : d->legends.first();
1670 }
1671 
legends()1672 LegendList Chart::legends()
1673 {
1674     return d->legends;
1675 }
1676 
mousePressEvent(QMouseEvent * event)1677 void Chart::mousePressEvent( QMouseEvent* event )
1678 {
1679     const QPoint pos = mapFromGlobal( event->globalPos() );
1680 
1681     Q_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes ) {
1682         if ( plane->geometry().contains( event->pos() ) && plane->diagrams().size() > 0 ) {
1683             QMouseEvent ev( QEvent::MouseButtonPress, pos, event->globalPos(),
1684                             event->button(), event->buttons(), event->modifiers() );
1685             plane->mousePressEvent( &ev );
1686             d->mouseClickedPlanes.append( plane );
1687        }
1688     }
1689 }
1690 
mouseDoubleClickEvent(QMouseEvent * event)1691 void Chart::mouseDoubleClickEvent( QMouseEvent* event )
1692 {
1693     const QPoint pos = mapFromGlobal( event->globalPos() );
1694 
1695     Q_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes ) {
1696         if ( plane->geometry().contains( event->pos() ) && plane->diagrams().size() > 0 ) {
1697             QMouseEvent ev( QEvent::MouseButtonPress, pos, event->globalPos(),
1698                             event->button(), event->buttons(), event->modifiers() );
1699             plane->mouseDoubleClickEvent( &ev );
1700         }
1701     }
1702 }
1703 
mouseMoveEvent(QMouseEvent * event)1704 void Chart::mouseMoveEvent( QMouseEvent* event )
1705 {
1706     QSet< AbstractCoordinatePlane* > eventReceivers = QSet< AbstractCoordinatePlane* >::fromList( d->mouseClickedPlanes );
1707 
1708     Q_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes ) {
1709         if ( plane->geometry().contains( event->pos() ) && plane->diagrams().size() > 0 ) {
1710             eventReceivers.insert( plane );
1711         }
1712     }
1713 
1714     const QPoint pos = mapFromGlobal( event->globalPos() );
1715 
1716     Q_FOREACH( AbstractCoordinatePlane* plane, eventReceivers ) {
1717         QMouseEvent ev( QEvent::MouseMove, pos, event->globalPos(),
1718                          event->button(), event->buttons(), event->modifiers() );
1719         plane->mouseMoveEvent( &ev );
1720     }
1721 }
1722 
mouseReleaseEvent(QMouseEvent * event)1723 void Chart::mouseReleaseEvent( QMouseEvent* event )
1724 {
1725     QSet< AbstractCoordinatePlane* > eventReceivers = QSet< AbstractCoordinatePlane* >::fromList( d->mouseClickedPlanes );
1726 
1727     Q_FOREACH( AbstractCoordinatePlane* plane, d->coordinatePlanes ) {
1728         if ( plane->geometry().contains( event->pos() ) && plane->diagrams().size() > 0 ) {
1729             eventReceivers.insert( plane );
1730         }
1731     }
1732 
1733     const QPoint pos = mapFromGlobal( event->globalPos() );
1734 
1735     Q_FOREACH( AbstractCoordinatePlane* plane, eventReceivers ) {
1736         QMouseEvent ev( QEvent::MouseButtonRelease, pos, event->globalPos(),
1737                          event->button(), event->buttons(), event->modifiers() );
1738         plane->mouseReleaseEvent( &ev );
1739     }
1740 
1741     d->mouseClickedPlanes.clear();
1742 }
1743 
event(QEvent * event)1744 bool Chart::event( QEvent* event )
1745 {
1746     if ( event->type() == QEvent::ToolTip ) {
1747         const QHelpEvent* const helpEvent = static_cast< QHelpEvent* >( event );
1748         Q_FOREACH( const AbstractCoordinatePlane* const plane, d->coordinatePlanes ) {
1749             // iterate diagrams in reverse, so that the top-most painted diagram is
1750             // queried first for a tooltip before the diagrams behind it
1751             const ConstAbstractDiagramList& diagrams = plane->diagrams();
1752             for (int i = diagrams.size() - 1; i >= 0; --i) {
1753                 const AbstractDiagram* diagram = diagrams[i];
1754                 if (diagram->isHidden()) {
1755                     continue;
1756                 }
1757                 const QModelIndex index = diagram->indexAt( helpEvent->pos() );
1758                 const QVariant toolTip = index.data( Qt::ToolTipRole );
1759                 if ( toolTip.isValid() ) {
1760                     QPoint pos = mapFromGlobal( helpEvent->pos() );
1761                     QRect rect( pos - QPoint( 1, 1 ), QSize( 3, 3 ) );
1762                     QToolTip::showText( QCursor::pos(), toolTip.toString(), this, rect );
1763                     return true;
1764                 }
1765             }
1766         }
1767     }
1768     return QWidget::event( event );
1769 }
1770 
useNewLayoutSystem() const1771 bool Chart::useNewLayoutSystem() const
1772 {
1773     return d_func()->useNewLayoutSystem;
1774 }
setUseNewLayoutSystem(bool value)1775 void Chart::setUseNewLayoutSystem( bool value )
1776 {
1777     if ( d_func()->useNewLayoutSystem != value )
1778         d_func()->useNewLayoutSystem = value;
1779 }
1780