GobChartsWidget  1.0
gobchartswidget.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 "gobchartswidget.h"
25 #include "view/gobchartsfactory.h"
26 #include "view/gobchartsview.h"
27 #include "toolswidget/gobchartstoolswidget.h"
28 
29 #include <QtCore/qmath.h>
30 #include <QVBoxLayout>
31 #include <QListWidget>
32 #include <QSplitter>
33 #include <QMessageBox>
34 #include <QFileDialog>
35 #include <QTextStream>
36 #include <QDomDocument>
37 #include <QIcon>
38 #include <QPixmap>
39 #include <QPainter>
40 
41 /*------------------------ NON-MEMBER HELPER FUNCTION ----------------------------*/
42 
43 static QIcon RoundedIcon( const QColor& color )
44 {
45  QPixmap pixMap( QSize( 16, 16 ) );
46  pixMap.fill( QColor( 0, 0, 0, 0 ) );
47 
48  QPainterPath path;
49  path.addRoundedRect( QRectF( pixMap.rect() ), 15, 45, Qt::RelativeSize );
50 
51  QPainter painter( &pixMap );
52  painter.setRenderHint( QPainter::Antialiasing );
53  painter.setPen( QPen( color, 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin ) );
54  painter.setBrush( color );
55  painter.drawPath( path );
56 
57  return QIcon( pixMap );
58 }
59 
60 /*--------------------------------- PIMPL CLASS ----------------------------------*/
61 
62 struct GobChartsWidget::GobChartsWidgetPrivate
63 {
64  /* Constructor and destructor. */
65  GobChartsWidgetPrivate() :
66  m_gobChartsView ( NULLPOINTER ),
67  m_toolsWidget ( new GobChartsToolsWidget ),
68  m_legend ( new QListWidget ),
69  m_horizontalSplitter( new QSplitter ),
70  m_verticalSplitter ( new QSplitter ),
71  m_frame ( new QFrame ),
72  m_model ( NULLPOINTER ),
73  m_selectionModel ( NULLPOINTER ),
74  m_loggingOn ( false )
75  {
76  m_legend->setAlternatingRowColors( true );
77  m_horizontalSplitter->addWidget( m_legend ); // chart view added later
78 
79  QVBoxLayout *frameLayout = new QVBoxLayout( m_frame );
80  frameLayout->addWidget( m_horizontalSplitter );
81  m_frame->setLayout( frameLayout );
82  m_frame->setFrameStyle( QFrame::Box | QFrame::Sunken );
83 
84  m_verticalSplitter->setOrientation( Qt::Vertical );
85  m_verticalSplitter->addWidget( m_toolsWidget );
86  m_verticalSplitter->addWidget( m_frame ); // takes ownership
87  m_verticalSplitter->setStretchFactor( 1, 1 );
88  }
89 
90  ~GobChartsWidgetPrivate()
91  {
92  if( m_gobChartsView )
93  {
94  delete m_gobChartsView;
95  }
96 
97  delete m_toolsWidget;
98  delete m_legend;
99  delete m_horizontalSplitter;
100  delete m_verticalSplitter;
101 
102  /* Not owned:
103  m_frame
104  m_model
105  m_selectionModel
106  */
107  }
108 
109  /* Members. */
110  GobChartsView *m_gobChartsView;
111  GobChartsToolsWidget *m_toolsWidget;
112  QListWidget *m_legend;
113  QSplitter *m_horizontalSplitter;
114  QSplitter *m_verticalSplitter;
115  QFrame *m_frame;
116  QAbstractItemModel *m_model; // model owned elsewhere
117  QItemSelectionModel *m_selectionModel; // selection model owned elsewhere
118  bool m_loggingOn;
119 };
120 
121 
122 /*------------------------------- MEMBER FUNCTIONS -------------------------------*/
123 
125  QFrame ( parent ),
126  m_private( new GobChartsWidgetPrivate )
127 {
128  connect( m_private->m_toolsWidget, SIGNAL( createChart( GobChartsType ) ),
129  this, SLOT ( createChart( GobChartsType ) ) );
130 
131  connect( m_private->m_toolsWidget, SIGNAL( clearChart() ),
132  this, SLOT ( clearChart() ) );
133 
134  connect( m_private->m_toolsWidget, SIGNAL( saveChart() ),
135  this, SLOT ( saveChart() ) );
136 
137  connect( m_private->m_toolsWidget, SIGNAL( loadChart() ),
138  this, SLOT ( loadChart() ) );
139 
140  connect( m_private->m_legend, SIGNAL( itemClicked( QListWidgetItem* ) ),
141  this, SLOT ( legendItemSelected( QListWidgetItem* ) ) );
142 
143  /* Hide chart until created. */
144  m_private->m_horizontalSplitter->setVisible( false );
145 
146  QVBoxLayout *mainLayout = new QVBoxLayout( this );
147  mainLayout->addWidget( m_private->m_verticalSplitter );
148  this->setLayout( mainLayout );
149  this->setFrameStyle( QFrame::Box | QFrame::Sunken );
150 }
151 
152 /*--------------------------------------------------------------------------------*/
153 
155 {
156  delete m_private;
157 }
158 
159 /*--------------------------------------------------------------------------------*/
160 
162 {
163  if( m_private->m_gobChartsView )
164  {
165  m_private->m_gobChartsView->setDebugLoggingOn( log );
166  }
167 
168  /* Remember setting for later when charts are created and/or recreated. */
169  m_private->m_loggingOn = log;
170 }
171 
172 /*--------------------------------------------------------------------------------*/
173 
174 void GobChartsWidget::setModel( QAbstractItemModel *model )
175 {
176  if( model )
177  {
178  m_private->m_model = model;
179  }
180 }
181 
182 /*--------------------------------------------------------------------------------*/
183 
184 void GobChartsWidget::setSelectionModel( QItemSelectionModel *selectionModel )
185 {
186  if( selectionModel )
187  {
188  m_private->m_selectionModel = selectionModel;
189  }
190 }
191 
192 /*--------------------------------------------------------------------------------*/
193 
194 void GobChartsWidget::graphicsItemSelected( const QString &legendText )
195 {
196  for( int i = 0; i < m_private->m_legend->count(); ++i )
197  {
198  QListWidgetItem *item = m_private->m_legend->item( i );
199 
200  if( legendText == item->text() )
201  {
202  m_private->m_legend->setCurrentItem( item );
203  break;
204  }
205  }
206 }
207 
208 /*--------------------------------------------------------------------------------*/
209 
210 void GobChartsWidget::legendItemSelected( QListWidgetItem *item )
211 {
212  if( m_private->m_gobChartsView )
213  {
214  m_private->m_gobChartsView->legendItemSelected( item->text() );
215  }
216 }
217 
218 /*--------------------------------------------------------------------------------*/
219 
220 void GobChartsWidget::createLegendItem( const QColor &colour, const QString &text )
221 {
222  QListWidgetItem *item = new QListWidgetItem( RoundedIcon( colour ), text, m_private->m_legend );
223  Q_UNUSED( item );
224 }
225 
226 /*--------------------------------------------------------------------------------*/
227 
228 void GobChartsWidget::clearLegend()
229 {
230  m_private->m_legend->clear(); // deletes items
231 }
232 
233 /*--------------------------------------------------------------------------------*/
234 
235 void GobChartsWidget::createChart( GobChartsType type )
236 {
237  /* Save the label state before whacking the chart (GobChartsView
238  is the only object that has access to all three labels' details). */
239  QString labels( "" );
240 
241  if( m_private->m_gobChartsView )
242  {
243  labels = m_private->m_gobChartsView->getStateXML( false );
244 
245  m_private->m_gobChartsView->close();
246  delete m_private->m_gobChartsView;
247  m_private->m_gobChartsView = NULLPOINTER;
248  }
249 
250  m_private->m_gobChartsView = GobChartsFactory::getInstance()->createChart( type, this );
251 
252  if( m_private->m_gobChartsView )
253  {
254  m_private->m_gobChartsView->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
255 
256  connect( m_private->m_gobChartsView, SIGNAL( lastDebugLogMsg( QString ) ),
257  this, SIGNAL( lastDebugLogMsg( QString ) ) );
258 
259  /* Data range. */
260  connect( m_private->m_toolsWidget, SIGNAL( setAllowedDataRange( qreal, qreal ) ),
261  m_private->m_gobChartsView, SLOT ( setAllowedDataRange( qreal, qreal ) ) );
262 
263  connect( m_private->m_toolsWidget, SIGNAL( setShowTotalRange() ),
264  m_private->m_gobChartsView, SLOT ( setShowTotalRange() ) );
265 
266  /* Colour related. */
267  connect( m_private->m_toolsWidget, SIGNAL( setFixedColour( QColor ) ),
268  m_private->m_gobChartsView, SLOT ( setFixedColour( QColor ) ) );
269 
270  connect( m_private->m_toolsWidget, SIGNAL( setRandomColours() ),
271  m_private->m_gobChartsView, SLOT ( setRandomColours() ) );
272 
273  /* Grid related. */
274  connect( m_private->m_toolsWidget, SIGNAL( setGridLineStyle( Qt::PenStyle ) ),
275  m_private->m_gobChartsView, SLOT ( setGridLineStyle( Qt::PenStyle ) ) );
276 
277  connect( m_private->m_toolsWidget, SIGNAL( setHorizontalGridLines( bool, int ) ),
278  m_private->m_gobChartsView, SLOT ( setHorizontalGridLines( bool, int ) ) );
279 
280  connect( m_private->m_toolsWidget, SIGNAL( setVerticalGridLines( bool, int ) ),
281  m_private->m_gobChartsView, SLOT ( setVerticalGridLines( bool, int ) ) );
282 
283  connect( m_private->m_toolsWidget, SIGNAL( setGridColour( QColor) ),
284  m_private->m_gobChartsView, SLOT ( setGridColour( QColor ) ) );
285 
286  /* Label related. */
287  connect( m_private->m_toolsWidget, SIGNAL( setLabelDetails( GobChartsLabel,QString,QFont,QColor,Qt::Alignment ) ),
288  m_private->m_gobChartsView, SLOT ( setLabelDetails( GobChartsLabel,QString,QFont,QColor,Qt::Alignment ) ) );
289 
290  connect( m_private->m_toolsWidget, SIGNAL( requestLabelDetails( GobChartsLabel ) ),
291  m_private->m_gobChartsView, SLOT ( requestLabelDetails( GobChartsLabel ) ) );
292 
293  connect( m_private->m_gobChartsView, SIGNAL( emitLabelDetails ( GobChartsLabel,QString,QFont,QColor,Qt::Alignment ) ),
294  m_private->m_toolsWidget, SLOT ( receiveLabelDetails( GobChartsLabel,QString,QFont,QColor,Qt::Alignment ) ) );
295 
296  /* Legend related. */
297  connect( m_private->m_gobChartsView, SIGNAL( highLightLegendItem( QString) ),
298  this, SLOT ( graphicsItemSelected( QString ) ) );
299 
300  connect( m_private->m_gobChartsView, SIGNAL( createLegendItem( QColor, QString ) ),
301  this, SLOT ( createLegendItem( QColor, QString ) ) );
302 
303  connect( m_private->m_gobChartsView, SIGNAL( clearLegend() ),
304  this, SLOT ( clearLegend() ) );
305 
306  /* The previous state (if any) saved in the tools widget will be re-applied by emitting all
307  the information via the signals above (with the exception of the label information). */
308  m_private->m_toolsWidget->emitStateSignals();
309 
310  /* Update the labels with the saved data from above. */
311  if( !labels.isEmpty() )
312  {
313  QDomDocument doc;
314 
315  /* If this fails, it isn't the end of the world. */
316  if( doc.setContent( labels ) )
317  {
318  m_private->m_gobChartsView->setStateXML( doc.namedItem( "View" ), QDomNode(), false );
319  }
320  }
321 
322  m_private->m_gobChartsView->setDebugLoggingOn( m_private->m_loggingOn );
323 
324  if( m_private->m_model )
325  {
326  m_private->m_gobChartsView->setModel( m_private->m_model );
327  }
328 
329  if( m_private->m_selectionModel )
330  {
331  m_private->m_gobChartsView->setSelectionModel( m_private->m_selectionModel );
332  }
333 
334  /* Chart should get maximum space. */
335  m_private->m_horizontalSplitter->insertWidget( 0, m_private->m_gobChartsView );
336  m_private->m_horizontalSplitter->setStretchFactor( 0, 1 );
337  m_private->m_verticalSplitter->setStretchFactor( 1, 1 );
338 
339  if( !m_private->m_horizontalSplitter->isVisible() )
340  {
341  QList< int > horList = QList< int >() << qFloor( static_cast< qreal >( rect().width() ) * 0.7 )
342  << qFloor( static_cast< qreal >( rect().width() ) * 0.3 );
343 
344  m_private->m_horizontalSplitter->setSizes( horList );
345  m_private->m_horizontalSplitter->setVisible( true );
346  }
347 
348  m_private->m_gobChartsView->drawChart();
349  m_private->m_gobChartsView->show();
350  }
351  else
352  {
353  QMessageBox::critical( this, tr( "Error" ), tr( "Unknown chart type selected." ) );
354  }
355 }
356 
357 /*--------------------------------------------------------------------------------*/
358 
359 void GobChartsWidget::clearChart()
360 {
361  m_private->m_horizontalSplitter->setVisible( false );
362 
363  if( m_private->m_gobChartsView )
364  {
365  m_private->m_gobChartsView->close();
366  delete m_private->m_gobChartsView;
367  m_private->m_gobChartsView = NULLPOINTER;
368  }
369 }
370 
371 /*--------------------------------------------------------------------------------*/
372 
373 void GobChartsWidget::saveChart()
374 {
375  if( m_private->m_gobChartsView )
376  {
377  QString fileName = QFileDialog::getSaveFileName( this, tr( "Save file as" ), QDir::homePath(), tr("XML files (*.xml)" ) );
378 
379  if( !fileName.isEmpty() ) // "Cancel" selected
380  {
381  QFile saveFile( fileName );
382 
383  if( saveFile.open( QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate ) )
384  {
385  /* I tried any number of combinations of QDomDocumentFragment (from the getStateXML() functions in
386  GobChartsView, GobChartsToolsWidget and GobChartsText) and QDomNode's and couldn't get it to work
387  so decided on manually creating the XML here as it is pretty straightforward. */
388  QString fileContent;
389  fileContent += "<GobChart>";
390  fileContent += m_private->m_gobChartsView->getStateXML();
391  fileContent += m_private->m_toolsWidget->getStateXML();
392  fileContent += "</GobChart>";
393 
394  QTextStream outStream( &saveFile );
395 
396  /* Check XML and apply "pretty print". */
397  QDomDocument doc;
398 
399  if( doc.setContent( fileContent ) )
400  {
401  outStream << doc.toString( 2 );
402  }
403  else
404  {
405  QMessageBox::critical( this, tr( "Error" ), tr( "Failed to convert chart to required file format. XML is broken." ) );
406  }
407 
408  saveFile.close();
409  }
410  else
411  {
412  QMessageBox::critical( this, tr( "Error" ), tr( "Failed to open file for saving." ) );
413  }
414  }
415  }
416  else
417  {
418  QMessageBox::information( this, tr( "Info" ), tr( "No active chart to save." ) );
419  }
420 }
421 
422 /*--------------------------------------------------------------------------------*/
423 
424 void GobChartsWidget::loadChart()
425 {
426  int returnButton = QMessageBox::question( this,
427  "Warning",
428  "This action will replace all current data. Continue?",
429  QMessageBox::Ok | QMessageBox::Cancel,
430  QMessageBox::Cancel );
431 
432  if( returnButton == QMessageBox::Ok )
433  {
434  QString fileName = QFileDialog::getOpenFileName( this, tr( "Load file" ), QDir::homePath(), tr("XML files (*.xml)" ) );
435 
436  if( !fileName.isEmpty() ) // "Cancel" selected
437  {
438  QFile loadFile( fileName );
439 
440  if( loadFile.open( QIODevice::ReadOnly | QIODevice::Text ) )
441  {
442  QDomDocument doc;
443 
444  if( doc.setContent( &loadFile ) )
445  {
446  QString strVal = doc.firstChildElement( "GobChart").
447  firstChildElement ( "View" ).
448  firstChildElement ( "ChartType" ).
449  attribute ( "value" );
450 
451  GobChartsType type = static_cast< GobChartsType >( strVal.toInt() );
452  createChart( type );
453 
454  if( m_private->m_gobChartsView )
455  {
456  m_private->m_gobChartsView->setStateXML( doc.namedItem( "GobChart" ).namedItem( "View" ),
457  doc.namedItem( "GobChart" ).namedItem( "Data" ) );
458  }
459 
460  m_private->m_toolsWidget->setStateXML( type, doc.namedItem( "GobChart" ).namedItem( "ToolsWidget" ) );
461  }
462  else
463  {
464  QMessageBox::critical( this, tr( "Error" ), tr( "Failed to load chart from file. XML is broken." ) );
465  }
466 
467  loadFile.close();
468  }
469  else
470  {
471  QMessageBox::critical( this, tr( "Error" ), tr( "Failed to open file for loading." ) );
472  }
473  }
474  }
475 }
476 
477 /*--------------------------------------------------------------------------------*/