File indexing completed on 2025-02-02 05:02:37
0001 /* 0002 SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "timelinemodel.h" 0008 #include "constants.h" 0009 #include "locationhelper.h" 0010 #include "locationinformation.h" 0011 #include "pkpassmanager.h" 0012 #include "reservationmanager.h" 0013 #include "tripgroup.h" 0014 #include "tripgroupmanager.h" 0015 #include "transfermanager.h" 0016 #include "weatherinformation.h" 0017 0018 #include "weatherforecast.h" 0019 #include "weatherforecastmanager.h" 0020 0021 #include <KItinerary/BusTrip> 0022 #include <KItinerary/CountryDb> 0023 #include <KItinerary/Event> 0024 #include <KItinerary/Flight> 0025 #include <KItinerary/JsonLdDocument> 0026 #include <KItinerary/LocationUtil> 0027 #include <KItinerary/MergeUtil> 0028 #include <KItinerary/Organization> 0029 #include <KItinerary/Reservation> 0030 #include <KItinerary/SortUtil> 0031 #include <KItinerary/TrainTrip> 0032 #include <KItinerary/Visit> 0033 0034 #include <KPkPass/Pass> 0035 0036 #include <KLocalizedString> 0037 0038 #include <QDebug> 0039 #include <QLocale> 0040 0041 #include <cassert> 0042 0043 using namespace KItinerary; 0044 0045 static bool needsSplitting(const QVariant &res) 0046 { 0047 // multi-day event? 0048 if (JsonLd::isA<EventReservation>(res)) { 0049 const auto ev = res.value<EventReservation>().reservationFor().value<Event>(); 0050 if (!ev.endDate().isValid() || ev.startDate().date() == ev.endDate().date()) { 0051 return false; 0052 } 0053 // don't split single day events that end at midnight either 0054 if (ev.startDate().secsTo(ev.endDate()) < 60 * 60 * 8 && ev.endDate().time().hour() == 0) { 0055 return false; 0056 } 0057 return true; 0058 } 0059 0060 return JsonLd::isA<LodgingReservation>(res) 0061 || JsonLd::isA<RentalCarReservation>(res); 0062 } 0063 0064 static QTimeZone timeZone(const QDateTime &dt) 0065 { 0066 return dt.timeSpec() == Qt::TimeZone ? dt.timeZone() : QTimeZone(); 0067 } 0068 0069 TimelineModel::TimelineModel(QObject *parent) 0070 : QAbstractListModel(parent) 0071 { 0072 connect(&m_dayUpdateTimer, &QTimer::timeout, this, &TimelineModel::dayChanged); 0073 m_dayUpdateTimer.setTimerType(Qt::VeryCoarseTimer); 0074 m_dayUpdateTimer.setSingleShot(true); 0075 m_dayUpdateTimer.setInterval((QTime::currentTime().secsTo({23, 59, 59}) + 1) * 1000); 0076 m_dayUpdateTimer.start(); 0077 0078 // make sure we properly update the empty today marker 0079 connect(this, &TimelineModel::todayRowChanged, this, [this]() { 0080 const auto idx = index(todayRow(), 0); 0081 if (m_todayEmpty == idx.data(TimelineModel::TodayEmptyRole).toBool()) { 0082 return; 0083 } 0084 m_todayEmpty = !m_todayEmpty; 0085 Q_EMIT dataChanged(idx, idx); 0086 }); 0087 0088 connect(&m_currentBatchTimer, &QTimer::timeout, this, &TimelineModel::currentBatchChanged); 0089 connect(&m_currentBatchTimer, &QTimer::timeout, this, &TimelineModel::updateTodayMarker); 0090 connect(&m_currentBatchTimer, &QTimer::timeout, this, &TimelineModel::scheduleCurrentBatchTimer); 0091 m_currentBatchTimer.setTimerType(Qt::VeryCoarseTimer); 0092 m_currentBatchTimer.setSingleShot(true); 0093 } 0094 0095 TimelineModel::~TimelineModel() = default; 0096 0097 void TimelineModel::setReservationManager(ReservationManager* mgr) 0098 { 0099 // for auto tests only 0100 if (Q_UNLIKELY(!mgr)) { 0101 beginResetModel(); 0102 disconnect(m_resMgr, &ReservationManager::batchAdded, this, &TimelineModel::batchAdded); 0103 disconnect(m_resMgr, &ReservationManager::batchChanged, this, &TimelineModel::batchChanged); 0104 disconnect(m_resMgr, &ReservationManager::batchContentChanged, this, &TimelineModel::batchChanged); 0105 disconnect(m_resMgr, &ReservationManager::batchRenamed, this, &TimelineModel::batchRenamed); 0106 disconnect(m_resMgr, &ReservationManager::batchRemoved, this, &TimelineModel::batchRemoved); 0107 m_resMgr = mgr; 0108 m_elements.clear(); 0109 endResetModel(); 0110 return; 0111 } 0112 0113 beginResetModel(); 0114 m_resMgr = mgr; 0115 for (const auto &resId : mgr->batches()) { 0116 const auto res = m_resMgr->reservation(resId); 0117 auto elem = TimelineElement(this, resId, res, TimelineElement::SelfContained); 0118 if (!elem.isReservation()) { // a type we can't handle 0119 continue; 0120 } 0121 if (needsSplitting(res)) { 0122 m_elements.push_back(TimelineElement{this, resId, res, TimelineElement::RangeBegin}); 0123 m_elements.push_back(TimelineElement{this, resId, res, TimelineElement::RangeEnd}); 0124 } else { 0125 m_elements.push_back(std::move(elem)); 0126 } 0127 } 0128 m_elements.push_back(TimelineElement{this, TimelineElement::TodayMarker, QDateTime(today(), QTime(0, 0))}); 0129 std::sort(m_elements.begin(), m_elements.end()); 0130 0131 connect(mgr, &ReservationManager::batchAdded, this, &TimelineModel::batchAdded); 0132 connect(mgr, &ReservationManager::batchChanged, this, &TimelineModel::batchChanged); 0133 connect(mgr, &ReservationManager::batchContentChanged, this, &TimelineModel::batchChanged); 0134 connect(mgr, &ReservationManager::batchRenamed, this, &TimelineModel::batchRenamed); 0135 connect(mgr, &ReservationManager::batchRemoved, this, &TimelineModel::batchRemoved); 0136 endResetModel(); 0137 0138 updateTodayMarker(); 0139 updateInformationElements(); 0140 Q_EMIT todayRowChanged(); 0141 0142 scheduleCurrentBatchTimer(); 0143 Q_EMIT currentBatchChanged(); 0144 } 0145 0146 void TimelineModel::setWeatherForecastManager(WeatherForecastManager* mgr) 0147 { 0148 m_weatherMgr = mgr; 0149 updateWeatherElements(); 0150 connect(m_weatherMgr, &WeatherForecastManager::forecastUpdated, this, &TimelineModel::updateWeatherElements); 0151 } 0152 0153 TripGroupManager* TimelineModel::tripGroupManager() const 0154 { 0155 return m_tripGroupManager; 0156 } 0157 0158 void TimelineModel::setTripGroupManager(TripGroupManager *mgr) 0159 { 0160 m_tripGroupManager = mgr; 0161 connect(mgr, &TripGroupManager::tripGroupAdded, this, &TimelineModel::tripGroupAdded); 0162 connect(mgr, &TripGroupManager::tripGroupChanged, this, &TimelineModel::tripGroupChanged); 0163 connect(mgr, &TripGroupManager::tripGroupRemoved, this, &TimelineModel::tripGroupRemoved); 0164 for (const auto &group : mgr->tripGroups()) { 0165 tripGroupAdded(group); 0166 } 0167 } 0168 0169 void TimelineModel::setHomeCountryIsoCode(const QString &isoCode) 0170 { 0171 m_homeCountry = isoCode; 0172 updateInformationElements(); 0173 } 0174 0175 void TimelineModel::setTransferManager(TransferManager *mgr) 0176 { 0177 m_transferManager = mgr; 0178 connect(mgr, &TransferManager::transferAdded, this, &TimelineModel::transferChanged); 0179 connect(mgr, &TransferManager::transferChanged, this, &TimelineModel::transferChanged); 0180 connect(mgr, &TransferManager::transferRemoved, this, &TimelineModel::transferRemoved); 0181 0182 // load existing transfers into the model 0183 for (const auto &batchId : m_resMgr->batches()) { 0184 updateTransfersForBatch(batchId); 0185 } 0186 } 0187 0188 int TimelineModel::rowCount(const QModelIndex& parent) const 0189 { 0190 if (parent.isValid() || !m_resMgr) { 0191 return 0; 0192 } 0193 return m_elements.size(); 0194 } 0195 0196 QVariant TimelineModel::data(const QModelIndex& index, int role) const 0197 { 0198 if (!index.isValid() || !m_resMgr) { 0199 return {}; 0200 } 0201 0202 const auto &elem = m_elements.at(index.row()); 0203 switch (role) { 0204 case SectionHeaderRole: 0205 // see TimelineSectionDelegateController 0206 return elem.dt.date().toString(Qt::ISODate); 0207 case BatchIdRole: 0208 return elem.isReservation() ? elem.batchId() : QString(); 0209 case ElementTypeRole: 0210 return elem.elementType; 0211 case TodayEmptyRole: 0212 if (elem.elementType == TimelineElement::TodayMarker) { 0213 return isDateEmpty(m_elements.at(index.row()).dt.date()); 0214 } 0215 return {}; 0216 case IsTodayRole: 0217 return elem.dt.date() == today(); 0218 case ElementRangeRole: 0219 return elem.rangeType; 0220 case LocationInformationRole: 0221 if (elem.elementType == TimelineElement::LocationInfo) 0222 return elem.content(); 0223 break; 0224 case WeatherForecastRole: 0225 if (elem.elementType == TimelineElement::WeatherForecast) 0226 return elem.content(); 0227 break; 0228 case ReservationsRole: 0229 { 0230 if (!elem.isReservation()) { 0231 return {}; 0232 } 0233 const auto resIds = m_resMgr->reservationsForBatch(elem.batchId()); 0234 QVector<QVariant> v; 0235 v.reserve(resIds.size()); 0236 for (const auto &resId : resIds) { 0237 v.push_back(m_resMgr->reservation(resId)); 0238 } 0239 std::sort(v.begin(), v.end(), SortUtil::isBefore); 0240 return QVariant::fromValue(v); 0241 } 0242 case TripGroupIdRole: 0243 if (elem.elementType == TimelineElement::TripGroup) { 0244 return elem.content(); 0245 } 0246 break; 0247 case TripGroupRole: 0248 if (elem.elementType == TimelineElement::TripGroup) 0249 return QVariant::fromValue(m_tripGroupManager->tripGroup(elem.content().toString())); 0250 break; 0251 case TransferRole: 0252 if (elem.elementType == TimelineElement::Transfer) { 0253 return elem.content(); 0254 } 0255 break; 0256 case StartDateTimeRole: 0257 return elem.dt; 0258 case EndDateTimeRole: 0259 return elem.endDateTime(); 0260 case IsTimeboxedRole: 0261 return elem.isTimeBoxed(); 0262 case IsCanceledRole: 0263 return elem.isCanceled(); 0264 } 0265 return {}; 0266 } 0267 0268 QHash<int, QByteArray> TimelineModel::roleNames() const 0269 { 0270 auto names = QAbstractListModel::roleNames(); 0271 names.insert(SectionHeaderRole, "sectionHeader"); 0272 names.insert(BatchIdRole, "batchId"); 0273 names.insert(ElementTypeRole, "type"); 0274 names.insert(TodayEmptyRole, "isTodayEmpty"); 0275 names.insert(IsTodayRole, "isToday"); 0276 names.insert(ElementRangeRole, "rangeType"); 0277 names.insert(LocationInformationRole, "locationInformation"); 0278 names.insert(WeatherForecastRole, "weatherInformation"); 0279 names.insert(ReservationsRole, "reservations"); 0280 names.insert(TripGroupIdRole, "tripGroupId"); 0281 names.insert(TripGroupRole, "tripGroup"); 0282 names.insert(TransferRole, "transfer"); 0283 return names; 0284 } 0285 0286 int TimelineModel::todayRow() const 0287 { 0288 const auto it = std::find_if(m_elements.begin(), m_elements.end(), [](const auto &e) { return e.elementType == TimelineElement::TodayMarker; }); 0289 return std::distance(m_elements.begin(), it); 0290 } 0291 0292 void TimelineModel::batchAdded(const QString &resId) 0293 { 0294 const auto res = m_resMgr->reservation(resId); 0295 if (needsSplitting(res)) { 0296 insertElement(TimelineElement{this, resId, res, TimelineElement::RangeBegin}); 0297 insertElement(TimelineElement{this, resId, res, TimelineElement::RangeEnd}); 0298 } else { 0299 insertElement(TimelineElement{this, resId, res, TimelineElement::SelfContained}); 0300 } 0301 0302 updateInformationElements(); 0303 updateTransfersForBatch(resId); 0304 Q_EMIT todayRowChanged(); 0305 0306 scheduleCurrentBatchTimer(); 0307 Q_EMIT currentBatchChanged(); 0308 } 0309 0310 void TimelineModel::insertElement(TimelineElement &&elem) 0311 { 0312 if (elem.elementType == TimelineElement::Undefined) { 0313 return; 0314 } 0315 0316 auto it = std::lower_bound(m_elements.begin(), m_elements.end(), elem); 0317 const auto row = std::distance(m_elements.begin(), it); 0318 0319 beginInsertRows({}, row, row); 0320 m_elements.insert(it, std::move(elem)); 0321 endInsertRows(); 0322 } 0323 0324 std::vector<TimelineElement>::iterator TimelineModel::insertOrUpdate(std::vector<TimelineElement>::iterator it, TimelineElement &&elem) 0325 { 0326 assert(elem.elementType != TimelineElement::Undefined); 0327 0328 while (it != m_elements.end() && (*it) < elem) { 0329 ++it; 0330 } 0331 0332 if (it != m_elements.end() && (*it) == elem) { 0333 const auto row = std::distance(m_elements.begin(), it); 0334 (*it) = std::move(elem); 0335 Q_EMIT dataChanged(index(row, 0), index(row, 0)); 0336 } else { 0337 const auto row = std::distance(m_elements.begin(), it); 0338 beginInsertRows({}, row, row); 0339 it = m_elements.insert(it, std::move(elem)); 0340 endInsertRows(); 0341 } 0342 return it; 0343 } 0344 0345 void TimelineModel::batchChanged(const QString &resId) 0346 { 0347 const auto res = m_resMgr->reservation(resId); 0348 if (needsSplitting(res)) { 0349 updateElement(resId, res, TimelineElement::RangeBegin); 0350 updateElement(resId, res, TimelineElement::RangeEnd); 0351 } else { 0352 updateElement(resId, res, TimelineElement::SelfContained); 0353 } 0354 0355 updateInformationElements(); 0356 0357 scheduleCurrentBatchTimer(); 0358 Q_EMIT currentBatchChanged(); 0359 } 0360 0361 void TimelineModel::batchRenamed(const QString& oldBatchId, const QString& newBatchId) 0362 { 0363 for (auto it = m_elements.begin(); it != m_elements.end(); ++it) { 0364 if (!(*it).isReservation() || (*it).batchId() != oldBatchId) { 0365 continue; 0366 } 0367 0368 (*it).setContent(newBatchId); 0369 const auto idx = index(std::distance(m_elements.begin(), it), 0); 0370 Q_EMIT dataChanged(idx, idx); 0371 0372 if ((*it).rangeType == TimelineElement::SelfContained || (*it).rangeType == TimelineElement::RangeEnd) { 0373 break; 0374 } 0375 } 0376 } 0377 0378 void TimelineModel::updateElement(const QString &resId, const QVariant &res, TimelineElement::RangeType rangeType) 0379 { 0380 const auto it = std::find_if(m_elements.begin(), m_elements.end(), [resId, rangeType](const auto &e) { 0381 return e.isReservation() && e.batchId() == resId && e.rangeType == rangeType; 0382 }); 0383 if (it == m_elements.end()) { 0384 return; 0385 } 0386 const auto row = std::distance(m_elements.begin(), it); 0387 const auto newDt = TimelineElement::relevantDateTime(res, rangeType); 0388 0389 if ((*it).dt != newDt) { 0390 // element moved 0391 beginRemoveRows({}, row, row); 0392 m_elements.erase(it); 0393 endRemoveRows(); 0394 insertElement(TimelineElement{this, resId, res, rangeType}); 0395 } else { 0396 Q_EMIT dataChanged(index(row, 0), index(row, 0)); 0397 } 0398 } 0399 0400 void TimelineModel::batchRemoved(const QString &resId) 0401 { 0402 const auto it = std::find_if(m_elements.begin(), m_elements.end(), [resId](const auto &e) { 0403 return e.isReservation() && e.batchId() == resId; 0404 }); 0405 if (it == m_elements.end()) { 0406 return; 0407 } 0408 const auto isSplit = (*it).rangeType == TimelineElement::RangeBegin; 0409 const auto row = std::distance(m_elements.begin(), it); 0410 0411 beginRemoveRows({}, row, row); 0412 m_elements.erase(it); 0413 endRemoveRows(); 0414 Q_EMIT todayRowChanged(); 0415 0416 if (isSplit) { 0417 batchRemoved(resId); 0418 } 0419 0420 updateInformationElements(); 0421 0422 scheduleCurrentBatchTimer(); 0423 Q_EMIT currentBatchChanged(); 0424 } 0425 0426 void TimelineModel::dayChanged() 0427 { 0428 updateTodayMarker(); 0429 updateWeatherElements(); 0430 0431 m_dayUpdateTimer.setInterval((QTime::currentTime().secsTo({23, 59, 59}) + 1) * 1000); 0432 m_dayUpdateTimer.start(); 0433 0434 scheduleCurrentBatchTimer(); 0435 Q_EMIT currentBatchChanged(); 0436 } 0437 0438 void TimelineModel::updateTodayMarker() 0439 { 0440 auto dt = now(); 0441 auto it = std::lower_bound(m_elements.begin(), m_elements.end(), dt); 0442 0443 if (it != m_elements.begin()) { 0444 const auto prevIt = std::prev(it); 0445 // check if the previous element is the old today marker, if so nothing to do 0446 if ((*prevIt).elementType == TimelineElement::TodayMarker) { 0447 (*prevIt).dt = dt; 0448 return; 0449 } 0450 // check if the previous element is still ongoing, in that case we want to be before that 0451 if ((*prevIt).dt.date() == today() && ((*prevIt).isTimeBoxed() && (*prevIt).endDateTime() > now())) { 0452 it = prevIt; 0453 dt = (*prevIt).dt; 0454 } 0455 } 0456 0457 const auto newRow = std::distance(m_elements.begin(), it); 0458 const auto oldRow = todayRow(); 0459 if (oldRow >= newRow) { 0460 return; 0461 } 0462 0463 beginInsertRows({}, newRow, newRow); 0464 m_elements.insert(it, TimelineElement{this, TimelineElement::TodayMarker, dt}); 0465 endInsertRows(); 0466 0467 beginRemoveRows({}, oldRow, oldRow); 0468 m_elements.erase(m_elements.begin() + oldRow); 0469 endRemoveRows(); 0470 Q_EMIT todayRowChanged(); 0471 } 0472 0473 void TimelineModel::updateInformationElements() 0474 { 0475 // the location information is shown after location changes or before stationary elements 0476 // when transitioning into a location that: 0477 // - differs in one or more properties from the home country, and the difference 0478 // was introduced by this transtion 0479 // - differs in timezone from the previous location, and that timezone has a different 0480 // offset at the time of transition 0481 0482 LocationInformation homeCountry; 0483 homeCountry.setIsoCode(m_homeCountry); 0484 0485 auto previousCountry = homeCountry; 0486 for (auto it = m_elements.begin(); it != m_elements.end();) { 0487 if ((*it).elementType == TimelineElement::LocationInfo) { // this is one we didn't generate, otherwise it would be beyond that 0488 const auto row = std::distance(m_elements.begin(), it); 0489 beginRemoveRows({}, row, row); 0490 it = m_elements.erase(it); 0491 endRemoveRows(); 0492 continue; 0493 } 0494 0495 if ((*it).isCanceled() || (*it).isInformational()) { 0496 ++it; 0497 continue; 0498 } 0499 0500 auto newCountry = homeCountry; 0501 newCountry.setIsoCode(LocationUtil::address((*it).destination()).addressCountry()); 0502 newCountry.setTimeZone(previousCountry.timeZone(), (*it).dt); 0503 newCountry.setTimeZone(timeZone((*it).endDateTime()), (*it).dt); 0504 if (newCountry == previousCountry) { 0505 ++it; 0506 continue; 0507 } 0508 if (!(newCountry == homeCountry) || newCountry.hasRelevantTimeZoneChange(previousCountry)) { 0509 // for location changes, we want this after the corresponding element 0510 const auto dt = (*it).isLocationChange() ? (*it).endDateTime() : (*it).dt; 0511 it = insertOrUpdate(it, TimelineElement{this, TimelineElement::LocationInfo, dt, QVariant::fromValue(newCountry)}); 0512 } 0513 0514 ++it; 0515 previousCountry = newCountry; 0516 } 0517 0518 updateWeatherElements(); 0519 } 0520 0521 void TimelineModel::updateWeatherElements() 0522 { 0523 if (!m_weatherMgr || !m_weatherMgr->allowNetworkAccess() || m_elements.empty()) { 0524 return; 0525 } 0526 0527 qDebug() << "recomputing weather elements"; 0528 GeoCoordinates geo; 0529 QString label; 0530 0531 auto date = now(); 0532 // round to next full hour 0533 date.setTime(QTime(date.time().hour(), 0)); 0534 date = date.addSecs(60 * 60); 0535 const auto maxForecastTime = m_weatherMgr->maximumForecastTime(date.date()); 0536 0537 // look through the past, clean up weather elements there and figure out where we are 0538 auto it = m_elements.begin(); 0539 for (; it != m_elements.end() && (*it).dt < now();) { 0540 if ((*it).elementType == TimelineElement::WeatherForecast) { 0541 const auto row = std::distance(m_elements.begin(), it); 0542 beginRemoveRows({}, row, row); 0543 it = m_elements.erase(it); 0544 endRemoveRows(); 0545 continue; 0546 } 0547 0548 if ((*it).isCanceled()) { 0549 ++it; 0550 continue; 0551 } 0552 const auto newGeo = LocationUtil::geo((*it).destination()); 0553 if ((*it).isLocationChange() || newGeo.isValid()) { 0554 geo = newGeo; 0555 label = WeatherInformation::labelForPlace((*it).destination()); 0556 0557 // if we are in an ongoing location change, start afterwards 0558 const auto endDt = (*it).endDateTime(); 0559 if ((*it).isLocationChange() && endDt.isValid() && date < endDt) { 0560 date = endDt; 0561 } 0562 } 0563 0564 ++it; 0565 } 0566 0567 while(it != m_elements.end() && date < maxForecastTime) { 0568 0569 if ((*it).dt < date || (*it).elementType == TimelineElement::TodayMarker) { 0570 // clean up outdated weather elements (happens when merging previously split ranges) 0571 if ((*it).elementType == TimelineElement::WeatherForecast) { 0572 const auto row = std::distance(m_elements.begin(), it); 0573 beginRemoveRows({}, row, row); 0574 it = m_elements.erase(it); 0575 endRemoveRows(); 0576 if (it == m_elements.end()) { 0577 break; 0578 } 0579 continue; 0580 } 0581 0582 // track where we are 0583 if ((*it).isCanceled()) { 0584 ++it; 0585 continue; 0586 } 0587 const auto newGeo = LocationUtil::geo((*it).destination()); 0588 if ((*it).isLocationChange() || newGeo.isValid()) { 0589 geo = newGeo; 0590 label = WeatherInformation::labelForPlace((*it).destination()); 0591 } 0592 0593 ++it; 0594 continue; 0595 } 0596 0597 // determine the length of the forecast range (at most until the end of the day) 0598 auto endTime = date; 0599 endTime.setTime(QTime(23, 59, 59)); 0600 auto nextStartTime = endTime; 0601 GeoCoordinates newGeo = geo; 0602 QString newLabel = label; 0603 for (auto it2 = it; it2 != m_elements.end(); ++it2) { 0604 if ((*it2).dt >= endTime) { 0605 break; 0606 } 0607 if ((*it2).isLocationChange()) { 0608 // exclude the actual travel time from forecast ranges 0609 endTime = std::min(endTime, (*it2).dt); 0610 nextStartTime = std::max(endTime, (*it2).endDateTime()); 0611 newGeo = LocationUtil::geo((*it2).destination()); 0612 newLabel = WeatherInformation::labelForPlace((*it2).destination()); 0613 break; 0614 } 0615 } 0616 0617 ::WeatherForecast fc; 0618 if (geo.isValid()) { 0619 m_weatherMgr->monitorLocation(geo.latitude(), geo.longitude()); 0620 fc = m_weatherMgr->forecast(geo.latitude(), geo.longitude(), date, endTime); 0621 } 0622 0623 // updated or new data 0624 if (fc.isValid()) { 0625 it = insertOrUpdate(it, TimelineElement{this, TimelineElement::WeatherForecast, date, QVariant::fromValue(WeatherInformation{fc, label})}); 0626 } 0627 // we have no forecast data, but a matching weather element: remove 0628 else if ((*it).elementType == TimelineElement::WeatherForecast && (*it).dt == date) { 0629 const auto row = std::distance(m_elements.begin(), it); 0630 beginRemoveRows({}, row, row); 0631 it = m_elements.erase(it); 0632 endRemoveRows(); 0633 } 0634 0635 geo = newGeo; 0636 label = newLabel; 0637 date = nextStartTime.addSecs(1); 0638 ++it; 0639 } 0640 0641 // append weather elements beyond the end of the list if necessary 0642 while (date < maxForecastTime && geo.isValid()) { 0643 auto endTime = date; 0644 endTime.setTime(QTime(23, 59, 59)); 0645 0646 m_weatherMgr->monitorLocation(geo.latitude(), geo.longitude()); 0647 const auto fc = m_weatherMgr->forecast(geo.latitude(), geo.longitude(), date, endTime); 0648 if (fc.isValid()) { 0649 const auto row = std::distance(m_elements.begin(), it); 0650 beginInsertRows({}, row, row); 0651 it = m_elements.insert(it, TimelineElement{this, TimelineElement::WeatherForecast, date, QVariant::fromValue(WeatherInformation{fc, label})}); 0652 ++it; 0653 endInsertRows(); 0654 } 0655 date = endTime.addSecs(1); 0656 } 0657 0658 qDebug() << "weather recomputation done"; 0659 } 0660 0661 void TimelineModel::updateTransfersForBatch(const QString& batchId) 0662 { 0663 if (!m_transferManager) { 0664 return; 0665 } 0666 0667 auto transfer = m_transferManager->transfer(batchId, Transfer::Before); 0668 if (transfer.state() != Transfer::UndefinedState) { 0669 transferChanged(transfer); 0670 } 0671 transfer = m_transferManager->transfer(batchId, Transfer::After); 0672 if (transfer.state() != Transfer::UndefinedState) { 0673 transferChanged(transfer); 0674 } 0675 } 0676 0677 QDateTime TimelineModel::now() const 0678 { 0679 if (Q_UNLIKELY(m_unitTestTime.isValid())) { 0680 return m_unitTestTime; 0681 } 0682 return QDateTime::currentDateTime(); 0683 } 0684 0685 QDate TimelineModel::today() const 0686 { 0687 if (Q_UNLIKELY(m_unitTestTime.isValid())) { 0688 return m_unitTestTime.date(); 0689 } 0690 return QDate::currentDate(); 0691 } 0692 0693 void TimelineModel::setCurrentDateTime(const QDateTime &dt) 0694 { 0695 const auto dayDiffers = today() != dt.date(); 0696 m_unitTestTime = dt; 0697 if (dayDiffers && !m_elements.empty()) { 0698 dayChanged(); 0699 } 0700 0701 scheduleCurrentBatchTimer(); 0702 } 0703 0704 void TimelineModel::tripGroupAdded(const QString& groupId) 0705 { 0706 const auto g = m_tripGroupManager->tripGroup(groupId); 0707 0708 TimelineElement beginElem{this, TimelineElement::TripGroup, g.beginDateTime(), groupId}; 0709 beginElem.rangeType = TimelineElement::RangeBegin; 0710 insertElement(std::move(beginElem)); 0711 0712 TimelineElement endElem{this, TimelineElement::TripGroup, g.endDateTime(), groupId}; 0713 endElem.rangeType = TimelineElement::RangeEnd; 0714 insertElement(std::move(endElem)); 0715 } 0716 0717 void TimelineModel::tripGroupChanged(const QString& groupId) 0718 { 0719 // ### this can be done better probably 0720 tripGroupRemoved(groupId); 0721 tripGroupAdded(groupId); 0722 } 0723 0724 void TimelineModel::tripGroupRemoved(const QString& groupId) 0725 { 0726 for (auto it = m_elements.begin(); it != m_elements.end();) { 0727 if ((*it).elementType != TimelineElement::TripGroup || (*it).content().toString() != groupId) { 0728 ++it; 0729 continue; 0730 } 0731 0732 const auto row = std::distance(m_elements.begin(), it); 0733 beginRemoveRows({}, row, row); 0734 it = m_elements.erase(it); 0735 endRemoveRows(); 0736 } 0737 } 0738 0739 void TimelineModel::transferChanged(const Transfer& transfer) 0740 { 0741 if (transfer.state() == Transfer::UndefinedState) { 0742 return; 0743 } 0744 if (transfer.state() == Transfer::Discarded) { 0745 transferRemoved(transfer.reservationId(), transfer.alignment()); 0746 return; 0747 } 0748 0749 auto it = std::find_if(m_elements.begin(), m_elements.end(), [transfer](const auto &e) { 0750 return e.isReservation() && e.batchId() == transfer.reservationId(); 0751 }); 0752 if (it == m_elements.end()) { 0753 return; 0754 } 0755 0756 if (transfer.alignment() == Transfer::Before) { 0757 if (it != m_elements.begin()) { 0758 --it; 0759 } 0760 } 0761 insertOrUpdate(it, TimelineElement(this, transfer)); 0762 0763 Q_EMIT todayRowChanged(); 0764 } 0765 0766 void TimelineModel::transferRemoved(const QString &resId, Transfer::Alignment alignment) 0767 { 0768 auto it = std::find_if(m_elements.begin(), m_elements.end(), [resId, alignment](const auto &e) { 0769 return e.elementType == TimelineElement::Transfer && e.batchId() == resId 0770 && e.content().template value<Transfer>().alignment() == alignment; 0771 }); 0772 if (it == m_elements.end()) { 0773 return; 0774 } 0775 0776 const auto row = std::distance(m_elements.begin(), it); 0777 beginRemoveRows({}, row, row); 0778 m_elements.erase(it); 0779 endRemoveRows(); 0780 0781 Q_EMIT todayRowChanged(); 0782 } 0783 0784 static bool isSelectableElement(const TimelineElement &elem) 0785 { 0786 return elem.isReservation() && elem.rangeType == TimelineElement::SelfContained; 0787 } 0788 0789 QString TimelineModel::currentBatchId() const 0790 { 0791 if (m_elements.empty()) { 0792 return {}; 0793 } 0794 0795 // find the next reservation 0796 auto it = std::lower_bound(m_elements.begin(), m_elements.end(), now()); 0797 for (; it != m_elements.end() && !isSelectableElement(*it); ++it) {} 0798 0799 QString nextResId; 0800 QDateTime nextStartTime; 0801 if (it != m_elements.end() && now().secsTo((*it).dt) < Constants::CurrentBatchLeadingMargin.count()) { 0802 nextResId = (*it).batchId(); 0803 nextStartTime = (*it).dt; 0804 } 0805 0806 // find the previous or current reservation 0807 if (it != m_elements.begin()) { 0808 --it; 0809 for (; it != m_elements.begin() && (it == m_elements.end() || !isSelectableElement(*it)); --it) {} 0810 } 0811 0812 const auto resId = (*it).batchId(); 0813 const auto res = m_resMgr->reservation(resId); 0814 auto endTime = SortUtil::endDateTime(res); 0815 if (endTime.secsTo(now()) > Constants::CurrentBatchTrailingMargin.count()) { 0816 endTime = {}; 0817 } 0818 0819 // only one side found 0820 if (!endTime.isValid()) { 0821 return nextResId; 0822 } 0823 if (!nextStartTime.isValid()) { 0824 return resId; 0825 } 0826 0827 // (*it) is still active 0828 if (endTime >= now()) { 0829 return resId; 0830 } 0831 0832 // take the one that is closer 0833 return endTime.secsTo(now()) < now().secsTo(nextStartTime) ? resId : nextResId; 0834 } 0835 0836 void TimelineModel::scheduleCurrentBatchTimer() 0837 { 0838 if (m_elements.empty()) { 0839 return; 0840 } 0841 0842 // we need the smallest valid time > now() of any of the following: 0843 // - end time of the current element + margin 0844 // - start time - margin of the next res 0845 // - end time of current + start time of next - endtime of current / 2 0846 // - end time of the next element (in case we are in the leading margin already) 0847 0848 QDateTime triggerTime; 0849 const auto updateTriggerTime = [&triggerTime, this](const QDateTime &dt) { 0850 if (!dt.isValid() || dt <= now()) { 0851 return; 0852 } 0853 if (!triggerTime.isValid()) { 0854 triggerTime = dt; 0855 } else { 0856 triggerTime = std::min(triggerTime, dt); 0857 } 0858 }; 0859 0860 // find the next reservation 0861 auto it = std::lower_bound(m_elements.begin(), m_elements.end(), now()); 0862 for (; it != m_elements.end() && !isSelectableElement(*it); ++it) {} 0863 0864 QDateTime nextStartTime; 0865 if (it != m_elements.end()) { 0866 nextStartTime = (*it).dt; 0867 updateTriggerTime(nextStartTime.addSecs(-Constants::CurrentBatchLeadingMargin.count())); 0868 updateTriggerTime(SortUtil::endDateTime(m_resMgr->reservation((*it).batchId()))); 0869 } 0870 0871 // find the previous or current reservation 0872 if (it != m_elements.begin()) { 0873 --it; 0874 for (; it != m_elements.begin() && (it == m_elements.end() || !isSelectableElement(*it)); --it) {} 0875 } 0876 0877 const auto res = m_resMgr->reservation((*it).batchId()); 0878 auto endTime = SortUtil::endDateTime(res); 0879 updateTriggerTime(endTime.addSecs(Constants::CurrentBatchTrailingMargin.count())); 0880 0881 if (nextStartTime.isValid() && endTime.isValid()) { 0882 updateTriggerTime(endTime.addSecs(endTime.secsTo(nextStartTime) / 2)); 0883 } 0884 0885 // QTimer only has 31bit for its msec interval, so don't schedule beyond a day 0886 // for longer distances we re-run this in the midnight timer above 0887 if (triggerTime.isValid() && triggerTime.date() == today()) { 0888 m_currentBatchTimer.setInterval(std::chrono::seconds(std::max<qint64>(60, now().secsTo(triggerTime)))); 0889 m_currentBatchTimer.start(); 0890 } 0891 } 0892 0893 QVariant TimelineModel::locationAtTime(const QDateTime& dt) const 0894 { 0895 auto it = std::lower_bound(m_elements.begin(), m_elements.end(), dt); 0896 if (it == m_elements.begin()) { 0897 return {}; 0898 } 0899 0900 for (--it ;; --it) { 0901 // this is a still ongoing non-location change 0902 if (it != m_elements.end() && !(*it).isLocationChange() && (*it).endDateTime().isValid() && (*it).endDateTime() > dt) { 0903 if ((*it).isReservation()) { 0904 auto loc = LocationUtil::location(m_resMgr->reservation((*it).batchId())); 0905 if (LocationUtil::geo(loc).isValid() || !LocationUtil::address(loc).addressCountry().isEmpty()) { 0906 return loc; 0907 } 0908 } 0909 } 0910 0911 if ((*it).isReservation() && (*it).isLocationChange()) { 0912 // TODO make this work for transfers too 0913 const auto res = m_resMgr->reservation((*it).batchId()); 0914 return LocationUtil::arrivalLocation(res); 0915 } 0916 0917 if (it == m_elements.begin()) { 0918 break; 0919 } 0920 } 0921 0922 return {}; 0923 } 0924 0925 bool TimelineModel::isDateEmpty(const QDate &date) const 0926 { 0927 auto it = std::lower_bound(m_elements.begin(), m_elements.end(), date, [](const auto &lhs, auto rhs) { 0928 return lhs.dt.date() < rhs; 0929 }); 0930 for (; it != m_elements.end(); ++it) { 0931 if ((*it).dt.date() == date && (*it).elementType != TimelineElement::TodayMarker) { 0932 return false; 0933 } 0934 if ((*it).dt.date() != date) { 0935 break; 0936 } 0937 } 0938 return true; 0939 } 0940 0941 #include "moc_timelinemodel.cpp"