Multi Metrics support for Agenda (#4793)

Agenda used to display entries similar to the Calendar: Primary line for
a field, secondary line for one metric, tertiary line for a multi-line
field.
As the agenda has more space available per line, this change adds
support for multiple metrics in the secondary line. Defaults are
Duration and TriScore.
This commit is contained in:
Joachim Kohlhammer
2026-01-06 19:41:10 +01:00
committed by GitHub
parent 6fde458393
commit 24269f0dbf
8 changed files with 226 additions and 177 deletions

View File

@@ -48,7 +48,6 @@ AgendaWindow::AgendaWindow(Context *context)
setAgendaFutureDays(7); setAgendaFutureDays(7);
setPrimaryMainField("Route"); setPrimaryMainField("Route");
setPrimaryFallbackField("Workout Code"); setPrimaryFallbackField("Workout Code");
setSecondaryMetric("workout_time");
setShowSecondaryLabel(true); setShowSecondaryLabel(true);
setTertiaryField("Notes"); setTertiaryField("Notes");
setShowTertiaryFor(0); setShowTertiaryFor(0);
@@ -69,7 +68,7 @@ AgendaWindow::AgendaWindow(Context *context)
connect(context, &Context::rideDeleted, this, &AgendaWindow::updateActivitiesIfInRange); connect(context, &Context::rideDeleted, this, &AgendaWindow::updateActivitiesIfInRange);
connect(context, &Context::rideChanged, this, &AgendaWindow::updateActivitiesIfInRange); connect(context, &Context::rideChanged, this, &AgendaWindow::updateActivitiesIfInRange);
connect(context, &Context::configChanged, this, &AgendaWindow::configChanged); connect(context, &Context::configChanged, this, &AgendaWindow::configChanged);
connect(agendaView, &AgendaView::showInTrainMode, this, [context](const CalendarEntry &activity) { connect(agendaView, &AgendaView::showInTrainMode, this, [context](const AgendaEntry &activity) {
for (RideItem *rideItem : context->athlete->rideCache->rides()) { for (RideItem *rideItem : context->athlete->rideCache->rides()) {
if (rideItem != nullptr && rideItem->fileName == activity.reference) { if (rideItem != nullptr && rideItem->fileName == activity.reference) {
QString filter = buildWorkoutFilter(rideItem); QString filter = buildWorkoutFilter(rideItem);
@@ -82,7 +81,7 @@ AgendaWindow::AgendaWindow(Context *context)
} }
} }
}); });
connect(agendaView, &AgendaView::viewActivity, this, [context](const CalendarEntry &activity) { connect(agendaView, &AgendaView::viewActivity, this, [context](const AgendaEntry &activity) {
for (RideItem *rideItem : context->athlete->rideCache->rides()) { for (RideItem *rideItem : context->athlete->rideCache->rides()) {
if (rideItem != nullptr && rideItem->fileName == activity.reference) { if (rideItem != nullptr && rideItem->fileName == activity.reference) {
context->notifyRideSelected(rideItem); context->notifyRideSelected(rideItem);
@@ -181,18 +180,34 @@ AgendaWindow::setPrimaryFallbackField
void void
AgendaWindow::setSecondaryMetric AgendaWindow::setSecondaryMetrics
(const QString &name) (const QString &metrics)
{ {
secondaryCombo->setCurrentIndex(std::max(0, secondaryCombo->findData(name))); multiMetricSelector->setSymbols(metrics.split(',', Qt::SkipEmptyParts));
}
void
AgendaWindow::setSecondaryMetrics
(const QStringList &metrics)
{
multiMetricSelector->setSymbols(metrics);
} }
QString QString
AgendaWindow::getSecondaryMetric AgendaWindow::getSecondaryMetrics
() const () const
{ {
return secondaryCombo->currentData(Qt::UserRole).toString(); return multiMetricSelector->getSymbols().join(',');
}
QStringList
AgendaWindow::getSecondaryMetricsList
() const
{
return multiMetricSelector->getSymbols();
} }
@@ -290,7 +305,7 @@ AgendaWindow::configChanged
if ( (what & CONFIG_FIELDS) if ( (what & CONFIG_FIELDS)
|| (what & CONFIG_USERMETRICS)) { || (what & CONFIG_USERMETRICS)) {
updatePrimaryConfigCombos(); updatePrimaryConfigCombos();
updateSecondaryConfigCombo(); multiMetricSelector->updateMetrics();
updateTertiaryConfigCombo(); updateTertiaryConfigCombo();
} }
if (what & CONFIG_APPEARANCE) { if (what & CONFIG_APPEARANCE) {
@@ -370,27 +385,31 @@ AgendaWindow::mkControls
agendaPastDaysSpin = new QSpinBox(); agendaPastDaysSpin = new QSpinBox();
agendaPastDaysSpin->setMaximum(31); agendaPastDaysSpin->setMaximum(31);
agendaPastDaysSpin->setSuffix(" " + tr("day(s)")); agendaPastDaysSpin->setSuffix(" " + tr("day(s)"));
agendaFutureDaysSpin = new QSpinBox(); agendaFutureDaysSpin = new QSpinBox();
agendaFutureDaysSpin->setMaximum(31); agendaFutureDaysSpin->setMaximum(31);
agendaFutureDaysSpin->setSuffix(" " + tr("day(s)")); agendaFutureDaysSpin->setSuffix(" " + tr("day(s)"));
primaryMainCombo = new QComboBox(); primaryMainCombo = new QComboBox();
primaryFallbackCombo = new QComboBox(); primaryFallbackCombo = new QComboBox();
secondaryCombo = new QComboBox();
showSecondaryLabelCheck = new QCheckBox(tr("Show Label")); QStringList summaryMetrics { "workout_time", "triscore" };
multiMetricSelector = new MultiMetricSelector(tr("Available Metrics"), tr("Selected Metrics"), summaryMetrics);
multiMetricSelector->setContentsMargins(10 * dpiXFactor, 10 * dpiYFactor, 10 * dpiXFactor, 10 * dpiYFactor);
multiMetricSelector->setMinimumHeight(300 * dpiYFactor);
QPushButton *gotoMetrics = new QPushButton(tr("Configure Metrics"));
showSecondaryLabelCheck = new QCheckBox(tr("Show Names of Metrics"));
showTertiaryForCombo = new QComboBox(); showTertiaryForCombo = new QComboBox();
tertiaryCombo = new QComboBox(); tertiaryCombo = new QComboBox();
updatePrimaryConfigCombos(); updatePrimaryConfigCombos();
updateSecondaryConfigCombo();
showTertiaryForCombo->addItem(tr("all dates")); showTertiaryForCombo->addItem(tr("all dates"));
showTertiaryForCombo->addItem(tr("today")); showTertiaryForCombo->addItem(tr("today"));
showTertiaryForCombo->addItem(tr("no dates")); showTertiaryForCombo->addItem(tr("no dates"));
updateTertiaryConfigCombo(); updateTertiaryConfigCombo();
primaryMainCombo->setCurrentText("Route"); primaryMainCombo->setCurrentText("Route");
primaryFallbackCombo->setCurrentText("Workout Code"); primaryFallbackCombo->setCurrentText("Workout Code");
int secondaryIndex = secondaryCombo->findData("workout_time");
if (secondaryIndex >= 0) {
secondaryCombo->setCurrentIndex(secondaryIndex);
}
activityMaxTertiaryLinesSpin = new QSpinBox(); activityMaxTertiaryLinesSpin = new QSpinBox();
activityMaxTertiaryLinesSpin->setRange(1, 5); activityMaxTertiaryLinesSpin->setRange(1, 5);
eventMaxTertiaryLinesSpin = new QSpinBox(); eventMaxTertiaryLinesSpin = new QSpinBox();
@@ -407,7 +426,7 @@ AgendaWindow::mkControls
activityForm->addRow(tr("Fallback Field"), primaryFallbackCombo); activityForm->addRow(tr("Fallback Field"), primaryFallbackCombo);
activityForm->addItem(new QSpacerItem(0, 20 * dpiYFactor)); activityForm->addItem(new QSpacerItem(0, 20 * dpiYFactor));
activityForm->addRow(new QLabel(HLO + tr("Metric Line") + HLC)); activityForm->addRow(new QLabel(HLO + tr("Metric Line") + HLC));
activityForm->addRow(tr("Metric"), secondaryCombo); activityForm->addRow("", gotoMetrics);
activityForm->addRow("", showSecondaryLabelCheck); activityForm->addRow("", showSecondaryLabelCheck);
activityForm->addItem(new QSpacerItem(0, 20 * dpiYFactor)); activityForm->addItem(new QSpacerItem(0, 20 * dpiYFactor));
activityForm->addRow(new QLabel(HLO + tr("Detail Line") + HLC)); activityForm->addRow(new QLabel(HLO + tr("Detail Line") + HLC));
@@ -430,13 +449,15 @@ AgendaWindow::mkControls
QTabWidget *controlsTabs = new QTabWidget(); QTabWidget *controlsTabs = new QTabWidget();
controlsTabs->addTab(activityScroller, tr("Activities")); controlsTabs->addTab(activityScroller, tr("Activities"));
controlsTabs->addTab(multiMetricSelector, tr("Metrics"));
controlsTabs->addTab(eventScroller, tr("Events")); controlsTabs->addTab(eventScroller, tr("Events"));
connect(agendaPastDaysSpin, &QSpinBox::valueChanged, this, &AgendaWindow::setAgendaPastDays); connect(agendaPastDaysSpin, &QSpinBox::valueChanged, this, &AgendaWindow::setAgendaPastDays);
connect(agendaFutureDaysSpin, &QSpinBox::valueChanged, this, &AgendaWindow::setAgendaFutureDays); connect(agendaFutureDaysSpin, &QSpinBox::valueChanged, this, &AgendaWindow::setAgendaFutureDays);
connect(primaryMainCombo, &QComboBox::currentIndexChanged, this, &AgendaWindow::updateActivities); connect(primaryMainCombo, &QComboBox::currentIndexChanged, this, &AgendaWindow::updateActivities);
connect(primaryFallbackCombo, &QComboBox::currentIndexChanged, this, &AgendaWindow::updateActivities); connect(primaryFallbackCombo, &QComboBox::currentIndexChanged, this, &AgendaWindow::updateActivities);
connect(secondaryCombo, &QComboBox::currentIndexChanged, this, &AgendaWindow::updateActivities); connect(multiMetricSelector, &MultiMetricSelector::selectedChanged, this, &AgendaWindow::updateActivities);
connect(gotoMetrics, &QPushButton::clicked, this, [controlsTabs]() { controlsTabs->setCurrentIndex(1); });
connect(showTertiaryForCombo, &QComboBox::currentIndexChanged, this, &AgendaWindow::updateActivities); connect(showTertiaryForCombo, &QComboBox::currentIndexChanged, this, &AgendaWindow::updateActivities);
connect(tertiaryCombo, &QComboBox::currentIndexChanged, this, &AgendaWindow::updateActivities); connect(tertiaryCombo, &QComboBox::currentIndexChanged, this, &AgendaWindow::updateActivities);
connect(activityMaxTertiaryLinesSpin, &QSpinBox::valueChanged, this, &AgendaWindow::setActivityMaxTertiaryLines); connect(activityMaxTertiaryLinesSpin, &QSpinBox::valueChanged, this, &AgendaWindow::setActivityMaxTertiaryLines);
@@ -473,27 +494,6 @@ AgendaWindow::updatePrimaryConfigCombos
} }
void
AgendaWindow::updateSecondaryConfigCombo
()
{
QString symbol = getSecondaryMetric();
secondaryCombo->blockSignals(true);
secondaryCombo->clear();
const RideMetricFactory &factory = RideMetricFactory::instance();
for (const QString &metricSymbol : factory.allMetrics()) {
if (metricSymbol.startsWith("compatibility_")) {
continue;
}
secondaryCombo->addItem(Utils::unprotect(factory.rideMetric(metricSymbol)->name()), metricSymbol);
}
secondaryCombo->blockSignals(false);
setSecondaryMetric(symbol);
}
void void
AgendaWindow::updateTertiaryConfigCombo AgendaWindow::updateTertiaryConfigCombo
() ()
@@ -514,26 +514,33 @@ AgendaWindow::updateTertiaryConfigCombo
} }
QHash<QDate, QList<CalendarEntry>> QHash<QDate, QList<AgendaEntry>>
AgendaWindow::getActivities AgendaWindow::getActivities
(const QDate &firstDay, const QDate &today, const QDate &lastDay) const (const QDate &firstDay, const QDate &today, const QDate &lastDay) const
{ {
QHash<QDate, QList<CalendarEntry>> activities; QHash<QDate, QList<AgendaEntry>> activities;
const RideMetricFactory &factory = RideMetricFactory::instance(); const RideMetricFactory &factory = RideMetricFactory::instance();
const RideMetric *rideMetric = factory.rideMetric(getSecondaryMetric()); QList<RideMetric const *> rideMetrics;
QString rideMetricName; QStringList rideMetricNames;
QString rideMetricUnit; QStringList rideMetricUnits;
if (rideMetric != nullptr) { for (const QString &metric : getSecondaryMetricsList()) {
rideMetricName = rideMetric->name(); RideMetric const *rideMetric = factory.rideMetric(metric);
if ( ! rideMetric->isTime() if (rideMetric != nullptr) {
&& ! rideMetric->isDate()) { rideMetrics << rideMetric;
rideMetricUnit = rideMetric->units(GlobalContext::context()->useMetricUnits); rideMetricNames << rideMetric->name();
if ( ! rideMetric->isTime()
&& ! rideMetric->isDate()) {
rideMetricUnits << rideMetric->units(GlobalContext::context()->useMetricUnits);
} else {
rideMetricUnits << "";
}
} }
} }
int showTertiaryFor = getShowTertiaryFor(); int showTertiaryFor = getShowTertiaryFor();
for (RideItem *rideItem : context->athlete->rideCache->rides()) { for (RideItem *rideItem : context->athlete->rideCache->rides()) {
if ( rideItem == nullptr if ( rideItem == nullptr
|| ! rideItem->planned
|| rideItem->dateTime.date() < firstDay || rideItem->dateTime.date() < firstDay
|| rideItem->dateTime.date() > lastDay || rideItem->dateTime.date() > lastDay
|| rideItem->hasLinkedActivity()) { || rideItem->hasLinkedActivity()) {
@@ -545,7 +552,7 @@ AgendaWindow::getActivities
} }
QString sport = rideItem->sport; QString sport = rideItem->sport;
CalendarEntry activity; AgendaEntry activity;
QString primaryMain = rideItem->getText(getPrimaryMainField(), "").trimmed(); QString primaryMain = rideItem->getText(getPrimaryMainField(), "").trimmed();
if (! primaryMain.isEmpty()) { if (! primaryMain.isEmpty()) {
@@ -560,42 +567,41 @@ AgendaWindow::getActivities
activity.primary = tr("<unknown>"); activity.primary = tr("<unknown>");
} }
} }
if (rideMetric != nullptr && rideMetric->isRelevantForRide(rideItem)) { for (int i = 0; i < rideMetrics.count(); ++i) {
activity.secondary = rideItem->getStringForSymbol(getSecondaryMetric(), GlobalContext::context()->useMetricUnits); RideMetric const *rideMetric = rideMetrics[i];
if (! rideMetricUnit.isEmpty()) { if (rideMetric->isRelevantForRide(rideItem)) {
activity.secondary += " " + rideMetricUnit; QString value = rideItem->getStringForSymbol(rideMetric->symbol(), GlobalContext::context()->useMetricUnits);
if (! rideMetricUnits.value(i, "").isEmpty()) {
value.append(" " + rideMetricUnits.value(i, ""));
}
activity.secondaryValues << Utils::unprotect(value);
if (isShowSecondaryLabel()) {
activity.secondaryLabels << Utils::unprotect(rideMetricNames.value(i, ""));
} else {
activity.secondaryLabels << "";
}
} }
if (isShowSecondaryLabel()) { }
activity.secondaryMetric = rideMetricName; if (activity.secondaryValues.count() == 0) {
} activity.secondaryValues << tr("N/A");
} else { activity.secondaryLabels << "";
activity.secondary = tr("N/A");
activity.secondaryMetric = "";
} }
if (showTertiaryFor == 0 || (showTertiaryFor == 1 && rideItem->dateTime.date() == today)) { if (showTertiaryFor == 0 || (showTertiaryFor == 1 && rideItem->dateTime.date() == today)) {
activity.tertiary = rideItem->getText(getTertiaryField(), "").trimmed(); activity.tertiary = rideItem->getText(getTertiaryField(), "").trimmed();
activity.tertiary = Utils::unprotect(activity.tertiary); activity.tertiary = Utils::unprotect(activity.tertiary);
} }
activity.primary = Utils::unprotect(activity.primary); activity.primary = Utils::unprotect(activity.primary);
activity.secondary = Utils::unprotect(activity.secondary);
activity.secondaryMetric = Utils::unprotect(activity.secondaryMetric);
activity.iconFile = IconManager::instance().getFilepath(rideItem); activity.iconFile = IconManager::instance().getFilepath(rideItem);
if (rideItem->color.alpha() < 255 || rideItem->planned) { activity.color = GColor(CCALPLANNED);
activity.color = GColor(CCALPLANNED);
} else {
activity.color = rideItem->color;
}
activity.reference = rideItem->fileName; activity.reference = rideItem->fileName;
activity.start = rideItem->dateTime.time(); activity.start = rideItem->dateTime.time();
activity.durationSecs = rideItem->getForSymbol("workout_time", GlobalContext::context()->useMetricUnits); activity.type = ENTRY_TYPE_PLANNED_ACTIVITY;
activity.type = rideItem->planned ? ENTRY_TYPE_PLANNED_ACTIVITY : ENTRY_TYPE_ACTIVITY; activity.hasTrainMode = sport == "Bike" && ! buildWorkoutFilter(rideItem).isEmpty();
activity.isRelocatable = rideItem->planned;
activity.hasTrainMode = rideItem->planned && sport == "Bike" && ! buildWorkoutFilter(rideItem).isEmpty();
activities[rideItem->dateTime.date()] << activity; activities[rideItem->dateTime.date()] << activity;
} }
for (auto dayIt = activities.begin(); dayIt != activities.end(); ++dayIt) { for (auto dayIt = activities.begin(); dayIt != activities.end(); ++dayIt) {
std::sort(dayIt.value().begin(), dayIt.value().end(), [](const CalendarEntry &a, const CalendarEntry &b) { std::sort(dayIt.value().begin(), dayIt.value().end(), [](const AgendaEntry &a, const AgendaEntry &b) {
if (a.start == b.start) { if (a.start == b.start) {
return a.primary < b.primary; return a.primary < b.primary;
} else { } else {
@@ -607,12 +613,12 @@ AgendaWindow::getActivities
} }
std::pair<QList<CalendarEntry>, QList<CalendarEntry>> std::pair<QList<AgendaEntry>, QList<AgendaEntry>>
AgendaWindow::getPhases AgendaWindow::getPhases
(const Season &season, const QDate &firstDay) const (const Season &season, const QDate &firstDay) const
{ {
QList<CalendarEntry> ongoingPhases; QList<AgendaEntry> ongoingPhases;
QList<CalendarEntry> futurePhases; QList<AgendaEntry> futurePhases;
for (const Phase &phase : season.phases) { for (const Phase &phase : season.phases) {
if (phase.getAbsoluteStart().isValid() && phase.getAbsoluteEnd().isValid()) { if (phase.getAbsoluteStart().isValid() && phase.getAbsoluteEnd().isValid()) {
QString phaseType; QString phaseType;
@@ -623,37 +629,36 @@ AgendaWindow::getPhases
default: default:
phaseType = Phase::types[static_cast<int>(phase.getType()) - static_cast<int>(Phase::phase)]; phaseType = Phase::types[static_cast<int>(phase.getType()) - static_cast<int>(Phase::phase)];
} }
CalendarEntry entry; AgendaEntry entry;
entry.primary = phase.getName(); entry.primary = phase.getName();
entry.iconFile = ":images/breeze/network-mobile-100.svg"; entry.iconFile = ":images/breeze/network-mobile-100.svg";
entry.color = GColor(CCALPHASE); entry.color = GColor(CCALPHASE);
entry.reference = phase.id().toString(); entry.reference = phase.id().toString();
entry.start = QTime(0, 0, 1); entry.start = QTime(0, 0, 1);
entry.type = ENTRY_TYPE_PHASE; entry.type = ENTRY_TYPE_PHASE;
entry.isRelocatable = false;
entry.spanStart = phase.getAbsoluteStart(); entry.spanStart = phase.getAbsoluteStart();
entry.spanEnd = phase.getAbsoluteEnd(); entry.spanEnd = phase.getAbsoluteEnd();
int duration = entry.spanStart.daysTo(entry.spanEnd); int duration = entry.spanStart.daysTo(entry.spanEnd);
ShowDaysAsUnit unit = showDaysAs(duration); ShowDaysAsUnit unit = showDaysAs(duration);
if (unit == ShowDaysAsUnit::Days) { if (unit == ShowDaysAsUnit::Days) {
if (duration > 1) { if (duration > 1) {
entry.secondary = tr("%1 • %2 days").arg(phaseType).arg(duration); entry.secondaryValues << tr("%1 • %2 days").arg(phaseType).arg(duration);
} else { } else {
entry.secondary = tr("%1 • %2 day").arg(phaseType).arg(duration); entry.secondaryValues << tr("%1 • %2 day").arg(phaseType).arg(duration);
} }
} else if (unit == ShowDaysAsUnit::Weeks) { } else if (unit == ShowDaysAsUnit::Weeks) {
duration = daysToWeeks(duration); duration = daysToWeeks(duration);
if (duration > 1) { if (duration > 1) {
entry.secondary = tr("%1 • %2 weeks").arg(phaseType).arg(duration); entry.secondaryValues << tr("%1 • %2 weeks").arg(phaseType).arg(duration);
} else { } else {
entry.secondary = tr("%1 • %2 week").arg(phaseType).arg(duration); entry.secondaryValues << tr("%1 • %2 week").arg(phaseType).arg(duration);
} }
} else { } else {
duration = daysToMonths(duration); duration = daysToMonths(duration);
if (duration > 1) { if (duration > 1) {
entry.secondary = tr("%1 • %2 months").arg(phaseType).arg(duration); entry.secondaryValues << tr("%1 • %2 months").arg(phaseType).arg(duration);
} else { } else {
entry.secondary = tr("%1 • %2 month").arg(phaseType).arg(duration); entry.secondaryValues << tr("%1 • %2 month").arg(phaseType).arg(duration);
} }
} }
if (phase.getAbsoluteStart() <= firstDay && phase.getAbsoluteEnd() >= firstDay) { if (phase.getAbsoluteStart() <= firstDay && phase.getAbsoluteEnd() >= firstDay) {
@@ -663,10 +668,10 @@ AgendaWindow::getPhases
} }
} }
} }
std::sort(ongoingPhases.begin(), ongoingPhases.end(), [](const CalendarEntry &a, const CalendarEntry &b) { std::sort(ongoingPhases.begin(), ongoingPhases.end(), [](const AgendaEntry &a, const AgendaEntry &b) {
return a.spanEnd < b.spanEnd; return a.spanEnd < b.spanEnd;
}); });
std::sort(futurePhases.begin(), futurePhases.end(), [](const CalendarEntry &a, const CalendarEntry &b) { std::sort(futurePhases.begin(), futurePhases.end(), [](const AgendaEntry &a, const AgendaEntry &b) {
return a.spanStart < b.spanStart; return a.spanStart < b.spanStart;
}); });
@@ -674,11 +679,11 @@ AgendaWindow::getPhases
} }
QHash<QDate, QList<CalendarEntry>> QHash<QDate, QList<AgendaEntry>>
AgendaWindow::getEvents AgendaWindow::getEvents
(const QDate &firstDay) const (const QDate &firstDay) const
{ {
QHash<QDate, QList<CalendarEntry>> events; QHash<QDate, QList<AgendaEntry>> events;
QList<Season> tmpSeasons = context->athlete->seasons->seasons; QList<Season> tmpSeasons = context->athlete->seasons->seasons;
std::sort(tmpSeasons.begin(), tmpSeasons.end(), Season::LessThanForStarts); std::sort(tmpSeasons.begin(), tmpSeasons.end(), Season::LessThanForStarts);
for (const Season &s : tmpSeasons) { for (const Season &s : tmpSeasons) {
@@ -686,36 +691,34 @@ AgendaWindow::getEvents
if ( ( ( firstDay.isValid() if ( ( ( firstDay.isValid()
&& event.date >= firstDay) && event.date >= firstDay)
|| ! firstDay.isValid())) { || ! firstDay.isValid())) {
CalendarEntry entry; AgendaEntry entry;
entry.primary = event.name; entry.primary = event.name;
if (event.priority == 0) { if (event.priority == 0) {
entry.iconFile = ":images/breeze/task-process-4.svg"; entry.iconFile = ":images/breeze/task-process-4.svg";
entry.secondary = tr("Uncategorized"); entry.secondaryValues << tr("Uncategorized");
} else if (event.priority == 1) { } else if (event.priority == 1) {
entry.iconFile = ":images/breeze/task-process-4.svg"; entry.iconFile = ":images/breeze/task-process-4.svg";
entry.secondary = tr("Category A"); entry.secondaryValues << tr("Category A");
} else if (event.priority == 2) { } else if (event.priority == 2) {
entry.iconFile = ":images/breeze/task-process-3.svg"; entry.iconFile = ":images/breeze/task-process-3.svg";
entry.secondary = tr("Category B"); entry.secondaryValues << tr("Category B");
} else if (event.priority == 3) { } else if (event.priority == 3) {
entry.iconFile = ":images/breeze/task-process-2.svg"; entry.iconFile = ":images/breeze/task-process-2.svg";
entry.secondary = tr("Category C"); entry.secondaryValues << tr("Category C");
} else if (event.priority == 4) { } else if (event.priority == 4) {
entry.iconFile = ":images/breeze/task-process-1.svg"; entry.iconFile = ":images/breeze/task-process-1.svg";
entry.secondary = tr("Category D"); entry.secondaryValues << tr("Category D");
} else { } else {
entry.iconFile = ":images/breeze/task-process-0.svg"; entry.iconFile = ":images/breeze/task-process-0.svg";
entry.secondary = tr("Category E"); entry.secondaryValues << tr("Category E");
} }
entry.tertiary = event.description.trimmed(); entry.tertiary = event.description.trimmed();
entry.color = GColor(CCALEVENT); entry.color = GColor(CCALEVENT);
entry.reference = event.id; entry.reference = event.id;
entry.start = QTime(0, 0, 0); entry.start = QTime(0, 0, 0);
entry.durationSecs = 0;
entry.type = ENTRY_TYPE_EVENT; entry.type = ENTRY_TYPE_EVENT;
entry.spanStart = event.date; entry.spanStart = event.date;
entry.spanEnd = event.date; entry.spanEnd = event.date;
entry.isRelocatable = false;
events[event.date] << entry; events[event.date] << entry;
} }
} }
@@ -733,9 +736,9 @@ AgendaWindow::updateActivities
agendaView->updateDate(); agendaView->updateDate();
return; return;
} }
QHash<QDate, QList<CalendarEntry>> activities = getActivities(agendaView->firstVisibleDay(), agendaView->selectedDate(), agendaView->lastVisibleDay()); QHash<QDate, QList<AgendaEntry>> activities = getActivities(agendaView->firstVisibleDay(), agendaView->selectedDate(), agendaView->lastVisibleDay());
std::pair<QList<CalendarEntry>, QList<CalendarEntry>> phases; std::pair<QList<AgendaEntry>, QList<AgendaEntry>> phases;
QHash<QDate, QList<CalendarEntry>> events; QHash<QDate, QList<AgendaEntry>> events;
QString seasonName; QString seasonName;
tertiaryCombo->setEnabled(getShowTertiaryFor() != 2); tertiaryCombo->setEnabled(getShowTertiaryFor() != 2);
@@ -765,7 +768,7 @@ AgendaWindow::updateActivitiesIfInRange
void void
AgendaWindow::editPhaseEntry AgendaWindow::editPhaseEntry
(const CalendarEntry &entry) (const AgendaEntry &entry)
{ {
if (entry.type != ENTRY_TYPE_PHASE) { if (entry.type != ENTRY_TYPE_PHASE) {
return; return;
@@ -799,7 +802,7 @@ AgendaWindow::editPhaseEntry
void void
AgendaWindow::editEventEntry AgendaWindow::editEventEntry
(const CalendarEntry &entry) (const AgendaEntry &entry)
{ {
if (entry.type != ENTRY_TYPE_EVENT) { if (entry.type != ENTRY_TYPE_EVENT) {
return; return;
@@ -810,13 +813,14 @@ AgendaWindow::editEventEntry
for (SeasonEvent &event : s.events) { for (SeasonEvent &event : s.events) {
// FIXME: Ugly comparison required because SeasonEvent::id is not populated // FIXME: Ugly comparison required because SeasonEvent::id is not populated
if ( event.name == entry.primary if ( event.name == entry.primary
&& ( (event.priority == 0 && entry.secondary == tr("Uncategorized")) && entry.secondaryValues.count() == 1
|| (event.priority == 1 && entry.secondary == tr("Category A")) && ( (event.priority == 0 && entry.secondaryValues[0] == tr("Uncategorized"))
|| (event.priority == 2 && entry.secondary == tr("Category B")) || (event.priority == 1 && entry.secondaryValues[0] == tr("Category A"))
|| (event.priority == 3 && entry.secondary == tr("Category C")) || (event.priority == 2 && entry.secondaryValues[0] == tr("Category B"))
|| (event.priority == 4 && entry.secondary == tr("Category D")) || (event.priority == 3 && entry.secondaryValues[0] == tr("Category C"))
|| (event.priority == 4 && entry.secondaryValues[0] == tr("Category D"))
|| ( (event.priority < 0 || event.priority > 4) || ( (event.priority < 0 || event.priority > 4)
&& entry.secondary == tr("Category E"))) && entry.secondaryValues[0] == tr("Category E")))
&& event.description.trimmed() == entry.tertiary && event.description.trimmed() == entry.tertiary
&& event.id == entry.reference && event.id == entry.reference
&& event.date == entry.spanStart && event.date == entry.spanStart

View File

@@ -42,7 +42,9 @@ class AgendaWindow : public GcChartWindow
Q_PROPERTY(int agendaFutureDays READ getAgendaFutureDays WRITE setAgendaFutureDays USER true) Q_PROPERTY(int agendaFutureDays READ getAgendaFutureDays WRITE setAgendaFutureDays USER true)
Q_PROPERTY(QString primaryMainField READ getPrimaryMainField WRITE setPrimaryMainField USER true) Q_PROPERTY(QString primaryMainField READ getPrimaryMainField WRITE setPrimaryMainField USER true)
Q_PROPERTY(QString primaryFallbackField READ getPrimaryFallbackField WRITE setPrimaryFallbackField USER true) Q_PROPERTY(QString primaryFallbackField READ getPrimaryFallbackField WRITE setPrimaryFallbackField USER true)
Q_PROPERTY(QString secondaryMetric READ getSecondaryMetric WRITE setSecondaryMetric USER true)
Q_PROPERTY(QString secondaryMetrics READ getSecondaryMetrics WRITE setSecondaryMetrics USER true)
Q_PROPERTY(bool showSecondaryLabel READ isShowSecondaryLabel WRITE setShowSecondaryLabel USER true) Q_PROPERTY(bool showSecondaryLabel READ isShowSecondaryLabel WRITE setShowSecondaryLabel USER true)
Q_PROPERTY(int showTertiaryFor READ getShowTertiaryFor WRITE setShowTertiaryFor USER true) Q_PROPERTY(int showTertiaryFor READ getShowTertiaryFor WRITE setShowTertiaryFor USER true)
Q_PROPERTY(QString tertiaryField READ getTertiaryField WRITE setTertiaryField USER true) Q_PROPERTY(QString tertiaryField READ getTertiaryField WRITE setTertiaryField USER true)
@@ -59,7 +61,8 @@ class AgendaWindow : public GcChartWindow
QString getPrimaryMainField() const; QString getPrimaryMainField() const;
QString getPrimaryFallbackField() const; QString getPrimaryFallbackField() const;
QString getSecondaryMetric() const; QString getSecondaryMetrics() const;
QStringList getSecondaryMetricsList() const;
bool isShowSecondaryLabel() const; bool isShowSecondaryLabel() const;
int getShowTertiaryFor() const; int getShowTertiaryFor() const;
QString getTertiaryField() const; QString getTertiaryField() const;
@@ -71,7 +74,8 @@ class AgendaWindow : public GcChartWindow
void setAgendaFutureDays(int days); void setAgendaFutureDays(int days);
void setPrimaryMainField(const QString &name); void setPrimaryMainField(const QString &name);
void setPrimaryFallbackField(const QString &name); void setPrimaryFallbackField(const QString &name);
void setSecondaryMetric(const QString &name); void setSecondaryMetrics(const QString &metrics);
void setSecondaryMetrics(const QStringList &metrics);
void setShowSecondaryLabel(bool showSecondaryLabel); void setShowSecondaryLabel(bool showSecondaryLabel);
void setShowTertiaryFor(int showFor); void setShowTertiaryFor(int showFor);
void setTertiaryField(const QString &name); void setTertiaryField(const QString &name);
@@ -90,7 +94,7 @@ class AgendaWindow : public GcChartWindow
QSpinBox *agendaFutureDaysSpin; QSpinBox *agendaFutureDaysSpin;
QComboBox *primaryMainCombo; QComboBox *primaryMainCombo;
QComboBox *primaryFallbackCombo; QComboBox *primaryFallbackCombo;
QComboBox *secondaryCombo; MultiMetricSelector *multiMetricSelector;
QCheckBox *showSecondaryLabelCheck; QCheckBox *showSecondaryLabelCheck;
QComboBox *showTertiaryForCombo; QComboBox *showTertiaryForCombo;
QComboBox *tertiaryCombo; QComboBox *tertiaryCombo;
@@ -100,17 +104,16 @@ class AgendaWindow : public GcChartWindow
void mkControls(); void mkControls();
void updatePrimaryConfigCombos(); void updatePrimaryConfigCombos();
void updateSecondaryConfigCombo();
void updateTertiaryConfigCombo(); void updateTertiaryConfigCombo();
QHash<QDate, QList<CalendarEntry>> getActivities(const QDate &firstDay, const QDate &today, const QDate &lastDay) const; QHash<QDate, QList<AgendaEntry>> getActivities(const QDate &firstDay, const QDate &today, const QDate &lastDay) const;
std::pair<QList<CalendarEntry>, QList<CalendarEntry>> getPhases(const Season &season, const QDate &firstDay) const; std::pair<QList<AgendaEntry>, QList<AgendaEntry>> getPhases(const Season &season, const QDate &firstDay) const;
QHash<QDate, QList<CalendarEntry>> getEvents(const QDate &firstDay) const; QHash<QDate, QList<AgendaEntry>> getEvents(const QDate &firstDay) const;
private slots: private slots:
void updateActivities(); void updateActivities();
void updateActivitiesIfInRange(RideItem *rideItem); void updateActivitiesIfInRange(RideItem *rideItem);
void editPhaseEntry(const CalendarEntry &entry); void editPhaseEntry(const AgendaEntry &entry);
void editEventEntry(const CalendarEntry &entry); void editEventEntry(const AgendaEntry &entry);
}; };
#endif #endif

View File

@@ -377,7 +377,7 @@ ActivityTree::ActivityTree
QVariant data = item->data(1, AgendaEntryDelegate::EntryRole); QVariant data = item->data(1, AgendaEntryDelegate::EntryRole);
if (! data.isNull()) { if (! data.isNull()) {
CalendarEntry entry = data.value<CalendarEntry>(); AgendaEntry entry = data.value<AgendaEntry>();
if (entry.type == ENTRY_TYPE_PLANNED_ACTIVITY) { if (entry.type == ENTRY_TYPE_PLANNED_ACTIVITY) {
emit viewActivity(entry); emit viewActivity(entry);
} }
@@ -406,7 +406,7 @@ ActivityTree::setFutureDays
void void
ActivityTree::fillEntries ActivityTree::fillEntries
(const QHash<QDate, QList<CalendarEntry>> &activities) (const QHash<QDate, QList<AgendaEntry>> &activities)
{ {
QDate today = selectedDate(); QDate today = selectedDate();
QDate pastFirst = today.addDays(-pastDays); QDate pastFirst = today.addDays(-pastDays);
@@ -417,13 +417,13 @@ ActivityTree::fillEntries
bool todayHasPlanned = false; bool todayHasPlanned = false;
QList<QDate> missedDays; QList<QDate> missedDays;
QList<QDate> upcomingDays; QList<QDate> upcomingDays;
QHash<QDate, QList<CalendarEntry>> missedEntries; QHash<QDate, QList<AgendaEntry>> missedEntries;
QList<CalendarEntry> todayEntries; QList<AgendaEntry> todayEntries;
QHash<QDate, QList<CalendarEntry>> upcomingEntries; QHash<QDate, QList<AgendaEntry>> upcomingEntries;
for (QDate date = pastFirst; date <= futureLast; date = date.addDays(1)) { for (QDate date = pastFirst; date <= futureLast; date = date.addDays(1)) {
QList<CalendarEntry> dateEntries; QList<AgendaEntry> dateEntries;
dateEntries = activities.value(date); dateEntries = activities.value(date);
for (const CalendarEntry &entry : dateEntries) { for (const AgendaEntry &entry : dateEntries) {
if (entry.type == ENTRY_TYPE_PLANNED_ACTIVITY) { if (entry.type == ENTRY_TYPE_PLANNED_ACTIVITY) {
if (date < today) { if (date < today) {
if (! missedDays.contains(date)) { if (! missedDays.contains(date)) {
@@ -547,29 +547,30 @@ ActivityTree::createContextMenu
if (entryData.isNull()) { if (entryData.isNull()) {
return nullptr; return nullptr;
} }
CalendarEntry entry = entryData.value<CalendarEntry>(); AgendaEntry entry = entryData.value<AgendaEntry>();
if (entry.type != ENTRY_TYPE_PLANNED_ACTIVITY) { if (entry.type != ENTRY_TYPE_PLANNED_ACTIVITY) {
return nullptr; return nullptr;
} }
QMenu *contextMenu = new QMenu(this); QMenu *contextMenu = new QMenu(this);
contextMenu->addAction(tr("View planned activity..."), this, [this, entry]() {
emit viewActivity(entry);
});
if (entry.hasTrainMode) { if (entry.hasTrainMode) {
contextMenu->addSeparator();
contextMenu->addAction(tr("Show in train mode..."), this, [this, entry]() { contextMenu->addAction(tr("Show in train mode..."), this, [this, entry]() {
emit showInTrainMode(entry); emit showInTrainMode(entry);
}); });
} }
contextMenu->addAction(tr("View planned activity..."), this, [this, entry]() {
emit viewActivity(entry);
});
return contextMenu; return contextMenu;
} }
void void
ActivityTree::addEntries ActivityTree::addEntries
(const QDate &today, const QDate &date, const QList<CalendarEntry> &activities, QTreeWidgetItem *parent, const Fonts &fonts) (const QDate &today, const QDate &date, const QList<AgendaEntry> &activities, QTreeWidgetItem *parent, const Fonts &fonts)
{ {
int activityIdx = 0; int activityIdx = 0;
for (const CalendarEntry &activity : activities) { for (const AgendaEntry &activity : activities) {
QTreeWidgetItem *entryItem = new QTreeWidgetItem(); QTreeWidgetItem *entryItem = new QTreeWidgetItem();
QString diffStr; QString diffStr;
int diff = date.daysTo(today); int diff = date.daysTo(today);
@@ -607,7 +608,7 @@ ActivityTree::addEntries
void void
PhaseTree::fillEntries PhaseTree::fillEntries
(const std::pair<QList<CalendarEntry>, QList<CalendarEntry>> &phases) (const std::pair<QList<AgendaEntry>, QList<AgendaEntry>> &phases)
{ {
QDate today = selectedDate(); QDate today = selectedDate();
if (! today.isValid()) { if (! today.isValid()) {
@@ -665,7 +666,7 @@ PhaseTree::createContextMenu
if (entryData.isNull()) { if (entryData.isNull()) {
return nullptr; return nullptr;
} }
CalendarEntry entry = entryData.value<CalendarEntry>(); AgendaEntry entry = entryData.value<AgendaEntry>();
if (entry.type != ENTRY_TYPE_PHASE) { if (entry.type != ENTRY_TYPE_PHASE) {
return nullptr; return nullptr;
} }
@@ -679,9 +680,9 @@ PhaseTree::createContextMenu
void void
PhaseTree::addEntries PhaseTree::addEntries
(const QDate &today, const QList<CalendarEntry> &phases, QTreeWidgetItem *parent, const Fonts &fonts) (const QDate &today, const QList<AgendaEntry> &phases, QTreeWidgetItem *parent, const Fonts &fonts)
{ {
for (const CalendarEntry &phase : phases) { for (const AgendaEntry &phase : phases) {
QTreeWidgetItem *entryItem = new QTreeWidgetItem(); QTreeWidgetItem *entryItem = new QTreeWidgetItem();
QString diffStr; QString diffStr;
int diffStart = today.daysTo(phase.spanStart); int diffStart = today.daysTo(phase.spanStart);
@@ -760,7 +761,7 @@ PhaseTree::addEntries
void void
EventTree::fillEntries EventTree::fillEntries
(const QHash<QDate, QList<CalendarEntry>> &events) (const QHash<QDate, QList<AgendaEntry>> &events)
{ {
QDate today = selectedDate(); QDate today = selectedDate();
if (! today.isValid()) { if (! today.isValid()) {
@@ -807,7 +808,7 @@ EventTree::createContextMenu
if (entryData.isNull()) { if (entryData.isNull()) {
return nullptr; return nullptr;
} }
CalendarEntry entry = entryData.value<CalendarEntry>(); AgendaEntry entry = entryData.value<AgendaEntry>();
if (entry.type != ENTRY_TYPE_EVENT) { if (entry.type != ENTRY_TYPE_EVENT) {
return nullptr; return nullptr;
} }
@@ -821,10 +822,10 @@ EventTree::createContextMenu
void void
EventTree::addEntries EventTree::addEntries
(const QDate &today, const QList<CalendarEntry> &events, QTreeWidgetItem *parent, const Fonts &fonts) (const QDate &today, const QList<AgendaEntry> &events, QTreeWidgetItem *parent, const Fonts &fonts)
{ {
bool firstEntry = true; bool firstEntry = true;
for (const CalendarEntry &event : events) { for (const AgendaEntry &event : events) {
QTreeWidgetItem *entryItem = new QTreeWidgetItem(); QTreeWidgetItem *entryItem = new QTreeWidgetItem();
int diffStart = today.daysTo(event.spanStart); int diffStart = today.daysTo(event.spanStart);
QString diffStr; QString diffStr;
@@ -899,16 +900,16 @@ AgendaView::AgendaView
activityTree = new ActivityTree(); activityTree = new ActivityTree();
connect(activityTree, &ActivityTree::dayChanged, this, [this](const QDate &date) { emit dayChanged(date); }); connect(activityTree, &ActivityTree::dayChanged, this, [this](const QDate &date) { emit dayChanged(date); });
connect(activityTree, &ActivityTree::showInTrainMode, this, [this](const CalendarEntry &activity) { emit showInTrainMode(activity); }); connect(activityTree, &ActivityTree::showInTrainMode, this, [this](const AgendaEntry &activity) { emit showInTrainMode(activity); });
connect(activityTree, &ActivityTree::viewActivity, this, [this](const CalendarEntry &activity) { emit viewActivity(activity); }); connect(activityTree, &ActivityTree::viewActivity, this, [this](const AgendaEntry &activity) { emit viewActivity(activity); });
phaseTree = new PhaseTree(); phaseTree = new PhaseTree();
connect(phaseTree, &PhaseTree::dayChanged, this, [this](const QDate &date) { emit dayChanged(date); }); connect(phaseTree, &PhaseTree::dayChanged, this, [this](const QDate &date) { emit dayChanged(date); });
connect(phaseTree, &PhaseTree::editPhaseEntry, this, [this](const CalendarEntry &phase) { emit editPhaseEntry(phase); }); connect(phaseTree, &PhaseTree::editPhaseEntry, this, [this](const AgendaEntry &phase) { emit editPhaseEntry(phase); });
eventTree = new EventTree(); eventTree = new EventTree();
connect(eventTree, &EventTree::dayChanged, this, [this](const QDate &date) { emit dayChanged(date); }); connect(eventTree, &EventTree::dayChanged, this, [this](const QDate &date) { emit dayChanged(date); });
connect(eventTree, &EventTree::editEventEntry, this, [this](const CalendarEntry &event) { emit editEventEntry(event); }); connect(eventTree, &EventTree::editEventEntry, this, [this](const AgendaEntry &event) { emit editEventEntry(event); });
QGridLayout* headLayout = new QGridLayout(); QGridLayout* headLayout = new QGridLayout();
headLayout->setColumnStretch(0, 1); headLayout->setColumnStretch(0, 1);
@@ -965,7 +966,7 @@ AgendaView::setFutureDays
void void
AgendaView::fillEntries AgendaView::fillEntries
(const QHash<QDate, QList<CalendarEntry>> &activities, std::pair<QList<CalendarEntry>, QList<CalendarEntry>> &phases , const QHash<QDate, QList<CalendarEntry>> &events, const QString &seasonName, bool isFiltered) (const QHash<QDate, QList<AgendaEntry>> &activities, std::pair<QList<AgendaEntry>, QList<AgendaEntry>> &phases , const QHash<QDate, QList<AgendaEntry>> &events, const QString &seasonName, bool isFiltered)
{ {
if (! seasonName.isNull()) { if (! seasonName.isNull()) {
seasonLabel->setText(tr("Season: %1").arg(seasonName)); seasonLabel->setText(tr("Season: %1").arg(seasonName));

View File

@@ -92,13 +92,13 @@ public:
void setPastDays(int days); void setPastDays(int days);
void setFutureDays(int days); void setFutureDays(int days);
void fillEntries(const QHash<QDate, QList<CalendarEntry>> &activities); void fillEntries(const QHash<QDate, QList<AgendaEntry>> &activities);
QDate firstVisibleDay() const; QDate firstVisibleDay() const;
QDate lastVisibleDay() const; QDate lastVisibleDay() const;
signals: signals:
void showInTrainMode(const CalendarEntry &activity); void showInTrainMode(const AgendaEntry &activity);
void viewActivity(const CalendarEntry &activity); void viewActivity(const AgendaEntry &activity);
protected: protected:
QMenu *createContextMenu(const QModelIndex &index) override; QMenu *createContextMenu(const QModelIndex &index) override;
@@ -107,7 +107,7 @@ private:
int pastDays = 7; int pastDays = 7;
int futureDays = 7; int futureDays = 7;
void addEntries(const QDate &today, const QDate &date, const QList<CalendarEntry> &activities, QTreeWidgetItem *parent, const Fonts &fonts); void addEntries(const QDate &today, const QDate &date, const QList<AgendaEntry> &activities, QTreeWidgetItem *parent, const Fonts &fonts);
}; };
@@ -115,16 +115,16 @@ class PhaseTree : public AgendaTree {
Q_OBJECT Q_OBJECT
public: public:
void fillEntries(const std::pair<QList<CalendarEntry>, QList<CalendarEntry>> &phases); void fillEntries(const std::pair<QList<AgendaEntry>, QList<AgendaEntry>> &phases);
signals: signals:
void editPhaseEntry(const CalendarEntry &entry); void editPhaseEntry(const AgendaEntry &entry);
protected: protected:
QMenu *createContextMenu(const QModelIndex &index) override; QMenu *createContextMenu(const QModelIndex &index) override;
private: private:
void addEntries(const QDate &today, const QList<CalendarEntry> &phases, QTreeWidgetItem *parent, const Fonts &fonts); void addEntries(const QDate &today, const QList<AgendaEntry> &phases, QTreeWidgetItem *parent, const Fonts &fonts);
}; };
@@ -132,16 +132,16 @@ class EventTree : public AgendaTree {
Q_OBJECT Q_OBJECT
public: public:
void fillEntries(const QHash<QDate, QList<CalendarEntry>> &events); void fillEntries(const QHash<QDate, QList<AgendaEntry>> &events);
signals: signals:
void editEventEntry(const CalendarEntry &entry); void editEventEntry(const AgendaEntry &entry);
protected: protected:
virtual QMenu *createContextMenu(const QModelIndex &index); virtual QMenu *createContextMenu(const QModelIndex &index);
private: private:
void addEntries(const QDate &today, const QList<CalendarEntry> &phases, QTreeWidgetItem *parent, const Fonts &fonts); void addEntries(const QDate &today, const QList<AgendaEntry> &phases, QTreeWidgetItem *parent, const Fonts &fonts);
}; };
@@ -155,7 +155,7 @@ public:
void setDateRange(const DateRange &dateRange); void setDateRange(const DateRange &dateRange);
void setPastDays(int days); void setPastDays(int days);
void setFutureDays(int days); void setFutureDays(int days);
void fillEntries(const QHash<QDate, QList<CalendarEntry>> &activities, std::pair<QList<CalendarEntry>, QList<CalendarEntry>> &phases, const QHash<QDate, QList<CalendarEntry>> &events, const QString &seasonName, bool isFiltered); void fillEntries(const QHash<QDate, QList<AgendaEntry>> &activities, std::pair<QList<AgendaEntry>, QList<AgendaEntry>> &phases, const QHash<QDate, QList<AgendaEntry>> &events, const QString &seasonName, bool isFiltered);
QDate firstVisibleDay() const; QDate firstVisibleDay() const;
QDate lastVisibleDay() const; QDate lastVisibleDay() const;
@@ -166,10 +166,10 @@ public slots:
void setEventMaxTertiaryLines(int maxTertiaryLines); void setEventMaxTertiaryLines(int maxTertiaryLines);
signals: signals:
void showInTrainMode(const CalendarEntry &activity); void showInTrainMode(const AgendaEntry &activity);
void viewActivity(const CalendarEntry &activity); void viewActivity(const AgendaEntry &activity);
void editPhaseEntry(const CalendarEntry &entry); void editPhaseEntry(const AgendaEntry &entry);
void editEventEntry(const CalendarEntry &entry); void editEventEntry(const AgendaEntry &entry);
void dayChanged(const QDate &date); void dayChanged(const QDate &date);
protected: protected:

View File

@@ -35,6 +35,22 @@
#define ENTRY_TYPE_OTHER 99 #define ENTRY_TYPE_OTHER 99
struct AgendaEntry {
QString primary;
QStringList secondaryValues;
QStringList secondaryLabels;
QString tertiary;
QString iconFile;
QColor color;
QString reference;
QTime start;
int type = 0;
bool hasTrainMode = false;
QDate spanStart = QDate();
QDate spanEnd = QDate();
};
struct CalendarEntry { struct CalendarEntry {
QString primary; QString primary;
QString secondary; QString secondary;
@@ -80,6 +96,7 @@ struct CalendarSummary {
QList<std::pair<QString, QString>> keyValues; QList<std::pair<QString, QString>> keyValues;
}; };
Q_DECLARE_METATYPE(AgendaEntry)
Q_DECLARE_METATYPE(CalendarEntry) Q_DECLARE_METATYPE(CalendarEntry)
Q_DECLARE_METATYPE(CalendarEntryLayout) Q_DECLARE_METATYPE(CalendarEntryLayout)
Q_DECLARE_METATYPE(CalendarDay) Q_DECLARE_METATYPE(CalendarDay)

View File

@@ -36,7 +36,7 @@ static bool toolTipHeadlineEntry(const QPoint &pos, QAbstractItemView *view, con
static bool toolTipDayEntry(const QPoint &pos, QAbstractItemView *view, const CalendarDay &day, int idx); static bool toolTipDayEntry(const QPoint &pos, QAbstractItemView *view, const CalendarDay &day, int idx);
static bool toolTipMore(const QPoint &pos, QAbstractItemView *view, const CalendarDay &day); static bool toolTipMore(const QPoint &pos, QAbstractItemView *view, const CalendarDay &day);
static QRect paintHeadline(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index, HitTester &headlineTester, const QString &dateFormat, int pressedEntryIdx, int leftMargin, int rightMargin, int topMargin, int lineSpacing, int radius); static QRect paintHeadline(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index, HitTester &headlineTester, const QString &dateFormat, int pressedEntryIdx, int leftMargin, int rightMargin, int topMargin, int lineSpacing, int radius);
static void paintMetric(QPainter *painter, const QRect &rect, const QFont::Weight &valueWeight, const QFont::Weight &labelWeight, const QString &value, const QString &label); static int paintMetric(QPainter *painter, const QRect &rect, const QFont::Weight &valueWeight, const QFont::Weight &labelWeight, const QString &value, const QString &label, bool first = true);
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
@@ -1318,7 +1318,7 @@ AgendaEntryDelegate::paint
if (column < 0) { if (column < 0) {
column = index.column(); column = index.column();
} }
CalendarEntry entry = index.data(EntryRole).value<CalendarEntry>(); AgendaEntry entry = index.data(EntryRole).value<AgendaEntry>();
painter->save(); painter->save();
painter->setRenderHint(QPainter::Antialiasing); painter->setRenderHint(QPainter::Antialiasing);
@@ -1360,7 +1360,6 @@ AgendaEntryDelegate::paint
QFontMetrics line1FM(line1Font); QFontMetrics line1FM(line1Font);
const int lineSpacing = attributes.lineSpacing * dpiYFactor; const int lineSpacing = attributes.lineSpacing * dpiYFactor;
const int lineHeight = line1FM.height(); const int lineHeight = line1FM.height();
// const int radius = 4 * dpiXFactor;
const int iconInnerSpacing = 4 * dpiXFactor; const int iconInnerSpacing = 4 * dpiXFactor;
const int iconTextSpacing = attributes.iconTextSpacing * dpiXFactor; const int iconTextSpacing = attributes.iconTextSpacing * dpiXFactor;
const int iconWidth = 2 * lineHeight + lineSpacing; const int iconWidth = 2 * lineHeight + lineSpacing;
@@ -1396,9 +1395,16 @@ AgendaEntryDelegate::paint
painter->setFont(line1Font); painter->setFont(line1Font);
painter->drawText(textRect, Qt::AlignLeft | Qt::AlignTop | Qt::TextDontClip, primary); painter->drawText(textRect, Qt::AlignLeft | Qt::AlignTop | Qt::TextDontClip, primary);
painter->restore(); painter->restore();
if (! entry.secondary.isEmpty()) { textRect.translate(0, lineHeight + lineSpacing);
textRect.translate(0, lineHeight + lineSpacing); int advance = 0;
paintMetric(painter, textRect, secondaryWeight, secondaryMetricWeight, entry.secondary, entry.secondaryMetric); for (int i = 0; i < entry.secondaryValues.count(); ++i) {
QString value = entry.secondaryValues[i];
QString label = entry.secondaryLabels.value(i, "");
textRect.setLeft(textRect.left() + advance);
if (textRect.width() <= 20 * dpiXFactor) {
break;
}
advance = paintMetric(painter, textRect, secondaryWeight, secondaryMetricWeight, value, label, i == 0);
} }
if (! entry.tertiary.isEmpty()) { if (! entry.tertiary.isEmpty()) {
painter->save(); painter->save();
@@ -1447,7 +1453,7 @@ AgendaEntryDelegate::sizeHint
if (! data.isNull()) { if (! data.isNull()) {
const int lineSpacing = attributes.lineSpacing * dpiYFactor; const int lineSpacing = attributes.lineSpacing * dpiYFactor;
const int lineHeight = option.fontMetrics.height(); const int lineHeight = option.fontMetrics.height();
CalendarEntry entry = data.value<CalendarEntry>(); AgendaEntry entry = data.value<AgendaEntry>();
int tertiaryHeight = 0; int tertiaryHeight = 0;
if (! entry.tertiary.isEmpty()) { if (! entry.tertiary.isEmpty()) {
const int iconWidth = 2 * lineHeight + lineSpacing; const int iconWidth = 2 * lineHeight + lineSpacing;
@@ -1485,7 +1491,7 @@ AgendaEntryDelegate::hasToolTip
if (! index.isValid()) { if (! index.isValid()) {
return false; return false;
} }
CalendarEntry entry = index.data(EntryRole).value<CalendarEntry>(); AgendaEntry entry = index.data(EntryRole).value<AgendaEntry>();
QString text(entry.tertiary.trimmed()); QString text(entry.tertiary.trimmed());
if (text.isEmpty()) { if (text.isEmpty()) {
return false; return false;
@@ -1872,24 +1878,41 @@ paintHeadline
} }
static void static int
paintMetric paintMetric
(QPainter *painter, const QRect &rect, const QFont::Weight &valueWeight, const QFont::Weight &labelWeight, const QString &value, const QString &label) (QPainter *painter, const QRect &rect, const QFont::Weight &valueWeight, const QFont::Weight &labelWeight, const QString &value, const QString &label, bool first)
{ {
int advance = 0;
painter->save(); painter->save();
QFont font = painter->font(); QFont font = painter->font();
font.setWeight(valueWeight); font.setWeight(valueWeight);
painter->setFont(font); painter->setFont(font);
QFontMetrics valueFM(font); QFontMetrics valueFM(font);
int valueWidth = valueFM.horizontalAdvance(value); QString fullValue(value);
painter->drawText(rect, Qt::AlignLeft | Qt::AlignTop, value); if (! first) {
fullValue.prepend("");
}
int valueWidth = valueFM.horizontalAdvance(fullValue);
advance += valueWidth;
painter->drawText(rect, Qt::AlignLeft | Qt::AlignTop, fullValue);
if (! label.isEmpty()) { if (! label.isEmpty()) {
QRect labelRect(rect); QRect labelRect(rect);
labelRect.setX(rect.x() + valueWidth + valueFM.horizontalAdvance(" ")); int spaceAdvance = valueFM.horizontalAdvance(" ");
advance += spaceAdvance;
labelRect.setX(rect.x() + valueWidth + spaceAdvance);
font.setWeight(labelWeight); font.setWeight(labelWeight);
QFontMetrics labelFM(font); QFontMetrics labelFM(font);
int labelWidth;
painter->setFont(font); painter->setFont(font);
if (labelWeight <= QFont::Light) {
QRect labelBounds = painter->boundingRect(labelRect, Qt::AlignLeft | Qt::AlignTop, label);
labelWidth = labelBounds.width();
} else {
labelWidth = labelFM.horizontalAdvance(label);
}
painter->drawText(labelRect, Qt::AlignLeft | Qt::AlignTop, label); painter->drawText(labelRect, Qt::AlignLeft | Qt::AlignTop, label);
advance += labelWidth;
} }
painter->restore(); painter->restore();
return advance;
} }

View File

@@ -239,18 +239,18 @@ class AgendaEntryDelegate : public QStyledItemDelegate {
Q_OBJECT Q_OBJECT
public: public:
enum Roles { enum Roles {
EntryRole = Qt::UserRole, // [CalendarEntry] Entry to be displayed EntryRole = Qt::UserRole, // [AgendaEntry] Entry to be displayed
EntryDateRole // [bool] Date of the CalendarEntry EntryDateRole // [bool] Date of the AgendaEntry
}; };
struct Attributes { struct Attributes {
QMargins padding; // Padding of the element QMargins padding; // Padding of the element
QFont::Weight primaryWeight = QFont::Medium; // Primary row QFont::Weight primaryWeight = QFont::Medium; // Primary row
QFont::Weight primaryHoverWeight = QFont::DemiBold; // Primary row (hovered) QFont::Weight primaryHoverWeight = QFont::Medium; // Primary row (hovered)
QFont::Weight secondaryWeight = QFont::Light; // Secondary row QFont::Weight secondaryWeight = QFont::Medium; // Secondary row
QFont::Weight secondaryHoverWeight = QFont::DemiBold; // Secondary row (hovered) QFont::Weight secondaryHoverWeight = QFont::Medium; // Secondary row (hovered)
QFont::Weight secondaryMetricWeight = QFont::ExtraLight; // Metric in the secondary row QFont::Weight secondaryMetricWeight = QFont::ExtraLight; // Metric in the secondary row
QFont::Weight secondaryMetricHoverWeight = QFont::Normal; // Metric in the secondary row (hovered) QFont::Weight secondaryMetricHoverWeight = QFont::ExtraLight; // Metric in the secondary row (hovered)
int lineSpacing = 2; // Vertical spacing between primary and secondary row (dpiYFactor not applied) int lineSpacing = 2; // Vertical spacing between primary and secondary row (dpiYFactor not applied)
int iconTextSpacing = 10; // Horizontal spacing between icon and text (dpiXFactor not applied) int iconTextSpacing = 10; // Horizontal spacing between icon and text (dpiXFactor not applied)
float tertiaryDimLevel = 0.5; // Dimming amount for tertiary row float tertiaryDimLevel = 0.5; // Dimming amount for tertiary row

View File

@@ -199,6 +199,7 @@ MultiMetricSelector::MultiMetricSelector
: QWidget(parent) : QWidget(parent)
{ {
filterEdit = new QLineEdit(); filterEdit = new QLineEdit();
filterEdit->setClearButtonEnabled(true);
filterEdit->setPlaceholderText(tr("Filter...")); filterEdit->setPlaceholderText(tr("Filter..."));
availList = new QListWidget(); availList = new QListWidget();