diff --git a/src/LTMPopup.cpp b/src/LTMPopup.cpp index 0c052c174..ad8a8108d 100644 --- a/src/LTMPopup.cpp +++ b/src/LTMPopup.cpp @@ -19,6 +19,8 @@ #include "LTMPopup.h" #include "MainWindow.h" #include "Athlete.h" +#include "Specification.h" +#include "RideCache.h" LTMPopup::LTMPopup(Context *context) : QWidget(context->mainWindow), context(context) { @@ -108,7 +110,7 @@ LTMPopup::setTitle(QString s) } void -LTMPopup::setData(QListdata, const RideMetric *metric, QString title) +LTMPopup::setData(Specification spec, const RideMetric *metric, QString title) { // create the ride list int count = 0; @@ -129,15 +131,15 @@ LTMPopup::setData(QListdata, const RideMetric *metric, QString t rides->setHorizontalHeaderItem(1,h); // now add rows to the table for each entry - foreach(SummaryMetrics x, data) { + foreach(RideItem *item, context->athlete->rideCache->rides()) { - QDateTime rideDate = x.getRideDate(); + if (!spec.pass(item)) continue; - // we'll select it for summary aggregation - selected << x; + // what rides were selected ? + selected << item->fileName; // date/time - QTableWidgetItem *t = new QTableWidgetItem(rideDate.toString(tr("ddd, dd MMM yy hh:mmA"))); + QTableWidgetItem *t = new QTableWidgetItem(item->dateTime.toString(tr("ddd, dd MMM yy hh:mmA"))); t->setFlags(t->flags() & (~Qt::ItemIsEditable)); t->setTextAlignment(Qt::AlignHCenter); rides->setRowCount(count+1); @@ -145,13 +147,15 @@ LTMPopup::setData(QListdata, const RideMetric *metric, QString t rides->setRowHeight(count, 14); // metrics - QString value = x.getStringForSymbol(metric->symbol(), context->athlete->useMetricUnits); + double d = item->getForSymbol(metric->symbol()); + const_cast(metric)->setValue(d); + QString value = metric->toString(context->athlete->useMetricUnits); + h = new QTableWidgetItem(value,QTableWidgetItem::Type); h->setFlags(t->flags() & (~Qt::ItemIsEditable)); h->setTextAlignment(Qt::AlignHCenter); rides->setItem(count, 1, h); - count++; } @@ -184,8 +188,9 @@ LTMPopup::setData(QListdata, const RideMetric *metric, QString t } void -LTMPopup::setData(LTMSettings &settings, QDate start, QDate end, QTime time) +LTMPopup::setData(LTMSettings &, QDate, QDate, QTime) { +#if 0 // DISABLED IN LTM WINDOW UNTIL IT IS MIGRATED TO RIDECACHE // set the title QString _title; switch (settings.groupBy) { @@ -342,12 +347,12 @@ LTMPopup::setData(LTMSettings &settings, QDate start, QDate end, QTime time) if (nonRideMetrics) _title += QString(tr(" / non ride-related metrics skipped")); setTitle(_title); - rideSelected(); +#endif } QString -LTMPopup::setSummaryHTML(SummaryMetrics &results) +LTMPopup::setSummaryHTML(RideItem *item) { // where we construct the text QString summaryText(""); @@ -435,12 +440,13 @@ LTMPopup::setSummaryHTML(SummaryMetrics &results) const RideMetric *metric = RideMetricFactory::instance().rideMetric(metricname); - QStringList empty; // filter list not used at present - QList resultList1; - resultList1 << results; - - // use getAggregated even if it's only 1 file to have consistent value treatment - QString value = SummaryMetrics::getAggregated(context, metricname, resultList1, empty, false, context->athlete->useMetricUnits); + // use getAggregate even if it's only 1 file to have consistent value treatment + double d = item->getForSymbol(metricname, true); + QString value; + if (metric) { + const_cast(metric)->setValue(d); + value = metric->toString(context->athlete->useMetricUnits); + } // Maximum Max and Average Average looks nasty, remove from name for display QString s = metric ? metric->name().replace(QRegExp(tr("^(Average|Max) ")), "") : "unknown"; @@ -475,21 +481,30 @@ LTMPopup::setSummaryHTML(SummaryMetrics &results) return summaryText; - } void LTMPopup::rideSelected() { + // which ride is selected int index = 0; - foreach (QTableWidgetItem *item, rides->selectedItems()) - index = item->row(); + if (rides->selectedItems().count()) + index = rides->selectedItems().first()->row(); + // do we have any rides and is the index within bounds if (selected.count() > index) { - // update summary - metrics->setText(setSummaryHTML(selected[index])); - notes->setText(selected[index].getText("Notes", "")); + RideItem *have = context->athlete->rideCache->getRide(selected[index]); + + if (have) { + + // update summary + metrics->setText(""); //! stop crash (?) + metrics->setText(setSummaryHTML(have)); + + notes->setText(""); //! stop crash (?) + notes->setText(have->getText("Notes", "")); + } } resizeEvent(NULL); } diff --git a/src/LTMPopup.h b/src/LTMPopup.h index d2c91eaf3..8bb1f090f 100644 --- a/src/LTMPopup.h +++ b/src/LTMPopup.h @@ -37,6 +37,7 @@ #include #include +class Specification; class LTMPopup : public QWidget { Q_OBJECT @@ -52,7 +53,7 @@ class LTMPopup : public QWidget void setData(LTMSettings &settings, QDate start, QDate end, QTime time); // when called from a TreeMap chart - void setData(QListdata, const RideMetric *metric, QString title); + void setData(Specification spec, const RideMetric *metric, QString title); signals: @@ -69,11 +70,9 @@ class LTMPopup : public QWidget QTextEdit *metrics; QTextEdit *notes; - QList selected; - - // builds HTML text for the selected ride - QString setSummaryHTML(SummaryMetrics &results); - + // builds HTML text for the selected ride(s) + QString setSummaryHTML(RideItem*); + QStringList selected; // filenames }; // some geometry stuff to make Mac displays nice diff --git a/src/RideCache.cpp b/src/RideCache.cpp index 8fd322154..5265087f1 100644 --- a/src/RideCache.cpp +++ b/src/RideCache.cpp @@ -23,6 +23,7 @@ #include "Context.h" #include "Athlete.h" #include "RideFileCache.h" +#include "Specification.h" #include "Route.h" #include "RouteWindow.h" @@ -325,6 +326,92 @@ RideCache::refresh() } } +QString +RideCache::getAggregate(QString name, Specification spec, 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 (RideItem *item, rides()) { + + // skip filtered rides + if (!spec.pass(item)) continue; + + // get this value + double value = item->getForSymbol(name); + double count = item->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(); + } + + // do we aggregate zero values ? + bool aggZero = metric->aggregateZero(); + + // set aggZero to false and value to zero if is temperature and -255 + if (metric->symbol() == "average_temp" && value == RideFile::NoTemp) { + value = 0; + aggZero = false; + } + + 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 + if (value || aggZero) { + rvalue += value*count; + rcount += count; + } + break; + } + case RideMetric::Low: + { + if (value < rvalue) rvalue = value; + 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" || + metric->units(useMetricUnits) == tr("seconds")) { + if (nofmt) result = QString("%1").arg(rvalue); + else result = time_to_string(rvalue); + + } else result = QString("%1").arg(rvalue, 0, 'f', metric->precision()); + + // 0 temp from aggregate means no values + if ((metric->symbol() == "average_temp" || metric->symbol() == "max_temp") && result == "0.0") result = "-"; + return result; +} + class RollingBests { private: diff --git a/src/RideCache.h b/src/RideCache.h index 1f40f4c3c..dcb3617ae 100644 --- a/src/RideCache.h +++ b/src/RideCache.h @@ -38,6 +38,7 @@ class Context; class RideCacheBackgroundRefresh; +class Specification; class RideCache : public QObject { @@ -53,6 +54,9 @@ class RideCache : public QObject QList getAllDates(); QStringList getAllFilenames(); + // get an aggregate applying the passed spec + QString getAggregate(QString name, Specification spec, bool useMetricUnits, bool nofmt=false); + // metadata QHash getRankedValues(QString name); // metadata QStringList getDistinctValues(QString name); // metadata diff --git a/src/RideMetric.cpp b/src/RideMetric.cpp index 8268a691b..552641e9a 100644 --- a/src/RideMetric.cpp +++ b/src/RideMetric.cpp @@ -70,7 +70,7 @@ RideMetric::computeMetrics(const Context *context, const RideFile *ride, const Z } QString -RideMetric::toString(bool useMetricUnits) +RideMetric::toString(bool useMetricUnits) const { if (isTime()) return time_to_string(value(useMetricUnits)); return QString("%1").arg(value(useMetricUnits), 0, 'f', precision()); diff --git a/src/RideMetric.h b/src/RideMetric.h index 1f1f2fc81..d250fffc4 100644 --- a/src/RideMetric.h +++ b/src/RideMetric.h @@ -118,7 +118,7 @@ public: virtual bool isTime() const { return false; } // Convert value to string, taking into account metric pref - virtual QString toString(bool useMetricUnits); + virtual QString toString(bool useMetricUnits) const; // Fill in the value of the ride metric using the mapping provided. For // example, average speed might be specified by the mapping diff --git a/src/Specification.h b/src/Specification.h index a25537839..3329c569a 100644 --- a/src/Specification.h +++ b/src/Specification.h @@ -72,6 +72,9 @@ class Specification void setDateRange(DateRange dr); void setFilterSet(FilterSet fs); + DateRange dateRange() { return dr; } + FilterSet filterSet() { return fs; } + private: DateRange dr; FilterSet fs; diff --git a/src/TreeMapWindow.cpp b/src/TreeMapWindow.cpp index 4cc34f872..cb06ab4a1 100644 --- a/src/TreeMapWindow.cpp +++ b/src/TreeMapWindow.cpp @@ -22,6 +22,7 @@ #include "LTMSettings.h" #include "Context.h" #include "Athlete.h" +#include "RideCache.h" #include "Settings.h" #include "math.h" #include "Units.h" // for MILES_PER_KM @@ -270,27 +271,40 @@ TreeMapWindow::fieldSelected(int) void TreeMapWindow::cellClicked(QString f1, QString f2) { - QList cell; + QStringList match; // create a list of activities in this cell int count = 0; - foreach(SummaryMetrics x, results) { + foreach(RideItem *item, context->athlete->rideCache->rides()) { + + // honour the settings + if (!settings.specification.pass(item)) continue; + // text may either not exists, then "unknown" or just be "" but f1, f2 don't know "" - QString x1 = x.getText(settings.field1, tr("(unknown)")); - QString x2 = x.getText(settings.field2, tr("(unknown)")); + QString x1 = item->getText(settings.field1, tr("(unknown)")); + QString x2 = item->getText(settings.field2, tr("(unknown)")); if (x1 == "") x1 = tr("(unknown)"); if (x2 == "") x2 = tr("(unknown)"); - // now we can compare and append + + // match ! if (x1 == f1 && x2 == f2) { - cell.append(x); + match << item->fileName; count++; } } + // create a specification for ours + Specification spec; + spec.setDateRange(settings.specification.dateRange()); + FilterSet fs = settings.specification.filterSet(); + fs.addFilter(true, match); + spec.setFilterSet(fs); + + // and the metric to display const RideMetricFactory &factory = RideMetricFactory::instance(); const RideMetric *metric = factory.rideMetric(settings.symbol); - ltmPopup->setData(cell, metric, QString(tr("%1 ride%2")).arg(count).arg(count == 1 ? "" : tr("s"))); + ltmPopup->setData(spec, metric, QString(tr("%1 ride%2")).arg(count).arg(count == 1 ? "" : tr("s"))); popup->show(); }