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