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

Klarälvdalens Datakonsult AB (KDAB)
Qt-related services and products
http://www.kdab.com/
http://www.kdab.com/products/kd-chart/