diff --git a/src/RideItem.cpp b/src/RideItem.cpp index 65f274ad3..30ad33c03 100644 --- a/src/RideItem.cpp +++ b/src/RideItem.cpp @@ -19,13 +19,9 @@ #include "RideItem.h" #include "RideMetric.h" #include "RideFile.h" -#include "Settings.h" -#include "TimeUtils.h" -#include "Units.h" #include "Zones.h" #include #include -#include RideItem::RideItem(int type, QString path, QString fileName, const QDateTime &dateTime, @@ -49,66 +45,6 @@ RideItem::~RideItem() } } -static void summarize(bool even, - QString &intervals, - const QString &name, - double km_start, double km_end, - double &int_watts_sum, - double &int_hr_sum, - QVector &int_hrs, - double &int_cad_sum, - double &int_kph_sum, - double &int_secs_hr, - double &int_max_power, - double dur) -{ - double mile_len = (km_end - km_start) * MILES_PER_KM; - double minutes = (int) (dur/60.0); - double seconds = dur - (60 * minutes); - double watts_avg = int_watts_sum / dur; - double hr_avg = int_secs_hr > 0.0 ? int_hr_sum / int_secs_hr : 0.0; - double cad_avg = int_cad_sum / dur; - double mph_avg = int_kph_sum * MILES_PER_KM / dur; - double energy = int_watts_sum / 1000.0; // watts_avg / 1000.0 * dur; - std::sort(int_hrs.begin(), int_hrs.end()); - double top5hr = int_hrs.size() > 0 ? int_hrs[int_hrs.size() * 0.95] : 0; - - if (even) - intervals += "%1"; - else { - QColor color = QApplication::palette().alternateBase().color(); - color = QColor::fromHsv(color.hue(), color.saturation() * 2, color.value()); - intervals += "%1"; - } - intervals += "%2:%3"; - intervals += "%4"; - intervals += "%5"; - intervals += "%6"; - intervals += "%7"; - intervals += "%8"; - intervals += "%9"; - intervals += "%10"; - intervals += "%11"; - intervals = intervals.arg(name); - intervals = intervals.arg(minutes, 0, 'f', 0); - intervals = intervals.arg(seconds, 2, 'f', 0, QLatin1Char('0')); - intervals = intervals.arg(mile_len, 0, 'f', 1); - intervals = intervals.arg(energy, 0, 'f', 0); - intervals = intervals.arg(int_max_power, 0, 'f', 0); - intervals = intervals.arg(watts_avg, 0, 'f', 0); - intervals = intervals.arg(top5hr, 0, 'f', 0); - intervals = intervals.arg(hr_avg, 0, 'f', 0); - intervals = intervals.arg(cad_avg, 0, 'f', 0); - - boost::shared_ptr settings = GetApplicationSettings(); - - QVariant unit = settings->value(GC_UNIT); - if(unit.toString() == "Metric") - intervals = intervals.arg(mph_avg * 1.60934, 0, 'f', 1); - else - intervals = intervals.arg(mph_avg, 0, 'f', 1); -} - int RideItem::zoneRange() { return zones->whichRange(dateTime.date()); @@ -129,45 +65,6 @@ double RideItem::timeInZone(int zone) return time_in_zone[zone]; } -static const char *metricsXml = - "\n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - "\n"; - void RideItem::freeMemory() { @@ -247,217 +144,4 @@ RideItem::computeMetrics() } } -QString -RideItem::htmlSummary() -{ - if (summary.isEmpty() || - (summaryGenerationTime < zones->modificationTime)) { - - summaryGenerationTime = QDateTime::currentDateTime(); - - QFile file(path + "/" + fileName); - QStringList errors; - ride = RideFileFactory::instance().openRideFile(file, errors); - if (!ride) { - summary = "

Couldn't read file \"" + file.fileName() + "\":"; - QListIterator i(errors); - while (i.hasNext()) - summary += "
" + i.next(); - return summary; - } - summary = ("

" - + dateTime.toString("dddd MMMM d, yyyy, h:mm AP") - + "

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

"); - - computeMetrics(); - - boost::shared_ptr settings = GetApplicationSettings(); - QVariant unit = settings->value(GC_UNIT); - - QString intervals = ""; - double secs_delta = ride->recIntSecs(); - - bool even = false; - foreach (RideFileInterval interval, ride->intervals()) { - int i = ride->intervalBegin(interval); - assert(i < ride->dataPoints().size()); - const RideFilePoint *firstPoint = ride->dataPoints()[i]; - - double secs_watts = 0.0; - double int_watts_sum = 0.0; - double int_hr_sum = 0.0; - QVector int_hrs; - double int_cad_sum = 0.0; - double int_kph_sum = 0.0; - double int_secs_hr = 0.0; - double int_max_power = 0.0; - double km_end = 0.0; - - while (i < ride->dataPoints().size()) { - const RideFilePoint *point = ride->dataPoints()[i++]; - if (point->secs >= interval.stop) - break; - if (point->watts >= 0.0) { - secs_watts += secs_delta; - int_watts_sum += point->watts * secs_delta; - if (point->watts > int_max_power) - int_max_power = point->watts; - } - if (point->hr > 0) { - int_hr_sum += point->hr * secs_delta; - int_secs_hr += secs_delta; - } - if (point->hr >= 0) - int_hrs.push_back(point->hr); - if (point->cad > 0) - int_cad_sum += point->cad * secs_delta; - if (point->kph >= 0) - int_kph_sum += point->kph * secs_delta; - - km_end = point->km; - } - summarize(even, intervals, interval.name, - firstPoint->km, km_end, int_watts_sum, - int_hr_sum, int_hrs, int_cad_sum, int_kph_sum, - int_secs_hr, int_max_power, - interval.stop - interval.start); - even = !even; - } - - summary += "

"; - - bool metricUnits = (unit.toString() == "Metric"); - - QDomDocument doc; - { - QString err; - int errLine, errCol; - if (!doc.setContent(QString(metricsXml), &err, &errLine, &errCol)){ - fprintf(stderr, "error: %s, line %d, col %d\n", - err.toAscii().constData(), errLine, errCol); - assert(false); - } - } - - QString noteString = ""; - QString stars; - QDomNodeList groups = doc.elementsByTagName("metric_group"); - const int columns = 3; - for (int groupNum = 0; groupNum < groups.size(); ++groupNum) { - QDomElement group = groups.at(groupNum).toElement(); - assert(!group.isNull()); - QString groupName = group.attribute("name"); - QString groupNote = group.attribute("note"); - assert(groupName.length() > 0); - if (groupNum % columns == 0) - summary += ""; - summary += ""; - if ((groupNum % columns == (columns - 1)) - || (groupNum == groups.size() - 1)) - summary += "
" - ""; - summary = summary.arg(90 / columns); - if (groupNote.length() > 0) { - stars += "*"; - summary = summary.arg(groupName + stars); - noteString += "
" + stars + " " + groupNote; - } - else { - summary = summary.arg(groupName); - } - QDomNodeList metricsList = group.childNodes(); - for (int i = 0; i < metricsList.size(); ++i) { - QDomElement metric = metricsList.at(i).toElement(); - QString name = metric.attribute("name"); - QString displayName = metric.attribute("display_name"); - int precision = metric.attribute("precision", "0").toInt(); - assert(name.length() > 0); - assert(displayName.length() > 0); - const RideMetric *m = metrics.value(name); - assert(m); - if (m->units(metricUnits) == "seconds") { - QString s(""); - s = s.arg(displayName); - s = s.arg(time_to_string(m->value(metricUnits))); - summary += s; - } - else { - QString s = ""; - if (precision == 0) - s = s.arg((unsigned) round(m->value(metricUnits))); - else - s = s.arg(m->value(metricUnits), 0, 'f', precision); - summary += s; - } - } - summary += "

%2

%1:%2
" + displayName; - if (m->units(metricUnits) != "") - s += " (" + m->units(metricUnits) + ")"; - s += ":%1
"; - } - - if (!time_in_zone.empty()) { - summary += "

Power Zones

"; - summary += zones->summarize(zoneRange(), time_in_zone); - } - - // TODO: Ergomo uses non-consecutive interval numbers. - // Seems to use 0 when not in an interval - // and an integer < 30 when in an interval. - // We'll need to create a counter for the intervals - // rather than relying on the final data point's interval number. - if (ride->intervals().size() > 1) { - summary += "

Intervals

\n

\n"; - summary += "Interval"; - summary += ""; - summary += ""; - summary += ""; - summary += ""; - summary += ""; - summary += ""; - summary += ""; - summary += ""; - summary += ""; - summary += ""; - summary += ""; - summary += ""; - if(unit.toString() == "Metric") - summary += ""; - else - summary += ""; - summary += ""; - summary += ""; - summary += ""; - summary += ""; - summary += ""; - summary += ""; - if(unit.toString() == "Metric") - summary += ""; - else - summary += ""; - summary += ""; - summary += intervals; - summary += "
DistanceWorkMax PowerAvg Power95% HRAvg HRAvg CadenceAvg Speed
NumberDuration(km)(miles)(kJ)(watts)(watts)(bpm)(bpm)(rpm)(km/h)(mph)
"; - } - - if (!errors.empty()) { - summary += "

Errors reading file:

    "; - QStringListIterator i(errors); - while(i.hasNext()) - summary += "
  • " + i.next(); - summary += "
"; - } - if (noteString.length() > 0) { - // The extra
works around a bug in QT 4.3.1, - // which will otherwise put the noteString above the
. - summary += "

" + noteString; - } - summary += "
"; - } - - return summary; -} diff --git a/src/RideItem.h b/src/RideItem.h index 559b92456..ed2327859 100644 --- a/src/RideItem.h +++ b/src/RideItem.h @@ -36,8 +36,7 @@ class RideItem : public QTreeWidgetItem { QString path; QString fileName; QDateTime dateTime; - QString summary; - QDateTime summaryGenerationTime, computeMetricsTime; + QDateTime computeMetricsTime; RideFile *ride; const Zones *zones; QString notesFileName; @@ -54,7 +53,6 @@ class RideItem : public QTreeWidgetItem { ~RideItem(); void computeMetrics(); - QString htmlSummary(); void freeMemory(); int zoneRange(); diff --git a/src/RideSummaryWindow.cpp b/src/RideSummaryWindow.cpp index fb7a75f5b..d2d7d49ca 100644 --- a/src/RideSummaryWindow.cpp +++ b/src/RideSummaryWindow.cpp @@ -18,13 +18,20 @@ #include "RideSummaryWindow.h" #include "MainWindow.h" +#include "RideFile.h" #include "RideItem.h" +#include "RideMetric.h" +#include "Settings.h" +#include "TimeUtils.h" +#include "Units.h" #include "Zones.h" #include +#include #include +#include RideSummaryWindow::RideSummaryWindow(MainWindow *mainWindow) : - QWidget(mainWindow), mainWindow(mainWindow), ride(NULL) + QWidget(mainWindow), mainWindow(mainWindow), rideItem(NULL) { QVBoxLayout *vlayout = new QVBoxLayout; rideSummary = new QTextEdit(this); @@ -37,18 +44,331 @@ RideSummaryWindow::RideSummaryWindow(MainWindow *mainWindow) : void RideSummaryWindow::setData(RideItem *ride) { - this->ride = ride; + rideItem = ride; refresh(); } void RideSummaryWindow::refresh() { - if (!ride) { + if (!rideItem) { rideSummary->clear(); return; } - rideSummary->setHtml(ride->htmlSummary()); + rideSummary->setHtml(htmlSummary()); rideSummary->setAlignment(Qt::AlignCenter); } +static void +summarize(bool even, + QString &intervals, + const QString &name, + double km_start, double km_end, + double &int_watts_sum, + double &int_hr_sum, + QVector &int_hrs, + double &int_cad_sum, + double &int_kph_sum, + double &int_secs_hr, + double &int_max_power, + double dur) +{ + double mile_len = (km_end - km_start) * MILES_PER_KM; + double minutes = (int) (dur/60.0); + double seconds = dur - (60 * minutes); + double watts_avg = int_watts_sum / dur; + double hr_avg = int_secs_hr > 0.0 ? int_hr_sum / int_secs_hr : 0.0; + double cad_avg = int_cad_sum / dur; + double mph_avg = int_kph_sum * MILES_PER_KM / dur; + double energy = int_watts_sum / 1000.0; // watts_avg / 1000.0 * dur; + std::sort(int_hrs.begin(), int_hrs.end()); + double top5hr = int_hrs.size() > 0 ? int_hrs[int_hrs.size() * 0.95] : 0; + + if (even) + intervals += "%1"; + else { + QColor color = QApplication::palette().alternateBase().color(); + color = QColor::fromHsv(color.hue(), color.saturation() * 2, color.value()); + intervals += "%1"; + } + intervals += "%2:%3"; + intervals += "%4"; + intervals += "%5"; + intervals += "%6"; + intervals += "%7"; + intervals += "%8"; + intervals += "%9"; + intervals += "%10"; + intervals += "%11"; + intervals = intervals.arg(name); + intervals = intervals.arg(minutes, 0, 'f', 0); + intervals = intervals.arg(seconds, 2, 'f', 0, QLatin1Char('0')); + intervals = intervals.arg(mile_len, 0, 'f', 1); + intervals = intervals.arg(energy, 0, 'f', 0); + intervals = intervals.arg(int_max_power, 0, 'f', 0); + intervals = intervals.arg(watts_avg, 0, 'f', 0); + intervals = intervals.arg(top5hr, 0, 'f', 0); + intervals = intervals.arg(hr_avg, 0, 'f', 0); + intervals = intervals.arg(cad_avg, 0, 'f', 0); + + boost::shared_ptr settings = GetApplicationSettings(); + + QVariant unit = settings->value(GC_UNIT); + if(unit.toString() == "Metric") + intervals = intervals.arg(mph_avg * 1.60934, 0, 'f', 1); + else + intervals = intervals.arg(mph_avg, 0, 'f', 1); +} + +static const char *metricsXml = + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; + +QString +RideSummaryWindow::htmlSummary() const +{ + QString summary; + + QFile file(rideItem->path + "/" + rideItem->fileName); + QStringList errors; + RideFile *ride = RideFileFactory::instance().openRideFile(file, errors); + if (!ride) { + summary = "

Couldn't read file \"" + file.fileName() + "\":"; + QListIterator i(errors); + while (i.hasNext()) + summary += "
" + i.next(); + return summary; + } + summary = ("

" + + rideItem->dateTime.toString("dddd MMMM d, yyyy, h:mm AP") + + "

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

"); + + rideItem->computeMetrics(); + + boost::shared_ptr settings = GetApplicationSettings(); + QVariant unit = settings->value(GC_UNIT); + + QString intervals = ""; + double secs_delta = ride->recIntSecs(); + + bool even = false; + foreach (RideFileInterval interval, ride->intervals()) { + int i = ride->intervalBegin(interval); + assert(i < ride->dataPoints().size()); + const RideFilePoint *firstPoint = ride->dataPoints()[i]; + + double secs_watts = 0.0; + double int_watts_sum = 0.0; + double int_hr_sum = 0.0; + QVector int_hrs; + double int_cad_sum = 0.0; + double int_kph_sum = 0.0; + double int_secs_hr = 0.0; + double int_max_power = 0.0; + double km_end = 0.0; + + while (i < ride->dataPoints().size()) { + const RideFilePoint *point = ride->dataPoints()[i++]; + if (point->secs >= interval.stop) + break; + if (point->watts >= 0.0) { + secs_watts += secs_delta; + int_watts_sum += point->watts * secs_delta; + if (point->watts > int_max_power) + int_max_power = point->watts; + } + if (point->hr > 0) { + int_hr_sum += point->hr * secs_delta; + int_secs_hr += secs_delta; + } + if (point->hr >= 0) + int_hrs.push_back(point->hr); + if (point->cad > 0) + int_cad_sum += point->cad * secs_delta; + if (point->kph >= 0) + int_kph_sum += point->kph * secs_delta; + + km_end = point->km; + } + summarize(even, intervals, interval.name, + firstPoint->km, km_end, int_watts_sum, + int_hr_sum, int_hrs, int_cad_sum, int_kph_sum, + int_secs_hr, int_max_power, + interval.stop - interval.start); + even = !even; + } + + summary += "

"; + + bool metricUnits = (unit.toString() == "Metric"); + + QDomDocument doc; + { + QString err; + int errLine, errCol; + if (!doc.setContent(QString(metricsXml), &err, &errLine, &errCol)){ + fprintf(stderr, "error: %s, line %d, col %d\n", + err.toAscii().constData(), errLine, errCol); + assert(false); + } + } + + QString noteString = ""; + QString stars; + QDomNodeList groups = doc.elementsByTagName("metric_group"); + const int columns = 3; + for (int groupNum = 0; groupNum < groups.size(); ++groupNum) { + QDomElement group = groups.at(groupNum).toElement(); + assert(!group.isNull()); + QString groupName = group.attribute("name"); + QString groupNote = group.attribute("note"); + assert(groupName.length() > 0); + if (groupNum % columns == 0) + summary += ""; + summary += ""; + if ((groupNum % columns == (columns - 1)) + || (groupNum == groups.size() - 1)) + summary += "
" + ""; + summary = summary.arg(90 / columns); + if (groupNote.length() > 0) { + stars += "*"; + summary = summary.arg(groupName + stars); + noteString += "
" + stars + " " + groupNote; + } + else { + summary = summary.arg(groupName); + } + QDomNodeList metricsList = group.childNodes(); + for (int i = 0; i < metricsList.size(); ++i) { + QDomElement metric = metricsList.at(i).toElement(); + QString name = metric.attribute("name"); + QString displayName = metric.attribute("display_name"); + int precision = metric.attribute("precision", "0").toInt(); + assert(name.length() > 0); + assert(displayName.length() > 0); + const RideMetric *m = rideItem->metrics.value(name); + assert(m); + if (m->units(metricUnits) == "seconds") { + QString s(""); + s = s.arg(displayName); + s = s.arg(time_to_string(m->value(metricUnits))); + summary += s; + } + else { + QString s = ""; + if (precision == 0) + s = s.arg((unsigned) round(m->value(metricUnits))); + else + s = s.arg(m->value(metricUnits), 0, 'f', precision); + summary += s; + } + } + summary += "

%2

%1:%2
" + displayName; + if (m->units(metricUnits) != "") + s += " (" + m->units(metricUnits) + ")"; + s += ":%1
"; + } + + if (rideItem->numZones() > 0) { + QVector time_in_zone(rideItem->numZones()); + for (int i = 0; i < rideItem->numZones(); ++i) + time_in_zone[i] = rideItem->timeInZone(i); + summary += "

Power Zones

"; + summary += mainWindow->zones()->summarize(rideItem->zoneRange(), time_in_zone); + } + + // TODO: Ergomo uses non-consecutive interval numbers. + // Seems to use 0 when not in an interval + // and an integer < 30 when in an interval. + // We'll need to create a counter for the intervals + // rather than relying on the final data point's interval number. + if (ride->intervals().size() > 1) { + summary += "

Intervals

\n

\n"; + summary += "Interval"; + summary += ""; + summary += ""; + summary += ""; + summary += ""; + summary += ""; + summary += ""; + summary += ""; + summary += ""; + summary += ""; + summary += ""; + summary += ""; + summary += ""; + if(unit.toString() == "Metric") + summary += ""; + else + summary += ""; + summary += ""; + summary += ""; + summary += ""; + summary += ""; + summary += ""; + summary += ""; + if(unit.toString() == "Metric") + summary += ""; + else + summary += ""; + summary += ""; + summary += intervals; + summary += "
DistanceWorkMax PowerAvg Power95% HRAvg HRAvg CadenceAvg Speed
NumberDuration(km)(miles)(kJ)(watts)(watts)(bpm)(bpm)(rpm)(km/h)(mph)
"; + } + + if (!errors.empty()) { + summary += "

Errors reading file:

    "; + QStringListIterator i(errors); + while(i.hasNext()) + summary += "
  • " + i.next(); + summary += "
"; + } + if (noteString.length() > 0) { + // The extra
works around a bug in QT 4.3.1, + // which will otherwise put the noteString above the
. + summary += "

" + noteString; + } + summary += "
"; + + return summary; +} + diff --git a/src/RideSummaryWindow.h b/src/RideSummaryWindow.h index c0ed9adbd..58964e8dc 100644 --- a/src/RideSummaryWindow.h +++ b/src/RideSummaryWindow.h @@ -40,8 +40,10 @@ class RideSummaryWindow : public QWidget protected: + QString htmlSummary() const; + MainWindow *mainWindow; - RideItem *ride; + RideItem *rideItem; QTextEdit *rideSummary; };