##// END OF EJS Templates
Marek Rosa -
r286:85d5e94564e0 merge
parent child
Show More
@@ -1,226 +1,227
1 /*!
1 /*!
2 \class Widget
2 \class Widget
3 \brief Ui for the application.
3 \brief Ui for the application.
4 \internal
4 \internal
5 */
5 */
6
6
7 #include "widget.h"
7 #include "widget.h"
8 #include <QGridLayout>
8 #include <QGridLayout>
9 #include <QPushButton>
9 #include <QPushButton>
10 #include <QLabel>
10 #include <QLabel>
11
11
12 #include <QSqlQuery>
12 #include <QSqlQuery>
13 #include <qscatterseries.h>
13 #include <qscatterseries.h>
14 #include <qchartview.h>
14 #include <qchartview.h>
15 #include <qchartaxis.h>
15 #include <qchartaxis.h>
16 #include <qbarcategory.h>
16 #include <qbarcategory.h>
17 #include <qbarset.h>
17 #include <qbarset.h>
18 #include <QListWidget>
18 #include <QListWidget>
19 #include <QPrinter>
19 #include <QPrinter>
20 #include <QPrintDialog>
20 #include <QPrintDialog>
21 #include <QRadioButton>
21 #include <QRadioButton>
22
22
23 QTCOMMERCIALCHART_USE_NAMESPACE
23 QTCOMMERCIALCHART_USE_NAMESPACE
24
24
25 Widget::Widget(QWidget *parent)
25 Widget::Widget(QWidget *parent)
26 : QWidget(parent)
26 : QWidget(parent)
27 {
27 {
28 setGeometry(100, 100, 1000, 600);
28 setGeometry(100, 100, 1000, 600);
29
29
30 // right panel layout
30 // right panel layout
31 barChartRadioButton = new QRadioButton(tr("Bar chart"));
31 barChartRadioButton = new QRadioButton(tr("Bar chart"));
32 barChartRadioButton->setChecked(true);
32 barChartRadioButton->setChecked(true);
33 scatterChartRadioButton = new QRadioButton(tr("Scatter chart"));
33 scatterChartRadioButton = new QRadioButton(tr("Scatter chart"));
34 scatterChartRadioButton->setChecked(false);
34 scatterChartRadioButton->setChecked(false);
35 countrieslist = new QListWidget;
35 countrieslist = new QListWidget;
36 countrieslist->setSelectionMode(QAbstractItemView::MultiSelection);
36 countrieslist->setSelectionMode(QAbstractItemView::MultiSelection);
37
37
38 yearslist = new QListWidget;
38 yearslist = new QListWidget;
39 yearslist->setSelectionMode(QAbstractItemView::ExtendedSelection);
39 yearslist->setSelectionMode(QAbstractItemView::ExtendedSelection);
40 for (int i = 1990; i < 2011; i++)
40 for (int i = 1990; i < 2011; i++)
41 yearslist->addItem(QString("%1").arg(i));
41 yearslist->addItem(QString("%1").arg(i));
42
42
43 QPushButton* refreshButton = new QPushButton(tr("Refresh"));
43 QPushButton* refreshButton = new QPushButton(tr("Refresh"));
44 connect(refreshButton, SIGNAL(clicked()), this, SLOT(refreshChart()));
44 connect(refreshButton, SIGNAL(clicked()), this, SLOT(refreshChart()));
45
45
46 QPushButton* printButton = new QPushButton(tr("Print chart"));
46 QPushButton* printButton = new QPushButton(tr("Print chart"));
47 connect(printButton, SIGNAL(clicked()), this, SLOT(printChart()));
47 connect(printButton, SIGNAL(clicked()), this, SLOT(printChart()));
48
48
49 QVBoxLayout* rightPanelLayout = new QVBoxLayout;
49 QVBoxLayout* rightPanelLayout = new QVBoxLayout;
50 rightPanelLayout->addWidget(barChartRadioButton);
50 rightPanelLayout->addWidget(barChartRadioButton);
51 rightPanelLayout->addWidget(scatterChartRadioButton);
51 rightPanelLayout->addWidget(scatterChartRadioButton);
52 rightPanelLayout->addWidget(countrieslist);
52 rightPanelLayout->addWidget(countrieslist);
53 rightPanelLayout->addWidget(yearslist);
53 rightPanelLayout->addWidget(yearslist);
54 rightPanelLayout->addWidget(refreshButton);
54 rightPanelLayout->addWidget(refreshButton);
55 rightPanelLayout->addWidget(printButton);
55 rightPanelLayout->addWidget(printButton);
56 rightPanelLayout->setStretch(0, 1);
56 rightPanelLayout->setStretch(0, 1);
57 rightPanelLayout->setStretch(1, 0);
57 rightPanelLayout->setStretch(1, 0);
58
58
59 // main layout
59 // main layout
60 chartArea = new QChartView(this);
60 chartArea = new QChartView(this);
61 chartArea->setChartTitle("GDP by country");
61 chartArea->setChartTitle("GDP by country");
62 QGridLayout* mainLayout = new QGridLayout;
62 QGridLayout* mainLayout = new QGridLayout;
63 mainLayout->addWidget(chartArea, 0, 0);
63 mainLayout->addWidget(chartArea, 0, 0);
64 mainLayout->addLayout(rightPanelLayout, 0, 1);
64 mainLayout->addLayout(rightPanelLayout, 0, 1);
65 mainLayout->setColumnStretch(0,1);
65 mainLayout->setColumnStretch(0,1);
66 setLayout(mainLayout);
66 setLayout(mainLayout);
67
67
68 // connect to the database
68 // connect to the database
69 db = QSqlDatabase::addDatabase("QSQLITE");
69 db = QSqlDatabase::addDatabase("QSQLITE");
70 db.setDatabaseName("gdpData");
70 db.setDatabaseName("gdpData");
71 if(!db.open())
71 if(!db.open())
72 {
72 {
73 qDebug() << "could not open database. SQLite db file missing (?)";
73 qDebug() << "could not open database. SQLite db file missing (?)";
74 return;
74 return;
75 }
75 }
76
76
77 // get the list of all countires and regions.
77 // get the list of all countires and regions.
78 QSqlQuery query;
78 QSqlQuery query;
79 query.exec("SELECT DISTINCT country FROM gdp2");
79 query.exec("SELECT DISTINCT country FROM gdp2");
80
80
81 // add the countries to the country filter
81 // add the countries to the country filter
82 while (query.next()) {
82 while (query.next()) {
83 countrieslist->addItem(query.value(0).toString());
83 countrieslist->addItem(query.value(0).toString());
84 }
84 }
85
85
86 // hide axis X labels
86 // hide axis X labels
87 QChartAxis* axis = chartArea->axisX();
87 QChartAxis* axis = chartArea->axisX();
88 // axis->setLabelsVisible(false);
88 // axis->setLabelsVisible(false);
89 // newAxis.setLabelsOrientation(QChartAxis::LabelsOrientationSlide);
89 // newAxis.setLabelsOrientation(QChartAxis::LabelsOrientationSlide);
90
90
91 }
91 }
92
92
93 Widget::~Widget()
93 Widget::~Widget()
94 {
94 {
95 //
95 //
96 db.close();
96 db.close();
97 }
97 }
98
98
99 /*!
99 /*!
100 refreshes the chart
100 refreshes the chart
101 */
101 */
102 void Widget::refreshChart()
102 void Widget::refreshChart()
103 {
103 {
104 chartArea->removeAllSeries();
104 chartArea->removeAllSeries();
105
105
106 // selected countries items list is not sorted. copy the values to QStringlist and sort them.
106 // selected countries items list is not sorted. copy the values to QStringlist and sort them.
107 QStringList selectedCountriesStrings;
107 QStringList selectedCountriesStrings;
108 QList<QListWidgetItem*> selectedCountriesItems = countrieslist->selectedItems();
108 QList<QListWidgetItem*> selectedCountriesItems = countrieslist->selectedItems();
109 for (int i = 0; i < selectedCountriesItems.size(); i++)
109 for (int i = 0; i < selectedCountriesItems.size(); i++)
110 selectedCountriesStrings.append(selectedCountriesItems[i]->text());
110 selectedCountriesStrings.append(selectedCountriesItems[i]->text());
111 selectedCountriesStrings.sort();
111 selectedCountriesStrings.sort();
112
112
113 QSqlQuery query;
113 QSqlQuery query;
114 // selected years items list is not sorted. copy the values to QList<Integer> and sort them.
114 // selected years items list is not sorted. copy the values to QList<Integer> and sort them.
115 QList<int> selectedYearsInts;
115 QList<int> selectedYearsInts;
116 QList<QListWidgetItem*> selectedYearsItems = yearslist->selectedItems();
116 QList<QListWidgetItem*> selectedYearsItems = yearslist->selectedItems();
117 for (int i = 0; i < selectedYearsItems.size(); i++)
117 for (int i = 0; i < selectedYearsItems.size(); i++)
118 selectedYearsInts.append(selectedYearsItems[i]->text().toInt());
118 selectedYearsInts.append(selectedYearsItems[i]->text().toInt());
119 qSort(selectedYearsInts.begin(), selectedYearsInts.end(), qGreater<int>());
119 qSort(selectedYearsInts.begin(), selectedYearsInts.end(), qGreater<int>());
120
120
121 if (barChartRadioButton->isChecked())
121 if (barChartRadioButton->isChecked())
122 {
122 {
123 // use the sorted selected coutries list to initialize BarCategory
123 // use the sorted selected coutries list to initialize BarCategory
124 QBarCategory* category = new QBarCategory;
124 QBarCategory* category = new QBarCategory;
125 for (int i = 0; i < selectedCountriesStrings.size(); i++)
125 for (int i = 0; i < selectedCountriesStrings.size(); i++)
126 *category << selectedCountriesStrings[i];
126 *category << selectedCountriesStrings[i];
127 series0 = new QBarChartSeries(category);
127 series0 = new QBarChartSeries(category);
128
128
129 // prepare the selected counries SQL query
129 // prepare the selected counries SQL query
130 QString countriesQuery = "country IN (";
130 QString countriesQuery = "country IN (";
131 for (int i = 0; i < selectedCountriesStrings.size(); i++)
131 for (int i = 0; i < selectedCountriesStrings.size(); i++)
132 {
132 {
133 countriesQuery.append("'" + selectedCountriesStrings[i] + "'");
133 countriesQuery.append("'" + selectedCountriesStrings[i] + "'");
134 if ( i < selectedCountriesStrings.size() - 1)
134 if ( i < selectedCountriesStrings.size() - 1)
135 countriesQuery.append(",");
135 countriesQuery.append(",");
136 else
136 else
137 countriesQuery.append(")");
137 countriesQuery.append(")");
138 }
138 }
139
139
140 // perform a query for each selected year
140 // perform a query for each selected year
141 for (int i = 0; i < selectedYearsInts.size(); i++)
141 for (int i = 0; i < selectedYearsInts.size(); i++)
142 {
142 {
143 query.exec("SELECT country,gdpvalue FROM gdp2 where year=" + QString("%1").arg(selectedYearsInts[i]) + " AND " + countriesQuery);
143 query.exec("SELECT country,gdpvalue FROM gdp2 where year=" + QString("%1").arg(selectedYearsInts[i]) + " AND " + countriesQuery);
144 QBarSet* barSet = new QBarSet(QString("GDP_%1").arg(selectedYearsInts[i]));
144 QBarSet* barSet = new QBarSet("Barset" + QString::number(i));
145
145 // while (query.next()) {
146 // while (query.next()) {
146 // qDebug() << query.value(0).toString() << " : " << query.value(1).toString();
147 // qDebug() << query.value(0).toString() << " : " << query.value(1).toString();
147 // }
148 // }
148 query.first();
149 query.first();
149
150
150 // the data for some of the coutries for some years might be missing.
151 // the data for some of the coutries for some years might be missing.
151 // QBarChart needs bars to have same size
152 // QBarChart needs bars to have same size
152 for (int k = 0; k < selectedCountriesStrings.size(); k++)
153 for (int k = 0; k < selectedCountriesStrings.size(); k++)
153 {
154 {
154 if (selectedCountriesStrings[k] == query.value(0).toString())
155 if (selectedCountriesStrings[k] == query.value(0).toString())
155 {
156 {
156 *barSet << query.value(1).toReal();
157 *barSet << query.value(1).toReal();
157 qDebug() << query.value(0).toString() << query.value(1).toReal() << " : " << QString("%1").arg(selectedYearsInts[i]);
158 qDebug() << query.value(0).toString() << query.value(1).toReal() << " : " << QString("%1").arg(selectedYearsInts[i]);
158 query.next();
159 query.next();
159 }
160 }
160 else
161 else
161 {
162 {
162 // data missing, put 0
163 // data missing, put 0
163 *barSet << 0.0f;
164 *barSet << 0.0f;
164 qDebug() << "Putting 0 for Bosnia" << " : " << QString("%1").arg(selectedYearsInts[i]);
165 qDebug() << "Putting 0 for Bosnia" << " : " << QString("%1").arg(selectedYearsInts[i]);
165 }
166 }
166 }
167 }
167 series0->addBarSet(barSet);
168 series0->addBarSet(barSet);
168 }
169 }
169 // add the serie to the chart
170 // add the serie to the chart
170 chartArea->addSeries(series0);
171 chartArea->addSeries(series0);
171
172
172 }
173 }
173 else if (scatterChartRadioButton->isChecked())
174 else if (scatterChartRadioButton->isChecked())
174 {
175 {
175 QString yearsQuery = "year IN (";
176 QString yearsQuery = "year IN (";
176 for (int i = 0; i < selectedYearsInts.size(); i++)
177 for (int i = 0; i < selectedYearsInts.size(); i++)
177 {
178 {
178 yearsQuery.append("'" + QString("%1").arg(selectedYearsInts[i]) + "'");
179 yearsQuery.append("'" + QString("%1").arg(selectedYearsInts[i]) + "'");
179 if ( i < selectedYearsInts.size() - 1)
180 if ( i < selectedYearsInts.size() - 1)
180 yearsQuery.append(",");
181 yearsQuery.append(",");
181 else
182 else
182 yearsQuery.append(")");
183 yearsQuery.append(")");
183 }
184 }
184
185
185 // perform a query for each selected year
186 // perform a query for each selected year
186 for (int i = 0; i < selectedCountriesStrings.size(); i++)
187 for (int i = 0; i < selectedCountriesStrings.size(); i++)
187 {
188 {
188 query.exec("SELECT year,gdpvalue FROM gdp2 where country='" + selectedCountriesStrings[i] + "' AND " + yearsQuery);
189 query.exec("SELECT year,gdpvalue FROM gdp2 where country='" + selectedCountriesStrings[i] + "' AND " + yearsQuery);
189 query.first();
190 query.first();
190
191
191 QScatterSeries* series = new QScatterSeries;
192 QScatterSeries* series = new QScatterSeries;
192 // the data for some of the coutries for some years might be missing.
193 // the data for some of the coutries for some years might be missing.
193 for (int k = 0; k < selectedYearsInts.size(); k++)
194 for (int k = 0; k < selectedYearsInts.size(); k++)
194 {
195 {
195 if (selectedYearsInts[k] == query.value(0).toInt())
196 if (selectedYearsInts[k] == query.value(0).toInt())
196 {
197 {
197 *series << QPointF(query.value(0).toInt() , query.value(1).toReal());
198 *series << QPointF(query.value(0).toInt() , query.value(1).toReal());
198 qDebug() << query.value(0).toString() << query.value(1).toReal() << " : " << QString("%1").arg(selectedYearsInts[k]);
199 qDebug() << query.value(0).toString() << query.value(1).toReal() << " : " << QString("%1").arg(selectedYearsInts[k]);
199 query.next();
200 query.next();
200 }
201 }
201 else
202 else
202 {
203 {
203 // data missing, put 0
204 // data missing, put 0
204 *series << QPointF(selectedYearsInts[k] , 0.0f);
205 *series << QPointF(selectedYearsInts[k] , 0.0f);
205 qDebug() << "Putting 0 for Bosnia" << " : " << QString("%1").arg(selectedYearsInts[i]) << " " << query.value(0).toInt();
206 qDebug() << "Putting 0 for Bosnia" << " : " << QString("%1").arg(selectedYearsInts[i]) << " " << query.value(0).toInt();
206 }
207 }
207 }
208 }
208 // chartArea->axisX()->setRange(selectedYearsInts[selectedYearsInts.size() - 1] + 1, selectedYearsInts[0] - 1);
209 // chartArea->axisX()->setRange(selectedYearsInts[selectedYearsInts.size() - 1] + 1, selectedYearsInts[0] - 1);
209 chartArea->addSeries(series);
210 chartArea->addSeries(series);
210 }
211 }
211 chartArea->axisX()->setRange(selectedYearsInts[selectedYearsInts.size() - 1] + 1, selectedYearsInts[0] - 1);
212 chartArea->axisX()->setRange(selectedYearsInts[selectedYearsInts.size() - 1] + 1, selectedYearsInts[0] - 1);
212 }
213 }
213 }
214 }
214
215
215 void Widget::printChart()
216 void Widget::printChart()
216 {
217 {
217 QPrinter printer;
218 QPrinter printer;
218 // QPrinter printer(QPrinter::HighResolution);
219 // QPrinter printer(QPrinter::HighResolution);
219 printer.setOutputFormat(QPrinter::PdfFormat);
220 printer.setOutputFormat(QPrinter::PdfFormat);
220 printer.setOrientation(QPrinter::Landscape);
221 printer.setOrientation(QPrinter::Landscape);
221 printer.setOutputFileName("print.pdf");
222 printer.setOutputFileName("print.pdf");
222
223
223 QPainter painter;
224 QPainter painter;
224 painter.begin(&printer);
225 painter.begin(&printer);
225 chartArea->render(&painter);
226 chartArea->render(&painter);
226 }
227 }
@@ -1,395 +1,369
1 #include "mainwidget.h"
1 #include "mainwidget.h"
2 #include "dataseriedialog.h"
2 #include "dataseriedialog.h"
3 #include "qchartseries.h"
3 #include "qchartseries.h"
4 #include "qpieseries.h"
4 #include "qpieseries.h"
5 #include "qscatterseries.h"
5 #include "qscatterseries.h"
6 #include <qlinechartseries.h>
6 #include <qlinechartseries.h>
7 #include <qbarset.h>
7 #include <qbarset.h>
8 #include <qbarcategory.h>
8 #include <qbarcategory.h>
9 #include <qbarchartseries.h>
9 #include <qbarchartseries.h>
10 #include <qstackedbarchartseries.h>
10 #include <qstackedbarchartseries.h>
11 #include <qpercentbarchartseries.h>
11 #include <qpercentbarchartseries.h>
12 #include <QPushButton>
12 #include <QPushButton>
13 #include <QComboBox>
13 #include <QComboBox>
14 #include <QSpinBox>
14 #include <QSpinBox>
15 #include <QCheckBox>
15 #include <QCheckBox>
16 #include <QGridLayout>
16 #include <QGridLayout>
17 #include <QHBoxLayout>
17 #include <QHBoxLayout>
18 #include <QLabel>
18 #include <QLabel>
19 #include <QSpacerItem>
19 #include <QSpacerItem>
20 #include <QMessageBox>
20 #include <QMessageBox>
21 #include <cmath>
21 #include <cmath>
22 #include <QDebug>
22 #include <QDebug>
23 #include <QStandardItemModel>
23 #include <QStandardItemModel>
24
24
25
25
26 QTCOMMERCIALCHART_USE_NAMESPACE
26 QTCOMMERCIALCHART_USE_NAMESPACE
27
27
28 MainWidget::MainWidget(QWidget *parent) :
28 MainWidget::MainWidget(QWidget *parent) :
29 QWidget(parent)
29 QWidget(parent)
30 {
30 {
31 m_chartWidget = new QChartView(this);
31 m_chartWidget = new QChartView(this);
32 m_chartWidget->setRubberBandPolicy(QChartView::HorizonalRubberBand);
32 m_chartWidget->setRubberBandPolicy(QChartView::HorizonalRubberBand);
33
33
34 // Grid layout for the controls for configuring the chart widget
34 // Grid layout for the controls for configuring the chart widget
35 QGridLayout *grid = new QGridLayout();
35 QGridLayout *grid = new QGridLayout();
36 QPushButton *addSeriesButton = new QPushButton("Add series");
36 QPushButton *addSeriesButton = new QPushButton("Add series");
37 connect(addSeriesButton, SIGNAL(clicked()), this, SLOT(addSeries()));
37 connect(addSeriesButton, SIGNAL(clicked()), this, SLOT(addSeries()));
38 grid->addWidget(addSeriesButton, 0, 1);
38 grid->addWidget(addSeriesButton, 0, 1);
39 initBackroundCombo(grid);
39 initBackroundCombo(grid);
40 initScaleControls(grid);
40 initScaleControls(grid);
41 initThemeCombo(grid);
41 initThemeCombo(grid);
42 QCheckBox *zoomCheckBox = new QCheckBox("Drag'n drop Zoom");
42 QCheckBox *zoomCheckBox = new QCheckBox("Drag'n drop Zoom");
43 connect(zoomCheckBox, SIGNAL(toggled(bool)), m_chartWidget, SLOT(setZoomEnabled(bool)));
43 connect(zoomCheckBox, SIGNAL(toggled(bool)), m_chartWidget, SLOT(setZoomEnabled(bool)));
44 zoomCheckBox->setChecked(true);
44 zoomCheckBox->setChecked(true);
45 grid->addWidget(zoomCheckBox, grid->rowCount(), 0);
45 grid->addWidget(zoomCheckBox, grid->rowCount(), 0);
46 // add row with empty label to make all the other rows static
46 // add row with empty label to make all the other rows static
47 grid->addWidget(new QLabel(""), grid->rowCount(), 0);
47 grid->addWidget(new QLabel(""), grid->rowCount(), 0);
48 grid->setRowStretch(grid->rowCount() - 1, 1);
48 grid->setRowStretch(grid->rowCount() - 1, 1);
49
49
50 // Another grid layout as a main layout
50 // Another grid layout as a main layout
51 QGridLayout *mainLayout = new QGridLayout();
51 QGridLayout *mainLayout = new QGridLayout();
52 mainLayout->addLayout(grid, 0, 0);
52 mainLayout->addLayout(grid, 0, 0);
53
53
54 // Init series type specific controls
54 // Init series type specific controls
55 initPieControls();
55 initPieControls();
56 mainLayout->addLayout(m_pieLayout, 2, 0);
56 mainLayout->addLayout(m_pieLayout, 2, 0);
57 // Scatter series specific settings
57 // Scatter series specific settings
58 // m_scatterLayout = new QGridLayout();
58 // m_scatterLayout = new QGridLayout();
59 // m_scatterLayout->addWidget(new QLabel("scatter"), 0, 0);
59 // m_scatterLayout->addWidget(new QLabel("scatter"), 0, 0);
60 // m_scatterLayout->setEnabled(false);
60 // m_scatterLayout->setEnabled(false);
61 // mainLayout->addLayout(m_scatterLayout, 1, 0);
61 // mainLayout->addLayout(m_scatterLayout, 1, 0);
62
62
63 // Add layouts and the chart widget to the main layout
63 // Add layouts and the chart widget to the main layout
64 mainLayout->addWidget(m_chartWidget, 0, 1, 3, 1);
64 mainLayout->addWidget(m_chartWidget, 0, 1, 3, 1);
65 setLayout(mainLayout);
65 setLayout(mainLayout);
66 }
66 }
67
67
68 // Combo box for selecting the chart's background
68 // Combo box for selecting the chart's background
69 void MainWidget::initBackroundCombo(QGridLayout *grid)
69 void MainWidget::initBackroundCombo(QGridLayout *grid)
70 {
70 {
71 QComboBox *backgroundCombo = new QComboBox(this);
71 QComboBox *backgroundCombo = new QComboBox(this);
72 backgroundCombo->addItem("Color");
72 backgroundCombo->addItem("Color");
73 backgroundCombo->addItem("Gradient");
73 backgroundCombo->addItem("Gradient");
74 backgroundCombo->addItem("Image");
74 backgroundCombo->addItem("Image");
75 connect(backgroundCombo, SIGNAL(currentIndexChanged(int)),
75 connect(backgroundCombo, SIGNAL(currentIndexChanged(int)),
76 this, SLOT(backgroundChanged(int)));
76 this, SLOT(backgroundChanged(int)));
77
77
78 grid->addWidget(new QLabel("Background:"), grid->rowCount(), 0);
78 grid->addWidget(new QLabel("Background:"), grid->rowCount(), 0);
79 grid->addWidget(backgroundCombo, grid->rowCount() - 1, 1);
79 grid->addWidget(backgroundCombo, grid->rowCount() - 1, 1);
80 }
80 }
81
81
82 // Scale related controls (auto-scale vs. manual min-max values)
82 // Scale related controls (auto-scale vs. manual min-max values)
83 void MainWidget::initScaleControls(QGridLayout *grid)
83 void MainWidget::initScaleControls(QGridLayout *grid)
84 {
84 {
85 m_autoScaleCheck = new QCheckBox("Automatic scaling");
85 m_autoScaleCheck = new QCheckBox("Automatic scaling");
86 connect(m_autoScaleCheck, SIGNAL(stateChanged(int)), this, SLOT(autoScaleChanged(int)));
86 connect(m_autoScaleCheck, SIGNAL(stateChanged(int)), this, SLOT(autoScaleChanged(int)));
87 // Allow setting also non-sense values (like -2147483648 and 2147483647)
87 // Allow setting also non-sense values (like -2147483648 and 2147483647)
88 m_xMinSpin = new QSpinBox();
88 m_xMinSpin = new QSpinBox();
89 m_xMinSpin->setMinimum(INT_MIN);
89 m_xMinSpin->setMinimum(INT_MIN);
90 m_xMinSpin->setMaximum(INT_MAX);
90 m_xMinSpin->setMaximum(INT_MAX);
91 m_xMinSpin->setValue(0);
91 m_xMinSpin->setValue(0);
92 connect(m_xMinSpin, SIGNAL(valueChanged(int)), this, SLOT(xMinChanged(int)));
92 connect(m_xMinSpin, SIGNAL(valueChanged(int)), this, SLOT(xMinChanged(int)));
93 m_xMaxSpin = new QSpinBox();
93 m_xMaxSpin = new QSpinBox();
94 m_xMaxSpin->setMinimum(INT_MIN);
94 m_xMaxSpin->setMinimum(INT_MIN);
95 m_xMaxSpin->setMaximum(INT_MAX);
95 m_xMaxSpin->setMaximum(INT_MAX);
96 m_xMaxSpin->setValue(10);
96 m_xMaxSpin->setValue(10);
97 connect(m_xMaxSpin, SIGNAL(valueChanged(int)), this, SLOT(xMaxChanged(int)));
97 connect(m_xMaxSpin, SIGNAL(valueChanged(int)), this, SLOT(xMaxChanged(int)));
98 m_yMinSpin = new QSpinBox();
98 m_yMinSpin = new QSpinBox();
99 m_yMinSpin->setMinimum(INT_MIN);
99 m_yMinSpin->setMinimum(INT_MIN);
100 m_yMinSpin->setMaximum(INT_MAX);
100 m_yMinSpin->setMaximum(INT_MAX);
101 m_yMinSpin->setValue(0);
101 m_yMinSpin->setValue(0);
102 connect(m_yMinSpin, SIGNAL(valueChanged(int)), this, SLOT(yMinChanged(int)));
102 connect(m_yMinSpin, SIGNAL(valueChanged(int)), this, SLOT(yMinChanged(int)));
103 m_yMaxSpin = new QSpinBox();
103 m_yMaxSpin = new QSpinBox();
104 m_yMaxSpin->setMinimum(INT_MIN);
104 m_yMaxSpin->setMinimum(INT_MIN);
105 m_yMaxSpin->setMaximum(INT_MAX);
105 m_yMaxSpin->setMaximum(INT_MAX);
106 m_yMaxSpin->setValue(10);
106 m_yMaxSpin->setValue(10);
107 connect(m_yMaxSpin, SIGNAL(valueChanged(int)), this, SLOT(yMaxChanged(int)));
107 connect(m_yMaxSpin, SIGNAL(valueChanged(int)), this, SLOT(yMaxChanged(int)));
108
108
109 grid->addWidget(m_autoScaleCheck, grid->rowCount(), 0);
109 grid->addWidget(m_autoScaleCheck, grid->rowCount(), 0);
110 grid->addWidget(new QLabel("x min:"), grid->rowCount(), 0);
110 grid->addWidget(new QLabel("x min:"), grid->rowCount(), 0);
111 grid->addWidget(m_xMinSpin, grid->rowCount() - 1, 1);
111 grid->addWidget(m_xMinSpin, grid->rowCount() - 1, 1);
112 grid->addWidget(new QLabel("x max:"), grid->rowCount(), 0);
112 grid->addWidget(new QLabel("x max:"), grid->rowCount(), 0);
113 grid->addWidget(m_xMaxSpin, grid->rowCount() - 1, 1);
113 grid->addWidget(m_xMaxSpin, grid->rowCount() - 1, 1);
114 grid->addWidget(new QLabel("y min:"), grid->rowCount(), 0);
114 grid->addWidget(new QLabel("y min:"), grid->rowCount(), 0);
115 grid->addWidget(m_yMinSpin, grid->rowCount() - 1, 1);
115 grid->addWidget(m_yMinSpin, grid->rowCount() - 1, 1);
116 grid->addWidget(new QLabel("y max:"), grid->rowCount(), 0);
116 grid->addWidget(new QLabel("y max:"), grid->rowCount(), 0);
117 grid->addWidget(m_yMaxSpin, grid->rowCount() - 1, 1);
117 grid->addWidget(m_yMaxSpin, grid->rowCount() - 1, 1);
118
118
119 m_autoScaleCheck->setChecked(true);
119 m_autoScaleCheck->setChecked(true);
120 }
120 }
121
121
122 // Combo box for selecting theme
122 // Combo box for selecting theme
123 void MainWidget::initThemeCombo(QGridLayout *grid)
123 void MainWidget::initThemeCombo(QGridLayout *grid)
124 {
124 {
125 QComboBox *chartTheme = new QComboBox();
125 QComboBox *chartTheme = new QComboBox();
126 chartTheme->addItem("Default");
126 chartTheme->addItem("Default");
127 chartTheme->addItem("Vanilla");
127 chartTheme->addItem("Vanilla");
128 chartTheme->addItem("Icy");
128 chartTheme->addItem("Icy");
129 chartTheme->addItem("Grayscale");
129 chartTheme->addItem("Grayscale");
130 chartTheme->addItem("Scientific");
130 chartTheme->addItem("Scientific");
131 chartTheme->addItem("Unnamed1");
131 chartTheme->addItem("Unnamed1");
132 connect(chartTheme, SIGNAL(currentIndexChanged(int)),
132 connect(chartTheme, SIGNAL(currentIndexChanged(int)),
133 this, SLOT(changeChartTheme(int)));
133 this, SLOT(changeChartTheme(int)));
134 grid->addWidget(new QLabel("Chart theme:"), 8, 0);
134 grid->addWidget(new QLabel("Chart theme:"), 8, 0);
135 grid->addWidget(chartTheme, 8, 1);
135 grid->addWidget(chartTheme, 8, 1);
136 }
136 }
137
137
138 void MainWidget::initPieControls()
138 void MainWidget::initPieControls()
139 {
139 {
140 // Pie series specific settings
140 // Pie series specific settings
141 // Pie size factory
141 // Pie size factory
142 QDoubleSpinBox *pieSizeSpin = new QDoubleSpinBox();
142 QDoubleSpinBox *pieSizeSpin = new QDoubleSpinBox();
143 pieSizeSpin->setMinimum(LONG_MIN);
143 pieSizeSpin->setMinimum(LONG_MIN);
144 pieSizeSpin->setMaximum(LONG_MAX);
144 pieSizeSpin->setMaximum(LONG_MAX);
145 pieSizeSpin->setValue(1.0);
145 pieSizeSpin->setValue(1.0);
146 pieSizeSpin->setSingleStep(0.1);
146 pieSizeSpin->setSingleStep(0.1);
147 connect(pieSizeSpin, SIGNAL(valueChanged(double)), this, SLOT(setPieSizeFactor(double)));
147 connect(pieSizeSpin, SIGNAL(valueChanged(double)), this, SLOT(setPieSizeFactor(double)));
148 // Pie position
148 // Pie position
149 QComboBox *piePosCombo = new QComboBox(this);
149 QComboBox *piePosCombo = new QComboBox(this);
150 piePosCombo->addItem("Maximized");
150 piePosCombo->addItem("Maximized");
151 piePosCombo->addItem("Top left");
151 piePosCombo->addItem("Top left");
152 piePosCombo->addItem("Top right");
152 piePosCombo->addItem("Top right");
153 piePosCombo->addItem("Bottom left");
153 piePosCombo->addItem("Bottom left");
154 piePosCombo->addItem("Bottom right");
154 piePosCombo->addItem("Bottom right");
155 connect(piePosCombo, SIGNAL(currentIndexChanged(int)),
155 connect(piePosCombo, SIGNAL(currentIndexChanged(int)),
156 this, SLOT(setPiePosition(int)));
156 this, SLOT(setPiePosition(int)));
157 m_pieLayout = new QGridLayout();
157 m_pieLayout = new QGridLayout();
158 m_pieLayout->setEnabled(false);
158 m_pieLayout->setEnabled(false);
159 m_pieLayout->addWidget(new QLabel("Pie size factor"), 0, 0);
159 m_pieLayout->addWidget(new QLabel("Pie size factor"), 0, 0);
160 m_pieLayout->addWidget(pieSizeSpin, 0, 1);
160 m_pieLayout->addWidget(pieSizeSpin, 0, 1);
161 m_pieLayout->addWidget(new QLabel("Pie position"), 1, 0);
161 m_pieLayout->addWidget(new QLabel("Pie position"), 1, 0);
162 m_pieLayout->addWidget(piePosCombo, 1, 1);
162 m_pieLayout->addWidget(piePosCombo, 1, 1);
163 }
163 }
164
164
165 void MainWidget::addSeries()
165 void MainWidget::addSeries()
166 {
166 {
167 DataSerieDialog dialog(m_defaultSeriesName, this);
167 DataSerieDialog dialog(m_defaultSeriesName, this);
168 connect(&dialog, SIGNAL(accepted(QString, int, int, QString, bool)),
168 connect(&dialog, SIGNAL(accepted(QString, int, int, QString, bool)),
169 this, SLOT(addSeries(QString, int, int, QString, bool)));
169 this, SLOT(addSeries(QString, int, int, QString, bool)));
170 dialog.exec();
170 dialog.exec();
171 }
171 }
172
172
173 QList<RealList> MainWidget::generateTestData(int columnCount, int rowCount, QString dataCharacteristics)
173 QList<RealList> MainWidget::generateTestData(int columnCount, int rowCount, QString dataCharacteristics)
174 {
174 {
175 // TODO: dataCharacteristics
175 // TODO: dataCharacteristics
176 QList<RealList> testData;
176 QList<RealList> testData;
177 for (int j(0); j < columnCount; j++) {
177 for (int j(0); j < columnCount; j++) {
178 QList <qreal> newColumn;
178 QList <qreal> newColumn;
179 for (int i(0); i < rowCount; i++) {
179 for (int i(0); i < rowCount; i++) {
180 if (dataCharacteristics == "Sin") {
180 if (dataCharacteristics == "Sin") {
181 newColumn.append(abs(sin(3.14159265358979 / 50 * i) * 100));
181 newColumn.append(abs(sin(3.14159265358979 / 50 * i) * 100));
182 } else if (dataCharacteristics == "Sin + random") {
182 } else if (dataCharacteristics == "Sin + random") {
183 newColumn.append(abs(sin(3.14159265358979 / 50 * i) * 100) + (rand() % 5));
183 newColumn.append(abs(sin(3.14159265358979 / 50 * i) * 100) + (rand() % 5));
184 } else if (dataCharacteristics == "Random") {
184 } else if (dataCharacteristics == "Random") {
185 newColumn.append(rand() % 5);
185 newColumn.append(rand() % 5);
186 } else if (dataCharacteristics == "Linear") {
186 } else if (dataCharacteristics == "Linear") {
187 //newColumn.append(i * (j + 1.0));
187 //newColumn.append(i * (j + 1.0));
188 // TODO: temporary hack to make pie work; prevent zero values:
188 // TODO: temporary hack to make pie work; prevent zero values:
189 newColumn.append(i * (j + 1.0) + 0.1);
189 newColumn.append(i * (j + 1.0) + 0.1);
190 } else { // "constant"
190 } else { // "constant"
191 newColumn.append((j + 1.0));
191 newColumn.append((j + 1.0));
192 }
192 }
193 }
193 }
194 testData.append(newColumn);
194 testData.append(newColumn);
195 }
195 }
196 return testData;
196 return testData;
197 }
197 }
198
198
199 QStringList MainWidget::generateLabels(int count)
199 QStringList MainWidget::generateLabels(int count)
200 {
200 {
201 QStringList result;
201 QStringList result;
202 for (int i(0); i < count; i++)
202 for (int i(0); i < count; i++)
203 result.append("label" + QString::number(i));
203 result.append("label" + QString::number(i));
204 return result;
204 return result;
205 }
205 }
206
206
207 void MainWidget::addSeries(QString seriesName, int columnCount, int rowCount, QString dataCharacteristics, bool labelsEnabled)
207 void MainWidget::addSeries(QString seriesName, int columnCount, int rowCount, QString dataCharacteristics, bool labelsEnabled)
208 {
208 {
209 qDebug() << "addSeries: " << seriesName
209 qDebug() << "addSeries: " << seriesName
210 << " columnCount: " << columnCount
210 << " columnCount: " << columnCount
211 << " rowCount: " << rowCount
211 << " rowCount: " << rowCount
212 << " dataCharacteristics: " << dataCharacteristics
212 << " dataCharacteristics: " << dataCharacteristics
213 << " labels enabled: " << labelsEnabled;
213 << " labels enabled: " << labelsEnabled;
214 m_defaultSeriesName = seriesName;
214 m_defaultSeriesName = seriesName;
215
215
216 QList<RealList> data = generateTestData(columnCount, rowCount, dataCharacteristics);
216 QList<RealList> data = generateTestData(columnCount, rowCount, dataCharacteristics);
217
217
218 // Line series and scatter series use similar data
218 // Line series and scatter series use similar data
219 if (seriesName.contains("line", Qt::CaseInsensitive)) {
219 if (seriesName.contains("line", Qt::CaseInsensitive)) {
220 for (int j(0); j < data.count(); j ++) {
220 for (int j(0); j < data.count(); j ++) {
221 QList<qreal> column = data.at(j);
221 QList<qreal> column = data.at(j);
222 QLineChartSeries *series = new QLineChartSeries();
222 QLineChartSeries *series = new QLineChartSeries();
223 for (int i(0); i < column.count(); i++) {
223 for (int i(0); i < column.count(); i++) {
224 series->add(i, column.at(i));
224 series->add(i, column.at(i));
225 }
225 }
226 m_chartWidget->addSeries(series);
226 m_chartWidget->addSeries(series);
227 setCurrentSeries(series);
227 setCurrentSeries(series);
228 }
228 }
229 } else if (seriesName.contains("scatter", Qt::CaseInsensitive)) {
229 } else if (seriesName.contains("scatter", Qt::CaseInsensitive)) {
230 for (int j(0); j < data.count(); j++) {
230 for (int j(0); j < data.count(); j++) {
231 QList<qreal> column = data.at(j);
231 QList<qreal> column = data.at(j);
232 QScatterSeries *series = new QScatterSeries();
232 QScatterSeries *series = new QScatterSeries();
233 for (int i(0); i < column.count(); i++) {
233 for (int i(0); i < column.count(); i++) {
234 (*series) << QPointF(i, column.at(i));
234 (*series) << QPointF(i, column.at(i));
235 }
235 }
236 m_chartWidget->addSeries(series);
236 m_chartWidget->addSeries(series);
237 setCurrentSeries(series);
237 setCurrentSeries(series);
238 }
238 }
239 } else if (seriesName.contains("pie", Qt::CaseInsensitive)) {
239 } else if (seriesName.contains("pie", Qt::CaseInsensitive)) {
240 QStringList labels = generateLabels(rowCount);
240 QStringList labels = generateLabels(rowCount);
241 for (int j(0); j < data.count(); j++) {
241 for (int j(0); j < data.count(); j++) {
242 QPieSeries *series = new QPieSeries();
242 QPieSeries *series = new QPieSeries();
243 QList<qreal> column = data.at(j);
243 QList<qreal> column = data.at(j);
244 for (int i(0); i < column.count(); i++) {
244 for (int i(0); i < column.count(); i++) {
245 series->add(column.at(i), labels.at(i));
245 series->add(column.at(i), labels.at(i));
246 }
246 }
247 m_chartWidget->addSeries(series);
247 m_chartWidget->addSeries(series);
248 setCurrentSeries(series);
248 setCurrentSeries(series);
249 }
249 }
250 } else if (seriesName == "Bar") {
250 } else if (seriesName == "Bar"
251 || seriesName == "Stacked bar"
252 || seriesName == "Percent bar") {
251 // TODO: replace QBarCategory with QStringList?
253 // TODO: replace QBarCategory with QStringList?
252 QBarCategory *category = new QBarCategory;
254 QBarCategory *category = new QBarCategory;
253 QStringList labels = generateLabels(rowCount);
255 QStringList labels = generateLabels(rowCount);
254 foreach(QString label, labels)
256 foreach(QString label, labels)
255 *category << label;
257 *category << label;
256 QBarChartSeries* series = new QBarChartSeries(category, this);
258 QBarChartSeries* series = 0;
259 if (seriesName == "Bar")
260 series = new QBarChartSeries(category, this);
261 else if (seriesName == "Stacked bar")
262 series = new QStackedBarChartSeries(category, this);
263 else
264 series = new QPercentBarChartSeries(category, this);
257
265
258 for (int j(0); j < data.count(); j++) {
266 for (int j(0); j < data.count(); j++) {
259 QList<qreal> column = data.at(j);
267 QList<qreal> column = data.at(j);
260 QBarSet *set = new QBarSet;
268 QBarSet *set = new QBarSet("set" + QString::number(j));
261 for (int i(0); i < column.count(); i++) {
262 *set << column.at(i);
263 }
264 series->addBarSet(set);
265 }
266 m_chartWidget->addSeries(series);
267 setCurrentSeries(series);
268 } else if (seriesName == "Stacked bar") {
269 QBarCategory *category = new QBarCategory;
270 QStringList labels = generateLabels(rowCount);
271 foreach(QString label, labels)
272 *category << label;
273 QStackedBarChartSeries* series = new QStackedBarChartSeries(category, this);
274
275 for (int j(0); j < data.count(); j++) {
276 QList<qreal> column = data.at(j);
277 QBarSet *set = new QBarSet;
278 for (int i(0); i < column.count(); i++) {
279 *set << column.at(i);
280 }
281 series->addBarSet(set);
282 }
283 m_chartWidget->addSeries(series);
284 setCurrentSeries(series);
285 } else if (seriesName == "Percent bar") {
286 QBarCategory *category = new QBarCategory;
287 QStringList labels = generateLabels(rowCount);
288 foreach(QString label, labels)
289 *category << label;
290 QPercentBarChartSeries* series = new QPercentBarChartSeries(category, this);
291
292 for (int j(0); j < data.count(); j++) {
293 QList<qreal> column = data.at(j);
294 QBarSet *set = new QBarSet;
295 for (int i(0); i < column.count(); i++) {
269 for (int i(0); i < column.count(); i++) {
296 *set << column.at(i);
270 *set << column.at(i);
297 }
271 }
298 series->addBarSet(set);
272 series->addBarSet(set);
299 }
273 }
300 m_chartWidget->addSeries(series);
274 m_chartWidget->addSeries(series);
301 setCurrentSeries(series);
275 setCurrentSeries(series);
302 }
276 }
303
277
304 // TODO: spline and area
278 // TODO: spline and area
305 }
279 }
306
280
307 void MainWidget::setCurrentSeries(QChartSeries *series)
281 void MainWidget::setCurrentSeries(QChartSeries *series)
308 {
282 {
309 if (series) {
283 if (series) {
310 m_currentSeries = series;
284 m_currentSeries = series;
311 switch (m_currentSeries->type()) {
285 switch (m_currentSeries->type()) {
312 case QChartSeries::SeriesTypeLine:
286 case QChartSeries::SeriesTypeLine:
313 break;
287 break;
314 case QChartSeries::SeriesTypeScatter:
288 case QChartSeries::SeriesTypeScatter:
315 break;
289 break;
316 case QChartSeries::SeriesTypePie:
290 case QChartSeries::SeriesTypePie:
317 break;
291 break;
318 case QChartSeries::SeriesTypeBar:
292 case QChartSeries::SeriesTypeBar:
319 qDebug() << "setCurrentSeries (bar)";
293 qDebug() << "setCurrentSeries (bar)";
320 break;
294 break;
321 case QChartSeries::SeriesTypeStackedBar:
295 case QChartSeries::SeriesTypeStackedBar:
322 qDebug() << "setCurrentSeries (Stackedbar)";
296 qDebug() << "setCurrentSeries (Stackedbar)";
323 break;
297 break;
324 case QChartSeries::SeriesTypePercentBar:
298 case QChartSeries::SeriesTypePercentBar:
325 qDebug() << "setCurrentSeries (Percentbar)";
299 qDebug() << "setCurrentSeries (Percentbar)";
326 break;
300 break;
327 default:
301 default:
328 Q_ASSERT(false);
302 Q_ASSERT(false);
329 break;
303 break;
330 }
304 }
331 }
305 }
332 }
306 }
333
307
334 void MainWidget::backgroundChanged(int itemIndex)
308 void MainWidget::backgroundChanged(int itemIndex)
335 {
309 {
336 qDebug() << "backgroundChanged: " << itemIndex;
310 qDebug() << "backgroundChanged: " << itemIndex;
337 }
311 }
338
312
339 void MainWidget::autoScaleChanged(int value)
313 void MainWidget::autoScaleChanged(int value)
340 {
314 {
341 if (value) {
315 if (value) {
342 // TODO: enable auto scaling
316 // TODO: enable auto scaling
343 } else {
317 } else {
344 // TODO: set scaling manually (and disable auto scaling)
318 // TODO: set scaling manually (and disable auto scaling)
345 }
319 }
346
320
347 m_xMinSpin->setEnabled(!value);
321 m_xMinSpin->setEnabled(!value);
348 m_xMaxSpin->setEnabled(!value);
322 m_xMaxSpin->setEnabled(!value);
349 m_yMinSpin->setEnabled(!value);
323 m_yMinSpin->setEnabled(!value);
350 m_yMaxSpin->setEnabled(!value);
324 m_yMaxSpin->setEnabled(!value);
351 }
325 }
352
326
353 void MainWidget::xMinChanged(int value)
327 void MainWidget::xMinChanged(int value)
354 {
328 {
355 qDebug() << "xMinChanged: " << value;
329 qDebug() << "xMinChanged: " << value;
356 }
330 }
357
331
358 void MainWidget::xMaxChanged(int value)
332 void MainWidget::xMaxChanged(int value)
359 {
333 {
360 qDebug() << "xMaxChanged: " << value;
334 qDebug() << "xMaxChanged: " << value;
361 }
335 }
362
336
363 void MainWidget::yMinChanged(int value)
337 void MainWidget::yMinChanged(int value)
364 {
338 {
365 qDebug() << "yMinChanged: " << value;
339 qDebug() << "yMinChanged: " << value;
366 }
340 }
367
341
368 void MainWidget::yMaxChanged(int value)
342 void MainWidget::yMaxChanged(int value)
369 {
343 {
370 qDebug() << "yMaxChanged: " << value;
344 qDebug() << "yMaxChanged: " << value;
371 }
345 }
372
346
373 void MainWidget::changeChartTheme(int themeIndex)
347 void MainWidget::changeChartTheme(int themeIndex)
374 {
348 {
375 qDebug() << "changeChartTheme: " << themeIndex;
349 qDebug() << "changeChartTheme: " << themeIndex;
376 m_chartWidget->setChartTheme((QChart::ChartTheme) themeIndex);
350 m_chartWidget->setChartTheme((QChart::ChartTheme) themeIndex);
377 //TODO: remove this hack. This is just to make it so that theme change is seen immediately.
351 //TODO: remove this hack. This is just to make it so that theme change is seen immediately.
378 QSize s = size();
352 QSize s = size();
379 s.setWidth(s.width()+1);
353 s.setWidth(s.width()+1);
380 resize(s);
354 resize(s);
381 }
355 }
382
356
383 void MainWidget::setPieSizeFactor(double size)
357 void MainWidget::setPieSizeFactor(double size)
384 {
358 {
385 QPieSeries *pie = qobject_cast<QPieSeries *>(m_currentSeries);
359 QPieSeries *pie = qobject_cast<QPieSeries *>(m_currentSeries);
386 if (pie)
360 if (pie)
387 pie->setSizeFactor(qreal(size));
361 pie->setSizeFactor(qreal(size));
388 }
362 }
389
363
390 void MainWidget::setPiePosition(int position)
364 void MainWidget::setPiePosition(int position)
391 {
365 {
392 QPieSeries *pie = qobject_cast<QPieSeries *>(m_currentSeries);
366 QPieSeries *pie = qobject_cast<QPieSeries *>(m_currentSeries);
393 if (pie)
367 if (pie)
394 pie->setPosition((QPieSeries::PiePosition) position);
368 pie->setPosition((QPieSeries::PiePosition) position);
395 }
369 }
General Comments 0
You need to be logged in to leave comments. Login now