diff --git a/src/DBAccess.h b/src/DBAccess.h index 6a1a26947..14e6a059f 100644 --- a/src/DBAccess.h +++ b/src/DBAccess.h @@ -62,8 +62,15 @@ class DBAccess // Query Records QList getAllMetricsFor(QDateTime start, QDateTime end); + QList getAllMetricsFor(DateRange dr) { + return getAllMetricsFor(QDateTime(dr.from,QTime(0,0,0)), QDateTime(dr.to, QTime(23,59,59))); + } + bool getRide(QString filename, SummaryMetrics &metrics, QColor&color); QList getAllMeasuresFor(QDateTime start, QDateTime end); + QList getAllMeasuresFor(DateRange dr) { + return getAllMeasuresFor(QDateTime(dr.from,QTime(0,0,0)), QDateTime(dr.to, QTime(23,59,59))); + } SummaryMetrics getRideMetrics(QString filename); // for a filename diff --git a/src/GcCalendar.cpp b/src/GcCalendar.cpp index f3ebf24e4..accd7ff3b 100644 --- a/src/GcCalendar.cpp +++ b/src/GcCalendar.cpp @@ -640,7 +640,7 @@ GcCalendar::setSummary() const RideMetric *metric = RideMetricFactory::instance().rideMetric(metricname); - QString value = getAggregated(metricname, results); + QString value = SummaryMetrics::getAggregated(metricname, results, useMetricUnits); // don't show units for time values @@ -670,72 +670,15 @@ GcCalendar::setSummary() // set webview contents summary->page()->mainFrame()->setHtml(summaryText); - } -} - -QString GcCalendar::getAggregated(QString name, QList &results) -{ - // get the metric details, so we can convert etc - const RideMetric *metric = RideMetricFactory::instance().rideMetric(name); - if (!metric) return QString("%1 unknown").arg(name); - - // do we need to convert from metric to imperial? - bool useMetricUnits = (appsettings->value(this, GC_UNIT).toString() == "Metric"); - - // what we will return - double rvalue = 0; - double rcount = 0; // using double to avoid rounding issues with int when dividing - - // loop through and aggregate - foreach (SummaryMetrics rideMetrics, results) { - - // get this value - double value = rideMetrics.getForSymbol(name); - double count = rideMetrics.getForSymbol("workout_time"); // for averaging - + // tell everyone the date range changed + QString name; + if (summarySelect->currentIndex() == 0) name = "Day of " + from.toString("dddd MMMM d"); + else if (summarySelect->currentIndex() == 1) name = QString("Week Commencing %1").arg(from.toString("dddd MMMM d")); + else if (summarySelect->currentIndex() == 2) name = from.toString("MMMM yyyy"); - // check values are bounded, just in case - if (isnan(value) || isinf(value)) value = 0; + emit dateRangeChanged(DateRange(from, to, name)); - // imperial / metric conversion - if (useMetricUnits == false) { - value *= metric->conversion(); - value += metric->conversionSum(); - } - - switch (metric->type()) { - case RideMetric::Total: - rvalue += value; - break; - case RideMetric::Average: - { - // average should be calculated taking into account - // the duration of the ride, otherwise high value but - // short rides will skew the overall average - rvalue += value*count; - rcount += count; - break; - } - case RideMetric::Peak: - { - if (value > rvalue) rvalue = value; - break; - } - } } - - // now compute the average - if (metric->type() == RideMetric::Average) { - if (rcount) rvalue = rvalue / rcount; - } - - - // Format appropriately - QString result; - if (metric->units(useMetricUnits) == "seconds") result = time_to_string(rvalue); - else result = QString("%1").arg(rvalue, 0, 'f', metric->precision()); - - return result; } void diff --git a/src/GcCalendar.h b/src/GcCalendar.h index eef6fe669..c2be5f420 100644 --- a/src/GcCalendar.h +++ b/src/GcCalendar.h @@ -25,6 +25,7 @@ #include #include "MainWindow.h" +#include "TimeUtils.h" #include "GcCalendarModel.h" #include "RideItem.h" #include "RideNavigator.h" @@ -82,9 +83,11 @@ class GcCalendar : public QWidget // not a GcWindow - belongs on sidebar void setSummary(); // set the summary at the bottom // summary metrics aggregator -- refactor later - QString getAggregated(QString name, QList &results); void splitterMoved(int pos, int index); + signals: + void dateRangeChanged(DateRange); + protected: MainWindow *main; RideItem *_ride; diff --git a/src/GcWindowRegistry.cpp b/src/GcWindowRegistry.cpp index e35057c89..674392b66 100644 --- a/src/GcWindowRegistry.cpp +++ b/src/GcWindowRegistry.cpp @@ -67,7 +67,7 @@ GcWindowRegistry GcWindows[] = { { VIEW_HOME, "Performance Manager",GcWindowTypes::PerformanceManager }, { VIEW_HOME, "Collection TreeMap",GcWindowTypes::TreeMap }, { VIEW_HOME, "Weekly Summary",GcWindowTypes::WeeklySummary }, - { VIEW_ANALYSIS, "Summary",GcWindowTypes::RideSummary }, + { VIEW_ANALYSIS, "Activity Summary",GcWindowTypes::RideSummary }, { VIEW_ANALYSIS, "Details",GcWindowTypes::MetadataWindow }, { VIEW_ANALYSIS, "Summary and Details",GcWindowTypes::Summary }, { VIEW_ANALYSIS, "Editor",GcWindowTypes::RideEditor }, @@ -83,6 +83,7 @@ GcWindowRegistry GcWindows[] = { { VIEW_ANALYSIS, "Aerolab Chung Analysis",GcWindowTypes::Aerolab }, { VIEW_DIARY, "Calendar",GcWindowTypes::Diary }, { VIEW_DIARY, "Navigator", GcWindowTypes::ActivityNavigator }, + { VIEW_DIARY, "Summary", GcWindowTypes::DateRangeSummary }, { VIEW_TRAIN, "Telemetry",GcWindowTypes::DialWindow }, { VIEW_TRAIN, "Workout",GcWindowTypes::WorkoutPlot }, { VIEW_TRAIN, "Realtime",GcWindowTypes::RealtimePlot }, @@ -119,7 +120,8 @@ GcWindowRegistry::newGcWindow(GcWinID id, MainWindow *main) //XXX mainWindow wil case GcWindowTypes::PfPv: returning = new PfPvWindow(main); break; case GcWindowTypes::HrPw: returning = new HrPwWindow(main); break; case GcWindowTypes::RideEditor: returning = new RideEditor(main); break; - case GcWindowTypes::RideSummary: returning = new RideSummaryWindow(main); break; + case GcWindowTypes::RideSummary: returning = new RideSummaryWindow(main, true); break; + case GcWindowTypes::DateRangeSummary: returning = new RideSummaryWindow(main, false); break; case GcWindowTypes::Scatter: returning = new ScatterWindow(main, main->home); break; case GcWindowTypes::Summary: returning = new SummaryWindow(main); break; case GcWindowTypes::TreeMap: returning = new TreeMapWindow(main, main->useMetricUnits, main->home); break; diff --git a/src/GcWindowRegistry.h b/src/GcWindowRegistry.h index f352de1eb..7f5e47997 100644 --- a/src/GcWindowRegistry.h +++ b/src/GcWindowRegistry.h @@ -57,6 +57,7 @@ enum gcwinid { RealtimeControls = 29, ActivityNavigator = 30, SpinScanPlot = 31, + DateRangeSummary = 32 }; }; typedef enum GcWindowTypes::gcwinid GcWinID; diff --git a/src/GoldenCheetah.cpp b/src/GoldenCheetah.cpp index 56f6d96a9..7f5b08cd0 100644 --- a/src/GoldenCheetah.cpp +++ b/src/GoldenCheetah.cpp @@ -90,6 +90,7 @@ void GcWindow::setRideItem(RideItem* x) void GcWindow::setDateRange(DateRange dr) { _dr = dr; + emit dateRangeChanged(_dr); } DateRange GcWindow::dateRange() const @@ -154,6 +155,7 @@ GcWindow::GcWindow() qRegisterMetaType("ride"); qRegisterMetaType("type"); qRegisterMetaType("color"); + qRegisterMetaType("dateRange"); setControls(NULL); setRideItem(NULL); setTitle(""); @@ -185,6 +187,7 @@ GcWindow::GcWindow(QWidget *parent) : QFrame(parent), dragState(None) { qRegisterMetaType("ride"); qRegisterMetaType("type"); qRegisterMetaType("color"); + qRegisterMetaType("dateRange"); setParent(parent); setControls(NULL); setRideItem(NULL); diff --git a/src/GoldenCheetah.h b/src/GoldenCheetah.h index f6e0c25be..409f00c12 100644 --- a/src/GoldenCheetah.h +++ b/src/GoldenCheetah.h @@ -23,6 +23,7 @@ #define G_OBJECT Q_PROPERTY(QString instanceName READ instanceName WRITE setInstanceName) #define setInstanceName(x) setProperty("instanceName", x) #define myRideItem property("ride").value() +#define myDateRange property("dateRange").value() #include #include diff --git a/src/HomeWindow.cpp b/src/HomeWindow.cpp index 9c09039a0..37282868b 100644 --- a/src/HomeWindow.cpp +++ b/src/HomeWindow.cpp @@ -202,6 +202,7 @@ HomeWindow::HomeWindow(MainWindow *mainWindow, QString name, QString /* windowti styleChanged(2); connect(this, SIGNAL(rideItemChanged(RideItem*)), this, SLOT(rideSelected())); + connect(this, SIGNAL(dateRangeChanged(DateRange)), this, SLOT(dateRangeChanged(DateRange))); connect(mainWindow, SIGNAL(configChanged()), this, SLOT(configChanged())); connect(tabbed, SIGNAL(currentChanged(int)), this, SLOT(tabSelected(int))); connect(tabbed, SIGNAL(tabCloseRequested(int)), this, SLOT(removeChart(int))); @@ -359,6 +360,27 @@ HomeWindow::rideSelected() } } +void +HomeWindow::dateRangeChanged(DateRange dr) +{ + if (amVisible()) { + + for (int i=0; i < charts.count(); i++) { + + // show if its not a tab + if (currentStyle) charts[i]->show(); // keep tabs hidden, show the rest + + // if we are tabbed AND its the current tab then mimic tabselected + // to force the tabwidget to refresh AND set its ride property (see below) + //otherwise just go ahead and notify it of a new ride + if (!currentStyle && charts.count() && i==tabbed->currentIndex()) + tabSelected(tabbed->currentIndex()); + else + charts[i]->setProperty("dateRange", property("dateRange")); + } + } +} + void HomeWindow::tabSelected(int index) { @@ -367,6 +389,7 @@ HomeWindow::tabSelected(int index) if (index >= 0) { charts[index]->show(); charts[index]->setProperty("ride", property("ride")); + charts[index]->setProperty("dateRange", property("dateRange")); controlStack->setCurrentIndex(index); titleEdit->setText(charts[index]->property("title").toString()); } @@ -430,6 +453,7 @@ HomeWindow::styleChanged(int id) tabbed->addTab(charts[i], charts[i]->property("title").toString()); charts[i]->setContentsMargins(0,25,0,0); charts[i]->setResizable(false); // we need to show on tab selection! + charts[i]->setProperty("dateRange", property("dateRange")); charts[i]->hide(); // we need to show on tab selection! break; case 1 : // they are lists in a GridLayout @@ -437,6 +461,7 @@ HomeWindow::styleChanged(int id) charts[i]->setContentsMargins(0,25,0,0); charts[i]->setResizable(false); // we need to show on tab selection! charts[i]->show(); + charts[i]->setProperty("dateRange", property("dateRange")); charts[i]->setProperty("ride", property("ride")); break; case 2 : // thet are in a FlowLayout @@ -444,6 +469,7 @@ HomeWindow::styleChanged(int id) charts[i]->setContentsMargins(0,15,0,0); charts[i]->setResizable(true); // we need to show on tab selection! charts[i]->show(); + charts[i]->setProperty("dateRange", property("dateRange")); charts[i]->setProperty("ride", property("ride")); default: break; @@ -571,6 +597,7 @@ HomeWindow::addChart(GcWindow* newone) RideItem *notconst = (RideItem*)mainWindow->currentRideItem(); newone->setProperty("ride", QVariant::fromValue(notconst)); + newone->setProperty("dateRange", property("dateRange")); // add to tabs switch (currentStyle) { @@ -1219,6 +1246,7 @@ GcWindowDialog::GcWindowDialog(GcWinID type, MainWindow *mainWindow) : mainWindo RideItem *notconst = (RideItem*)mainWindow->currentRideItem(); win->setProperty("ride", QVariant::fromValue(notconst)); + win->setProperty("dateRange", property("dateRange")); layout->setStretch(0, 100); layout->setStretch(1, 50); diff --git a/src/HomeWindow.h b/src/HomeWindow.h index 937fb61f2..2e5f2a09c 100644 --- a/src/HomeWindow.h +++ b/src/HomeWindow.h @@ -55,6 +55,7 @@ class HomeWindow : public GcWindow // GC signals void rideSelected(); + void dateRangeChanged(DateRange); void configChanged(); // QT Widget events and signals diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index c0fa14c9f..1c30ebdc8 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -517,10 +517,6 @@ MainWindow::MainWindow(const QDir &home) : chartTool = new GcWindowTool(this); #endif - // RIGHT SIDEBAR - gcCalendar = new GcCalendar(this); - //gcCalendar->setStyleSheet("background: #B3B4BA;"); - //gcCalendar->setStyleSheet("background: white;"); // TOOLBOX toolBox = new QToolBox(this); @@ -584,6 +580,10 @@ MainWindow::MainWindow(const QDir &home) : // POPULATE TOOLBOX + // do controllers after home windows -- they need their first signals caught + gcCalendar = new GcCalendar(this); + connect(gcCalendar, SIGNAL(dateRangeChanged(DateRange)), this, SLOT(dateRangeChanged(DateRange))); + toolBox->addItem(intervalSplitter, QIcon(":images/activity.png"), "Activity History"); toolBox->addItem(gcCalendar, QIcon(":images/toolbar/main/diary.png"), "Calendar"); toolBox->addItem(trainTool->controls(), QIcon(":images/library.png"), "Workout Library"); @@ -1037,6 +1037,15 @@ MainWindow::rideTreeWidgetSelectionChanged() } } +void +MainWindow::dateRangeChanged(DateRange dr) +{ + // we got signalled date range changed, tell the current view + // when we have multiple sidebars that change date we need to connect + // them up individually.... i.e. LTM.... + diaryWindow->setProperty("dateRange", QVariant::fromValue(dr)); +} + void MainWindow::enableSaveButton() { @@ -1326,6 +1335,7 @@ MainWindow::selectDiary() analButtons->hide(); trainTool->getToolbarButtons()->hide(); toolBox->setCurrentIndex(1); + gcCalendar->refresh(); // get that signal with the date range... setStyle(); } diff --git a/src/MainWindow.h b/src/MainWindow.h index 708d0fd15..780387289 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -323,6 +323,8 @@ class MainWindow : public QMainWindow void toggleFullScreen(); #endif + void dateRangeChanged(DateRange); + protected: static QString notesFileName(QString rideFileName); diff --git a/src/MetricAggregator.cpp b/src/MetricAggregator.cpp index a9b9f2432..84a2a4f86 100644 --- a/src/MetricAggregator.cpp +++ b/src/MetricAggregator.cpp @@ -306,6 +306,12 @@ MetricAggregator::writeAsCSV(QString filename) file.close(); } +QList +MetricAggregator::getAllMetricsFor(DateRange dr) +{ + return getAllMetricsFor(QDateTime(dr.from, QTime(0,0,0)), QDateTime(dr.to, QTime(23,59,59))); +} + QList MetricAggregator::getAllMetricsFor(QDateTime start, QDateTime end) { @@ -349,6 +355,12 @@ MetricAggregator::getAllMetricsFor(QString filename) return results; } +QList +MetricAggregator::getAllMeasuresFor(DateRange dr) +{ + return getAllMeasuresFor(QDateTime(dr.from, QTime(0,0,0)), QDateTime(dr.to, QTime(23,59,59))); +} + QList MetricAggregator::getAllMeasuresFor(QDateTime start, QDateTime end) { diff --git a/src/MetricAggregator.h b/src/MetricAggregator.h index fc497d808..f0d6112a1 100644 --- a/src/MetricAggregator.h +++ b/src/MetricAggregator.h @@ -48,7 +48,9 @@ class MetricAggregator : public QObject DBAccess *db() { return dbaccess; } SummaryMetrics getAllMetricsFor(QString filename); // for a single ride QList getAllMetricsFor(QDateTime start, QDateTime end); + QList getAllMetricsFor(DateRange); QList getAllMeasuresFor(QDateTime start, QDateTime end); + QList getAllMeasuresFor(DateRange); SummaryMetrics getRideMetrics(QString filename); void writeAsCSV(QString filename); // export all... diff --git a/src/RideSummaryWindow.cpp b/src/RideSummaryWindow.cpp index c5172c6ca..0593712e3 100644 --- a/src/RideSummaryWindow.cpp +++ b/src/RideSummaryWindow.cpp @@ -32,8 +32,8 @@ #include #include -RideSummaryWindow::RideSummaryWindow(MainWindow *mainWindow) : - GcWindow(mainWindow), mainWindow(mainWindow) +RideSummaryWindow::RideSummaryWindow(MainWindow *mainWindow, bool ridesummary) : + GcWindow(mainWindow), mainWindow(mainWindow), ridesummary(ridesummary) { setInstanceName("Ride Summary Window"); setControls(NULL); @@ -54,9 +54,15 @@ RideSummaryWindow::RideSummaryWindow(MainWindow *mainWindow) : vlayout->addWidget(rideSummary); - connect(this, SIGNAL(rideItemChanged(RideItem*)), this, SLOT(rideItemChanged())); - connect(mainWindow, SIGNAL(zonesChanged()), this, SLOT(refresh())); - connect(mainWindow, SIGNAL(intervalsChanged()), this, SLOT(refresh())); + if (ridesummary) { + connect(this, SIGNAL(rideItemChanged(RideItem*)), this, SLOT(rideItemChanged())); + connect(mainWindow, SIGNAL(zonesChanged()), this, SLOT(refresh())); + connect(mainWindow, SIGNAL(intervalsChanged()), this, SLOT(refresh())); + } else { + connect(this, SIGNAL(dateRangeChanged(DateRange)), this, SLOT(dateRangeChanged(DateRange))); + connect(mainWindow, SIGNAL(rideAdded(RideItem*)), this, SLOT(refresh())); + connect(mainWindow, SIGNAL(rideDeleted(RideItem*)), this, SLOT(refresh())); + } setLayout(vlayout); } @@ -93,15 +99,27 @@ RideSummaryWindow::refresh() { // XXX: activeTab is never equaly to RideSummaryWindow right now because // it's wrapped in the summarySplitter in MainWindow. - if (!myRideItem) { + if (ridesummary && !myRideItem) { rideSummary->page()->mainFrame()->setHtml(""); return; } - RideItem *rideItem = myRideItem; - setSubTitle(rideItem->dateTime.toString(tr("dddd MMMM d, yyyy, h:mm AP"))); + + if (ridesummary) { + RideItem *rideItem = myRideItem; + setSubTitle(rideItem->dateTime.toString(tr("dddd MMMM d, yyyy, h:mm AP"))); + } else { + + if (myDateRange.name != "") setSubTitle(myDateRange.name); + else { + setSubTitle(myDateRange.from.toString("dddd MMMM d") + + " - " + + myDateRange.to.toString("dddd MMMM d")); + } + } rideSummary->page()->mainFrame()->setHtml(htmlSummary()); } + QString RideSummaryWindow::htmlSummary() const { @@ -109,9 +127,11 @@ RideSummaryWindow::htmlSummary() const RideItem *rideItem = myRideItem; RideFile *ride = rideItem->ride(); + QVariant unit = appsettings->value(this, GC_UNIT); + bool useMetricUnits = (unit.toString() == "Metric"); - // ridefile read errors? - if (!ride) { + // ride summary and there were ridefile read errors? + if (ridesummary && !ride) { summary = tr("

Couldn't read file \""); summary += rideItem->fileName + "\":"; QListIterator i(rideItem->errors()); @@ -120,58 +140,48 @@ RideSummaryWindow::htmlSummary() const return summary; } - summary = ("

" + tr("Device Type: ") + ride->deviceType() + "

"); + // always centered + summary = "
"; - QVariant unit = appsettings->value(this, GC_UNIT); + // device summary for ride summary, otherwise how many activities? + if (ridesummary) summary += ("

" + tr("Device Type: ") + ride->deviceType() + "

"); + else summary += ("

" + + QString("%1").arg(data.count()) + (data.count() == 1 ? tr(" activity") : tr(" activities")) + + "

"); - summary += "

"; - - bool metricUnits = (unit.toString() == "Metric"); - - QStringList columnNames = QStringList() << "Totals" <<"Averages" <<"Maximums" <<"Metrics*"; - QStringList totalColumn = QStringList() + // All the metrics we will display + static const QStringList columnNames = QStringList() << "Totals" <<"Averages" <<"Maximums" <<"Metrics*"; + static const QStringList totalColumn = QStringList() << "workout_time" << "time_riding" << "total_distance" << "total_work" << "elevation_gain"; - QStringList averageColumn = QStringList() + static QStringList averageColumn = QStringList() // not const as modified below.. << "average_speed" << "average_power" << "average_hr" << "average_cad"; - // show temp if it is available - if (ride->areDataPresent()->temp || ride->getTag("Temperature", "-") != "-") averageColumn << "average_temp"; - - QStringList maximumColumn = QStringList() + static QStringList maximumColumn = QStringList() // not const as modified below.. << "max_speed" << "max_power" << "max_heartrate" << "max_cadence"; - // show temp if it is available - if (ride->areDataPresent()->temp || ride->getTag("Temperature", "-") != "-") maximumColumn << "max_temp"; + // show average and max temp if it is available (in ride summary mode) + if (ridesummary && (ride->areDataPresent()->temp || ride->getTag("Temperature", "-") != "-")) { + averageColumn << "average_temp"; + maximumColumn << "max_temp"; + } + // users determine the metrics to display QString s = appsettings->value(this, GC_SETTINGS_SUMMARY_METRICS, GC_SETTINGS_SUMMARY_METRICS_DEFAULT).toString(); - - // in case they were set tand then unset if (s == "") s = GC_SETTINGS_SUMMARY_METRICS_DEFAULT; QStringList metricColumn = s.split(","); - /* ORIGINAL HARDCODED (AND NOW DEFAULT METRICS) - QStringList metricColumn = QStringList() - << "skiba_xpower" - << "skiba_relative_intensity" - << "skiba_bike_score" - << "daniels_points" - << "daniels_equivalent_power" - << "trimp_points" - << "aerobic_decoupling"; - */ - - QStringList timeInZones = QStringList() + static const QStringList timeInZones = QStringList() << "time_in_zone_L1" << "time_in_zone_L2" << "time_in_zone_L3" @@ -183,7 +193,7 @@ RideSummaryWindow::htmlSummary() const << "time_in_zone_L9" << "time_in_zone_L10"; - QStringList timeInZonesHR = QStringList() + static const QStringList timeInZonesHR = QStringList() << "time_in_zone_H1" << "time_in_zone_H2" << "time_in_zone_H3" @@ -195,35 +205,41 @@ RideSummaryWindow::htmlSummary() const // Use pre-computed and saved metric values if the ride has not // been edited. Otherwise we need to re-compute every time. + // this is only for ride summary, when showing for a date range + // we already have a summary metrics array SummaryMetrics metrics; RideMetricFactory &factory = RideMetricFactory::instance(); - if (rideItem->isDirty()) { - // make a list if the metrics we want computed - // instead of calculating them all, just do the - // ones we display - QStringList worklist; - worklist += totalColumn; - worklist += averageColumn; - worklist += maximumColumn; - worklist += metricColumn; - worklist += timeInZones; - worklist += timeInZonesHR; - // go calculate them then... - QHash computed = RideMetric::computeMetrics(mainWindow, ride, mainWindow->zones(), mainWindow->hrZones(), worklist); - for(int i = 0; i < worklist.count(); ++i) { - if (worklist[i] != "") { - RideMetricPtr m = computed.value(worklist[i]); - if (m) metrics.setForSymbol(worklist[i], m->value(true)); - else metrics.setForSymbol(worklist[i], 0.00); + if (ridesummary) { + if (rideItem->isDirty()) { + // make a list if the metrics we want computed + // instead of calculating them all, just do the + // ones we display + QStringList worklist; + worklist += totalColumn; + worklist += averageColumn; + worklist += maximumColumn; + worklist += metricColumn; + worklist += timeInZones; + worklist += timeInZonesHR; + + // go calculate them then... + QHash computed = RideMetric::computeMetrics(mainWindow, ride, mainWindow->zones(), mainWindow->hrZones(), worklist); + for(int i = 0; i < worklist.count(); ++i) { + if (worklist[i] != "") { + RideMetricPtr m = computed.value(worklist[i]); + if (m) metrics.setForSymbol(worklist[i], m->value(true)); + else metrics.setForSymbol(worklist[i], 0.00); + } } - } - } else { + } else { - // just use the metricDB versions, nice 'n fast - metrics = mainWindow->metricDB->getRideMetrics(rideItem->fileName); + // just use the metricDB versions, nice 'n fast + metrics = mainWindow->metricDB->getRideMetrics(rideItem->fileName); + } } + // // 3 top columns - total, average, maximums and metrics for entire ride // @@ -257,19 +273,28 @@ RideSummaryWindow::htmlSummary() const s = s.arg(m->name().replace(QRegExp(tr("^(Average|Max) ")), "")); // Add units (if needed) and value (with right precision) - if (m->units(metricUnits) == "seconds") { + if (m->units(useMetricUnits) == "seconds") { s = s.arg(""); // no units - s = s.arg(time_to_string(metrics.getForSymbol(symbol))); + + // get the value - from metrics or from data array + if (ridesummary) s = s.arg(time_to_string(metrics.getForSymbol(symbol))); + else s = s.arg(SummaryMetrics::getAggregated(symbol, data, useMetricUnits)); + } else { - if (m->units(metricUnits) != "") s = s.arg(" (" + m->units(metricUnits) + ")"); + if (m->units(useMetricUnits) != "") s = s.arg(" (" + m->units(useMetricUnits) + ")"); else s = s.arg(""); // temperature is a special case, if it is not present fall back to metadata tag // if that is not present then just display '-' if ((symbol == "average_temp" || symbol == "max_temp") && metrics.getForSymbol(symbol) == RideFile::noTemp) s = s.arg(ride->getTag("Temperature", "-")); - else - s = s.arg(metrics.getForSymbol(symbol) * (metricUnits ? 1 : m->conversion()) + (metricUnits ? 0 : m->conversionSum()), 0, 'f', m->precision()); + else { + + // get the value - from metrics or from data array + if (ridesummary) s = s.arg(metrics.getForSymbol(symbol) * (useMetricUnits ? 1 : m->conversion()) + + (useMetricUnits ? 0 : m->conversionSum()), 0, 'f', m->precision()); + else s = s.arg(SummaryMetrics::getAggregated(symbol, data, useMetricUnits)); + } } summary += s; @@ -283,10 +308,14 @@ RideSummaryWindow::htmlSummary() const // if (rideItem->numZones() > 0) { QVector time_in_zone(rideItem->numZones()); - for (int i = 0; i < rideItem->numZones(); ++i) - time_in_zone[i] = metrics.getForSymbol(timeInZones[i]); + for (int i = 0; i < rideItem->numZones(); ++i) { + + // if using metrics or data + if (ridesummary) time_in_zone[i] = metrics.getForSymbol(timeInZones[i]); + else time_in_zone[i] = SummaryMetrics::getAggregated(timeInZones[i], data, useMetricUnits, true).toDouble(); + } summary += tr("

Power Zones

"); - summary += mainWindow->zones()->summarize(rideItem->zoneRange(), time_in_zone); + summary += mainWindow->zones()->summarize(rideItem->zoneRange(), time_in_zone); //XXX aggregating? } // @@ -294,84 +323,95 @@ RideSummaryWindow::htmlSummary() const // if (rideItem->numHrZones() > 0) { QVector time_in_zone(rideItem->numHrZones()); - for (int i = 0; i < rideItem->numHrZones(); ++i) - time_in_zone[i] = metrics.getForSymbol(timeInZonesHR[i]); + for (int i = 0; i < rideItem->numHrZones(); ++i) { + + // if using metrics or data + if (ridesummary) time_in_zone[i] = metrics.getForSymbol(timeInZonesHR[i]); + else time_in_zone[i] = SummaryMetrics::getAggregated(timeInZonesHR[i], data, useMetricUnits, true).toDouble(); + } + summary += tr("

Heart Rate Zones

"); - summary += mainWindow->hrZones()->summarize(rideItem->hrZoneRange(), time_in_zone); + summary += mainWindow->hrZones()->summarize(rideItem->hrZoneRange(), time_in_zone); //XXX aggregating } - // - // Interval Summary (recalculated on every refresh since they are not cached at present) - // - if (ride->intervals().size() > 0) { - bool firstRow = true; - QString s; - if (appsettings->contains(GC_SETTINGS_INTERVAL_METRICS)) - s = appsettings->value(this, GC_SETTINGS_INTERVAL_METRICS).toString(); - else - s = GC_SETTINGS_INTERVAL_METRICS_DEFAULT; - QStringList intervalMetrics = s.split(","); - summary += "

"+tr("Intervals")+"

\n

\n"; - summary += "intervals()) { - RideFile f(ride->startTime(), ride->recIntSecs()); - for (int i = ride->intervalBegin(interval); i < ride->dataPoints().size(); ++i) { - const RideFilePoint *p = ride->dataPoints()[i]; - if (p->secs >= interval.stop) - break; - f.appendPoint(p->secs, p->cad, p->hr, p->km, p->kph, p->nm, - p->watts, p->alt, p->lon, p->lat, p->headwind, - p->slope, p->temp, p->lrbalance, 0); - } - if (f.dataPoints().size() == 0) { - // Interval empty, do not compute any metrics - continue; - } + // Only get interval summary for a ride summary + if (ridesummary) { - QHash metrics = - RideMetric::computeMetrics(mainWindow, &f, mainWindow->zones(), mainWindow->hrZones(), intervalMetrics); - if (firstRow) { - summary += ""; - summary += ""; + // + // Interval Summary (recalculated on every refresh since they are not cached at present) + // + if (ride->intervals().size() > 0) { + bool firstRow = true; + QString s; + if (appsettings->contains(GC_SETTINGS_INTERVAL_METRICS)) + s = appsettings->value(this, GC_SETTINGS_INTERVAL_METRICS).toString(); + else + s = GC_SETTINGS_INTERVAL_METRICS_DEFAULT; + QStringList intervalMetrics = s.split(","); + summary += "

"+tr("Intervals")+"

\n

\n"; + summary += "

Interval Name
intervals()) { + RideFile f(ride->startTime(), ride->recIntSecs()); + for (int i = ride->intervalBegin(interval); i < ride->dataPoints().size(); ++i) { + const RideFilePoint *p = ride->dataPoints()[i]; + if (p->secs >= interval.stop) + break; + f.appendPoint(p->secs, p->cad, p->hr, p->km, p->kph, p->nm, + p->watts, p->alt, p->lon, p->lat, p->headwind, + p->slope, p->temp, p->lrbalance, 0); + } + if (f.dataPoints().size() == 0) { + // Interval empty, do not compute any metrics + continue; + } + + QHash metrics = + RideMetric::computeMetrics(mainWindow, &f, mainWindow->zones(), mainWindow->hrZones(), intervalMetrics); + if (firstRow) { + summary += ""; + summary += ""; + foreach (QString symbol, intervalMetrics) { + RideMetricPtr m = metrics.value(symbol); + if (!m) continue; + summary += ""; + } + summary += ""; + firstRow = false; + } + if (even) + summary += ""; + else { + QColor color = QApplication::palette().alternateBase().color(); + color = QColor::fromHsv(color.hue(), color.saturation() * 2, color.value()); + summary += ""; + } + even = !even; + summary += ""; foreach (QString symbol, intervalMetrics) { RideMetricPtr m = metrics.value(symbol); if (!m) continue; - summary += ""; + QString s(""); + if (m->units(useMetricUnits) == "seconds") + summary += s.arg(time_to_string(m->value(useMetricUnits))); + else + summary += s.arg(m->value(useMetricUnits), 0, 'f', m->precision()); } summary += ""; - firstRow = false; } - if (even) - summary += ""; - else { - QColor color = QApplication::palette().alternateBase().color(); - color = QColor::fromHsv(color.hue(), color.saturation() * 2, color.value()); - summary += ""; - } - even = !even; - summary += ""; - foreach (QString symbol, intervalMetrics) { - RideMetricPtr m = metrics.value(symbol); - if (!m) continue; - QString s(""); - if (m->units(metricUnits) == "seconds") - summary += s.arg(time_to_string(m->value(metricUnits))); - else - summary += s.arg(m->value(metricUnits), 0, 'f', m->precision()); - } - summary += ""; + summary += "
Interval Name" + m->name(); + if (m->units(useMetricUnits) == "seconds") + ; // don't do anything + else if (m->units(useMetricUnits).size() > 0) + summary += " (" + m->units(useMetricUnits) + ")"; + summary += "
" + interval.name + "" + m->name(); - if (m->units(metricUnits) == "seconds") - ; // don't do anything - else if (m->units(metricUnits).size() > 0) - summary += " (" + m->units(metricUnits) + ")"; - summary += "%1
" + interval.name + "%1
"; } - summary += ""; } - if (!rideItem->errors().empty()) { + // sumarise errors reading file if it was a ride summary + if (ridesummary && !rideItem->errors().empty()) { + summary += tr("

Errors reading file:

    "); QStringListIterator i(rideItem->errors()); while(i.hasNext()) @@ -389,3 +429,14 @@ RideSummaryWindow::htmlSummary() const return summary; } +void RideSummaryWindow::dateRangeChanged(DateRange dr) +{ + if (!amVisible()) return; + + // range didnt change ignore it... + if (dr.from == current.from && dr.to == current.to) return; + else current = dr; + + data = mainWindow->metricDB->getAllMetricsFor(myDateRange); + refresh(); +} diff --git a/src/RideSummaryWindow.h b/src/RideSummaryWindow.h index ea3c005a6..1c34e9e8d 100644 --- a/src/RideSummaryWindow.h +++ b/src/RideSummaryWindow.h @@ -25,6 +25,8 @@ #include #include +#include "SummaryMetrics.h" + class RideSummaryWindow : public GcWindow { @@ -34,12 +36,14 @@ class RideSummaryWindow : public GcWindow public: - RideSummaryWindow(MainWindow *parent); + // two modes - summarise ride or summarise date range + RideSummaryWindow(MainWindow *parent, bool ridesummary = true); protected slots: void refresh(); void rideSelected(); + void dateRangeChanged(DateRange); void rideItemChanged(); void metadataChanged(); @@ -51,6 +55,10 @@ class RideSummaryWindow : public GcWindow QWebView *rideSummary; RideItem *_connected; + bool ridesummary; // do we summarise ride or daterange? + + QList data; // when in date range mode + DateRange current; }; #endif // _GC_RideSummaryWindow_h diff --git a/src/SummaryMetrics.cpp b/src/SummaryMetrics.cpp index 8ac9c7379..d30973ee6 100644 --- a/src/SummaryMetrics.cpp +++ b/src/SummaryMetrics.cpp @@ -99,3 +99,68 @@ SummaryMetrics::getUnitsForSymbol(QString symbol, bool UseMetric) const if (m) return m->units(UseMetric); else return QString("units"); } + +QString SummaryMetrics::getAggregated(QString name, const QList &results, bool useMetricUnits, bool nofmt) +{ + // get the metric details, so we can convert etc + const RideMetric *metric = RideMetricFactory::instance().rideMetric(name); + if (!metric) return QString("%1 unknown").arg(name); + + // what we will return + double rvalue = 0; + double rcount = 0; // using double to avoid rounding issues with int when dividing + + // loop through and aggregate + foreach (SummaryMetrics rideMetrics, results) { + + // get this value + double value = rideMetrics.getForSymbol(name); + double count = rideMetrics.getForSymbol("workout_time"); // for averaging + + + // check values are bounded, just in case + if (isnan(value) || isinf(value)) value = 0; + + // imperial / metric conversion + if (useMetricUnits == false) { + value *= metric->conversion(); + value += metric->conversionSum(); + } + + switch (metric->type()) { + case RideMetric::Total: + rvalue += value; + break; + case RideMetric::Average: + { + // average should be calculated taking into account + // the duration of the ride, otherwise high value but + // short rides will skew the overall average + rvalue += value*count; + rcount += count; + break; + } + case RideMetric::Peak: + { + if (value > rvalue) rvalue = value; + break; + } + } + } + + // now compute the average + if (metric->type() == RideMetric::Average) { + if (rcount) rvalue = rvalue / rcount; + } + + + // Format appropriately + QString result; + if (metric->units(useMetricUnits) == "seconds") { + if (nofmt) result = QString("%1").arg(rvalue); + else result = time_to_string(rvalue); + + } else result = QString("%1").arg(rvalue, 0, 'f', metric->precision()); + + return result; +} diff --git a/src/SummaryMetrics.h b/src/SummaryMetrics.h index f81e3d12f..50007f2b1 100644 --- a/src/SummaryMetrics.h +++ b/src/SummaryMetrics.h @@ -61,6 +61,9 @@ class SummaryMetrics // get unit string to use for this symbol QString getUnitsForSymbol(QString symbol, bool UseMetric) const; + // when passed a list of summary metrics and a name return aggregated value as a string + static QString getAggregated(QString name, const QList &results, bool useMetricUnits, bool nofmt = false); + QMap &values() { return value; } QMap &texts() { return text; } diff --git a/src/TimeUtils.cpp b/src/TimeUtils.cpp index 7590103de..6e1371dda 100644 --- a/src/TimeUtils.cpp +++ b/src/TimeUtils.cpp @@ -97,22 +97,25 @@ QDateTime convertToLocalTime(QString timestamp) } } -DateRange::DateRange(QDate from, QDate to) : QObject() +DateRange::DateRange(QDate from, QDate to, QString name) : QObject() { this->from=from; this->to=to; + this->name=name; } DateRange::DateRange(const DateRange &other) : QObject() { from=other.from; to=other.to; + name=other.name; } DateRange& DateRange::operator=(const DateRange &other) { from=other.from; to=other.to; + name=other.name; emit changed(from, to); return *this; diff --git a/src/TimeUtils.h b/src/TimeUtils.h index 7f6a061f7..2d9f4b492 100644 --- a/src/TimeUtils.h +++ b/src/TimeUtils.h @@ -38,9 +38,10 @@ class DateRange : QObject public: DateRange(const DateRange& other); - DateRange(QDate from = QDate(), QDate to = QDate()); + DateRange(QDate from = QDate(), QDate to = QDate(), QString name =""); DateRange& operator=(const DateRange &); QDate from, to; + QString name; signals: void changed(QDate from, QDate to);