2
* Copyright (c) 2012 Mark Liversedge (liversedge@gmail.com)
4
* This program is free software; you can redistribute it and/or modify it
5
* under the terms of the GNU General Public License as published by the Free
6
* Software Foundation; either version 2 of the License, or (at your option)
9
* This program is distributed in the hope that it will be useful, but WITHOUT
10
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14
* You should have received a copy of the GNU General Public License along
15
* with this program; if not, write to the Free Software Foundation, Inc., 51
16
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
#include "LTMSidebar.h"
20
#include "MainWindow.h"
24
#include <QApplication>
31
#include "SeasonParser.h"
32
#include <QXmlInputSource>
33
#include <QXmlSimpleReader>
36
#include "RideMetadata.h"
37
#include "SpecialFields.h"
39
#include "MetricAggregator.h"
40
#include "SummaryMetrics.h"
42
LTMSidebar::LTMSidebar(MainWindow *parent, const QDir &home) : QWidget(parent), home(home), main(parent), active(false)
44
QVBoxLayout *mainLayout = new QVBoxLayout(this);
45
mainLayout->setContentsMargins(0,0,0,0);
46
mainLayout->setSpacing(0);
47
setContentsMargins(0,0,0,0);
49
seasonsWidget = new GcSplitterItem(tr("Date Ranges"), iconFromPNG(":images/sidebar/calendar.png"), this);
50
QAction *moreSeasonAct = new QAction(iconFromPNG(":images/sidebar/extra.png"), tr("Menu"), this);
51
seasonsWidget->addAction(moreSeasonAct);
52
connect(moreSeasonAct, SIGNAL(triggered(void)), this, SLOT(dateRangePopup(void)));
54
dateRangeTree = new SeasonTreeView;
55
allDateRanges=dateRangeTree->invisibleRootItem();
57
allDateRanges->setFlags(Qt::ItemIsEnabled | Qt::ItemIsDropEnabled);
58
allDateRanges->setText(0, tr("Date Ranges"));
59
dateRangeTree->setFrameStyle(QFrame::NoFrame);
60
dateRangeTree->setColumnCount(1);
61
dateRangeTree->setSelectionMode(QAbstractItemView::SingleSelection);
62
dateRangeTree->header()->hide();
63
dateRangeTree->setIndentation(5);
64
dateRangeTree->expandItem(allDateRanges);
65
dateRangeTree->setContextMenuPolicy(Qt::CustomContextMenu);
67
dateRangeTree->setAttribute(Qt::WA_MacShowFocusRect, 0);
69
seasonsWidget->addWidget(dateRangeTree);
72
eventsWidget = new GcSplitterItem(tr("Events"), iconFromPNG(":images/sidebar/bookmark.png"), this);
73
QAction *moreEventAct = new QAction(iconFromPNG(":images/sidebar/extra.png"), tr("Menu"), this);
74
eventsWidget->addAction(moreEventAct);
75
connect(moreEventAct, SIGNAL(triggered(void)), this, SLOT(eventPopup(void)));
78
eventTree = new QTreeWidget;
79
eventTree->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
80
allEvents = eventTree->invisibleRootItem();
81
allEvents->setText(0, tr("Events"));
82
eventTree->setFrameStyle(QFrame::NoFrame);
83
eventTree->setColumnCount(2);
84
eventTree->setSelectionMode(QAbstractItemView::SingleSelection);
85
eventTree->header()->hide();
86
eventTree->setIndentation(5);
87
eventTree->expandItem(allEvents);
88
eventTree->setContextMenuPolicy(Qt::CustomContextMenu);
89
eventTree->horizontalScrollBar()->setDisabled(true);
90
eventTree->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
92
eventTree->setAttribute(Qt::WA_MacShowFocusRect, 0);
94
eventsWidget->addWidget(eventTree);
96
seasons = parent->seasons;
97
resetSeasons(); // reset the season list
99
configChanged(); // will reset the metric tree
101
splitter = new GcSplitter(Qt::Vertical);
102
splitter->addWidget(seasonsWidget);
103
splitter->addWidget(eventsWidget);
105
GcSplitterItem *summaryWidget = new GcSplitterItem(tr("Summary"), iconFromPNG(":images/sidebar/dashboard.png"), this);
107
summary = new QWebView(this);
108
summary->setContentsMargins(0,0,0,0);
109
summary->page()->view()->setContentsMargins(0,0,0,0);
110
summary->page()->mainFrame()->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff);
111
summary->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
112
summary->setAcceptDrops(false);
114
summaryWidget->addWidget(summary);
116
QFont defaultFont; // mainwindow sets up the defaults.. we need to apply
117
summary->settings()->setFontSize(QWebSettings::DefaultFontSize, defaultFont.pointSize());
118
summary->settings()->setFontFamily(QWebSettings::StandardFont, defaultFont.family());
119
splitter->addWidget(summaryWidget);
121
mainLayout->addWidget(splitter);
123
splitter->prepare(main->cyclist, "LTM");
126
connect(dateRangeTree,SIGNAL(itemSelectionChanged()), this, SLOT(dateRangeTreeWidgetSelectionChanged()));
127
connect(dateRangeTree,SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(dateRangePopup(const QPoint &)));
128
connect(dateRangeTree,SIGNAL(itemChanged(QTreeWidgetItem *,int)), this, SLOT(dateRangeChanged(QTreeWidgetItem*, int)));
129
connect(dateRangeTree,SIGNAL(itemMoved(QTreeWidgetItem *,int, int)), this, SLOT(dateRangeMoved(QTreeWidgetItem*, int, int)));
130
connect(eventTree,SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(eventPopup(const QPoint &)));
132
connect(main, SIGNAL(configChanged()), this, SLOT(configChanged()));
133
connect(seasons, SIGNAL(seasonsChanged()), this, SLOT(resetSeasons()));
135
connect(this, SIGNAL(dateRangeChanged(DateRange)), this, SLOT(setSummary(DateRange)));
137
// let everyone know what date range we are starting with
138
dateRangeTreeWidgetSelectionChanged();
143
LTMSidebar::configChanged()
147
/*----------------------------------------------------------------------
149
*----------------------------------------------------------------------*/
152
LTMSidebar::dateRangeTreeWidgetSelectionChanged()
154
if (active == true) return;
156
const Season *dateRange = NULL;
158
if (dateRangeTree->selectedItems().isEmpty()) dateRange = NULL;
160
QTreeWidgetItem *which = dateRangeTree->selectedItems().first();
161
if (which != allDateRanges) {
162
dateRange = &seasons->seasons.at(allDateRanges->indexOfChild(which));
170
// clear events - we need to add for currently selected season
171
for (i=allEvents->childCount(); i > 0; i--) {
172
delete allEvents->takeChild(0);
175
// add this seasons events
176
for (i=0; i <dateRange->events.count(); i++) {
177
SeasonEvent event = dateRange->events.at(i);
178
QTreeWidgetItem *add = new QTreeWidgetItem(allEvents);
179
add->setText(0, event.name);
180
add->setText(1, event.date.toString("MMM d, yyyy"));
183
// make sure they fit
184
eventTree->header()->resizeSections(QHeaderView::ResizeToContents);
185
appsettings->setCValue(main->cyclist, GC_LTM_LAST_DATE_RANGE, dateRange->id().toString());
189
// Let the view know its changed....
190
if (dateRange) emit dateRangeChanged(DateRange(dateRange->start, dateRange->end, dateRange->name));
191
else emit dateRangeChanged(DateRange());
195
/*----------------------------------------------------------------------
197
*--------------------------------------------------------------------*/
200
LTMSidebar::resetSeasons()
202
if (active == true) return;
206
for (i=allDateRanges->childCount(); i > 0; i--) {
207
delete allDateRanges->takeChild(0);
209
QString id = appsettings->cvalue(main->cyclist, GC_LTM_LAST_DATE_RANGE, seasons->seasons.at(0).id().toString()).toString();
210
for (i=0; i <seasons->seasons.count(); i++) {
211
Season season = seasons->seasons.at(i);
212
QTreeWidgetItem *add = new QTreeWidgetItem(allDateRanges, season.getType());
213
if (season.id().toString()==id)
214
add->setSelected(true);
216
// No Drag/Drop for temporary Season
217
if (season.getType() == Season::temporary)
218
add->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
220
add->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
221
add->setText(0, season.getName());
228
LTMSidebar::newSeason(QString name, QDate start, QDate end, int type)
230
seasons->newSeason(name, start, end, type);
232
QTreeWidgetItem *item = new QTreeWidgetItem(Season::season);
233
item->setText(0, name);
234
allDateRanges->insertChild(0, item);
235
return 0; // always add at the top
239
LTMSidebar::updateSeason(int index, QString name, QDate start, QDate end, int type)
241
seasons->updateSeason(index, name, start, end, type);
242
allDateRanges->child(index)->setText(0, name);
246
LTMSidebar::dateRangePopup(QPoint pos)
248
QTreeWidgetItem *item = dateRangeTree->itemAt(pos);
251
// out of bounds or not user defined
252
int index = allDateRanges->indexOfChild(item);
253
if (index == -1 || index >= seasons->seasons.count()
254
|| seasons->seasons[index].getType() == Season::temporary) {
255
// on system date we just offer to add a Season, since its
256
// the only way of doing it when no seasons are defined!!
258
// create context menu
259
QMenu menu(dateRangeTree);
260
QAction *add = new QAction(tr("Add season"), dateRangeTree);
263
// connect menu to functions
264
connect(add, SIGNAL(triggered(void)), this, SLOT(addRange(void)));
267
menu.exec(dateRangeTree->mapToGlobal(pos));
271
// create context menu
272
QMenu menu(dateRangeTree);
273
QAction *add = new QAction(tr("Add season"), dateRangeTree);
274
QAction *edit = new QAction(tr("Edit season"), dateRangeTree);
275
QAction *del = new QAction(tr("Delete season"), dateRangeTree);
276
QAction *event = new QAction(tr("Add Event"), dateRangeTree);
278
menu.addAction(edit);
280
menu.addAction(event);
282
// connect menu to functions
283
connect(add, SIGNAL(triggered(void)), this, SLOT(addRange(void)));
284
connect(edit, SIGNAL(triggered(void)), this, SLOT(editRange(void)));
285
connect(del, SIGNAL(triggered(void)), this, SLOT(deleteRange(void)));
286
connect(event, SIGNAL(triggered(void)), this, SLOT(addEvent(void)));
289
menu.exec(dateRangeTree->mapToGlobal(pos));
295
LTMSidebar::dateRangePopup()
297
// no current season selected
298
if (dateRangeTree->selectedItems().isEmpty()) return;
300
QTreeWidgetItem *item = dateRangeTree->selectedItems().at(0);
302
// OK - we are working with a specific event..
303
QMenu menu(dateRangeTree);
304
QAction *add = new QAction(tr("Add season"), dateRangeTree);
306
connect(add, SIGNAL(triggered(void)), this, SLOT(addRange(void)));
308
if (item != NULL && allDateRanges->indexOfChild(item) != -1) {
309
QAction *edit = new QAction(tr("Edit season"), dateRangeTree);
310
QAction *del = new QAction(tr("Delete season"), dateRangeTree);
311
QAction *event = new QAction(tr("Add Event"), dateRangeTree);
313
menu.addAction(edit);
315
menu.addAction(event);
317
// connect menu to functions
319
connect(edit, SIGNAL(triggered(void)), this, SLOT(editRange(void)));
320
connect(del, SIGNAL(triggered(void)), this, SLOT(deleteRange(void)));
321
connect(event, SIGNAL(triggered(void)), this, SLOT(addEvent(void)));
324
menu.exec(splitter->mapToGlobal(QPoint(seasonsWidget->pos().x()+seasonsWidget->width()-20,
325
seasonsWidget->pos().y())));
329
LTMSidebar::eventPopup(QPoint pos)
331
// no current season selected
332
if (dateRangeTree->selectedItems().isEmpty()) return;
334
QTreeWidgetItem *item = eventTree->itemAt(pos);
336
// save context - which season and event are we working with?
337
QTreeWidgetItem *which = dateRangeTree->selectedItems().first();
338
if (!which || which == allDateRanges) return;
340
// OK - we are working with a specific event..
341
QMenu menu(eventTree);
342
if (item != NULL && allEvents->indexOfChild(item) != -1) {
344
QAction *edit = new QAction(tr("Edit details"), eventTree);
345
QAction *del = new QAction(tr("Delete event"), eventTree);
346
menu.addAction(edit);
349
// connect menu to functions
350
connect(edit, SIGNAL(triggered(void)), this, SLOT(editEvent(void)));
351
connect(del, SIGNAL(triggered(void)), this, SLOT(deleteEvent(void)));
354
// we can always add, regardless of any event being selected...
355
QAction *addEvent = new QAction(tr("Add event"), eventTree);
356
menu.addAction(addEvent);
357
connect(addEvent, SIGNAL(triggered(void)), this, SLOT(addEvent(void)));
360
menu.exec(eventTree->mapToGlobal(pos));
364
LTMSidebar::eventPopup()
366
// events are against a selected season
367
if (dateRangeTree->selectedItems().count() == 0) return; // need a season selected!
369
// and the season must be user defined not temporary
370
int seasonindex = allDateRanges->indexOfChild(dateRangeTree->selectedItems().first());
371
if (seasons->seasons[seasonindex].getType() == Season::temporary) return;
373
// have we selected an event?
374
QTreeWidgetItem *item = NULL;
375
if (!eventTree->selectedItems().isEmpty()) item = eventTree->selectedItems().at(0);
377
QMenu menu(eventTree);
379
// we can always add, regardless of any event being selected...
380
QAction *addEvent = new QAction(tr("Add event"), eventTree);
381
menu.addAction(addEvent);
382
connect(addEvent, SIGNAL(triggered(void)), this, SLOT(addEvent(void)));
384
if (item != NULL && allEvents->indexOfChild(item) != -1) {
386
QAction *edit = new QAction(tr("Edit details"), eventTree);
387
QAction *del = new QAction(tr("Delete event"), eventTree);
388
menu.addAction(edit);
391
// connect menu to functions
392
connect(edit, SIGNAL(triggered(void)), this, SLOT(editEvent(void)));
393
connect(del, SIGNAL(triggered(void)), this, SLOT(deleteEvent(void)));
397
menu.exec(splitter->mapToGlobal(QPoint(eventsWidget->pos().x()+eventsWidget->width()-20, eventsWidget->pos().y())));
401
LTMSidebar::dateRangeChanged(QTreeWidgetItem*item, int)
403
if (active == true) return;
405
int index = allDateRanges->indexOfChild(item);
406
seasons->seasons[index].setName(item->text(0));
410
seasons->writeSeasons();
413
// signal date selected changed
414
//dateRangeSelected(&seasons->seasons[index]);
418
LTMSidebar::dateRangeMoved(QTreeWidgetItem*item, int oldposition, int newposition)
420
// no drop in the temporary seasons
421
if (newposition>allDateRanges->childCount()-12) {
422
newposition = allDateRanges->childCount()-12;
423
allDateRanges->removeChild(item);
424
allDateRanges->insertChild(newposition, item);
427
// report the move in the seasons
428
seasons->seasons.move(oldposition, newposition);
432
seasons->writeSeasons();
435
// deselect actual selection
436
dateRangeTree->selectedItems().first()->setSelected(false);
437
// select the move/drop item
438
item->setSelected(true);
442
LTMSidebar::addRange()
446
EditSeasonDialog dialog(main, &newOne);
453
seasons->seasons.insert(0, newOne);
454
seasons->writeSeasons();
457
// signal its changed!
463
LTMSidebar::editRange()
465
// throw up modal dialog box to edit all the season
466
if (dateRangeTree->selectedItems().count() != 1) return;
468
int index = allDateRanges->indexOfChild(dateRangeTree->selectedItems().first());
469
EditSeasonDialog dialog(main, &seasons->seasons[index]);
476
dateRangeTree->selectedItems().first()->setText(0, seasons->seasons[index].getName());
479
seasons->writeSeasons();
486
LTMSidebar::deleteRange()
488
if (dateRangeTree->selectedItems().count() != 1) return;
489
int index = allDateRanges->indexOfChild(dateRangeTree->selectedItems().first());
492
delete allDateRanges->takeChild(index);
493
seasons->deleteSeason(index);
497
LTMSidebar::addEvent()
499
if (dateRangeTree->selectedItems().count() == 0) {
500
QMessageBox::warning(this, tr("Add Event"), tr("You can only add events to user defined seasons. Please select a season you have created before adding an event."));
501
return; // need a season selected!
504
int seasonindex = allDateRanges->indexOfChild(dateRangeTree->selectedItems().first());
506
if (seasons->seasons[seasonindex].getType() == Season::temporary) {
507
QMessageBox::warning(this, tr("Add Event"), tr("You can only add events to user defined seasons. Please select a season you have created before adding an event."));
508
return; // must be a user season
511
SeasonEvent myevent("", QDate());
512
EditSeasonEventDialog dialog(main, &myevent);
517
seasons->seasons[seasonindex].events.append(myevent);
519
QTreeWidgetItem *add = new QTreeWidgetItem(allEvents);
520
add->setText(0, myevent.name);
521
add->setText(1, myevent.date.toString("MMM d, yyyy"));
523
// make sure they fit
524
eventTree->header()->resizeSections(QHeaderView::ResizeToContents);
527
seasons->writeSeasons();
533
LTMSidebar::deleteEvent()
537
if (dateRangeTree->selectedItems().count()) {
539
int seasonindex = allDateRanges->indexOfChild(dateRangeTree->selectedItems().first());
541
// only delete those that are selected
542
if (eventTree->selectedItems().count() > 0) {
545
foreach(QTreeWidgetItem *d, eventTree->selectedItems()) {
546
int index = allEvents->indexOfChild(d);
548
delete allEvents->takeChild(index);
549
seasons->seasons[seasonindex].events.removeAt(index);
554
seasons->writeSeasons();
561
LTMSidebar::editEvent()
565
if (dateRangeTree->selectedItems().count()) {
567
int seasonindex = allDateRanges->indexOfChild(dateRangeTree->selectedItems().first());
569
// only delete those that are selected
570
if (eventTree->selectedItems().count() == 1) {
572
QTreeWidgetItem *ours = eventTree->selectedItems().first();
573
int index = allEvents->indexOfChild(ours);
575
EditSeasonEventDialog dialog(main, &seasons->seasons[seasonindex].events[index]);
580
ours->setText(0, seasons->seasons[seasonindex].events[index].name);
581
ours->setText(1, seasons->seasons[seasonindex].events[index].date.toString("MMM d, yyyy"));
584
seasons->writeSeasons();
592
LTMSidebar::setSummary(DateRange dateRange)
594
// where we construct the text
595
QString summaryText("");
598
static const QStringList totalColumn = QStringList()
605
static const QStringList averageColumn = QStringList()
611
static const QStringList maximumColumn = QStringList()
618
QString s = appsettings->value(this, GC_SETTINGS_SUMMARY_METRICS, GC_SETTINGS_SUMMARY_METRICS_DEFAULT).toString();
620
// in case they were set tand then unset
621
if (s == "") s = GC_SETTINGS_SUMMARY_METRICS_DEFAULT;
622
QStringList metricColumn = s.split(",");
624
// what date range should we use?
625
QDate newFrom = dateRange.from;
626
QDate newTo = dateRange.to;
628
if (newFrom == from && newTo == to) return;
631
// date range changed lets refresh
635
// lets get the metrics
636
QList<SummaryMetrics>results = main->metricDB->getAllMetricsFor(QDateTime(from,QTime(0,0,0)), QDateTime(to, QTime(24,59,59)));
638
// foreach of the metrics get an aggregated value
640
summaryText = QString("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 3.2//EN\">"
648
for (int i=0; i<4; i++) {
655
aggname = tr("Totals");
660
aggname = tr("Averages");
661
list = averageColumn;
665
aggname = tr("Maximums");
666
list = maximumColumn;
669
case 2 : // User defined..
670
aggname = tr("Metrics");
676
summaryText += QString("<p><table width=\"85%\">"
678
"<td align=\"center\" colspan=\"2\">"
681
"</tr>").arg(aggname);
683
foreach(QString metricname, list) {
685
const RideMetric *metric = RideMetricFactory::instance().rideMetric(metricname);
687
QStringList empty; // filter list not used at present
688
QString value = SummaryMetrics::getAggregated(main, metricname, results, empty, false, main->useMetricUnits);
690
// Maximum Max and Average Average looks nasty, remove from name for display
691
QString s = metric ? metric->name().replace(QRegExp(tr("^(Average|Max) ")), "") : "unknown";
693
// don't show units for time values
694
if (metric && (metric->units(main->useMetricUnits) == "seconds" ||
695
metric->units(main->useMetricUnits) == tr("seconds") ||
696
metric->units(main->useMetricUnits) == "")) {
698
summaryText += QString("<tr><td>%1:</td><td align=\"right\"> %2</td>")
703
summaryText += QString("<tr><td>%1(%2):</td><td align=\"right\"> %3</td>")
705
.arg(metric ? metric->units(main->useMetricUnits) : "unknown")
709
summaryText += QString("</tr>" "</table>");
713
// finish off the html document
714
summaryText += QString("</center>"
718
// set webview contents
719
summary->page()->mainFrame()->setHtml(summaryText);