Crashes related to seasons (#4834)

* Added safeguards
* Blocking signals during construction
* DateRange no longer inherits from QObject since signal 'changed' was nowhere connected in the codebase
* Safeguards when dragging seasons; preventing phases from being dragged
* Preventing the use of dangling pointers when creating a new season
[publish binaries]
This commit is contained in:
Joachim Kohlhammer
2026-03-07 15:02:36 +01:00
committed by GitHub
parent 1d37396334
commit d813585927
6 changed files with 75 additions and 27 deletions

View File

@@ -519,6 +519,14 @@ AgendaWindow::getActivities
(const QDate &firstDay, const QDate &today, const QDate &lastDay) const
{
QHash<QDate, QList<AgendaEntry>> activities;
if (! context || ! context->athlete || ! context->athlete->rideCache) {
return activities;
}
const QList<RideItem*> rides = context->athlete->rideCache->rides();
if (rides.isEmpty()) {
return activities;
}
const RideMetricFactory &factory = RideMetricFactory::instance();
QList<RideMetric const *> rideMetrics;
QStringList rideMetricNames;
@@ -538,11 +546,15 @@ AgendaWindow::getActivities
}
int showTertiaryFor = getShowTertiaryFor();
for (RideItem *rideItem : context->athlete->rideCache->rides()) {
for (RideItem *rideItem : rides) {
if ( rideItem == nullptr
|| ! rideItem->planned
|| rideItem->dateTime.date() < firstDay
|| rideItem->dateTime.date() > lastDay
|| ! rideItem->dateTime.isValid()) {
continue;
}
QDate rideDate = rideItem->dateTime.date();
if ( ! rideItem->planned
|| rideDate < firstDay
|| rideDate > lastDay
|| rideItem->hasLinkedActivity()) {
continue;
}
@@ -586,7 +598,7 @@ AgendaWindow::getActivities
activity.secondaryValues << tr("N/A");
activity.secondaryLabels << "";
}
if (showTertiaryFor == 0 || (showTertiaryFor == 1 && rideItem->dateTime.date() == today)) {
if (showTertiaryFor == 0 || (showTertiaryFor == 1 && rideDate == today)) {
activity.tertiary = rideItem->getText(getTertiaryField(), "").trimmed();
activity.tertiary = Utils::unprotect(activity.tertiary);
}
@@ -598,7 +610,7 @@ AgendaWindow::getActivities
activity.start = rideItem->dateTime.time();
activity.type = ENTRY_TYPE_PLANNED_ACTIVITY;
activity.hasTrainMode = sport == "Bike" && ! buildWorkoutFilter(rideItem).isEmpty();
activities[rideItem->dateTime.date()] << activity;
activities[rideDate] << activity;
}
for (auto dayIt = activities.begin(); dayIt != activities.end(); ++dayIt) {
std::sort(dayIt.value().begin(), dayIt.value().end(), [](const AgendaEntry &a, const AgendaEntry &b) {

View File

@@ -948,6 +948,15 @@ CalendarWindow::getActivities
(const QDate &firstDay, const QDate &lastDay) const
{
QHash<QDate, QList<CalendarEntry>> activities;
if (! context || ! context->athlete || ! context->athlete->rideCache) {
return activities;
}
const QList<RideItem*> rides = context->athlete->rideCache->rides();
if (rides.isEmpty()) {
return activities;
}
const RideMetricFactory &factory = RideMetricFactory::instance();
const RideMetric *rideMetric = factory.rideMetric(getSecondaryMetric());
QString rideMetricName;
@@ -960,10 +969,14 @@ CalendarWindow::getActivities
}
}
for (RideItem *rideItem : context->athlete->rideCache->rides()) {
if ( rideItem->dateTime.date() < firstDay
|| rideItem->dateTime.date() > lastDay
|| rideItem == nullptr) {
for (RideItem *rideItem : rides) {
if ( rideItem == nullptr
|| ! rideItem->dateTime.isValid()) {
continue;
}
QDate rideDate = rideItem->dateTime.date();
if ( rideDate < firstDay
|| rideDate > lastDay) {
continue;
}
if ( (context->isfiltered && ! context->filters.contains(rideItem->fileName))
@@ -1011,7 +1024,7 @@ CalendarWindow::getActivities
}
RideItem *linkedRide = context->athlete->rideCache->getLinkedActivity(rideItem);
if (linkedRide != nullptr) {
if (linkedRide != nullptr && linkedRide->dateTime.isValid()) {
activity.linkedReference = linkedRide->fileName;
activity.linkedPrimary = getPrimary(linkedRide);
activity.linkedStartDT = linkedRide->dateTime;
@@ -1020,7 +1033,7 @@ CalendarWindow::getActivities
}
}
activities[rideItem->dateTime.date()] << activity;
activities[rideDate] << activity;
}
for (auto dayIt = activities.begin(); dayIt != activities.end(); ++dayIt) {
std::sort(dayIt.value().begin(), dayIt.value().end(), [](const CalendarEntry &a, const CalendarEntry &b) {

View File

@@ -189,7 +189,7 @@ QDateTime convertToLocalTime(QString timestamp)
return QDateTime::fromString(timestamp);
}
DateRange::DateRange(QDate from, QDate to, QString name, QColor color) : QObject()
DateRange::DateRange(QDate from, QDate to, QString name, QColor color)
{
this->from=from;
this->to=to;
@@ -198,7 +198,7 @@ DateRange::DateRange(QDate from, QDate to, QString name, QColor color) : QObject
valid = from.isValid() && to.isValid();
}
DateRange::DateRange(const DateRange &other) : QObject()
DateRange::DateRange(const DateRange &other)
{
from=other.from;
to=other.to;
@@ -215,7 +215,6 @@ DateRange& DateRange::operator=(const DateRange &other)
name=other.name;
color=other.color;
valid = from.isValid() && to.isValid();
emit changed(from, to);
return *this;
}

View File

@@ -49,10 +49,8 @@ extern ShowDaysAsUnit showDaysAs(int days);
*/
QDateTime convertToLocalTime(QString timestamp);
class DateRange : QObject
class DateRange
{
Q_OBJECT
public:
DateRange(const DateRange& other);
DateRange(QDate from = QDate(), QDate to = QDate(), QString name ="", QColor=QColor(127,127,127));
@@ -81,14 +79,9 @@ class DateRange : QObject
}
bool isValid() const { return valid; }
Q_SIGNALS:
void changed(QDate from, QDate to);
protected:
bool valid;
};
Q_DECLARE_METATYPE(DateRange)
class DateSettingsEdit : public QWidget
{

View File

@@ -2350,6 +2350,9 @@ void
Calendar::activateDateRange
(const DateRange &dr, bool allowKeepMonth, bool canHavePhasesOrEvents)
{
if (dateRange == dr) {
return;
}
QDate currentDate = selectedDate();
dateRange = dr;
monthView->limitDateRange(dr, allowKeepMonth, canHavePhasesOrEvents);

View File

@@ -400,6 +400,9 @@ LTMSidebar::dateRangeTreeWidgetSelectionChanged()
phase = NULL;
}
} else if (which->type() >= Phase::phase) {
if (which->parent() == nullptr) {
return;
}
int seasonIdx = allDateRanges->indexOfChild(which->parent());
int phaseIdx = which->parent()->indexOfChild(which);
if (which != allDateRanges) {
@@ -492,7 +495,7 @@ LTMSidebar::resetSeasons()
addSeason->setExpanded(true);
addPhase->setSelected(true);
}
addPhase->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
addPhase->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
addPhase->setText(0, phase.getName());
}
@@ -1091,6 +1094,14 @@ LTMSidebar::deleteFilter()
void
LTMSidebar::dateRangeMoved(QTreeWidgetItem*item, int oldposition, int newposition)
{
if ( oldposition < 0
|| oldposition >= seasons->seasons.count()
|| newposition < 0
|| newposition >= seasons->seasons.count()) {
return;
}
QSignalBlocker blocker(dateRangeTree);
// report the move in the seasons
seasons->seasons.move(oldposition, newposition);
@@ -1100,9 +1111,15 @@ LTMSidebar::dateRangeMoved(QTreeWidgetItem*item, int oldposition, int newpositio
active = false;
// deselect actual selection
dateRangeTree->selectedItems().first()->setSelected(false);
dateRangeTree->clearSelection();
// select the move/drop item
item->setSelected(true);
if (item) {
item->setSelected(true);
dateRangeTree->setCurrentItem(item);
}
blocker.unblock();
dateRangeTreeWidgetSelectionChanged();
}
void
@@ -1123,11 +1140,22 @@ LTMSidebar::addRange()
newOne.setAbsoluteEnd(temp);
}
// Ensure the current season is nullptr during creation of the new season
// to prevent usage of a dangling pointer (see comment #ref1# below)
context->notifySeasonChanged(nullptr);
// save
seasons->seasons.insert(0, newOne);
seasons->writeSeasons();
active = false;
if (seasons->seasons.count() > 0) {
// #ref1#
// Ensure the pointer to the season is guaranteed to be valid if the
// QList<Season> (Seasons::seasons) was reorganized during insertion
// and its objects were relocated
context->notifySeasonChanged(&seasons->seasons[0]);
}
// signal its changed!
resetSeasons();
}
@@ -1426,7 +1454,7 @@ LTMSidebar::addPhase()
QTreeWidgetItem *addPhase = new QTreeWidgetItem(selectedDateRange, myphase.getType());
addPhase->setSelected(true);
addPhase->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
addPhase->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
addPhase->setText(0, myphase.getName());
// save changes away