diff --git a/src/Charts/CalendarWindow.cpp b/src/Charts/CalendarWindow.cpp index 5ee2dd6c2..a8147cbd3 100644 --- a/src/Charts/CalendarWindow.cpp +++ b/src/Charts/CalendarWindow.cpp @@ -1006,12 +1006,18 @@ CalendarWindow::getActivities activity.isRelocatable = rideItem->planned; activity.hasTrainMode = rideItem->planned && sport == "Bike" && ! buildWorkoutFilter(rideItem).isEmpty(); activity.dirty = rideItem->isDirty(); + if (rideItem->planned) { + activity.originalPlanLabel = buildOriginalLabel(rideItem); + } RideItem *linkedRide = context->athlete->rideCache->getLinkedActivity(rideItem); if (linkedRide != nullptr) { activity.linkedReference = linkedRide->fileName; activity.linkedPrimary = getPrimary(linkedRide); activity.linkedStartDT = linkedRide->dateTime; + if (linkedRide->planned) { + activity.originalPlanLabel = buildOriginalLabel(linkedRide); + } } activities[rideItem->dateTime.date()] << activity; @@ -1328,6 +1334,51 @@ CalendarWindow::findFreeSlot } +QString +CalendarWindow::buildOriginalLabel +(RideItem const * const item) const +{ + QDate originalPlan = QDate::fromString(item->getText("Original Date", ""), "yyyy/MM/dd"); + if (! originalPlan.isValid() || originalPlan == item->dateTime.date()) { + return ""; + } + + QLocale locale; + QString unitLabel; + int days = originalPlan.daysTo(item->dateTime.date()); + QChar sign = days > 0 ? '+' : '-'; + ShowDaysAsUnit unit = showDaysAs(days); + int c = 0; + if (unit == ShowDaysAsUnit::Days) { + c = std::abs(days); + if (c == 1) { + unitLabel = tr("day"); + } else { + unitLabel = tr("days"); + } + } else if (unit == ShowDaysAsUnit::Weeks) { + c = daysToWeeks(days); + if (c == 1) { + unitLabel = tr("week"); + } else { + unitLabel = tr("weeks"); + } + } else if (unit == ShowDaysAsUnit::Months) { + c = daysToMonths(days); + if (c == 1) { + unitLabel = tr("month"); + } else { + unitLabel = tr("months"); + } + } + return QString("%1 (%2%3 %4)") + .arg(locale.toString(originalPlan, QLocale::NarrowFormat)) + .arg(sign) + .arg(c) + .arg(unitLabel); +} + + void CalendarWindow::updateActivities () diff --git a/src/Charts/CalendarWindow.h b/src/Charts/CalendarWindow.h index 526d4aed8..0601003cd 100644 --- a/src/Charts/CalendarWindow.h +++ b/src/Charts/CalendarWindow.h @@ -161,6 +161,7 @@ class CalendarWindow : public GcChartWindow QString getPrimary(RideItem const * const rideItem) const; QTime findFreeSlot(RideItem *sourceItem, QDate newDate, QTime time); QTime findFreeSlot(QList> busySlots, QTime targetTime, int requiredDurationSeconds) const; + QString buildOriginalLabel(RideItem const * const item) const; private slots: void updateActivities(); diff --git a/src/Core/RideCache.cpp b/src/Core/RideCache.cpp index 0bf3b2aae..36d71f7b5 100644 --- a/src/Core/RideCache.cpp +++ b/src/Core/RideCache.cpp @@ -439,6 +439,7 @@ RideCache::removeRide(const QString& filenameToDelete) { context->notifyRideSelected(context->ride); } + refresh(); // model estimates (lazy refresh) estimator->refresh(); @@ -1187,21 +1188,38 @@ RideCache::moveActivity return result; } + QDate originalDate = QDate::fromString(ride->getTag("Original Date", ""), "yyyy/MM/dd"); + if (! originalDate.isValid()) { + ride->setTag("Original Date", oldDateTime.date().toString("yyyy/MM/dd")); + } item->setStartTime(newDateTime); ride->setTag("Year", newDateTime.toString("yyyy")); ride->setTag("Month", newDateTime.toString("MMMM")); ride->setTag("Weekday", newDateTime.toString("ddd")); + ride->setTag("Filename", newFileName); item->metadata_.insert("Calendar Text", GlobalContext::context()->rideMetadata->calendarText(item)); - item->close(); QString renameError; if (! renameRideFiles(oldFileName, newFileName, item->planned, renameError)) { item->dateTime = oldDateTime; item->fileName = oldFileName; result.error = tr("Failed to rename files: %1").arg(renameError); + item->close(); return result; } + QString newPath = (item->planned ? plannedDirectory : directory).canonicalPath() + "/" + newFileName; + QFile outFile(newPath); + if (! RideFileFactory::instance().writeRideFile(context, ride, outFile, QFileInfo(newFileName).suffix())) { + renameRideFiles(newFileName, oldFileName, item->planned, renameError); + item->dateTime = oldDateTime; + item->fileName = oldFileName; + result.error = tr("Failed to save activity file after rename"); + item->close(); + return result; + } + item->close(); + int index = rides_.indexOf(item); if (index >= 0) { model_->startRemove(index); @@ -1236,6 +1254,7 @@ RideCache::moveActivity if (context->ride == item) { context->notifyRideSelected(item); } + refresh(); estimator->refresh(); result.success = true; @@ -1302,7 +1321,8 @@ RideCache::copyPlannedActivity std::sort(rides_.begin(), rides_.end(), rideCacheLessThan); model_->endReset(); - newItem->refresh(); + refresh(); + estimator->refresh(); result.success = true; result.affectedCount = 1; @@ -1541,18 +1561,33 @@ RideCache::shiftPlannedActivities continue; } + QDate originalDate = QDate::fromString(ride->getTag("Original Date", ""), "yyyy/MM/dd"); + if (! originalDate.isValid()) { + ride->setTag("Original Date", item->dateTime.date().toString("yyyy/MM/dd")); + } item->setStartTime(newDateTime); ride->setTag("Year", newDateTime.toString("yyyy")); ride->setTag("Month", newDateTime.toString("MMMM")); ride->setTag("Weekday", newDateTime.toString("ddd")); + ride->setTag("Filename", newFileName); item->metadata_.insert("Calendar Text", GlobalContext::context()->rideMetadata->calendarText(item)); - item->close(); QString renameError; if (! renameRideFiles(oldFileName, newFileName, true, renameError)) { failedFiles << oldFileName; + item->close(); continue; } + + QString newPath = plannedDirectory.canonicalPath() + "/" + newFileName; + QFile outFile(newPath); + if (! RideFileFactory::instance().writeRideFile(context, ride, outFile, QFileInfo(newFileName).suffix())) { + renameRideFiles(newFileName, oldFileName, true, renameError); + failedFiles << oldFileName; + item->close(); + continue; + } + item->close(); item->setFileName(plannedDirectory.canonicalPath(), newFileName); updateFromWorkout(item, true); item->isstale = true; @@ -1840,6 +1875,7 @@ RideCache::copyPlannedRideFile newRide->setTag("Year", newDateTime.toString("yyyy")); newRide->setTag("Month", newDateTime.toString("MMMM")); newRide->setTag("Weekday", newDateTime.toString("ddd")); + newRide->setTag("Original Date", newDateTime.date().toString("yyyy/MM/dd")); if (! newRide->getTag("Linked Filename", "").isEmpty()) { newRide->removeTag("Linked Filename"); diff --git a/src/Gui/CalendarData.h b/src/Gui/CalendarData.h index 94de6d18b..c73707f47 100644 --- a/src/Gui/CalendarData.h +++ b/src/Gui/CalendarData.h @@ -69,6 +69,7 @@ struct CalendarEntry { bool dirty = false; QDate spanStart = QDate(); QDate spanEnd = QDate(); + QString originalPlanLabel = QString(); QString linkedReference = QString(); QString linkedPrimary = QString(); diff --git a/src/Gui/CalendarItemDelegates.cpp b/src/Gui/CalendarItemDelegates.cpp index 581ad2d4d..0a56a5c16 100644 --- a/src/Gui/CalendarItemDelegates.cpp +++ b/src/Gui/CalendarItemDelegates.cpp @@ -1773,10 +1773,18 @@ toolTipDayEntry(const QPoint &pos, QAbstractItemView *view, const CalendarDay &d } tooltip += QString("%1:%2").arg(QObject::tr("When")).arg(time); } + if (calEntry.type == ENTRY_TYPE_PLANNED_ACTIVITY && ! calEntry.originalPlanLabel.isEmpty()) { + tooltip += QString("%1:%2") + .arg(QObject::tr("Originally")) + .arg(calEntry.originalPlanLabel); + } if (! calEntry.linkedReference.isEmpty()) { QString countertype; countertype = QObject::tr("Linked"); - tooltip += QString("%2:%3").arg(6 * dpiYFactor).arg(countertype).arg(calEntry.linkedPrimary); + tooltip += QString("%2:%3") + .arg(6 * dpiYFactor) + .arg(countertype) + .arg(calEntry.linkedPrimary); if (calEntry.linkedStartDT.isValid()) { QString linkedWhen; QLocale locale; @@ -1786,6 +1794,11 @@ toolTipDayEntry(const QPoint &pos, QAbstractItemView *view, const CalendarDay &d linkedWhen = locale.toString(calEntry.linkedStartDT, QLocale::NarrowFormat); } tooltip += QString("%1:%2").arg(QObject::tr("On")).arg(linkedWhen); + if (calEntry.type == ENTRY_TYPE_ACTUAL_ACTIVITY && ! calEntry.originalPlanLabel.isEmpty()) { + tooltip += QString("%1:%2") + .arg(QObject::tr("Originally")) + .arg(calEntry.originalPlanLabel); + } } } tooltip += ""; diff --git a/src/Gui/ManualActivityWizard.cpp b/src/Gui/ManualActivityWizard.cpp index 6afcbd682..8b266ad8a 100644 --- a/src/Gui/ManualActivityWizard.cpp +++ b/src/Gui/ManualActivityWizard.cpp @@ -111,6 +111,9 @@ ManualActivityWizard::done rideFile.setRecIntSecs(0.00); rideFile.setDeviceType("Manual"); rideFile.setFileFormat("GoldenCheetah Json"); + if (plan) { + rideFile.setTag("Original Date", field("activityDate").toDate().toString("yyyy/MM/dd")); + } field2TagString(rideFile, "sport", "Sport"); field2TagString(rideFile, "subSport", "SubSport");