diff --git a/src/Core/RideCache.cpp b/src/Core/RideCache.cpp index 210e47c68..684709aab 100644 --- a/src/Core/RideCache.cpp +++ b/src/Core/RideCache.cpp @@ -31,6 +31,8 @@ #include "HrZones.h" #include "PaceZones.h" +#include "ErgFile.h" + #include "JsonRideFile.h" // for DATETIME_FORMAT #ifdef SLOW_REFRESH @@ -1225,6 +1227,10 @@ RideCache::moveActivity result.affectedCount = 1; } + if (item->planned) { + updateFromWorkout(item, false); + } + item->refresh(); context->notifyRideChanged(item); if (context->ride == item) { @@ -1540,6 +1546,7 @@ RideCache::shiftPlannedActivities continue; } item->setFileName(plannedDirectory.canonicalPath(), newFileName); + updateFromWorkout(item, true); item->isstale = true; RideItem *linkedItem = getLinkedActivity(item); @@ -1683,6 +1690,99 @@ RideCache::findSuggestion } +bool +RideCache::updateFromWorkout +(RideItem *item, bool autoSave) +{ + if (item == nullptr || ! item->planned) { + return false; + } + QString workoutFilename = item->getText("WorkoutFilename", item->ride()->getTag("WorkoutFilename", "")).trimmed(); + if (workoutFilename.isEmpty()) { + return false; + } + ErgFile ergFile(workoutFilename, ErgFileFormat::unknown, context, item->dateTime.date()); + if (! ergFile.hasRelativeWatts()) { + return false; + } + bool changed = false; + for (const QString &name : item->overrides_) { + int value = static_cast(item->getForSymbol(name)); + // Operate only on the values overridden by ManualActivityWizard + if (name == "average_power") { + if (value != std::round(ergFile.AP())) { + QMap values; + values.insert("value", QString::number(std::round(ergFile.AP()))); + item->ride()->metricOverrides.insert(name, values); + changed = true; + } + } else if (name == "coggan_np") { + if (value != std::round(ergFile.IsoPower())) { + QMap values; + values.insert("value", QString::number(std::round(ergFile.IsoPower()))); + item->ride()->metricOverrides.insert(name, values); + changed = true; + } + } else if (name == "coggan_tss") { + if (value != std::round(ergFile.bikeStress())) { + QMap values; + values.insert("value", QString::number(std::round(ergFile.bikeStress()))); + item->ride()->metricOverrides.insert(name, values); + changed = true; + } + } else if (name == "skiba_bike_score") { + if (value != std::round(ergFile.BS())) { + QMap values; + values.insert("value", QString::number(std::round(ergFile.BS()))); + item->ride()->metricOverrides.insert(name, values); + changed = true; + } + } else if (name == "skiba_xpower") { + if (value != std::round(ergFile.XP())) { + QMap values; + values.insert("value", QString::number(std::round(ergFile.XP()))); + item->ride()->metricOverrides.insert(name, values); + changed = true; + } + } + } + if (changed) { + item->setDirty(true); + item->isstale = true; + if (autoSave) { + QString error; + saveActivity(item, error); + } + } + return changed; +} + + +bool +RideCache::updateFromWorkoutAfter +(const QDate &when, bool autoSave) +{ + QList changedItems; + for (RideItem *item : context->athlete->rideCache->rides()) { + if (item->planned && item->dateTime.date() >= when) { + if (context->athlete->rideCache->updateFromWorkout(item, false)) { + changedItems << item; + } + } + } + if (changedItems.count() > 0) { + if (autoSave) { + QString error; + saveActivities(changedItems, error); + } + cancel(); + refresh(); + estimator->refresh(); + } + return changedItems.count() > 0; +} + + bool RideCache::isValidLink (RideItem *item1, RideItem *item2, QString &error) @@ -1747,6 +1847,7 @@ RideCache::copyPlannedRideFile delete newRide; RideItem *newItem = new RideItem(plannedDirectory.canonicalPath(), newFileName, newDateTime, context, true); + updateFromWorkout(newItem, true); newItem->isstale = true; return newItem; diff --git a/src/Core/RideCache.h b/src/Core/RideCache.h index ba9de8292..a50bbf4ea 100644 --- a/src/Core/RideCache.h +++ b/src/Core/RideCache.h @@ -150,6 +150,9 @@ class RideCache : public QObject RideItem *getLinkedActivity(RideItem *item); RideItem *findSuggestion(RideItem *rideItem); + bool updateFromWorkout(RideItem *item, bool autoSave = false); + bool updateFromWorkoutAfter(const QDate &when, bool autoSave = false); + public slots: // restore / dump cache to disk (json) diff --git a/src/Gui/Calendar.cpp b/src/Gui/Calendar.cpp index 322173bd3..564c0396b 100644 --- a/src/Gui/Calendar.cpp +++ b/src/Gui/Calendar.cpp @@ -205,7 +205,7 @@ CalendarBaseTable::buildContextMenu } if (entry.hasTrainMode) { contextMenu->addSeparator(); - contextMenu->addAction(tr("Show in train node..."), this, [this, entry]() { emit showInTrainMode(entry); }); + contextMenu->addAction(tr("Show in train mode..."), this, [this, entry]() { emit showInTrainMode(entry); }); } contextMenu->addSeparator(); contextMenu->addAction(tr("Delete planned activity"), this, [this, entry]() { emit delActivity(entry); }); diff --git a/src/Gui/LTMSidebar.cpp b/src/Gui/LTMSidebar.cpp index 7e1f79d09..03bbb160c 100644 --- a/src/Gui/LTMSidebar.cpp +++ b/src/Gui/LTMSidebar.cpp @@ -26,6 +26,7 @@ #include "Colors.h" #include "Units.h" #include "HelpWhatsThis.h" +#include "ErgFile.h" #include "GcWindowRegistry.h" // for GcWinID types #include "Perspective.h" // for GcWindowDialog @@ -329,7 +330,7 @@ LTMSidebar::presetSelectionChanged() } void -LTMSidebar::configChanged(qint32) +LTMSidebar::configChanged(qint32 why) { seasonsWidget->setStyleSheet(GCColor::stylesheet()); eventsWidget->setStyleSheet(GCColor::stylesheet()); @@ -343,6 +344,9 @@ LTMSidebar::configChanged(qint32) // let everyone know what date range we are starting with dateRangeTreeWidgetSelectionChanged(); + if (why & CONFIG_ZONES) { + context->athlete->rideCache->updateFromWorkoutAfter(QDate::currentDate(), true); + } } /*---------------------------------------------------------------------- diff --git a/src/Gui/ManualActivityWizard.cpp b/src/Gui/ManualActivityWizard.cpp index f2eb9ea26..6afcbd682 100644 --- a/src/Gui/ManualActivityWizard.cpp +++ b/src/Gui/ManualActivityWizard.cpp @@ -676,12 +676,33 @@ ManualActivityPageWorkout::selectionChanged QString title = workoutModel->data(workoutModel->index(target.row(), TdbWorkoutModelIdx::displayname), Qt::DisplayRole).toString(); QString type = workoutModel->data(workoutModel->index(target.row(), TdbWorkoutModelIdx::type), Qt::DisplayRole).toString(); QString description = workoutModel->data(workoutModel->index(target.row(), TdbWorkoutModelIdx::description), Qt::DisplayRole).toString(); - int avgPower = workoutModel->data(workoutModel->index(target.row(), TdbWorkoutModelIdx::avgPower), Qt::DisplayRole).toInt(); - int bikeStress = workoutModel->data(workoutModel->index(target.row(), TdbWorkoutModelIdx::bikestress), Qt::DisplayRole).toInt(); - int bikeScore = workoutModel->data(workoutModel->index(target.row(), TdbWorkoutModelIdx::bs), Qt::DisplayRole).toInt(); + if (ergFile != nullptr) { + delete ergFile; + ergFile = nullptr; + } + if (type != "code") { + QDate when = field("activityDate").toDate(); + ergFile = new ErgFile(filename, ErgFileFormat::unknown, context, when); + if (! ergFile->isValid()) { + delete ergFile; + ergFile = nullptr; + } + } + + int avgPower = 0; + int bikeStress = 0; + int bikeScore = 0; + int isoPower = 0; + int xPower = 0; + if (ergFile != nullptr && type == "erg") { + avgPower = static_cast(ergFile->AP()); + bikeStress = static_cast(ergFile->bikeStress()); + bikeScore = static_cast(ergFile->BS()); + isoPower = static_cast(ergFile->IsoPower()); + xPower = static_cast(ergFile->XP()); + } + int elevationGain = workoutModel->data(workoutModel->index(target.row(), TdbWorkoutModelIdx::elevation), Qt::DisplayRole).toInt(); - int isoPower = workoutModel->data(workoutModel->index(target.row(), TdbWorkoutModelIdx::isoPower), Qt::DisplayRole).toInt(); - int xPower = workoutModel->data(workoutModel->index(target.row(), TdbWorkoutModelIdx::xp), Qt::DisplayRole).toInt(); setField("woFilename", filename); setField("woTitle", title); setField("woFileType", type); @@ -703,18 +724,6 @@ ManualActivityPageWorkout::selectionChanged setField("distance", distanceKM * (useMetricUnits ? 1.0 : MILES_PER_KM)); } - if (ergFile != nullptr) { - delete ergFile; - ergFile = nullptr; - } - - if (type != "code") { - ergFile = new ErgFile(filename, ErgFileFormat::unknown, context); - if (! ergFile->isValid()) { - delete ergFile; - ergFile = nullptr; - } - } contentStack->setCurrentIndex(ergFile != nullptr ? 1 : 2); context->workout = ergFile; ergFilePlot->setData(ergFile); diff --git a/src/Train/ErgFile.cpp b/src/Train/ErgFile.cpp index 4a3320ee4..51f985507 100644 --- a/src/Train/ErgFile.cpp +++ b/src/Train/ErgFile.cpp @@ -62,28 +62,38 @@ bool ErgFile::isWorkout(QString name) return false; } -ErgFile::ErgFile(QString filename, ErgFileFormat mode, Context *context) +ErgFile::ErgFile(QString filename, ErgFileFormat mode, Context *context, QDate when) : context(context) { + if (when.isValid()) { + this->when = when; + } else { + this->when = QDate::currentDate(); + } this->filename(filename); this->mode(mode); strictGradient(true); fHasGPS(false); if (context->athlete->zones("Bike")) { - int zonerange = context->athlete->zones("Bike")->whichRange(QDateTime::currentDateTime().date()); + int zonerange = context->athlete->zones("Bike")->whichRange(this->when); if (zonerange >= 0) CP(context->athlete->zones("Bike")->getCP(zonerange)); } reload(); } -ErgFile::ErgFile(Context *context) +ErgFile::ErgFile(Context *context, QDate when) : context(context) { + if (when.isValid()) { + this->when = when; + } else { + this->when = QDate::currentDate(); + } mode(ErgFileFormat::unknown); strictGradient(true); fHasGPS(false); if (context->athlete->zones("Bike")) { - int zonerange = context->athlete->zones("Bike")->whichRange(QDateTime::currentDateTime().date()); + int zonerange = context->athlete->zones("Bike")->whichRange(this->when); if (zonerange >= 0) CP(context->athlete->zones("Bike")->getCP(zonerange)); } else { CP(300); @@ -107,9 +117,9 @@ ErgFile::setFrom(ErgFile *f) } ErgFile * -ErgFile::fromContent(QString contents, Context *context) +ErgFile::fromContent(QString contents, Context *context, QDate when) { - ErgFile *p = new ErgFile(context); + ErgFile *p = new ErgFile(context, when); p->parseComputrainer(contents); p->finalize(); @@ -118,9 +128,9 @@ ErgFile::fromContent(QString contents, Context *context) } ErgFile * -ErgFile::fromContent2(QString contents, Context *context) +ErgFile::fromContent2(QString contents, Context *context, QDate when) { - ErgFile *p = new ErgFile(context); + ErgFile *p = new ErgFile(context, when); p->parseErg2(contents); p->finalize(); @@ -1235,7 +1245,7 @@ ErgFile::ZoneSections() QList ret; const Zones *zones = context->athlete->zones("Bike"); - int zoneRange = zones->whichRange(QDate::currentDate()); + int zoneRange = zones->whichRange(when); QList zoneNames = zones->getZoneNames(zoneRange); if (hasWatts() && duration() > 0) { for (int i = 0; i < Points.size() - 1; ++i) { @@ -1302,7 +1312,7 @@ ErgFile::save(QStringList &errors) // get CP so we can scale back etc int lCP=0; if (context->athlete->zones("Bike")) { - int zonerange = context->athlete->zones("Bike")->whichRange(QDateTime::currentDateTime().date()); + int zonerange = context->athlete->zones("Bike")->whichRange(when); if (zonerange >= 0) lCP = context->athlete->zones("Bike")->getCP(zonerange); } @@ -1934,7 +1944,7 @@ ErgFile::calculateMetrics() bool first = true; // CP - int zonerange = context->athlete->zones("Bike")->whichRange(QDateTime::currentDateTime().date()); + int zonerange = context->athlete->zones("Bike")->whichRange(when); if (context->athlete->zones("Bike")) { if (zonerange >= 0) CP(context->athlete->zones("Bike")->getCP(zonerange)); } diff --git a/src/Train/ErgFile.h b/src/Train/ErgFile.h index b9b1c1c88..466f1433d 100644 --- a/src/Train/ErgFile.h +++ b/src/Train/ErgFile.h @@ -118,8 +118,8 @@ class ErgFileLap class ErgFile : public ErgFileBase { public: - ErgFile(QString, ErgFileFormat, Context *context); // constructor uses filename - ErgFile(Context *context); // no filename, going to use a string + ErgFile(QString, ErgFileFormat, Context *context, QDate when = QDate()); // constructor uses filename + ErgFile(Context *context, QDate when = QDate()); // no filename, going to use a string ~ErgFile(); // delete the contents @@ -128,8 +128,8 @@ class ErgFile : public ErgFileBase void setFrom(ErgFile *f); // clone an existing workout bool save(QStringList &errors); // save back, with changes - static ErgFile *fromContent(QString, Context *); // read from memory *.erg - static ErgFile *fromContent2(QString, Context *); // read from memory *.erg2 + static ErgFile *fromContent(QString, Context *, QDate when = QDate()); // read from memory *.erg + static ErgFile *fromContent2(QString, Context *, QDate when = QDate()); // read from memory *.erg2 static bool isWorkout(QString); // is this a supported workout? @@ -150,6 +150,8 @@ private: bool coalescedSections = false; + QDate when; + public: void coalesceSections(); bool hasCoalescedSections() const;