File indexing completed on 2025-02-02 05:02:29

0001 /*
0002     SPDX-FileCopyrightText: 2023 Volker Krause <vkrause@kde.org>
0003     SPDX-License-Identifier: LGPL-2.0-or-later
0004 */
0005 
0006 #include "journeysectionmodel.h"
0007 #include "logging.h"
0008 
0009 #include <KPublicTransport/Stopover>
0010 
0011 #include <QDebug>
0012 
0013 JourneySectionModel::JourneySectionModel(QObject *parent)
0014     : QAbstractListModel(parent)
0015 {
0016     connect(this, &JourneySectionModel::departureTrailingSegmentLengthChanged, this, [this]() {
0017         if (!m_data.empty()) {
0018             Q_EMIT dataChanged(index(0, 0), index(0, 0));
0019         }
0020     });
0021     connect(this, &JourneySectionModel::arrivalLeadingSegmentLengthChanged, this, [this]() {
0022         if (!m_data.empty()) {
0023             Q_EMIT dataChanged(index(rowCount() - 1, 0), index(rowCount() - 1, 0));
0024         }
0025     });
0026 
0027     connect(this, &JourneySectionModel::showProgressChanged, this, [this]() {
0028         if (!m_data.empty()) {
0029             Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0));
0030         }
0031         Q_EMIT journeySectionChanged();
0032     });
0033 }
0034 
0035 JourneySectionModel::~JourneySectionModel() = default;
0036 
0037 KPublicTransport::JourneySection JourneySectionModel::journeySection() const
0038 {
0039     return m_journey;
0040 }
0041 
0042 void JourneySectionModel::setJourneySection(const KPublicTransport::JourneySection& section)
0043 {
0044     // is this an update to the current state? if so, try to avoid resetting the model
0045     if (KPublicTransport::JourneySection::isSame(m_journey, section) && m_journey.intermediateStops().size() == section.intermediateStops().size()) {
0046         m_journey = section;
0047         Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0));
0048         Q_EMIT journeySectionChanged();
0049         return;
0050     }
0051 
0052     beginResetModel();
0053     m_journey = section;
0054     m_data.clear();
0055     m_data.resize(m_journey.intermediateStops().size());
0056     endResetModel();
0057     Q_EMIT journeySectionChanged();
0058 }
0059 
0060 int JourneySectionModel::rowCount(const QModelIndex &parent) const
0061 {
0062     if (parent.isValid()) {
0063         return 0;
0064     }
0065     return m_journey.intermediateStops().size();
0066 }
0067 
0068 
0069 QVariant JourneySectionModel::data(const QModelIndex &index, int role) const
0070 {
0071     if (!checkIndex(index)) {
0072         return {};
0073     }
0074 
0075     switch (role) {
0076         case LeadingSegmentLengthRole:
0077             return m_data[index.row()].leadingLength;
0078         case TrailingSegmentLengthtRole:
0079             return m_data[index.row()].trailingLength;
0080         case LeadingSegmentProgressRole:
0081             return leadingProgress(index.row());
0082         case TrailingSegmentProgressRole:
0083             return trailingProgress(index.row());
0084         case StopoverRole:
0085         {
0086             auto stop = m_journey.intermediateStops()[index.row()];
0087             if (stop.route().line().mode() == KPublicTransport::Line::Unknown) {
0088                 stop.setRoute(m_journey.route());
0089             }
0090             return stop;
0091         }
0092         case StopoverPassedRole:
0093             return stopoverPassed(index.row());
0094     }
0095 
0096     return {};
0097 }
0098 
0099 bool JourneySectionModel::setData(const QModelIndex &index, const QVariant &value, int role)
0100 {
0101     const auto length = value.toFloat();
0102     if (length <= 0.0f) {
0103         return false;
0104     }
0105 
0106     qCDebug(Log) << index << value << role;
0107     switch (role) {
0108         case LeadingSegmentLengthRole:
0109             m_data[index.row()].leadingLength = length;
0110             break;
0111         case TrailingSegmentLengthtRole:
0112             m_data[index.row()].trailingLength = length;
0113             break;
0114         default:
0115             return false;
0116     }
0117     Q_EMIT dataChanged(index, index);
0118     if (index.row() > 0) {
0119         Q_EMIT dataChanged(index.sibling(index.row() - 1, 0), index.sibling(index.row() - 1, 0));
0120     }
0121     if (index.row() < rowCount() - 1) {
0122         Q_EMIT dataChanged(index.sibling(index.row() + 1, 0), index.sibling(index.row() + 1, 0));
0123     }
0124     return true;
0125 }
0126 
0127 QHash<int, QByteArray> JourneySectionModel::roleNames() const
0128 {
0129     auto n = QAbstractListModel::roleNames();
0130     n.insert(LeadingSegmentLengthRole, "leadingLength");
0131     n.insert(TrailingSegmentLengthtRole, "trailingLength");
0132     n.insert(LeadingSegmentProgressRole, "leadingProgress");
0133     n.insert(TrailingSegmentProgressRole, "trailingProgress");
0134     n.insert(StopoverRole, "stopover");
0135     n.insert(StopoverPassedRole, "stopoverPassed");
0136     return n;
0137 }
0138 
0139 float JourneySectionModel::departureTrailingProgress() const
0140 {
0141     return trailingProgress(-1);
0142 }
0143 
0144 float JourneySectionModel::arrivalLeadingProgress() const
0145 {
0146     return leadingProgress(rowCount());
0147 }
0148 
0149 bool JourneySectionModel::departed() const
0150 {
0151     return stopoverPassed(-1);
0152 }
0153 
0154 bool JourneySectionModel::arrived() const
0155 {
0156     return stopoverPassed(rowCount());
0157 }
0158 
0159 KPublicTransport::Stopover JourneySectionModel::stopoverForRow(int row) const
0160 {
0161     if (row < 0) {
0162         return m_journey.departure();
0163     }
0164     if (row >= 0 && row < rowCount()) {
0165         return m_journey.intermediateStops()[row];
0166     }
0167     return m_journey.arrival();
0168 }
0169 
0170 static QDateTime departureTime(const KPublicTransport::Stopover &stop)
0171 {
0172     return stop.hasExpectedDepartureTime() ? stop.expectedDepartureTime() : stop.scheduledDepartureTime();
0173 }
0174 
0175 static QDateTime arrivalTime(const KPublicTransport::Stopover &stop)
0176 {
0177     if (stop.hasExpectedArrivalTime()) {
0178         return stop.expectedArrivalTime();
0179     }
0180     if (stop.scheduledArrivalTime().isValid()) {
0181         return stop.scheduledArrivalTime();
0182     }
0183     return departureTime(stop);
0184 }
0185 
0186 float JourneySectionModel::leadingProgress(int row) const
0187 {
0188     if (!m_showProgress) {
0189         return 0.0f;
0190     }
0191 
0192     const auto now = currentDateTime();
0193     const auto stop = stopoverForRow(row);
0194     if (arrivalTime(stop) <= now) {
0195         qCDebug(Log) << row << stop.stopPoint().name() << "already passed" << arrivalTime(stop);
0196         return 1.0f;
0197     }
0198 
0199     const auto prevStop = stopoverForRow(row - 1);
0200     if (departureTime(prevStop) >= now) {
0201         qCDebug(Log) << row << stop.stopPoint().name() << "not passed yet";
0202         return 0.0f;
0203     }
0204 
0205     const float totalTime = departureTime(prevStop).secsTo(arrivalTime(stop));
0206     const float progressTime = departureTime(prevStop).secsTo(now);
0207 
0208     const float prevLength = row > 0 ? m_data[row - 1].trailingLength : m_departureTrailingLength;
0209     const float leadingLength = row >= rowCount() ? m_arrivalLeadingLength : m_data[row].leadingLength;
0210     const float totalLength = leadingLength + prevLength;
0211 
0212     const float progressLength = totalLength * (progressTime / totalTime);
0213     qCDebug(Log) << row << stop.stopPoint().name() << totalTime << progressTime << totalLength << progressLength << prevLength << (progressLength < prevLength ? 0.0f : ((progressLength - prevLength) / leadingLength));
0214     return progressLength < prevLength ? 0.0f : ((progressLength - prevLength) / leadingLength);
0215 }
0216 
0217 float JourneySectionModel::trailingProgress(int row) const
0218 {
0219     if (!m_showProgress) {
0220         return 0.0f;
0221     }
0222 
0223     const auto now = currentDateTime();
0224     const auto stop = stopoverForRow(row);
0225     if (departureTime(stop) >= now) {
0226         qCDebug(Log) << row << stop.stopPoint().name()<< "not passed yet";
0227         return 0.0f;
0228     }
0229 
0230     const auto nextStop = stopoverForRow(row + 1);
0231     if (arrivalTime(nextStop) <= now) {
0232         qCDebug(Log) << row << stop.stopPoint().name()<< "already passed";
0233         return 1.0f;
0234     }
0235 
0236     const float totalTime = departureTime(stop).secsTo(arrivalTime(nextStop));
0237     const float progressTime = departureTime(stop).secsTo(now);
0238 
0239     const float nextLength = row < rowCount() - 1 ? m_data[row + 1].leadingLength : m_arrivalLeadingLength;
0240     const float trailingLength = row < 0 ? m_departureTrailingLength : m_data[row].trailingLength;
0241     const float totalLength = trailingLength + nextLength;
0242 
0243     const float progressLength = totalLength * (progressTime / totalTime);
0244     qCDebug(Log) << row << stop.stopPoint().name()<< totalTime << progressTime << totalLength << progressLength << nextLength << (progressLength > trailingLength ? 1.0f : (progressLength / trailingLength));
0245     return progressLength > trailingLength ? 1.0f : (progressLength / trailingLength);
0246 }
0247 
0248 float JourneySectionModel::stopoverPassed(int row) const
0249 {
0250     if (!m_showProgress) {
0251         return false;
0252     }
0253 
0254     const auto now = currentDateTime();
0255     const auto stop = stopoverForRow(row);
0256     return arrivalTime(stop) <= now;
0257 }
0258 
0259 void JourneySectionModel::setCurrentDateTime(const QDateTime& dt)
0260 {
0261     m_unitTestTime = dt;
0262 }
0263 
0264 QDateTime JourneySectionModel::currentDateTime() const
0265 {
0266     if (Q_UNLIKELY(m_unitTestTime.isValid())) {
0267         return m_unitTestTime;
0268     }
0269     return QDateTime::currentDateTime();
0270 }
0271 
0272 #include "moc_journeysectionmodel.cpp"