File indexing completed on 2025-02-02 05:02:36
0001 /* 0002 SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "timelinedelegatecontroller.h" 0008 0009 #include "calendarhelper.h" 0010 #include "constants.h" 0011 #include "documentmanager.h" 0012 #include "livedatamanager.h" 0013 #include "locationhelper.h" 0014 #include "logging.h" 0015 #include "reservationhelper.h" 0016 #include "reservationmanager.h" 0017 #include "publictransport.h" 0018 #include "publictransportmatcher.h" 0019 #include "transfer.h" 0020 #include "transfermanager.h" 0021 0022 #include <KItinerary/BusTrip> 0023 #include <KItinerary/CalendarHandler> 0024 #include <KItinerary/DocumentUtil> 0025 #include <KItinerary/Flight> 0026 #include <KItinerary/JsonLdDocument> 0027 #include <KItinerary/LocationUtil> 0028 #include <KItinerary/SortUtil> 0029 #include <KItinerary/Reservation> 0030 #include <KItinerary/Ticket> 0031 #include <KItinerary/TrainTrip> 0032 0033 #include <KPublicTransport/Platform> 0034 #include <KPublicTransport/PlatformLayout> 0035 #include <KPublicTransport/Vehicle> 0036 0037 #include <QJSEngine> 0038 #include <QJSValue> 0039 0040 #include <QCoreApplication> 0041 #include <QDateTime> 0042 #include <QDebug> 0043 #include <QPointF> 0044 #include <QTimer> 0045 #include <QTimeZone> 0046 0047 QTimer* TimelineDelegateController::s_currentTimer = nullptr; 0048 int TimelineDelegateController::s_progressRefCount = 0; 0049 QTimer* TimelineDelegateController::s_progressTimer = nullptr; 0050 0051 using namespace KItinerary; 0052 0053 TimelineDelegateController::TimelineDelegateController(QObject *parent) 0054 : QObject(parent) 0055 { 0056 if (!s_currentTimer) { 0057 s_currentTimer = new QTimer(QCoreApplication::instance()); 0058 s_currentTimer->setSingleShot(true); 0059 s_currentTimer->setTimerType(Qt::VeryCoarseTimer); 0060 } 0061 connect(s_currentTimer, &QTimer::timeout, this, [this]() { checkForUpdate(m_batchId); }); 0062 0063 connect(this, &TimelineDelegateController::contentChanged, this, &TimelineDelegateController::connectionWarningChanged); 0064 } 0065 0066 TimelineDelegateController::~TimelineDelegateController() = default; 0067 0068 QObject* TimelineDelegateController::reservationManager() const 0069 { 0070 return m_resMgr; 0071 } 0072 0073 void TimelineDelegateController::setReservationManager(QObject *resMgr) 0074 { 0075 if (m_resMgr == resMgr) { 0076 return; 0077 } 0078 0079 m_resMgr = qobject_cast<ReservationManager*>(resMgr); 0080 Q_EMIT setupChanged(); 0081 Q_EMIT contentChanged(); 0082 Q_EMIT departureChanged(); 0083 Q_EMIT arrivalChanged(); 0084 Q_EMIT journeyChanged(); 0085 Q_EMIT previousLocationChanged(); 0086 Q_EMIT layoutChanged(); 0087 0088 connect(m_resMgr, &ReservationManager::batchChanged, this, &TimelineDelegateController::batchChanged); 0089 connect(m_resMgr, &ReservationManager::batchContentChanged, this, &TimelineDelegateController::batchChanged); 0090 // ### could be done more efficiently 0091 connect(m_resMgr, &ReservationManager::batchAdded, this, &TimelineDelegateController::previousLocationChanged); 0092 connect(m_resMgr, &ReservationManager::batchRemoved, this, &TimelineDelegateController::previousLocationChanged); 0093 0094 checkForUpdate(m_batchId); 0095 } 0096 0097 QObject* TimelineDelegateController::liveDataManager() const 0098 { 0099 return m_liveDataMgr; 0100 } 0101 0102 void TimelineDelegateController::setLiveDataManager(QObject* liveDataMgr) 0103 { 0104 if (m_liveDataMgr == liveDataMgr) { 0105 return; 0106 } 0107 0108 m_liveDataMgr = qobject_cast<LiveDataManager*>(liveDataMgr); 0109 Q_EMIT setupChanged(); 0110 Q_EMIT departureChanged(); 0111 Q_EMIT arrivalChanged(); 0112 Q_EMIT journeyChanged(); 0113 Q_EMIT layoutChanged(); 0114 0115 connect(m_liveDataMgr, &LiveDataManager::arrivalUpdated, this, &TimelineDelegateController::checkForUpdate); 0116 connect(m_liveDataMgr, &LiveDataManager::departureUpdated, this, &TimelineDelegateController::checkForUpdate); 0117 connect(m_liveDataMgr, &LiveDataManager::arrivalUpdated, this, [this](const auto &batchId) { 0118 if (batchId == m_batchId) { 0119 Q_EMIT arrivalChanged(); 0120 Q_EMIT journeyChanged(); 0121 } 0122 }); 0123 connect(m_liveDataMgr, &LiveDataManager::departureUpdated, this, [this](const auto &batchId) { 0124 if (batchId == m_batchId) { 0125 Q_EMIT departureChanged(); 0126 Q_EMIT journeyChanged(); 0127 } 0128 }); 0129 connect(m_liveDataMgr, &LiveDataManager::journeyUpdated, this, [this](const auto &batchId) { 0130 if (batchId == m_batchId) { 0131 Q_EMIT departureChanged(); 0132 Q_EMIT arrivalChanged(); 0133 Q_EMIT journeyChanged(); 0134 } 0135 }); 0136 0137 checkForUpdate(m_batchId); 0138 } 0139 0140 QObject* TimelineDelegateController::transferManager() const 0141 { 0142 return m_transferMgr; 0143 } 0144 0145 void TimelineDelegateController::setTransferManager(QObject *transferMgr) 0146 { 0147 if (m_transferMgr == transferMgr) { 0148 return; 0149 } 0150 0151 m_transferMgr = qobject_cast<TransferManager*>(transferMgr); 0152 Q_EMIT setupChanged(); 0153 } 0154 0155 QObject* TimelineDelegateController::documentManager() const 0156 { 0157 return m_documentMgr; 0158 } 0159 0160 void TimelineDelegateController::setDocumentManager(QObject *documentMgr) 0161 { 0162 if (m_documentMgr == documentMgr) { 0163 return; 0164 } 0165 0166 m_documentMgr = qobject_cast<DocumentManager*>(documentMgr); 0167 Q_EMIT setupChanged(); 0168 Q_EMIT contentChanged(); 0169 } 0170 0171 QString TimelineDelegateController::batchId() const 0172 { 0173 return m_batchId; 0174 } 0175 0176 void TimelineDelegateController::setBatchId(const QString &batchId) 0177 { 0178 if (m_batchId == batchId) 0179 return; 0180 0181 m_batchId = batchId; 0182 Q_EMIT batchIdChanged(); 0183 Q_EMIT contentChanged(); 0184 Q_EMIT departureChanged(); 0185 Q_EMIT arrivalChanged(); 0186 Q_EMIT journeyChanged(); 0187 Q_EMIT previousLocationChanged(); 0188 checkForUpdate(batchId); 0189 } 0190 0191 bool TimelineDelegateController::isCurrent() const 0192 { 0193 return m_isCurrent; 0194 } 0195 0196 void TimelineDelegateController::setCurrent(bool current, const QVariant &res) 0197 { 0198 if (current == m_isCurrent) { 0199 return; 0200 } 0201 0202 m_isCurrent = current; 0203 Q_EMIT currentChanged(); 0204 0205 if (!LocationUtil::isLocationChange(res)) { 0206 return; 0207 } 0208 0209 if (!s_progressTimer && m_isCurrent) { 0210 s_progressTimer = new QTimer(QCoreApplication::instance()); 0211 s_progressTimer->setInterval(std::chrono::minutes(1)); 0212 s_progressTimer->setTimerType(Qt::VeryCoarseTimer); 0213 s_progressTimer->setSingleShot(false); 0214 } 0215 0216 if (m_isCurrent) { 0217 connect(s_progressTimer, &QTimer::timeout, this, &TimelineDelegateController::progressChanged); 0218 if (s_progressRefCount++ == 0) { 0219 s_progressTimer->start(); 0220 } 0221 } else { 0222 disconnect(s_progressTimer, &QTimer::timeout, this, &TimelineDelegateController::progressChanged); 0223 if (--s_progressRefCount == 0) { 0224 s_progressTimer->stop(); 0225 } 0226 } 0227 } 0228 0229 float TimelineDelegateController::progress() const 0230 { 0231 if (!m_resMgr || m_batchId.isEmpty() || !m_isCurrent) { 0232 return 0.0f; 0233 } 0234 0235 const auto res = m_resMgr->reservation(m_batchId); 0236 const auto startTime = liveStartDateTime(res); 0237 const auto endTime = liveEndDateTime(res); 0238 0239 const auto tripLength = startTime.secsTo(endTime); 0240 if (tripLength <= 0) { 0241 return 0.0f; 0242 } 0243 const auto progress = startTime.secsTo(QDateTime::currentDateTime()); 0244 0245 return std::min(std::max(0.0f, (float)progress / (float)tripLength), 1.0f); 0246 } 0247 0248 KPublicTransport::Stopover TimelineDelegateController::arrival() const 0249 { 0250 if (!m_liveDataMgr || m_batchId.isEmpty()) { 0251 return {}; 0252 } 0253 return m_liveDataMgr->arrival(m_batchId); 0254 } 0255 0256 KPublicTransport::Stopover TimelineDelegateController::departure() const 0257 { 0258 if (!m_liveDataMgr || m_batchId.isEmpty()) { 0259 return {}; 0260 } 0261 return m_liveDataMgr->departure(m_batchId); 0262 } 0263 0264 KPublicTransport::JourneySection TimelineDelegateController::journey() const 0265 { 0266 if (!m_liveDataMgr || m_batchId.isEmpty()) { 0267 return {}; 0268 } 0269 return m_liveDataMgr->journey(m_batchId); 0270 } 0271 0272 void TimelineDelegateController::checkForUpdate(const QString& batchId) 0273 { 0274 if (!m_resMgr || m_batchId.isEmpty()) { 0275 setCurrent(false); 0276 return; 0277 } 0278 if (batchId != m_batchId) { 0279 return; 0280 } 0281 0282 const auto res = m_resMgr->reservation(batchId); 0283 if (!LocationUtil::isLocationChange(res)) { 0284 return; 0285 } 0286 if (ReservationHelper::isCancelled(res)) { 0287 setCurrent(false); 0288 return; 0289 } 0290 0291 const auto now = QDateTime::currentDateTime(); 0292 const auto startTime = relevantStartDateTime(res); 0293 const auto endTime = liveEndDateTime(res); 0294 0295 setCurrent(startTime < now && now < endTime, res); 0296 0297 if (now < startTime) { 0298 scheduleNextUpdate(std::chrono::seconds(now.secsTo(startTime) + 1)); 0299 } else if (now < endTime) { 0300 scheduleNextUpdate(std::chrono::seconds(now.secsTo(endTime) + 1)); 0301 } 0302 } 0303 0304 QDateTime TimelineDelegateController::relevantStartDateTime(const QVariant &res) const 0305 { 0306 auto startTime = SortUtil::startDateTime(res); 0307 if (JsonLd::isA<FlightReservation>(res)) { 0308 const auto flight = res.value<FlightReservation>().reservationFor().value<Flight>(); 0309 if (flight.boardingTime().isValid()) { 0310 startTime = flight.boardingTime(); 0311 } 0312 } else if (JsonLd::isA<TrainReservation>(res) || JsonLd::isA<BusReservation>(res)) { 0313 startTime = startTime.addSecs(-5 * 60); 0314 } 0315 0316 return startTime; 0317 } 0318 0319 QDateTime TimelineDelegateController::liveStartDateTime(const QVariant& res) const 0320 { 0321 if (m_liveDataMgr) { 0322 const auto dep = m_liveDataMgr->departure(m_batchId); 0323 if (dep.expectedDepartureTime().isValid()) { 0324 return dep.expectedDepartureTime(); 0325 } 0326 } 0327 return SortUtil::startDateTime(res); 0328 } 0329 0330 QDateTime TimelineDelegateController::liveEndDateTime(const QVariant& res) const 0331 { 0332 if (m_liveDataMgr) { 0333 const auto arr = m_liveDataMgr->arrival(m_batchId); 0334 if (arr.expectedArrivalTime().isValid()) { 0335 return arr.expectedArrivalTime(); 0336 } 0337 } 0338 return SortUtil::endDateTime(res); 0339 } 0340 0341 void TimelineDelegateController::scheduleNextUpdate(std::chrono::milliseconds ms) 0342 { 0343 if (s_currentTimer->isActive() && s_currentTimer->remainingTimeAsDuration() < ms) { 0344 return; 0345 } 0346 s_currentTimer->start(ms); 0347 } 0348 0349 void TimelineDelegateController::batchChanged(const QString& batchId) 0350 { 0351 if (batchId != m_batchId || m_batchId.isEmpty()) { 0352 return; 0353 } 0354 checkForUpdate(batchId); 0355 Q_EMIT contentChanged(); 0356 Q_EMIT arrivalChanged(); 0357 Q_EMIT departureChanged(); 0358 Q_EMIT journeyChanged(); 0359 Q_EMIT previousLocationChanged(); 0360 } 0361 0362 QVariant TimelineDelegateController::previousLocation() const 0363 { 0364 if (m_batchId.isEmpty() || !m_resMgr) { 0365 return {}; 0366 } 0367 0368 const auto prevBatch = m_resMgr->previousBatch(m_batchId); 0369 if (prevBatch.isEmpty()) { 0370 return {}; 0371 } 0372 0373 const auto res = m_resMgr->reservation(prevBatch); 0374 auto endTime = SortUtil::endDateTime(res); 0375 if (m_liveDataMgr) { 0376 const auto arr = m_liveDataMgr->arrival(prevBatch); 0377 if (arr.hasExpectedArrivalTime()) { 0378 endTime = arr.expectedArrivalTime(); 0379 } 0380 } 0381 0382 if (endTime < QDateTime::currentDateTime()) { 0383 // past event, we can use GPS rather than predict our location from the itinerary 0384 return {}; 0385 } 0386 0387 if (LocationUtil::isLocationChange(res)) { 0388 return LocationUtil::arrivalLocation(res); 0389 } else { 0390 return LocationUtil::location(res); 0391 } 0392 } 0393 0394 QDateTime TimelineDelegateController::effectiveEndTime() const 0395 { 0396 if (!m_resMgr || m_batchId.isEmpty()) { 0397 return {}; 0398 } 0399 0400 const auto arr = arrival(); 0401 if (arr.hasExpectedArrivalTime()) { 0402 return arr.expectedArrivalTime(); 0403 } 0404 return SortUtil::endDateTime(m_resMgr->reservation(m_batchId)); 0405 } 0406 0407 bool TimelineDelegateController::isLocationChange() const 0408 { 0409 if (!m_resMgr || m_batchId.isEmpty()) { 0410 return false; 0411 } 0412 0413 const auto res = m_resMgr->reservation(m_batchId); 0414 return LocationUtil::isLocationChange(res); 0415 } 0416 0417 bool TimelineDelegateController::isPublicTransport() const 0418 { 0419 if (!m_resMgr || m_batchId.isEmpty()) { 0420 return false; 0421 } 0422 0423 const auto res = m_resMgr->reservation(m_batchId); 0424 return LocationUtil::isLocationChange(res) && !JsonLd::isA<RentalCarReservation>(res); 0425 } 0426 0427 static bool isJourneyCandidate(const QVariant &res) 0428 { 0429 // TODO do we really need to constrain this to trains/buses? a long distance train can be a suitable alternative for a missed short distance flight for example 0430 return LocationUtil::isLocationChange(res) && (JsonLd::isA<TrainReservation>(res) || JsonLd::isA<BusReservation>(res)); 0431 } 0432 0433 static bool isLayover(const QVariant &res1, const QVariant &res2) 0434 { 0435 if (!LocationUtil::isLocationChange(res1) || !LocationUtil::isLocationChange(res2) || ReservationHelper::isUnbound(res1) || ReservationHelper::isUnbound(res2)) { 0436 return false; 0437 } 0438 0439 const auto arrDt = SortUtil::endDateTime(res1); 0440 const auto depDt = SortUtil::startDateTime(res2); 0441 const auto layoverTime = arrDt.secsTo(depDt); 0442 if (layoverTime < 0 || layoverTime > Constants::MaximumLayoverTime.count()) { 0443 return false; 0444 } 0445 0446 return LocationUtil::isSameLocation(LocationUtil::arrivalLocation(res1), LocationUtil::departureLocation(res2), LocationUtil::WalkingDistance); 0447 } 0448 0449 KPublicTransport::JourneyRequest TimelineDelegateController::journeyRequestOne() const 0450 { 0451 if (!m_resMgr || m_batchId.isEmpty() || !m_liveDataMgr) { 0452 return {}; 0453 } 0454 0455 const auto res = m_resMgr->reservation(m_batchId); 0456 if (!isJourneyCandidate(res)) { 0457 return {}; 0458 } 0459 0460 KPublicTransport::JourneyRequest req; 0461 req.setFrom(PublicTransport::locationFromPlace(LocationUtil::departureLocation(res), res)); 0462 req.setTo(PublicTransport::locationFromPlace(LocationUtil::arrivalLocation(res), res)); 0463 req.setDateTime(std::max(QDateTime::currentDateTime(), SortUtil::startDateTime(res))); 0464 req.setDateTimeMode(KPublicTransport::JourneyRequest::Departure); 0465 req.setDownloadAssets(true); 0466 req.setIncludeIntermediateStops(true); 0467 req.setIncludePaths(true); 0468 PublicTransport::selectBackends(req, m_liveDataMgr->publicTransportManager(), res); 0469 return req; 0470 } 0471 0472 KPublicTransport::JourneyRequest TimelineDelegateController::journeyRequestFull() const 0473 { 0474 auto req = journeyRequestOne(); 0475 if (!req.isValid()) { 0476 return {}; 0477 } 0478 0479 // find full journey by looking at subsequent elements 0480 auto prevRes = m_resMgr->reservation(m_batchId); 0481 auto prevBatchId = m_batchId; 0482 while (true) { 0483 auto endBatchId = m_resMgr->nextBatch(prevBatchId); 0484 auto endRes = m_resMgr->reservation(endBatchId); 0485 if (!isJourneyCandidate(endRes) || !isLayover(prevRes, endRes)) { 0486 break; 0487 } 0488 0489 req.setTo(PublicTransport::locationFromPlace(LocationUtil::arrivalLocation(endRes), endRes)); 0490 prevRes = endRes; 0491 prevBatchId = endBatchId; 0492 } 0493 0494 return req; 0495 } 0496 0497 void TimelineDelegateController::applyJourney(const QVariant &journey, bool includeFollowing) 0498 { 0499 if (!m_resMgr || m_batchId.isEmpty()) { 0500 return; 0501 } 0502 0503 const auto jny = journey.value<KPublicTransport::Journey>(); 0504 std::vector<KPublicTransport::JourneySection> sections; 0505 std::copy_if(jny.sections().begin(), jny.sections().end(), std::back_inserter(sections), [](const auto §ion) { 0506 return section.mode() == KPublicTransport::JourneySection::PublicTransport; 0507 }); 0508 if (sections.empty()) { 0509 return; 0510 } 0511 0512 // find all batches we are replying here (same logic as in journeyRequest) 0513 std::vector<QString> oldBatches({m_batchId}); 0514 if (includeFollowing) { 0515 auto prevRes = m_resMgr->reservation(m_batchId); 0516 auto prevBatchId = m_batchId; 0517 while (true) { 0518 auto endBatchId = m_resMgr->nextBatch(prevBatchId); 0519 auto endRes = m_resMgr->reservation(endBatchId); 0520 qDebug() << endRes << isJourneyCandidate(endRes) << isLayover(prevRes, endRes); 0521 if (!isJourneyCandidate(endRes) || !isLayover(prevRes, endRes)) { 0522 break; 0523 } 0524 0525 oldBatches.push_back(endBatchId); 0526 prevRes = endRes; 0527 prevBatchId = endBatchId; 0528 } 0529 } 0530 qCDebug(Log) << "Affected batches:" << oldBatches; 0531 0532 // align sections with affected batches, by type, and insert/update accordingly 0533 auto it = oldBatches.begin(); 0534 QString lastResId; 0535 for (const auto §ion : sections) { 0536 QVariant oldRes; 0537 if (it != oldBatches.end()) { 0538 lastResId = *it; 0539 oldRes = m_resMgr->reservation(*it); 0540 } 0541 0542 // same type -> update the existing one 0543 if (PublicTransportMatcher::isSameMode(oldRes, section)) { 0544 const auto resIds = m_resMgr->reservationsForBatch(*it); 0545 for (const auto &resId : resIds) { 0546 auto res = m_resMgr->reservation(resId); 0547 res = PublicTransport::applyJourneySection(res, section); 0548 m_resMgr->updateReservation(resId, res); 0549 m_liveDataMgr->setJourney(resId, section); 0550 } 0551 ++it; 0552 } else { 0553 auto res = PublicTransport::reservationFromJourneySection(section); 0554 0555 // copy ticket data from previous element 0556 // TODO this would need to be done for the entire batch! 0557 if (!lastResId.isEmpty()) { 0558 auto lastRes = m_resMgr->reservation(lastResId); 0559 JsonLdDocument::writeProperty(lastRes, "reservationFor", {}); 0560 res = JsonLdDocument::apply(lastRes, res); 0561 } 0562 0563 const auto resId = m_resMgr->addReservation(res); 0564 m_liveDataMgr->setJourney(resId, section); 0565 } 0566 } 0567 0568 // remove left over reservations 0569 for (; it != oldBatches.end(); ++it) { 0570 m_resMgr->removeBatch(*it); 0571 } 0572 } 0573 0574 bool TimelineDelegateController::connectionWarning() const 0575 { 0576 if (!m_resMgr || m_batchId.isEmpty() || !m_liveDataMgr) { 0577 return false; 0578 } 0579 0580 const auto curRes = m_resMgr->reservation(m_batchId); 0581 if (!LocationUtil::isLocationChange(curRes) || ReservationHelper::isCancelled(curRes)) { 0582 return false; 0583 } 0584 0585 // if the current item has canceled departure/arrival, warn as well 0586 if (departure().disruptionEffect() == KPublicTransport::Disruption::NoService || arrival().disruptionEffect() == KPublicTransport::Disruption::NoService) { 0587 return true; 0588 } 0589 0590 const auto prevResId = m_resMgr->previousBatch(m_batchId); 0591 const auto prevRes = m_resMgr->reservation(prevResId); 0592 if (!LocationUtil::isLocationChange(prevRes) || ReservationHelper::isCancelled(prevRes)) { 0593 return false; 0594 } 0595 0596 const auto prevArr = m_liveDataMgr->arrival(prevResId); 0597 const auto prevArrDt = std::max(SortUtil::endDateTime(prevRes), prevArr.expectedArrivalTime()); 0598 0599 const auto curDepDt = std::max(SortUtil::startDateTime(curRes), departure().expectedDepartureTime()); 0600 if (curDepDt.isValid() && prevArrDt.isValid()) { 0601 return curDepDt < prevArrDt; 0602 } 0603 0604 return false; 0605 } 0606 0607 bool TimelineDelegateController::isCanceled() const 0608 { 0609 if (!m_resMgr || m_batchId.isEmpty()) { 0610 return false; 0611 } 0612 0613 const auto res = m_resMgr->reservation(m_batchId); 0614 return ReservationHelper::isCancelled(res); 0615 } 0616 0617 static void mapArgumentsForLocation(QJSValue &args, const QVariant &location, QJSEngine *engine) 0618 { 0619 args.setProperty(QStringLiteral("placeName"), LocationUtil::name(location)); 0620 args.setProperty(QStringLiteral("region"), LocationHelper::regionCode(location)); 0621 0622 const auto geo = LocationUtil::geo(location); 0623 args.setProperty(QStringLiteral("coordinate"), engine->toScriptValue(QPointF(geo.longitude(), geo.latitude()))); 0624 } 0625 0626 struct CoachData { 0627 QString coachName; 0628 KPublicTransport::VehicleSection::Class coachClass = KPublicTransport::VehicleSection::UnknownClass; 0629 }; 0630 0631 static CoachData coachDataForReservation(const QVariant &res) 0632 { 0633 if (!JsonLd::isA<TrainReservation>(res)) { 0634 return {}; 0635 } 0636 0637 const auto trainRes = res.value<TrainReservation>(); 0638 const auto seat = trainRes.reservedTicket().value<Ticket>().ticketedSeat(); 0639 0640 CoachData data; 0641 data.coachName = seat.seatSection(); 0642 if (seat.seatingType() == QLatin1StringView("1")) { 0643 data.coachClass = KPublicTransport::VehicleSection::FirstClass; 0644 } else if (seat.seatingType() == QLatin1StringView("2")) { 0645 data.coachClass = KPublicTransport::VehicleSection::SecondClass; 0646 } 0647 return data; 0648 } 0649 0650 static QString platformSectionsForCoachData(const KPublicTransport::Stopover &stop, const CoachData &coach) 0651 { 0652 KPublicTransport::PlatformLayout layouter; 0653 if (!coach.coachName.isEmpty()) { 0654 return layouter.sectionsForVehicleSection(stop, coach.coachName); 0655 } else if (coach.coachClass != KPublicTransport::VehicleSection::UnknownClass) { 0656 return layouter.sectionsForClass(stop, coach.coachClass); 0657 } else { 0658 return layouter.sectionsForVehicle(stop); 0659 } 0660 0661 return {}; 0662 } 0663 0664 static void mapArgumentsForPt(QJSValue &args, QLatin1StringView prefix, const KPublicTransport::Stopover &stop, const CoachData &coach) 0665 { 0666 const auto platformName = stop.hasExpectedPlatform() ? stop.expectedPlatform() : stop.scheduledPlatform(); 0667 if (!platformName.isEmpty()) { 0668 const auto sections = platformSectionsForCoachData(stop, coach); 0669 args.setProperty(prefix + QLatin1StringView("PlatformName"), sections.isEmpty() ? platformName : (platformName + QLatin1Char(' ') + sections)); 0670 } 0671 0672 if (stop.route().line().mode() != KPublicTransport::Line::Unknown) { 0673 args.setProperty(prefix + QLatin1StringView("PlatformMode"), PublicTransport::lineModeToPlatformMode(stop.route().line().mode())); 0674 } 0675 0676 const auto ifopt = stop.stopPoint().identifier(QStringLiteral("ifopt")); 0677 if (!ifopt.isEmpty()) { 0678 args.setProperty(prefix + QLatin1StringView("PlatformIfopt"), ifopt); 0679 } 0680 } 0681 0682 static void mapArrivalArgumesForRes(QJSValue &args, const QVariant &res) 0683 { 0684 if (JsonLd::isA<TrainReservation>(res)) { 0685 const auto trip = res.value<TrainReservation>().reservationFor().value<TrainTrip>(); 0686 args.setProperty(QStringLiteral("arrivalPlatformName"), trip.arrivalPlatform()); 0687 args.setProperty(QStringLiteral("arrivalPlatformMode"), KOSMIndoorMap::Platform::Rail); 0688 } else if (JsonLd::isA<BusReservation>(res)) { 0689 const auto trip = res.value<BusReservation>().reservationFor().value<BusTrip>(); 0690 args.setProperty(QStringLiteral("arrivalPlatformName"), trip.arrivalPlatform()); 0691 args.setProperty(QStringLiteral("arrivalPlatformMode"), KOSMIndoorMap::Platform::Bus); 0692 } 0693 // TODO there is no arrival gate property (yet) 0694 } 0695 0696 static void mapDepartureArgumentsForRes(QJSValue &args, const QVariant &res) 0697 { 0698 if (JsonLd::isA<TrainReservation>(res)) { 0699 const auto trip = res.value<TrainReservation>().reservationFor().value<TrainTrip>(); 0700 args.setProperty(QStringLiteral("departurePlatformName"), trip.departurePlatform()); 0701 args.setProperty(QStringLiteral("departurePlatformMode"), KOSMIndoorMap::Platform::Rail); 0702 } else if (JsonLd::isA<BusReservation>(res)) { 0703 const auto trip = res.value<BusReservation>().reservationFor().value<BusTrip>(); 0704 args.setProperty(QStringLiteral("departurePlatformName"), trip.departurePlatform()); 0705 args.setProperty(QStringLiteral("departurePlatformMode"), KOSMIndoorMap::Platform::Bus); 0706 } else if (JsonLd::isA<FlightReservation>(res)) { 0707 const auto flight = res.value<FlightReservation>().reservationFor().value<Flight>(); 0708 args.setProperty(QStringLiteral("departureGateName"), flight.departureGate()); 0709 } 0710 } 0711 0712 QJSValue TimelineDelegateController::arrivalMapArguments() const 0713 { 0714 const auto engine = qjsEngine(this); 0715 if (!engine || !m_resMgr || m_batchId.isEmpty() || !m_transferMgr || !m_liveDataMgr) { 0716 return {}; 0717 } 0718 0719 const auto res = m_resMgr->reservation(m_batchId); 0720 if (!LocationUtil::isLocationChange(res)) { 0721 return {}; 0722 } 0723 0724 auto args = engine->newObject(); 0725 mapArgumentsForLocation(args, LocationUtil::arrivalLocation(res), engine); 0726 0727 // arrival location 0728 mapArrivalArgumesForRes(args, res); 0729 const auto arr = arrival(); 0730 mapArgumentsForPt(args, QLatin1StringView("arrival"), arr, coachDataForReservation(res)); 0731 0732 // arrival time 0733 auto arrTime = arr.hasExpectedArrivalTime() ? arr.expectedArrivalTime() : arr.scheduledArrivalTime(); 0734 if (!arrTime.isValid()) { 0735 arrTime = SortUtil::endDateTime(res); 0736 } 0737 args.setProperty(QStringLiteral("beginTime"), engine->toScriptValue(arrTime)); 0738 if (arrTime.timeSpec() == Qt::TimeZone) { 0739 args.setProperty(QStringLiteral("timeZone"), QString::fromUtf8(arrTime.timeZone().id())); 0740 } 0741 0742 // look for departure for a following transfer 0743 const auto transfer = m_transferMgr->transfer(m_batchId, Transfer::After); 0744 if (transfer.state() == Transfer::Selected) { 0745 const auto dep = PublicTransport::firstTransportSection(transfer.journey()).departure(); 0746 mapArgumentsForPt(args, QLatin1StringView("departure"), dep, {}); 0747 args.setProperty(QStringLiteral("endTime"), engine->toScriptValue(dep.hasExpectedDepartureTime() ? dep.expectedDepartureTime() : dep.scheduledDepartureTime())); 0748 return args; 0749 } 0750 0751 // ... or layover 0752 const auto nextResId = m_resMgr->nextBatch(m_batchId); 0753 const auto nextRes = m_resMgr->reservation(nextResId); 0754 if (!isLayover(res, nextRes)) { 0755 return args; 0756 } 0757 mapDepartureArgumentsForRes(args, nextRes); 0758 const auto dep = m_liveDataMgr->departure(nextResId); 0759 mapArgumentsForPt(args, QLatin1StringView("departure"), dep, coachDataForReservation(nextRes)); 0760 0761 auto depTime = dep.hasExpectedDepartureTime() ? dep.expectedDepartureTime() : dep.scheduledDepartureTime(); 0762 if (!depTime.isValid()) { 0763 depTime = SortUtil::startDateTime(nextRes); 0764 } 0765 args.setProperty(QStringLiteral("endTime"), engine->toScriptValue(depTime)); 0766 0767 return args; 0768 } 0769 0770 QJSValue TimelineDelegateController::departureMapArguments() const 0771 { 0772 const auto engine = qjsEngine(this); 0773 if (!engine || !m_resMgr || m_batchId.isEmpty() || !m_transferMgr || !m_liveDataMgr) { 0774 return {}; 0775 } 0776 0777 const auto res = m_resMgr->reservation(m_batchId); 0778 if (!LocationUtil::isLocationChange(res)) { 0779 return {}; 0780 } 0781 0782 auto args = engine->newObject(); 0783 mapArgumentsForLocation(args, LocationUtil::departureLocation(res), engine); 0784 0785 // departure location 0786 mapDepartureArgumentsForRes(args, res); 0787 const auto dep = departure(); 0788 mapArgumentsForPt(args, QLatin1StringView("departure"), dep, coachDataForReservation(res)); 0789 0790 // departure time 0791 auto depTime = dep.hasExpectedDepartureTime() ? dep.expectedDepartureTime() : dep.scheduledDepartureTime(); 0792 if (!depTime.isValid()) { 0793 depTime = SortUtil::startDateTime(res); 0794 } 0795 args.setProperty(QStringLiteral("endTime"), engine->toScriptValue(depTime)); 0796 if (depTime.timeSpec() == Qt::TimeZone) { 0797 args.setProperty(QStringLiteral("timeZone"), QString::fromUtf8(depTime.timeZone().id())); 0798 } 0799 0800 // look for arrival for a preceding transfer 0801 const auto transfer = m_transferMgr->transfer(m_batchId, Transfer::Before); 0802 if (transfer.state() == Transfer::Selected) { 0803 const auto arr = PublicTransport::lastTransportSection(transfer.journey()).arrival(); 0804 mapArgumentsForPt(args, QLatin1StringView("arrival"), arr, {}); 0805 args.setProperty(QStringLiteral("beginTime"), engine->toScriptValue(arr.hasExpectedArrivalTime() ? arr.expectedArrivalTime() : arr.scheduledArrivalTime())); 0806 return args; 0807 } 0808 0809 // ... or layover 0810 const auto prevResId = m_resMgr->previousBatch(m_batchId); 0811 const auto prevRes = m_resMgr->reservation(prevResId); 0812 if (!isLayover(prevRes, res)) { 0813 return args; 0814 } 0815 mapArrivalArgumesForRes(args, prevRes); 0816 const auto arr = m_liveDataMgr->arrival(prevResId); 0817 mapArgumentsForPt(args, QLatin1StringView("arrival"), arr, coachDataForReservation(prevRes)); 0818 0819 auto arrTime = arr.hasExpectedArrivalTime() ? arr.expectedArrivalTime() : arr.scheduledArrivalTime(); 0820 if (!arrTime.isValid()) { 0821 arrTime = SortUtil::endDateTime(prevRes); 0822 } 0823 args.setProperty(QStringLiteral("beginTime"), engine->toScriptValue(arrTime)); 0824 0825 return args; 0826 } 0827 0828 QJSValue TimelineDelegateController::mapArguments() const 0829 { 0830 const auto engine = qjsEngine(this); 0831 if (!engine || !m_resMgr || m_batchId.isEmpty() || !m_transferMgr || !m_liveDataMgr) { 0832 return {}; 0833 } 0834 0835 const auto res = m_resMgr->reservation(m_batchId); 0836 if (LocationUtil::isLocationChange(res)) { 0837 return {}; 0838 } 0839 0840 auto args = engine->newObject(); 0841 mapArgumentsForLocation(args, LocationUtil::location(res), engine); 0842 0843 // determine time on site, considering the following sources: 0844 // (1) the full days res is covering 0845 // (2) arrival time of a preceding location change, departure time of a following location change 0846 // (3) arrival time of a preceding transfer, departure time of a following transfer 0847 0848 auto beginDt = SortUtil::startDateTime(res); 0849 beginDt.setTime({}); 0850 0851 for (auto prevResId = m_resMgr->previousBatch(m_batchId); !prevResId.isEmpty(); prevResId = m_resMgr->previousBatch(prevResId)) { 0852 const auto prevRes = m_resMgr->reservation(prevResId); 0853 if (LocationUtil::isLocationChange(prevRes)) { 0854 beginDt = std::max(SortUtil::endDateTime(prevRes), beginDt); 0855 break; 0856 } 0857 } 0858 0859 auto transfer = m_transferMgr->transfer(m_batchId, Transfer::Before); 0860 if (transfer.state() == Transfer::Selected) { 0861 const auto arr = PublicTransport::lastTransportSection(transfer.journey()).arrival(); 0862 mapArgumentsForPt(args, QLatin1StringView("arrival"), arr, {}); 0863 beginDt = std::max(arr.hasExpectedArrivalTime() ? arr.expectedArrivalTime() : arr.scheduledArrivalTime(), beginDt); 0864 } 0865 0866 args.setProperty(QStringLiteral("beginTime"), engine->toScriptValue(beginDt)); 0867 0868 auto endDt = SortUtil::endDateTime(res); 0869 if (endDt.isValid()) { 0870 endDt = endDt.addDays(1); 0871 endDt.setTime({}); 0872 } 0873 0874 transfer = m_transferMgr->transfer(m_batchId, Transfer::After); 0875 if (transfer.state() == Transfer::Selected) { 0876 const auto dep = PublicTransport::firstTransportSection(transfer.journey()).departure(); 0877 mapArgumentsForPt(args, QLatin1StringView("departure"), dep, {}); 0878 const auto depDt = dep.hasExpectedDepartureTime() ? dep.expectedDepartureTime() : dep.scheduledDepartureTime(); 0879 endDt = endDt.isValid() ? std::min(endDt, depDt) : depDt; 0880 } 0881 0882 // search the first location change after the end (which might not always be the next element) 0883 const auto dt = SortUtil::endDateTime(res); 0884 auto nextResId = m_resMgr->nextBatch(m_batchId); 0885 auto nextRes = m_resMgr->reservation(nextResId); 0886 while (dt.isValid() && !nextResId.isEmpty() && SortUtil::startDateTime(nextRes).date() <= dt.date()) { 0887 if (LocationUtil::isLocationChange(nextRes)) { 0888 const auto depDt = SortUtil::startDateTime(nextRes); 0889 if (depDt.isValid() && depDt >= SortUtil::endDateTime((res))) { 0890 endDt = endDt.isValid() ? std::min(depDt, endDt) : depDt; 0891 break; 0892 } 0893 } 0894 0895 nextResId = m_resMgr->nextBatch(nextResId); 0896 nextRes = m_resMgr->reservation(nextResId); 0897 } 0898 0899 if (endDt.isValid()) { 0900 args.setProperty(QStringLiteral("endTime"), engine->toScriptValue(endDt)); 0901 } 0902 0903 return args; 0904 } 0905 0906 void TimelineDelegateController::addToCalendar(KCalendarCore::Calendar *cal) 0907 { 0908 const auto resIds = m_resMgr->reservationsForBatch(m_batchId); 0909 if (resIds.isEmpty() || !cal) { 0910 return; 0911 } 0912 QVector<QVariant> reservations; 0913 reservations.reserve(resIds.size()); 0914 for (const auto &resId : resIds) { 0915 reservations.push_back(m_resMgr->reservation(resId)); 0916 } 0917 0918 const auto existingEvents = CalendarHandler::findEvents(cal, reservations.at(0)); 0919 KCalendarCore::Event::Ptr event; 0920 if (existingEvents.size() == 1) { 0921 event = existingEvents.at(0); 0922 event->startUpdates(); 0923 } else { 0924 event = KCalendarCore::Event::Ptr(new KCalendarCore::Event); 0925 } 0926 0927 CalendarHandler::fillEvent(reservations, event); 0928 CalendarHelper::fillPreTransfer(event, m_transferMgr->transfer(m_batchId, Transfer::Before)); 0929 0930 if (existingEvents.size() == 1) { 0931 event->endUpdates(); 0932 } else { 0933 cal->addEvent(event); 0934 } 0935 } 0936 0937 static void applyVehicleLayout(KPublicTransport::Stopover &stop, const KPublicTransport::Stopover &layout) 0938 { 0939 if (PublicTransport::isSameStopoverForLayout(stop, layout)) { 0940 stop = KPublicTransport::Stopover::merge(stop, layout); 0941 } 0942 } 0943 0944 void TimelineDelegateController::setVehicleLayout(const KPublicTransport::Stopover& stopover, bool arrival) 0945 { 0946 auto jny = journey(); 0947 if (!arrival) { 0948 auto dep = jny.departure(); 0949 applyVehicleLayout(dep, stopover); 0950 jny.setDeparture(dep); 0951 m_liveDataMgr->applyJourney(m_batchId, jny); 0952 } else { 0953 auto arr = jny.arrival(); 0954 applyVehicleLayout(arr, stopover); 0955 jny.setArrival(arr); 0956 m_liveDataMgr->applyJourney(m_batchId, jny); 0957 } 0958 0959 Q_EMIT layoutChanged(); 0960 } 0961 0962 QString TimelineDelegateController::departurePlatformSections() const 0963 { 0964 if (!m_resMgr) { 0965 return {}; 0966 } 0967 const auto res = m_resMgr->reservation(m_batchId); 0968 return platformSectionsForCoachData(departure(), coachDataForReservation(res)); 0969 } 0970 0971 QString TimelineDelegateController::arrivalPlatformSections() const 0972 { 0973 if (!m_resMgr) { 0974 return {}; 0975 } 0976 const auto res = m_resMgr->reservation(m_batchId); 0977 return platformSectionsForCoachData(arrival(), coachDataForReservation(res)); 0978 } 0979 0980 QStringList TimelineDelegateController::documentIds() const 0981 { 0982 if (!m_resMgr || !m_documentMgr || m_batchId.isEmpty()) { 0983 return {}; 0984 } 0985 0986 QStringList result; 0987 const auto resIds = m_resMgr->reservationsForBatch(m_batchId); 0988 for (const auto &resId : resIds) { 0989 const auto res = m_resMgr->reservation(resId); 0990 const auto docIds = DocumentUtil::documentIds(res); 0991 for (const auto &docId : docIds) { 0992 const auto id = docId.toString(); 0993 if (!id.isEmpty() && m_documentMgr->hasDocument(id)) { 0994 result.push_back(id); 0995 } 0996 } 0997 } 0998 0999 std::sort(result.begin(), result.end()); 1000 result.erase(std::unique(result.begin(), result.end()), result.end()); 1001 return result; 1002 } 1003 1004 #include "moc_timelinedelegatecontroller.cpp"