From f17257885ba0f58ec4653739a0ee941a4e2da193 Mon Sep 17 00:00:00 2001 From: Joachim Kohlhammer Date: Sun, 14 Dec 2025 13:36:52 +0100 Subject: [PATCH] Modify phases and events in calendar (#4753) * Phases and events can be created, edited, deleted in the calendar (all views) * LTMSidebar reacts to modified events * Phase and Event dialogs show the associated season * Menu entries are only active within the current season --- src/Charts/AgendaWindow.cpp | 2 +- src/Charts/CalendarWindow.cpp | 223 +++++++++++++++++++++++++++- src/Charts/CalendarWindow.h | 6 + src/Core/Season.cpp | 11 ++ src/Core/Season.h | 1 + src/Core/SeasonDialogs.cpp | 19 ++- src/Gui/Calendar.cpp | 268 +++++++++++++++++++++++++--------- src/Gui/Calendar.h | 44 +++++- src/Gui/LTMSidebar.cpp | 21 +++ 9 files changed, 512 insertions(+), 83 deletions(-) diff --git a/src/Charts/AgendaWindow.cpp b/src/Charts/AgendaWindow.cpp index 3a25bd03b..1a0becb36 100644 --- a/src/Charts/AgendaWindow.cpp +++ b/src/Charts/AgendaWindow.cpp @@ -786,7 +786,7 @@ AgendaWindow::editEventEntry SeasonEvent *seasonEvent = nullptr; for (Season &s : context->athlete->seasons->seasons) { for (SeasonEvent &event : s.events) { - // FIXME: Ugly comparison required becuase SeasonEvent::id is not populated + // FIXME: Ugly comparison required because SeasonEvent::id is not populated if ( event.name == entry.primary && ( (event.priority == 0 && entry.secondary == tr("Uncategorized")) || (event.priority == 1 && entry.secondary == tr("Category A")) diff --git a/src/Charts/CalendarWindow.cpp b/src/Charts/CalendarWindow.cpp index 428092662..8165ebaed 100644 --- a/src/Charts/CalendarWindow.cpp +++ b/src/Charts/CalendarWindow.cpp @@ -30,6 +30,7 @@ #include "RepeatScheduleWizard.h" #include "WorkoutFilter.h" #include "IconManager.h" +#include "SeasonDialogs.h" #define HLO "

" #define HLC "

" @@ -153,6 +154,12 @@ CalendarWindow::CalendarWindow(Context *context) connect(calendar, &Calendar::dayChanged, this, &CalendarWindow::updateActivities); connect(calendar, &Calendar::monthChanged, this, &CalendarWindow::updateActivities); connect(calendar, &Calendar::viewChanged, this, &CalendarWindow::updateActivities); + connect(calendar, &Calendar::addEvent, this, &CalendarWindow::addEvent); + connect(calendar, &Calendar::editEvent, this, &CalendarWindow::editEvent); + connect(calendar, &Calendar::delEvent, this, &CalendarWindow::delEvent); + connect(calendar, &Calendar::addPhase, this, &CalendarWindow::addPhase); + connect(calendar, &Calendar::editPhase, this, &CalendarWindow::editPhase); + connect(calendar, &Calendar::delPhase, this, &CalendarWindow::delPhase); QTimer::singleShot(0, this, [this]() { configChanged(CONFIG_APPEARANCE); @@ -811,7 +818,11 @@ CalendarWindow::getPhasesEvents entry.iconFile = ":images/breeze/task-process-0.svg"; } entry.color = GColor(CCALEVENT); - entry.reference = event.id; + if (event.id.isEmpty()) { + entry.reference = QString("0x%1").arg(reinterpret_cast(&event), 0, 16); + } else { + entry.reference = event.id; + } entry.start = QTime(0, 0, 0); entry.durationSecs = 0; entry.type = ENTRY_TYPE_EVENT; @@ -897,10 +908,10 @@ CalendarWindow::updateSeason { if (season == nullptr) { DateRange dr(QDate(), QDate(), ""); - calendar->activateDateRange(dr, allowKeepMonth); + calendar->activateDateRange(dr, allowKeepMonth, false); } else { DateRange dr(DateRange(season->getStart(), season->getEnd(), season->getName())); - calendar->activateDateRange(dr, allowKeepMonth); + calendar->activateDateRange(dr, allowKeepMonth, season->canHavePhasesOrEvents()); } } @@ -938,3 +949,209 @@ CalendarWindow::movePlannedActivity } return ret; } + + +void +CalendarWindow::addEvent +(const QDate &date) +{ + Season const *currentSeason = context->currentSeason(); + if (currentSeason == nullptr) { + return; + } + if (! currentSeason->canHavePhasesOrEvents()) { + return; + } + Season *season = nullptr; + for (Season &s : context->athlete->seasons->seasons) { + if (&s == currentSeason) { + season = &s; + break; + } + } + if (season == nullptr) { + return; + } + + SeasonEvent myevent("", date); + EditSeasonEventDialog dialog(context, &myevent, *season); + if (dialog.exec()) { + season->events.append(myevent); + context->athlete->seasons->writeSeasons(); + } +} + + +void +CalendarWindow::editEvent +(const CalendarEntry &entry) +{ + if (entry.type != ENTRY_TYPE_EVENT) { + return; + } + Season *season = nullptr; + SeasonEvent *seasonEvent = nullptr; + for (Season &s : context->athlete->seasons->seasons) { + for (SeasonEvent &event : s.events) { + QString evId = event.id; + if (evId.isEmpty()) { + evId = QString("0x%1").arg(reinterpret_cast(&event), 0, 16); + } + if (entry.reference == evId) { + season = &s; + seasonEvent = &event; + break; + } + } + if (seasonEvent != nullptr) { + break; + } + } + if (seasonEvent == nullptr) { + return; + } + EditSeasonEventDialog dialog(context, seasonEvent, *season); + if (dialog.exec()) { + context->athlete->seasons->writeSeasons(); + } +} + + +void +CalendarWindow::delEvent +(const CalendarEntry &entry) +{ + if (entry.type != ENTRY_TYPE_EVENT) { + return; + } + bool done = false; + for (Season &s : context->athlete->seasons->seasons) { + int idx = 0; + for (SeasonEvent &event : s.events) { + QString evId = event.id; + if (evId.isEmpty()) { + evId = QString("0x%1").arg(reinterpret_cast(&event), 0, 16); + } + if (entry.reference == evId) { + s.events.removeAt(idx); + context->athlete->seasons->writeSeasons(); + done = true; + break; + } + ++idx; + } + if (done) { + break; + } + } +} + + +void +CalendarWindow::addPhase +(const QDate &date) +{ + Season const *currentSeason = context->currentSeason(); + if (currentSeason == nullptr) { + return; + } + if (! currentSeason->canHavePhasesOrEvents()) { + return; + } + Season *phaseSeason = nullptr; + for (Season &s : context->athlete->seasons->seasons) { + if (s.id() == currentSeason->id()) { + phaseSeason = &s; + break; + } + } + if (phaseSeason == nullptr) { + return; + } + QDate seasonStart = phaseSeason->getStart(); + QDate seasonEnd = phaseSeason->getEnd(); + if (seasonStart == seasonEnd) { + return; + } + qint64 daysBefore = std::min(static_cast(6), seasonStart.daysTo(date)); + qint64 daysAfter = std::min(static_cast(6), date.daysTo(seasonEnd)); + QDate start(date); + QDate end(date); + if (daysAfter > 0) { + end = end.addDays(daysAfter); + } else { + start = start.addDays(-daysBefore); + } + Phase myphase("", start, end); + EditPhaseDialog dialog(context, &myphase, *phaseSeason); + if (dialog.exec()) { + phaseSeason->phases.append(myphase); + context->athlete->seasons->writeSeasons(); + } +} + + +void +CalendarWindow::editPhase +(const CalendarEntry &entry) +{ + if (entry.type != ENTRY_TYPE_PHASE) { + return; + } + Season const *currentSeason = context->currentSeason(); + if (currentSeason == nullptr) { + return; + } + Season *phaseSeason = nullptr; + for (Season &s : context->athlete->seasons->seasons) { + if (s.id() == currentSeason->id()) { + phaseSeason = &s; + break; + } + } + if (phaseSeason == nullptr) { + return; + } + for (Phase &editPhase : phaseSeason->phases) { + if (entry.reference == editPhase.id().toString()) { + EditPhaseDialog dialog(context, &editPhase, *phaseSeason); + if (dialog.exec()) { + context->athlete->seasons->writeSeasons(); + } + break; + } + } +} + + +void +CalendarWindow::delPhase +(const CalendarEntry &entry) +{ + if (entry.type != ENTRY_TYPE_PHASE) { + return; + } + Season const *currentSeason = context->currentSeason(); + if (currentSeason == nullptr) { + return; + } + Season *phaseSeason = nullptr; + for (Season &s : context->athlete->seasons->seasons) { + if (s.id() == currentSeason->id()) { + phaseSeason = &s; + break; + } + } + if (phaseSeason == nullptr) { + return; + } + int idx = 0; + for (Phase &editPhase : phaseSeason->phases) { + if (entry.reference == editPhase.id().toString()) { + phaseSeason->phases.removeAt(idx); + context->athlete->seasons->writeSeasons(); + break; + } + ++idx; + } +} diff --git a/src/Charts/CalendarWindow.h b/src/Charts/CalendarWindow.h index 53d5f74ce..1968b7262 100644 --- a/src/Charts/CalendarWindow.h +++ b/src/Charts/CalendarWindow.h @@ -123,6 +123,12 @@ class CalendarWindow : public GcChartWindow void updateActivitiesIfInRange(RideItem *rideItem); void updateSeason(Season const *season, bool allowKeepMonth = false); bool movePlannedActivity(RideItem *rideItem, const QDate &destDay, const QTime &destTime = QTime()); + void addEvent(const QDate &date); + void editEvent(const CalendarEntry &entry); + void delEvent(const CalendarEntry &entry); + void addPhase(const QDate &date); + void editPhase(const CalendarEntry &entry); + void delPhase(const CalendarEntry &entry); }; #endif diff --git a/src/Core/Season.cpp b/src/Core/Season.cpp index 215b34beb..05f392d2e 100644 --- a/src/Core/Season.cpp +++ b/src/Core/Season.cpp @@ -502,6 +502,17 @@ Season::hasPhaseOrEvent } +bool +Season::canHavePhasesOrEvents +() const +{ + return ( getType() == Season::season + || getType() == Season::cycle + || getType() == Season::adhoc) + && isAbsolute(); +} + + bool Season::LessThanForStarts(const Season &a, const Season &b) { return a.getStart().toJulianDay() < b.getStart().toJulianDay(); diff --git a/src/Core/Season.h b/src/Core/Season.h index 42ac1b7ba..405a69648 100644 --- a/src/Core/Season.h +++ b/src/Core/Season.h @@ -197,6 +197,7 @@ class Season bool isAbsolute() const; bool hasPhaseOrEvent() const; + bool canHavePhasesOrEvents() const; static bool LessThanForStarts(const Season &a, const Season &b); diff --git a/src/Core/SeasonDialogs.cpp b/src/Core/SeasonDialogs.cpp index b3946c560..6901fe2ea 100644 --- a/src/Core/SeasonDialogs.cpp +++ b/src/Core/SeasonDialogs.cpp @@ -19,6 +19,7 @@ #include "SeasonDialogs.h" #include "Seasons.h" #include "Athlete.h" +#include "Colors.h" #include #include @@ -152,9 +153,9 @@ EditSeasonDialog::EditSeasonDialog endValueStack->addWidget(new QLabel(tr("Up to current day and month"))); statusLabel = new QLabel(); - statusLabel->setWordWrap(true); + statusLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); warningLabel = new QLabel(); - warningLabel->setWordWrap(true); + warningLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); seedEdit = new QDoubleSpinBox(); seedEdit->setDecimals(0); @@ -174,7 +175,7 @@ EditSeasonDialog::EditSeasonDialog applyButton = buttonBox->addButton(tr("&OK"), QDialogButtonBox::AcceptRole); QPushButton *cancelButton = buttonBox->addButton(tr("&Cancel"), QDialogButtonBox::RejectRole); - QFormLayout *formLayout = new QFormLayout(this); + QFormLayout *formLayout = newQFormLayout(this); formLayout->addRow(tr("Name"), nameEdit); formLayout->addRow(tr("Type"), typeCombo); formLayout->addRow(startCombo, startValueStack); @@ -475,6 +476,9 @@ EditSeasonEventDialog::EditSeasonEventDialog(Context *context, SeasonEvent *even { setWindowTitle(tr("Edit Event")); + QLabel *seasonLabel = new QLabel(); + seasonLabel->setText(season.getName()); + nameEdit = new QLineEdit(this); nameEdit->setText(event->name); @@ -494,7 +498,8 @@ EditSeasonEventDialog::EditSeasonEventDialog(Context *context, SeasonEvent *even applyButton = buttonBox->addButton(tr("&OK"), QDialogButtonBox::AcceptRole); QPushButton *cancelButton = buttonBox->addButton(tr("&Cancel"), QDialogButtonBox::RejectRole); - QFormLayout *formLayout = new QFormLayout(this); + QFormLayout *formLayout = newQFormLayout(this); + formLayout->addRow(tr("Season"), seasonLabel); formLayout->addRow(tr("Name"), nameEdit); formLayout->addRow(tr("Date"), dateEdit); formLayout->addRow(tr("Priority"), priorityEdit); @@ -542,6 +547,9 @@ EditPhaseDialog::EditPhaseDialog(Context *context, Phase *phase, Season &season) { setWindowTitle(tr("Edit Date Range")); + QLabel *seasonLabel = new QLabel(); + seasonLabel->setText(season.getName()); + nameEdit = new QLineEdit(); nameEdit->setText(phase->getName()); @@ -583,7 +591,8 @@ EditPhaseDialog::EditPhaseDialog(Context *context, Phase *phase, Season &season) applyButton = buttonBox->addButton(tr("&OK"), QDialogButtonBox::AcceptRole); QPushButton *cancelButton = buttonBox->addButton(tr("&Cancel"), QDialogButtonBox::RejectRole); - QFormLayout *formLayout = new QFormLayout(this); + QFormLayout *formLayout = newQFormLayout(this); + formLayout->addRow(tr("Season"), seasonLabel); formLayout->addRow(tr("Name"), nameEdit); formLayout->addRow(tr("Type"), typeEdit); formLayout->addRow(tr("From"), fromEdit); diff --git a/src/Gui/Calendar.cpp b/src/Gui/Calendar.cpp index 667507ed3..51513f2fa 100644 --- a/src/Gui/Calendar.cpp +++ b/src/Gui/Calendar.cpp @@ -356,11 +356,12 @@ CalendarDayTable::fillEntries void CalendarDayTable::limitDateRange -(const DateRange &dr) +(const DateRange &dr, bool canHavePhasesOrEvents) { if (dr.from.isValid() && dr.to.isValid() && dr.from > dr.to) { return; } + this->canHavePhasesOrEvents = canHavePhasesOrEvents; this->dr = dr; if (! dr.pass(selectedDate())) { if (dr.pass(lastVisibleDay())) { @@ -687,73 +688,33 @@ CalendarDayTable::showContextMenu (const QPoint &pos) { QModelIndex index = indexAt(pos); + if (! index.isValid()) { + return; + } int row = index.row(); int column = index.column(); - if ( ! index.isValid() - || row != 1 - || column == 0) { + if (pressedIndex.isValid() && (row != 1 || column == 0)) { + QTableWidgetItem *item = this->item(pressedIndex.row(), pressedIndex.column()); + if (item != nullptr) { + item->setData(CalendarDetailedDayDelegate::PressedEntryRole, QVariant()); + } + } + + QMenu *contextMenu = nullptr; + if (row == 0 && column > 0) { + contextMenu = makeHeaderMenu(index, pos); + } else if (row == 1 && column > 0) { + contextMenu = makeActivityMenu(index, pos); + } + if (contextMenu != nullptr) { + contextMenu->exec(viewport()->mapToGlobal(pos)); + delete contextMenu; if (pressedIndex.isValid()) { QTableWidgetItem *item = this->item(pressedIndex.row(), pressedIndex.column()); if (item != nullptr) { item->setData(CalendarDetailedDayDelegate::PressedEntryRole, QVariant()); } } - return; - } - ColumnDelegatingItemDelegate *delegatingDelegate = static_cast(itemDelegateForRow(row)); - CalendarDetailedDayDelegate *delegate = static_cast(delegatingDelegate->getDelegate(column)); - int entryIdx = delegate->entryTester.hitTest(index, pos); - CalendarDay day = index.data(CalendarDetailedDayDelegate::DayRole).value(); - - QMenu contextMenu(this); - if (entryIdx >= 0) { - CalendarEntry calEntry = day.entries[entryIdx]; - switch (calEntry.type) { - case ENTRY_TYPE_ACTIVITY: - contextMenu.addAction(tr("View activity..."), this, [=]() { - emit viewActivity(calEntry); - }); - contextMenu.addAction(tr("Delete activity"), this, [=]() { - emit delActivity(calEntry); - }); - break; - case ENTRY_TYPE_PLANNED_ACTIVITY: - if (calEntry.hasTrainMode) { - contextMenu.addAction(tr("Show in train node..."), this, [=]() { - emit showInTrainMode(calEntry); - }); - } - contextMenu.addAction(tr("View planned activity..."), this, [=]() { - emit viewActivity(calEntry); - }); - contextMenu.addAction(tr("Delete planned activity"), this, [=]() { - emit delActivity(calEntry); - }); - break; - case ENTRY_TYPE_OTHER: - default: - break; - } - } else { - QTime time = timeScaleData.timeFromYInTable(pos.y(), visualRect(index)); - if ( day.date < QDate::currentDate() - || ( day.date == QDate::currentDate() - && time < QTime::currentTime())) { - contextMenu.addAction(tr("Add activity..."), this, [=]() { - emit addActivity(false, day.date, time); - }); - } else { - contextMenu.addAction(tr("Add planned activity..."), this, [=]() { - emit addActivity(true, day.date, time); - }); - } - } - contextMenu.exec(viewport()->mapToGlobal(pos)); - if (pressedIndex.isValid()) { - QTableWidgetItem *item = this->item(pressedIndex.row(), pressedIndex.column()); - if (item != nullptr) { - item->setData(CalendarDetailedDayDelegate::PressedEntryRole, QVariant()); - } } } @@ -768,6 +729,110 @@ CalendarDayTable::setDropIndicator } +QMenu* +CalendarDayTable::makeHeaderMenu +(const QModelIndex &index, const QPoint &pos) +{ + CalendarHeadlineDelegate *delegate = static_cast(itemDelegateForRow(index.row())); + int entryIdx = delegate->headlineTester.hitTest(index, pos); + CalendarDay day = index.data(CalendarHeadlineDelegate::DayRole).value(); + QMenu *contextMenu = new QMenu(this); + if (entryIdx >= 0) { + const CalendarEntry &entry = day.headlineEntries[entryIdx]; + switch (entry.type) { + case ENTRY_TYPE_EVENT: { + QAction *editEventAction = contextMenu->addAction(tr("Edit event..."), this, [=]() { emit editEvent(entry); }); + QAction *delEventAction = contextMenu->addAction(tr("Delete event"), this, [=]() { emit delEvent(entry); }); + if (! isInDateRange(day.date) || ! canHavePhasesOrEvents) { + editEventAction->setEnabled(false); + delEventAction->setEnabled(false); + } + break; + } + case ENTRY_TYPE_PHASE: { + QAction *editPhaseAction = contextMenu->addAction(tr("Edit phase..."), this, [=]() { emit editPhase(entry); }); + QAction *delPhaseAction = contextMenu->addAction(tr("Delete phase..."), this, [=]() { emit delPhase(entry); }); + if (! isInDateRange(day.date) || ! canHavePhasesOrEvents) { + editPhaseAction->setEnabled(false); + delPhaseAction->setEnabled(false); + } + break; + } + default: + break; + } + } else { + QAction *addPhaseAction = contextMenu->addAction(tr("Add phase..."), this, [=]() { emit addPhase(day.date); }); + QAction *addEventAction = contextMenu->addAction(tr("Add event..."), this, [=]() { emit addEvent(day.date); }); + if (! isInDateRange(day.date) || ! canHavePhasesOrEvents) { + addPhaseAction->setEnabled(false); + addEventAction->setEnabled(false); + } + } + return contextMenu; +} + + +QMenu* +CalendarDayTable::makeActivityMenu +(const QModelIndex &index, const QPoint &pos) +{ + ColumnDelegatingItemDelegate *delegatingDelegate = static_cast(itemDelegateForRow(index.row())); + CalendarDetailedDayDelegate *delegate = static_cast(delegatingDelegate->getDelegate(index.column())); + int entryIdx = delegate->entryTester.hitTest(index, pos); + CalendarDay day = index.data(CalendarDetailedDayDelegate::DayRole).value(); + QMenu *contextMenu = new QMenu(this); + if (entryIdx >= 0) { + CalendarEntry calEntry = day.entries[entryIdx]; + switch (calEntry.type) { + case ENTRY_TYPE_ACTIVITY: + contextMenu->addAction(tr("View activity..."), this, [=]() { + emit viewActivity(calEntry); + }); + contextMenu->addAction(tr("Delete activity"), this, [=]() { + emit delActivity(calEntry); + }); + break; + case ENTRY_TYPE_PLANNED_ACTIVITY: + if (calEntry.hasTrainMode) { + contextMenu->addAction(tr("Show in train node..."), this, [=]() { + emit showInTrainMode(calEntry); + }); + } + contextMenu->addAction(tr("View planned activity..."), this, [=]() { + emit viewActivity(calEntry); + }); + contextMenu->addAction(tr("Delete planned activity"), this, [=]() { + emit delActivity(calEntry); + }); + break; + default: + break; + } + } else { + QTime time = timeScaleData.timeFromYInTable(pos.y(), visualRect(index)); + if ( day.date < QDate::currentDate() + || ( day.date == QDate::currentDate() + && time < QTime::currentTime())) { + contextMenu->addAction(tr("Add activity..."), this, [=]() { + emit addActivity(false, day.date, time); + }); + } else { + contextMenu->addAction(tr("Add planned activity..."), this, [=]() { + emit addActivity(true, day.date, time); + }); + } + QAction *addPhaseAction = contextMenu->addAction(tr("Add phase..."), this, [=]() { emit addPhase(day.date); }); + QAction *addEventAction = contextMenu->addAction(tr("Add event..."), this, [=]() { emit addEvent(day.date); }); + if (! isInDateRange(day.date) || ! canHavePhasesOrEvents) { + addPhaseAction->setEnabled(false); + addEventAction->setEnabled(false); + } + } + return contextMenu; +} + + ////////////////////////////////////////////////////////////////////////////// // CalendarMonthTable @@ -958,12 +1023,13 @@ CalendarMonthTable::selectedDate void CalendarMonthTable::limitDateRange -(const DateRange &dr, bool allowKeepMonth) +(const DateRange &dr, bool allowKeepMonth, bool canHavePhasesOrEvents) { if (dr.from.isValid() && dr.to.isValid() && dr.from > dr.to) { return; } this->dr = dr; + this->canHavePhasesOrEvents = canHavePhasesOrEvents; if (currentItem() != nullptr && isInDateRange(currentItem()->data(DateRole).toDate())) { setMonth(currentItem()->data(DateRole).toDate()); } else if (isInDateRange(QDate::currentDate())) { @@ -1296,6 +1362,7 @@ CalendarMonthTable::showContextMenu return; } int entryIdx = delegate->entryTester.hitTest(index, pos); + int headlineEntryIdx = delegate->headlineTester.hitTest(index, pos); CalendarDay day = index.data(CalendarCompactDayDelegate::DayRole).value(); QMenu contextMenu(this); @@ -1323,7 +1390,30 @@ CalendarMonthTable::showContextMenu emit delActivity(calEntry); }); break; - case ENTRY_TYPE_OTHER: + default: + break; + } + } else if (headlineEntryIdx >= 0) { + const CalendarEntry &entry = day.headlineEntries[headlineEntryIdx]; + switch (entry.type) { + case ENTRY_TYPE_EVENT: { + QAction *editEventAction = contextMenu.addAction(tr("Edit event..."), this, [=]() { emit editEvent(entry); }); + QAction *delEventAction = contextMenu.addAction(tr("Delete event"), this, [=]() { emit delEvent(entry); }); + if (! isInDateRange(day.date) || ! canHavePhasesOrEvents) { + editEventAction->setEnabled(false); + delEventAction->setEnabled(false); + } + break; + } + case ENTRY_TYPE_PHASE: { + QAction *editPhaseAction = contextMenu.addAction(tr("Edit phase..."), this, [=]() { emit editPhase(entry); }); + QAction *delPhaseAction = contextMenu.addAction(tr("Delete phase"), this, [=]() { emit delPhase(entry); }); + if (! isInDateRange(day.date) || ! canHavePhasesOrEvents) { + editPhaseAction->setEnabled(false); + delPhaseAction->setEnabled(false); + } + break; + } default: break; } @@ -1345,6 +1435,15 @@ CalendarMonthTable::showContextMenu } emit addActivity(true, day.date, time); }); + } + QAction *addPhaseAction = contextMenu.addAction(tr("Add phase..."), this, [=]() { emit addPhase(day.date); }); + QAction *addEventAction = contextMenu.addAction(tr("Add event..."), this, [=]() { emit addEvent(day.date); }); + if (! isInDateRange(day.date) || ! canHavePhasesOrEvents) { + addPhaseAction->setEnabled(false); + addEventAction->setEnabled(false); + } + if (day.date >= QDate::currentDate()) { + contextMenu.addSeparator(); contextMenu.addAction(tr("Repeat schedule..."), this, [=]() { emit repeatSchedule(day.date); }); @@ -1415,6 +1514,12 @@ CalendarDayView::CalendarDayView connect(dayTable, &CalendarDayTable::showInTrainMode, this, &CalendarDayView::showInTrainMode); connect(dayTable, &CalendarDayTable::delActivity, this, &CalendarDayView::delActivity); connect(dayTable, &CalendarDayTable::entryMoved, this, &CalendarDayView::entryMoved); + connect(dayTable, &CalendarDayTable::addPhase, this, &CalendarDayView::addPhase); + connect(dayTable, &CalendarDayTable::editPhase, this, &CalendarDayView::editPhase); + connect(dayTable, &CalendarDayTable::delPhase, this, &CalendarDayView::delPhase); + connect(dayTable, &CalendarDayTable::addEvent, this, &CalendarDayView::addEvent); + connect(dayTable, &CalendarDayTable::editEvent, this, &CalendarDayView::editEvent); + connect(dayTable, &CalendarDayTable::delEvent, this, &CalendarDayView::delEvent); } @@ -1470,9 +1575,10 @@ CalendarDayView::fillEntries void CalendarDayView::limitDateRange -(const DateRange &dr) +(const DateRange &dr, bool canHavePhasesOrEvents) { dayDateSelector->limitDateRange(dr); + dayTable->limitDateRange(dr, canHavePhasesOrEvents); } @@ -1698,6 +1804,12 @@ CalendarWeekView::CalendarWeekView connect(weekTable, &CalendarDayTable::showInTrainMode, this, &CalendarWeekView::showInTrainMode); connect(weekTable, &CalendarDayTable::delActivity, this, &CalendarWeekView::delActivity); connect(weekTable, &CalendarDayTable::entryMoved, this, &CalendarWeekView::entryMoved); + connect(weekTable, &CalendarDayTable::addPhase, this, &CalendarWeekView::addPhase); + connect(weekTable, &CalendarDayTable::editPhase, this, &CalendarWeekView::editPhase); + connect(weekTable, &CalendarDayTable::delPhase, this, &CalendarWeekView::delPhase); + connect(weekTable, &CalendarDayTable::addEvent, this, &CalendarWeekView::addEvent); + connect(weekTable, &CalendarDayTable::editEvent, this, &CalendarWeekView::editEvent); + connect(weekTable, &CalendarDayTable::delEvent, this, &CalendarWeekView::delEvent); setDay(date); } @@ -1753,9 +1865,9 @@ CalendarWeekView::fillEntries void CalendarWeekView::limitDateRange -(const DateRange &dr) +(const DateRange &dr, bool canHavePhasesOrEvents) { - weekTable->limitDateRange(dr); + weekTable->limitDateRange(dr, canHavePhasesOrEvents); } @@ -1880,6 +1992,12 @@ Calendar::Calendar connect(dayView, &CalendarDayView::showInTrainMode, this, &Calendar::showInTrainMode); connect(dayView, &CalendarDayView::delActivity, this, &Calendar::delActivity); connect(dayView, &CalendarDayView::entryMoved, this, &Calendar::moveActivity); + connect(dayView, &CalendarDayView::addPhase, this, &Calendar::addPhase); + connect(dayView, &CalendarDayView::editPhase, this, &Calendar::editPhase); + connect(dayView, &CalendarDayView::delPhase, this, &Calendar::delPhase); + connect(dayView, &CalendarDayView::addEvent, this, &Calendar::addEvent); + connect(dayView, &CalendarDayView::editEvent, this, &Calendar::editEvent); + connect(dayView, &CalendarDayView::delEvent, this, &Calendar::delEvent); connect(weekView, &CalendarWeekView::dayChanged, [=](const QDate &date) { if (currentView() == CalendarView::Week) { @@ -1893,6 +2011,12 @@ Calendar::Calendar connect(weekView, &CalendarWeekView::showInTrainMode, this, &Calendar::showInTrainMode); connect(weekView, &CalendarWeekView::delActivity, this, &Calendar::delActivity); connect(weekView, &CalendarWeekView::entryMoved, this, &Calendar::moveActivity); + connect(weekView, &CalendarWeekView::addPhase, this, &Calendar::addPhase); + connect(weekView, &CalendarWeekView::editPhase, this, &Calendar::editPhase); + connect(weekView, &CalendarWeekView::delPhase, this, &Calendar::delPhase); + connect(weekView, &CalendarWeekView::addEvent, this, &Calendar::addEvent); + connect(weekView, &CalendarWeekView::editEvent, this, &Calendar::editEvent); + connect(weekView, &CalendarWeekView::delEvent, this, &Calendar::delEvent); connect(monthView, &CalendarMonthTable::entryDblClicked, [=](const CalendarDay &day, int entryIdx) { viewActivity(day.entries[entryIdx]); @@ -1912,6 +2036,12 @@ Calendar::Calendar connect(monthView, &CalendarMonthTable::repeatSchedule, this, &Calendar::repeatSchedule); connect(monthView, &CalendarMonthTable::delActivity, this, &Calendar::delActivity); connect(monthView, &CalendarMonthTable::entryMoved, this, &Calendar::moveActivity); + connect(monthView, &CalendarMonthTable::addPhase, this, &Calendar::addPhase); + connect(monthView, &CalendarMonthTable::editPhase, this, &Calendar::editPhase); + connect(monthView, &CalendarMonthTable::delPhase, this, &Calendar::delPhase); + connect(monthView, &CalendarMonthTable::addEvent, this, &Calendar::addEvent); + connect(monthView, &CalendarMonthTable::editEvent, this, &Calendar::editEvent); + connect(monthView, &CalendarMonthTable::delEvent, this, &Calendar::delEvent); connect(monthView, &CalendarMonthTable::insertRestday, this, &Calendar::insertRestday); connect(monthView, &CalendarMonthTable::delRestday, this, &Calendar::delRestday); connect(monthView, &CalendarMonthTable::monthChanged, [=](const QDate &month, const QDate &firstVisible, const QDate &lastVisible) { @@ -2124,13 +2254,13 @@ Calendar::isInDateRange void Calendar::activateDateRange -(const DateRange &dr, bool allowKeepMonth) +(const DateRange &dr, bool allowKeepMonth, bool canHavePhasesOrEvents) { QDate currentDate = selectedDate(); dateRange = dr; - monthView->limitDateRange(dr, allowKeepMonth); - weekView->limitDateRange(dr); - dayView->limitDateRange(dr); + monthView->limitDateRange(dr, allowKeepMonth, canHavePhasesOrEvents); + weekView->limitDateRange(dr, canHavePhasesOrEvents); + dayView->limitDateRange(dr, canHavePhasesOrEvents); if (currentView() == CalendarView::Day || currentView() == CalendarView::Week) { setDate(currentDate, false); } else if (currentView() == CalendarView::Month) { diff --git a/src/Gui/Calendar.h b/src/Gui/Calendar.h index 9549cb59e..3fbc918d7 100644 --- a/src/Gui/Calendar.h +++ b/src/Gui/Calendar.h @@ -84,7 +84,7 @@ public: QDate selectedDate() const; bool isInDateRange(const QDate &date) const; void fillEntries(const QHash> &activityEntries, const QList &summaries, const QHash> &headlineEntries); - void limitDateRange(const DateRange &dr); + void limitDateRange(const DateRange &dr, bool canHavePhasesOrEvents = false); void setFirstDayOfWeek(Qt::DayOfWeek firstDayOfWeek); void setStartHour(int hour); void setEndHour(int hour); @@ -100,6 +100,12 @@ signals: void viewActivity(const CalendarEntry &activity); void addActivity(bool plan, const QDate &day, const QTime &time); void delActivity(const CalendarEntry &activity); + void addEvent(const QDate &date); + void editEvent(const CalendarEntry &entry); + void delEvent(const CalendarEntry &entry); + void addPhase(const QDate &date); + void editPhase(const CalendarEntry &entry); + void delPhase(const CalendarEntry &entry); protected: void changeEvent(QEvent *event) override; @@ -122,6 +128,7 @@ private: Qt::DayOfWeek firstDayOfWeek = Qt::Monday; QDate date; DateRange dr; + bool canHavePhasesOrEvents = false; CalendarDayTableType type; int defaultStartHour = 8; int defaultEndHour = 21; @@ -133,6 +140,8 @@ private: TimeScaleData timeScaleData; void setDropIndicator(int y, BlockIndicator block); + QMenu *makeHeaderMenu(const QModelIndex &index, const QPoint &pos); + QMenu *makeActivityMenu(const QModelIndex &index, const QPoint &pos); }; @@ -155,7 +164,7 @@ public: QDate firstVisibleDay() const; QDate lastVisibleDay() const; QDate selectedDate() const; - void limitDateRange(const DateRange &dr, bool allowKeepMonth = false); + void limitDateRange(const DateRange &dr, bool allowKeepMonth = false, bool canHavePhasesOrEvents = false); void setFirstDayOfWeek(Qt::DayOfWeek firstDayOfWeek); signals: @@ -180,6 +189,12 @@ signals: void repeatSchedule(const QDate &day); void insertRestday(const QDate &day); void delRestday(const QDate &day); + void addEvent(const QDate &date); + void editEvent(const CalendarEntry &entry); + void delEvent(const CalendarEntry &entry); + void addPhase(const QDate &date); + void editPhase(const CalendarEntry &entry); + void delPhase(const CalendarEntry &entry); protected: void changeEvent(QEvent *event) override; @@ -205,6 +220,7 @@ private: QDate endDate; // last visible date QDate currentDate; // currently selected date DateRange dr; + bool canHavePhasesOrEvents = false; QTimer dragTimer; QPoint pressedPos; @@ -232,7 +248,7 @@ public: void setEndHour(int hour); void setSummaryVisible(bool visible); void fillEntries(const QHash> &activityEntries, const QList &summaries, const QHash> &headlineEntries); - void limitDateRange(const DateRange &dr); + void limitDateRange(const DateRange &dr, bool canHavePhasesOrEvents = false); QDate firstVisibleDay() const; QDate lastVisibleDay() const; QDate selectedDate() const; @@ -245,6 +261,12 @@ signals: void delActivity(const CalendarEntry &activity); void entryMoved(const CalendarEntry &activity, const QDate &srcDay, const QDate &destDay, const QTime &destTime); void dayChanged(const QDate &date); + void addEvent(const QDate &date); + void editEvent(const CalendarEntry &entry); + void delEvent(const CalendarEntry &entry); + void addPhase(const QDate &date); + void editPhase(const CalendarEntry &entry); + void delPhase(const CalendarEntry &entry); private: Measures * const athleteMeasures; @@ -269,7 +291,7 @@ public: void setEndHour(int hour); void setSummaryVisible(bool visible); void fillEntries(const QHash> &activityEntries, const QList &summaries, const QHash> &headlineEntries); - void limitDateRange(const DateRange &dr); + void limitDateRange(const DateRange &dr, bool canHavePhasesOrEvents = false); QDate firstVisibleDay() const; QDate firstVisibleDay(const QDate &date) const; QDate lastVisibleDay() const; @@ -283,6 +305,12 @@ signals: void delActivity(const CalendarEntry &activity); void entryMoved(const CalendarEntry &activity, const QDate &srcDay, const QDate &destDay, const QTime &destTime); void dayChanged(const QDate &date); + void addEvent(const QDate &date); + void editEvent(const CalendarEntry &entry); + void delEvent(const CalendarEntry &entry); + void addPhase(const QDate &date); + void editPhase(const CalendarEntry &entry); + void delPhase(const CalendarEntry &entry); private: CalendarDayTable *weekTable; @@ -311,7 +339,7 @@ public: public slots: void setView(CalendarView view); - void activateDateRange(const DateRange &dr, bool allowKeepMonth = false); + void activateDateRange(const DateRange &dr, bool allowKeepMonth = false, bool canHavePhasesOrEvents = false); void setFirstDayOfWeek(Qt::DayOfWeek firstDayOfWeek); void setStartHour(int hour); void setEndHour(int hour); @@ -337,6 +365,12 @@ signals: void moveActivity(const CalendarEntry &activity, const QDate &srcDay, const QDate &destDay, const QTime &destTime); void insertRestday(const QDate &day); void delRestday(const QDate &day); + void addEvent(const QDate &date); + void editEvent(const CalendarEntry &entry); + void delEvent(const CalendarEntry &entry); + void addPhase(const QDate &date); + void editPhase(const CalendarEntry &entry); + void delPhase(const CalendarEntry &entry); private: QToolBar *toolbar; diff --git a/src/Gui/LTMSidebar.cpp b/src/Gui/LTMSidebar.cpp index 7121e6521..8615e172c 100644 --- a/src/Gui/LTMSidebar.cpp +++ b/src/Gui/LTMSidebar.cpp @@ -460,6 +460,11 @@ LTMSidebar::resetSeasons() // delete it and its children dateRangeTree->clear(); + // clear events - we need to add for currently selected season + for (int i = allEvents->childCount(); i > 0; i--) { + delete allEvents->takeChild(0); + } + // by default choose last 3 months not first one, since the first one is all dates // and that means aggregating all data when first starting... QString id = appsettings->cvalue(context->athlete->cyclist, GC_LTM_LAST_DATE_RANGE, "{00000000-0000-0000-0000-000000000012}").toString(); @@ -486,6 +491,22 @@ LTMSidebar::resetSeasons() addPhase->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); addPhase->setText(0, phase.getName()); } + + // Events + if (context->currentSeason() != nullptr && context->currentSeason()->id() == season.id()) { + // add this seasons events + for (int j = 0; j < season.events.count(); j++) { + SeasonEvent event = season.events.at(j); + QTreeWidgetItem *add = new QTreeWidgetItem(allEvents); + add->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); + add->setText(0, event.name); + add->setText(1, event.date.toString("MMM d, yyyy")); + add->setText(2, SeasonEvent::priorityList().at(event.priority)); + } + + // make sure they fit + eventTree->header()->resizeSections(QHeaderView::ResizeToContents); + } } active = false;