GobChartsWidget  1.0
gobchartsview.cpp
1 /* Copyright (C) 2012 by William Hallatt.
2  *
3  * This file forms part of the "GobChartsWidget" library.
4  *
5  * This library is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have downloaded a copy of the GNU General Public License
16  * (GNUGPL.txt) and GNU Lesser General Public License (GNULGPL.txt)
17  * along with this library. If not, see <http://www.gnu.org/licenses/>.
18  *
19  * The official website for this project is www.goblincoding.com and,
20  * although not compulsory, it would be appreciated if all works of whatever
21  * nature referring to or using this library include a reference to this site.
22  */
23 
24 #include "gobchartsview.h"
25 #include "label/gobchartstextitem.h"
26 #include "utils/gobchartscolours.h"
27 #include "utils/gobchartsgrid.h"
28 #include "utils/gobchartsgraphitems.h"
29 #include "utils/gobchartsvaliditems.h"
30 
31 #include <QtCore/qmath.h>
32 #include <QGraphicsView>
33 #include <QVBoxLayout>
34 #include <QDomDocument>
35 
36 /*--------------------------------------------------------------------------------*/
37 
38 /* Percentages to be used for header and label text positioning. */
39 
40 /* PERC_HEADER_SPACE + PERC_HEADER_TOP <= 1.0 */
41 const qreal PERC_HEADER_SPACE = 0.7; // of total Y margin
42 const qreal PERC_HEADER_TOP = 0.2; // from top of scene
43 
44 /* PERC_XLABEL_SPACE + ( 1.0 - PERC_XLABEL_BOTTOM ) <= 1.0 */
45 const qreal PERC_XLABEL_SPACE = 0.7; // of total Y margin
46 const qreal PERC_XLABEL_BOTTOM = 0.8; // from bottom of the scene
47 
48 /* PERC_YLABEL_SPACE + PERC_YLABEL_LEFT <= 1.0 */
49 const qreal PERC_YLABEL_SPACE = 0.7; // of total X margin
50 const qreal PERC_YLABEL_LEFT = 0.2; // from left-hand side of the scene
51 
52 const qreal LEFT_RIGHT_MARGIN_PERC = 0.15; // of total width
53 const qreal TOP_BOTTOM_MARGIN_PERC = 0.15; // of total height
54 
55 /* Arbitrary number selected on the basis of the resulting cosmetic appearance. */
56 const qreal STRIPSPACE_OFFSET = 0.05;
57 
58 
59 
60 /*--------------------------------- PIMPL CLASS ----------------------------------*/
61 
63 {
64 public:
65 
66  /* Emits the lastDebugLogMsg signal if logging is turned on. */
67  inline void emitDebugLogMsg( QString msg )
68  {
69  if( m_loggingOn )
70  {
71  emit m_gobChartsView->lastDebugLogMsg( msg );
72  }
73  }
74 
75  /*--------------------------------------------------------------------------------*/
76 
77  /* Calculates the total for all active rows and also determines which value is the maximum value. */
78  void calculateActiveTotals( int end )
79  {
80  m_totalValue = 0.0;
81  m_maxValue = 0.0;
82  m_maxRow = 0;
83  m_validItems->clear();
84 
85  bool toDoubleOK = true;
86 
87  for( int row = 0; row < end; row++ )
88  {
89  QString cat = m_gobChartsView->model()->data( m_gobChartsView->model()->index( row, CATEGORY ) ).toString().trimmed();
90  QString val = m_gobChartsView->model()->data( m_gobChartsView->model()->index( row, VALUE ) ).toString().trimmed();
91  qreal value;
92 
93  /* Check for empty entries */
94  if( !cat.isEmpty() || !val.isEmpty() )
95  {
96  if( cat.isEmpty() )
97  {
98  cat = QString( "Uncategorised" );
99  emitDebugLogMsg( tr( "GobChartsView::calculateActiveTotals# No category provided for row [%1]." ).arg( row + 1 ) );
100  }
101 
102  if( val.isEmpty() )
103  {
104  value = 0.0;
105  emitDebugLogMsg( tr( "GobChartsView::calculateActiveTotals# No value provided for row [%1], defaulting to \"0.0\"." ).arg( row + 1 ) );
106  }
107  else
108  {
109  value = val.toDouble( &toDoubleOK );
110 
111  if( toDoubleOK )
112  {
113  if ( value < 0.0)
114  {
115  value = 0.0;
116  emitDebugLogMsg( tr( "GobChartsView::calculateActiveTotals# Value provided for row [%1] is negative, defaulting to \"0.0\"." ).arg( row + 1 ) );
117  }
118 
119  m_totalValue += value;
120  m_maxValue = ( value > m_maxValue ) ? value : m_maxValue;
121  }
122  else
123  {
124  toDoubleOK = true; // reset for next iteration
125  emitDebugLogMsg( tr( "GobChartsView::calculateActiveTotals# Failed to convert the data value of row [%1] to \"double\"" ).arg( row + 1 ) );
126  }
127  }
128 
129  m_validItems->addValidItem( row, cat, value );
130  }
131 
132  m_maxRow++;
133  }
134  }
135 
136  /*--------------------------------------------------------------------------------*/
137 
138  /* Calculates and sets all the chart's dimensions and allowed areas. */
139  void calculateGeometries()
140  {
141  m_leftRightMargin = m_gobChartsView->rect().width() * LEFT_RIGHT_MARGIN_PERC;
142  m_topBottomMargin = m_gobChartsView->rect().height() * TOP_BOTTOM_MARGIN_PERC;
143 
144  m_graphScene->setSceneRect( QRectF( m_gobChartsView->rect() ) );
145  m_graphicsView->setSceneRect( QRectF( m_gobChartsView->rect() ) );
146 
147  m_innerSceneRectF.setRect( m_gobChartsView->rect().x() + m_leftRightMargin,
148  m_gobChartsView->rect().y() + m_topBottomMargin,
149  m_gobChartsView->rect().width() - 2 * m_leftRightMargin,
150  m_gobChartsView->rect().height() - 2 * m_topBottomMargin );
151 
152  m_grid->setGridRectF( QRectF( m_innerSceneRectF.left(),
153  m_innerSceneRectF.top(),
154  m_innerSceneRectF.width(),
155  m_innerSceneRectF.height() ) );
156 
157  m_header->setRectF( QRectF( QPointF( m_innerSceneRectF.left(), m_topBottomMargin * PERC_HEADER_TOP ),
158  QSizeF( m_innerSceneRectF.width(), m_topBottomMargin * PERC_HEADER_SPACE ) ) );
159 
160  m_xLabel->setRectF( QRectF( QPointF( m_innerSceneRectF.left(), m_gobChartsView->rect().height() - m_topBottomMargin * PERC_XLABEL_BOTTOM ),
161  QSizeF( m_innerSceneRectF.width(), m_topBottomMargin * PERC_XLABEL_SPACE ) ) );
162 
163  m_yLabel->setRectF( QRectF( QPointF( m_leftRightMargin * PERC_YLABEL_LEFT, m_innerSceneRectF.top() ),
164  QSizeF( m_leftRightMargin * PERC_YLABEL_SPACE, m_innerSceneRectF.height() ) ) );
165  }
166 
167  /*--------------------------------------------------------------------------------*/
168 
169  QString labelText( GobChartsLabel label )
170  {
171  if( m_labels.contains( label ) )
172  {
173  return m_labels.value( label )->toPlainText();
174  }
175 
176  return QString( "" );
177  }
178 
179  /*--------------------------------------------------------------------------------*/
180 
181  QFont labelFont( GobChartsLabel label )
182  {
183  if( m_labels.contains( label ) )
184  {
185  return m_labels.value( label )->font();
186  }
187 
188  return QFont();
189  }
190 
191  /*--------------------------------------------------------------------------------*/
192 
193  QColor labelColour( GobChartsLabel label )
194  {
195  if( m_labels.contains( label ) )
196  {
197  return m_labels.value( label )->defaultTextColor();
198  }
199 
200  return QColor();
201  }
202 
203  /*--------------------------------------------------------------------------------*/
204 
205  Qt::Alignment labelAlignment( GobChartsLabel label )
206  {
207  if( m_labels.contains( label ) )
208  {
209  return m_labels.value( label )->alignment();
210  }
211 
212  return Qt::AlignHCenter;
213  }
214 
215  /*--------------------------------------------------------------------------------*/
216 
217  GobChartsTextItem *labelGraphicsItem( GobChartsLabel label )
218  {
219  if( m_labels.contains( label ) )
220  {
221  return const_cast< GobChartsTextItem* >( m_labels.value( label ) );
222  }
223 
224  /* We really shouldn't ever get here...but rather safe than sorry. */
225  return m_header;
226  }
227 
228  /*--------------------------------------------------------------------------------*/
229 
230  /* Constructor and destructor. */
232  :
233  m_gobChartsView ( view ),
234  m_graphicsView ( new QGraphicsView ),
235  m_graphScene ( new QGraphicsScene ),
236  m_header ( new GobChartsTextItem( Qt::Horizontal, "HEADER" ) ),
237  m_yLabel ( new GobChartsTextItem( Qt::Vertical, "YLABEL" ) ),
238  m_xLabel ( new GobChartsTextItem( Qt::Horizontal, "XLABEL" ) ),
239  m_graphItems ( new GobChartsGraphItems ),
240  m_grid ( new GobChartsGrid ),
241  m_validItems ( new GobChartsValidItems ),
242  m_selectedLabel ( NONE ),
243  m_innerSceneRectF (),
244  m_fixedColour (),
245  m_legendText ( "" ),
246  m_leftRightMargin ( 0.0 ),
247  m_topBottomMargin ( 0.0 ),
248  m_maxValue ( 0.0 ),
249  m_lowerDataBoundary( 0.0 ),
250  m_upperDataBoundary( 0.0 ),
251  m_totalValue ( 0.0 ),
252  m_maxRow ( 0 ),
253  m_showTotalRange ( true ),
254  m_loggingOn ( false ),
255  m_fixedColourOn ( false ),
256  m_chartIsLoading ( false )
257  {
258  m_graphScene->setBackgroundBrush( QBrush( QColor( 245,245,245 ) ) );
259 
260  m_graphScene->addItem( m_header ); // takes ownership
261  m_graphScene->addItem( m_xLabel ); // takes ownership
262  m_graphScene->addItem( m_yLabel ); // takes ownership
263 
264  m_graphicsView->setScene( m_graphScene );
265  m_graphicsView->setResizeAnchor( QGraphicsView::AnchorViewCenter );
266  m_graphicsView->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
267  m_graphicsView->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
268 
269  /* Convenience mapping. */
270  m_labels.insert( HEADER, m_header );
271  m_labels.insert( YLABEL, m_yLabel );
272  m_labels.insert( XLABEL, m_xLabel );
273 
274  m_labelNames.insert( "HEADER", HEADER );
275  m_labelNames.insert( "YLABEL", YLABEL );
276  m_labelNames.insert( "XLABEL", XLABEL );
277  }
278 
280  {
281  m_graphItems->removeItemsFromScene( m_graphScene );
282  m_grid->removeGridFromScene( m_graphScene );
283 
284  delete m_graphicsView;
285  delete m_graphScene;
286  delete m_graphItems;
287  delete m_grid;
288  delete m_validItems;
289  }
290 
291  GobChartsView *m_gobChartsView; // pointer to the object to which we have friend access to
292  QGraphicsView *m_graphicsView;
293  QGraphicsScene *m_graphScene;
294  GobChartsTextItem *m_header;
295  GobChartsTextItem *m_yLabel;
296  GobChartsTextItem *m_xLabel;
297  GobChartsGraphItems *m_graphItems;
298  GobChartsGrid *m_grid;
299  GobChartsValidItems *m_validItems;
300  GobChartsLabel m_selectedLabel; // to keep track of the selected text item to ensure the correct item receives the keyboard input
301  QRectF m_innerSceneRectF;
302  QColor m_fixedColour;
303  QString m_legendText;
304  qreal m_leftRightMargin;
305  qreal m_topBottomMargin;
306  qreal m_maxValue;
307  qreal m_lowerDataBoundary;
308  qreal m_upperDataBoundary;
309  qreal m_totalValue;
310  int m_maxRow; // the last row containing valid items
311  bool m_showTotalRange;
312  bool m_loggingOn;
313  bool m_fixedColourOn;
314  bool m_chartIsLoading;
315 
316  /* Convenience mappings to rid us of all the "switch" statements required otherwise. */
317  QMap< GobChartsLabel, GobChartsTextItem* > m_labels;
318  QMap< QString, GobChartsLabel > m_labelNames;
319 };
320 
321 
322 /*------------------------------- MEMBER FUNCTIONS -------------------------------*/
323 
324 GobChartsView::GobChartsView( QWidget *parent ) :
325  QAbstractItemView( parent ),
326  m_private ( new GobChartsViewPrivate( this ) )
327 {
328  setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
329  setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
330 
331  m_private->calculateGeometries();
332 
333  /* Labels. */
334  connect( m_private->m_header, SIGNAL( identity ( const QString& ) ),
335  this, SLOT ( setSelectedTextItem( const QString& ) ) );
336 
337  connect( m_private->m_xLabel, SIGNAL( identity ( const QString& ) ),
338  this, SLOT ( setSelectedTextItem( const QString& ) ) );
339 
340  connect( m_private->m_yLabel, SIGNAL( identity ( const QString& ) ),
341  this, SLOT ( setSelectedTextItem( const QString& ) ) );
342 
343  /* Debug logging. */
344  connect( m_private->m_graphItems, SIGNAL( lastDebugLogMsg( QString ) ), this, SLOT( debugLog( QString ) ) );
345  connect( m_private->m_grid, SIGNAL( lastDebugLogMsg( QString ) ), this, SLOT( debugLog( QString ) ) );
346 
347  QVBoxLayout *layout = new QVBoxLayout;
348  layout->addWidget( m_private->m_graphicsView );
349  setLayout( layout );
350 
351  setViewport( m_private->m_graphicsView );
352 }
353 
354 /*--------------------------------------------------------------------------------*/
355 
357 {
358  delete m_private;
359 }
360 
361 /*--------------------------------------------------------------------------------*/
362 
363 void GobChartsView::setLabelDetails( GobChartsLabel label, const QString &text, const QFont &font, const QColor &colour, Qt::Alignment align )
364 {
365  if( m_private->m_labels.contains( label ) )
366  {
367  /* Values returned from the map aren't modifiable by default. */
368  const_cast< GobChartsTextItem* >( m_private->m_labels.value( label ) )->setPlainText( text );
369  const_cast< GobChartsTextItem* >( m_private->m_labels.value( label ) )->setMaxFontSize( font );
370  const_cast< GobChartsTextItem* >( m_private->m_labels.value( label ) )->setAlignment( align );
371  const_cast< GobChartsTextItem* >( m_private->m_labels.value( label ) )->setDefaultTextColor( colour );
372  }
373 }
374 
375 /*--------------------------------------------------------------------------------*/
376 
377 void GobChartsView::requestLabelDetails( GobChartsLabel label )
378 {
379  if( m_private->m_labels.contains( label ) )
380  {
381  m_private->m_selectedLabel = label;
382  emit emitLabelDetails( label, m_private->labelText( label ), m_private->labelFont( label ), m_private->labelColour( label ), m_private->labelAlignment( label ) );
383 
384  m_private->m_graphScene->clearFocus();
385  m_private->m_graphScene->setFocusItem( m_private->labelGraphicsItem( label ) );
386  }
387  else
388  {
389  m_private->m_selectedLabel = NONE;
390  }
391 }
392 
393 /*--------------------------------------------------------------------------------*/
394 
395 void GobChartsView::setSelectedTextItem( const QString &itemName )
396 {
397  requestLabelDetails( m_private->m_labelNames.value( itemName ) );
398 }
399 
400 /*--------------------------------------------------------------------------------*/
401 
402 void GobChartsView::setGridLineStyle( Qt::PenStyle style )
403 {
404  m_private->m_grid->setGridLineStyle( style );
405  drawChart();
406 }
407 
408 /*--------------------------------------------------------------------------------*/
409 
410 void GobChartsView::setHorizontalGridLines( bool set, int number )
411 {
412  m_private->m_grid->setHorizontalGridLines( set, number );
413  drawChart();
414 }
415 
416 /*--------------------------------------------------------------------------------*/
417 
418 void GobChartsView::setVerticalGridLines( bool set, int number )
419 {
420  m_private->m_grid->setVerticalGridLines( set, number );
421  drawChart();
422 }
423 
424 /*--------------------------------------------------------------------------------*/
425 
426 void GobChartsView::setGridColour( QColor colour )
427 {
428  m_private->m_grid->setGridColour( colour );
429  drawChart();
430 }
431 
432 /*--------------------------------------------------------------------------------*/
433 
435 {
436  if( model() )
437  {
438  m_private->calculateGeometries();
439 
440  m_private->m_graphItems->removeItemsFromScene( m_private->m_graphScene );
441  m_private->m_graphItems->deleteItems();
442 
443  if( needsGrid() )
444  {
445  m_private->m_grid->removeGridFromScene( m_private->m_graphScene );
446  m_private->m_grid->constructGrid(); // delete old lines and generate new ones
447  m_private->m_grid->addGridToScene( m_private->m_graphScene );
448  }
449 
452 
453  m_private->m_graphItems->addItemsToScene( m_private->m_graphScene );
454  }
455  else
456  {
457  m_private->emitDebugLogMsg( tr( "GobChartsView::drawChart# No valid data model set." ) );
458  }
459 }
460 
461 /*--------------------------------------------------------------------------------*/
462 
463 void GobChartsView::legendItemSelected( const QString &text )
464 {
465  QRectF rectF = m_private->m_graphItems->getItemRectF( text );
466  QRect rect = rectF.toRect();
467  m_private->m_legendText = text;
468 
469  selectionModel()->setCurrentIndex( m_private->m_graphItems->getModelIndex( text ), QItemSelectionModel::NoUpdate );
470  setSelection( rect, QItemSelectionModel::Select /* this flag isn't actually used */ );
471 }
472 
473 /*--------------------------------------------------------------------------------*/
474 
475 void GobChartsView::setStateXML( const QDomNode &viewNode, const QDomNode &dataNode, bool includeData )
476 {
477  /* View related. */
478  if( !viewNode.isNull() )
479  {
480  /* Header. */
481  QDomNode label = viewNode.firstChildElement( "Header" );
482 
483  if( !m_private->m_header->setStateXML( label ) )
484  {
485  m_private->emitDebugLogMsg( tr( "GobChartsView::setStateXML# Header node is invalid or NULL." ) );;
486  }
487 
488  /* Y Label. */
489  label = viewNode.firstChildElement( "YLabel" );
490 
491  if( !m_private->m_yLabel->setStateXML( label ) )
492  {
493  m_private->emitDebugLogMsg( tr( "GobChartsView::setStateXML# Y Label node is invalid or NULL." ) );
494  }
495 
496  /* X Label. */
497  label = viewNode.firstChildElement( "XLabel" );
498 
499  if( !m_private->m_xLabel->setStateXML( label ) )
500  {
501  m_private->emitDebugLogMsg( tr( "GobChartsView::setStateXML# Y Label node is invalid or NULL." ) );
502  }
503 
504  /* If the chart is not of a type that can have a grid, then it makes no sense
505  to set the x or y labels either (e.g. PIE charts). */
506  if( needsGrid() )
507  {
508  m_private->m_yLabel->setVisible( true );
509  m_private->m_xLabel->setVisible( true );
510  }
511  else
512  {
513  m_private->m_yLabel->setVisible( false );
514  m_private->m_xLabel->setVisible( false );
515  }
516  }
517  else
518  {
519  m_private->emitDebugLogMsg( tr( "GobChartsView::setStateXML# View node is invalid or NULL." ) );
520  }
521 
522  /* Data related. */
523  if( includeData )
524  {
525  if( model() )
526  {
527  if( !dataNode.isNull() )
528  {
529  /* We don't want to execute the functionality in dataChanged() until we're done. */
530  m_private->m_chartIsLoading = true;
531 
532  /* Clear all existing data from the model. */
533  for( int row = 0; row < model()->rowCount(); row++ )
534  {
535  model()->setData( model()->index( row, CATEGORY ), QString() );
536  model()->setData( model()->index( row, VALUE ), QString() );
537  }
538 
539  /* Add new data to model. */
540  QDomElement itemElement = dataNode.firstChildElement( "Item" );
541  int row( 0 );
542 
543  while( !itemElement.isNull() )
544  {
545  model()->setData( model()->index( row, CATEGORY ), itemElement.attribute( "category", "Uncategorised" ) );
546  model()->setData( model()->index( row, VALUE ), itemElement.attribute( "value", "0.0" ) );
547  itemElement = itemElement.nextSiblingElement( "Item" );
548  row++;
549  }
550 
551  m_private->m_chartIsLoading = false;
552  m_private->calculateActiveTotals( row );
553  }
554  else
555  {
556  m_private->emitDebugLogMsg( tr( "GobChartsView::setStateXML# Data node is invalid or NULL." ) );
557  }
558  }
559  else
560  {
561  m_private->emitDebugLogMsg( tr( "GobChartsView::setStateXML# No data model associated with this view." ) );
562  }
563  }
564 }
565 
566 /*--------------------------------------------------------------------------------*/
567 
568 QString GobChartsView::getStateXML( bool includeData ) const
569 {
570  /* Tried returning a QDomDocumentFragment but couldn't get it to work
571  so decided on manually creating the XML here as it is pretty straightforward. */
572  QString xml;
573  xml += "<View>";
574 
575  xml += "<Header>";
576  xml += m_private->m_header->getStateXML();
577  xml += "</Header>";
578 
579  xml += "<YLabel>";
580  xml += m_private->m_yLabel->getStateXML();
581  xml += "</YLabel>";
582 
583  xml += "<XLabel>";
584  xml += m_private->m_xLabel->getStateXML();
585  xml += "</XLabel>";
586 
587  xml += "<ChartType value=\"" + typeInteger() + "\" />";
588  xml += "</View>";
589 
590  if( includeData )
591  {
592  xml += "<Data>";
593 
594  foreach( int i, validRowList() )
595  {
596  xml += "<Item category=\"" + category( i ) + "\" value=\"" + QString( "%1" ).arg( value( i ) ) + "\" />";
597  }
598 
599  xml += "</Data>";
600  }
601 
602  return xml;
603 }
604 
605 /*--------------------------------------------------------------------------------*/
606 
607 /* The maximum value is used to determine the available "free space" at the top
608  of the chart (BAR and LINE), i.e. the space that will not be entered into by any of the
609  categories, so we'll strip this space out to maximise visual effect. */
610 qreal GobChartsView::stripSpace( qreal perc ) const
611 {
612  if( m_private->m_totalValue > 0.0 )
613  {
614  /* I know, this calculation looks nasty, but it really isn't. */
615  qreal maxStrip = m_private->m_innerSceneRectF.height() - ( ( m_private->m_maxValue/m_private->m_totalValue ) + STRIPSPACE_OFFSET ) * m_private->m_innerSceneRectF.height();
616  return ( perc/( m_private->m_maxValue/m_private->m_totalValue ) ) * maxStrip;
617  }
618 
619  return 0;
620 }
621 
622 /*--------------------------------------------------------------------------------*/
623 
624 void GobChartsView::setFixedColour( QColor colour )
625 {
626  if( colour.isValid() )
627  {
628  m_private->m_fixedColour = colour;
629  m_private->m_fixedColourOn = true;
630  }
631  else
632  {
633  m_private->emitDebugLogMsg( tr( "GobChartsView::setFixedColour# Invalid colour provided" ) );
634  m_private->m_fixedColourOn = false;
635  }
636 
637  drawChart();
638 }
639 
640 /*--------------------------------------------------------------------------------*/
641 
643 {
644  m_private->m_fixedColourOn = false;
645  drawChart();
646 }
647 
648 /*--------------------------------------------------------------------------------*/
649 
651 {
652  return m_private->m_fixedColourOn;
653 }
654 
655 /*--------------------------------------------------------------------------------*/
656 
658 {
659  return m_private->m_fixedColour;
660 }
661 
662 /*--------------------------------------------------------------------------------*/
663 
665 {
666  m_private->m_loggingOn = logging;
667  m_private->m_grid->setDebugLoggingOn( logging );
668  m_private->m_graphItems->setDebugLoggingOn( logging );
669 }
670 
671 /*--------------------------------------------------------------------------------*/
672 
673 void GobChartsView::setAllowedDataRange( qreal lowerBoundary, qreal upperBoundary )
674 {
675  m_private->m_lowerDataBoundary = lowerBoundary;
676  m_private->m_upperDataBoundary = upperBoundary;
677  m_private->m_showTotalRange = false;
678  drawChart();
679 }
680 
681 /*--------------------------------------------------------------------------------*/
682 
684 {
685  m_private->m_showTotalRange = true;
686  drawChart();
687 }
688 
689 /*--------------------------------------------------------------------------------*/
690 
691 bool GobChartsView::isWithinAllowedRange( qreal value ) const
692 {
693  if( m_private->m_showTotalRange )
694  {
695  return true;
696  }
697  else
698  {
699  return ( value >= m_private->m_lowerDataBoundary ) && ( value <= m_private->m_upperDataBoundary );
700  }
701 }
702 
703 /*--------------------------------------------------------------------------------*/
704 
705 void GobChartsView::addToGraphItemsContainer( const QModelIndex &valueIndex, QGraphicsItem *item, const QString &legendText )
706 {
707  m_private->m_graphItems->addItem( valueIndex, item, legendText );
708 }
709 
710 /*--------------------------------------------------------------------------------*/
711 
713 {
714  return m_private->m_grid->gridWidth();
715 }
716 
717 /*--------------------------------------------------------------------------------*/
718 
720 {
721  return m_private->m_validItems->size();
722 }
723 
724 /*--------------------------------------------------------------------------------*/
725 
726 QList<int> GobChartsView::validRowList() const
727 {
728  return m_private->m_validItems->validRows();
729 }
730 
731 /*--------------------------------------------------------------------------------*/
732 
733 QString GobChartsView::category( int row ) const
734 {
735  return m_private->m_validItems->category( row );
736 }
737 
738 /*--------------------------------------------------------------------------------*/
739 
740 qreal GobChartsView::value( int row ) const
741 {
742  return m_private->m_validItems->data( row );
743 }
744 
745 /*--------------------------------------------------------------------------------*/
746 
747 const QRectF& GobChartsView::innerSceneRectF() const
748 {
749  return m_private->m_innerSceneRectF;
750 }
751 
752 /*--------------------------------------------------------------------------------*/
753 
755 {
756  return m_private->m_totalValue;
757 }
758 
759 /*--------------------------------------------------------------------------------*/
760 
761 void GobChartsView::debugLog( QString msg )
762 {
763  m_private->emitDebugLogMsg( msg );
764 }
765 
766 
767 
768 /*---------------------- RE-IMPLEMENTED FROM QABSTRACTITEMVIEW -------------------*/
769 
770 void GobChartsView::resizeEvent( QResizeEvent *event )
771 {
772  QAbstractItemView::resizeEvent( event );
773  drawChart();
774 }
775 
776 /*--------------------------------------------------------------------------------*/
777 
778 void GobChartsView::dataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight )
779 {
780  QAbstractItemView::dataChanged( topLeft, bottomRight );
781 
782  if( !m_private->m_chartIsLoading )
783  {
784  int rowCount = ( ( bottomRight.row() + 1 ) > m_private->m_maxRow ) ? ( bottomRight.row() + 1 ) : m_private->m_maxRow;
785  m_private->calculateActiveTotals( rowCount );
786  drawChart();
787  }
788 }
789 
790 /*--------------------------------------------------------------------------------*/
791 
792 void GobChartsView::rowsInserted( const QModelIndex &parent, int start, int end )
793 {
794  QAbstractItemView::rowsInserted( parent, start, end );
795  int rowCount = ( ( end + 1 ) > m_private->m_maxRow ) ? ( end + 1 ) : m_private->m_maxRow;
796  m_private->calculateActiveTotals( rowCount );
797 }
798 
799 /*--------------------------------------------------------------------------------*/
800 
801 void GobChartsView::rowsAboutToBeRemoved( const QModelIndex &parent, int start, int end )
802 {
803  QAbstractItemView::rowsAboutToBeRemoved( parent, start, end );
804  int rowCount = ( ( end + 1 ) > m_private->m_maxRow ) ? ( end + 1 ) : m_private->m_maxRow;
805  m_private->calculateActiveTotals( rowCount - ( end - start ) );
806 }
807 
808 /*--------------------------------------------------------------------------------*/
809 
810 QRect GobChartsView::visualRect( const QModelIndex &index ) const
811 {
812  if( !m_private->m_graphItems->getItemRectF( index ).isNull() )
813  {
814  QRectF rect = m_private->m_graphItems->getItemRectF( index );
815  return rect.toRect();
816  }
817 
818  return m_private->m_graphicsView->rect();
819 }
820 
821 /*--------------------------------------------------------------------------------*/
822 
823 void GobChartsView::scrollTo( const QModelIndex &index, ScrollHint hint )
824 {
825  Q_UNUSED( index );
826  Q_UNUSED( hint );
827 }
828 
829 /*--------------------------------------------------------------------------------*/
830 
831 void GobChartsView::setModel( QAbstractItemModel *model )
832 {
833  QAbstractItemView::setModel( model );
834 
835  if( model )
836  {
837  m_private->calculateActiveTotals( model->rowCount() );
838  }
839  else
840  {
841  m_private->emitDebugLogMsg( tr( "GobChartsView::setModel# Attempting to set a NULL model on this view." ) );
842  }
843 }
844 
845 /*--------------------------------------------------------------------------------*/
846 
847 QModelIndex GobChartsView::indexAt( const QPoint &point ) const
848 {
849  if( m_private->m_graphItems->getModelIndex( m_private->m_graphScene->itemAt( point) ).isValid() )
850  {
851  return m_private->m_graphItems->getModelIndex( m_private->m_graphScene->itemAt( point ) );
852  }
853 
854  return QModelIndex();
855 }
856 
857 /*--------------------------------------------------------------------------------*/
858 
859 QModelIndex GobChartsView::moveCursor( QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers )
860 {
861  Q_UNUSED( modifiers );
862 
863  QModelIndex current = currentIndex();
864 
865  switch ( cursorAction )
866  {
867  case MoveLeft:
868  /* Deliberate fall-through. */
869  case MoveUp:
870  if( current.row() > 0 )
871  {
872  current = model()->index( current.row() - 1, current.column(), rootIndex() );
873  }
874  else
875  {
876  current = model()->index( 0, current.column(), rootIndex() );
877  }
878  break;
879  case MoveRight:
880  /* Deliberate fall-through. */
881  case MoveDown:
882  if( current.row() < model()->rowCount( model()->parent( current ) ) - 1 )
883  {
884  current = model()->index( current.row() + 1, current.column(), rootIndex() );
885  }
886  else
887  {
888  current = model()->index( model()->rowCount( model()->parent( current ) ) - 1,
889  current.column(), rootIndex() );
890  }
891  break;
892  default:
893  break;
894  }
895 
896  drawChart();
897  return current;
898 }
899 
900 /*--------------------------------------------------------------------------------*/
901 
903 {
904  return 0;
905 }
906 
907 /*--------------------------------------------------------------------------------*/
908 
910 {
911  return 0;
912 }
913 
914 /*--------------------------------------------------------------------------------*/
915 
916 bool GobChartsView::isIndexHidden( const QModelIndex &index) const
917 {
918  Q_UNUSED( index );
919  return false;
920 }
921 
922 /*--------------------------------------------------------------------------------*/
923 
924 void GobChartsView::setSelection( const QRect &rect, QItemSelectionModel::SelectionFlags command )
925 {
926  Q_UNUSED( command );
927 
928  if( selectionModel() )
929  {
930  int firstRow = m_private->m_maxRow;
931  int lastRow = 0;
932  int firstColumn = 1;
933  int lastColumn = 0;
934 
935  QList< QGraphicsItem* > itemList = m_private->m_graphScene->items( rect );
936 
937  /* We don't need or want multiple selections to be made. */
938  if( itemList.size() == 1 )
939  {
940  foreach( QGraphicsItem* itemIt, itemList )
941  {
942  QModelIndex index = m_private->m_graphItems->getModelIndex( itemIt );
943 
944  m_private->m_legendText = m_private->m_graphItems->getItemLegend( itemIt );
945  emit highLightLegendItem( m_private->m_legendText );
946 
947  if( index.isValid() )
948  {
949  m_private->m_graphItems->setSelected( index.row() ); //highlight graph item corresponding to selection
950  firstRow = qMin( firstRow, index.row() );
951  lastRow = qMax( lastRow, index.row() );
952  firstColumn = qMin( firstColumn, index.column() );
953  lastColumn = qMax( firstColumn, index.column() );
954  }
955  }
956 
957  QItemSelection selection( model()->index( firstRow, firstColumn, rootIndex() ),
958  model()->index( lastRow, lastColumn, rootIndex() ) );
959 
960  selectionModel()->select( selection, QItemSelectionModel::ClearAndSelect );
961 
962  }
963  else
964  {
965  QModelIndex noIndex = m_private->m_graphItems->getModelIndex( m_private->m_legendText );
966  QItemSelection selection( noIndex, noIndex );
967  selectionModel()->select( selection, QItemSelectionModel::ClearAndSelect );
968  m_private->m_graphItems->clearSelection();
969  }
970  }
971  else
972  {
973  m_private->emitDebugLogMsg( tr( "GobChartsView::setModel# No selection model associated with this view." ) );
974  }
975 }
976 
977 /*--------------------------------------------------------------------------------*/
978 
979 void GobChartsView::mousePressEvent( QMouseEvent *event )
980 {
981  QAbstractItemView::mousePressEvent( event );
982  m_private->m_selectedLabel = NONE; // reset if selected item isn't a label or header
983 }
984 
985 /*--------------------------------------------------------------------------------*/
986 
987 void GobChartsView::keyPressEvent( QKeyEvent *event )
988 {
989  if( m_private->m_selectedLabel == HEADER )
990  {
991  m_private->m_header->receiveKeyEvent( event ); // forward event to header
992  requestLabelDetails( HEADER );
993  }
994  else if( m_private->m_selectedLabel == XLABEL )
995  {
996  m_private->m_xLabel->receiveKeyEvent( event ); // forward event to y label
997  requestLabelDetails( XLABEL );
998  }
999  else if( m_private->m_selectedLabel == YLABEL )
1000  {
1001  m_private->m_yLabel->receiveKeyEvent( event ); // forward event to x label
1002  requestLabelDetails( YLABEL );
1003  }
1004  else
1005  {
1006  QAbstractItemView::keyPressEvent( event );
1007  }
1008 }
1009 
1010 /*--------------------------------------------------------------------------------*/
1011 
1012 QRegion GobChartsView::visualRegionForSelection( const QItemSelection &selection ) const
1013 {
1014  QRegion region;
1015 
1016  foreach( QModelIndex indexIt, selection.indexes() )
1017  {
1018  QRectF rect = m_private->m_graphItems->getItemRectF( indexIt );
1019 
1020  if( !rect.isNull() )
1021  {
1022  region += rect.toRect();
1023  }
1024  }
1025 
1026  return region;
1027 }
1028 
1029 /*--------------------------------------------------------------------------------*/