File indexing completed on 2025-03-09 04:45:38

0001 /*
0002     SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "timelineelement.h"
0008 
0009 #include "publictransport.h"
0010 #include "reservationmanager.h"
0011 #include "timelinemodel.h"
0012 #include "transfer.h"
0013 
0014 #include <KItinerary/Event>
0015 #include <KItinerary/LocationUtil>
0016 #include <KItinerary/Place>
0017 #include <KItinerary/Reservation>
0018 #include <KItinerary/SortUtil>
0019 #include <KItinerary/Visit>
0020 
0021 #include <KPublicTransport/Journey>
0022 #include <KPublicTransport/Stopover>
0023 
0024 using namespace KItinerary;
0025 
0026 static TimelineElement::ElementType elementType(const QVariant &res)
0027 {
0028     if (JsonLd::isA<FlightReservation>(res)) { return TimelineElement::Flight; }
0029     if (JsonLd::isA<LodgingReservation>(res)) { return TimelineElement::Hotel; }
0030     if (JsonLd::isA<TrainReservation>(res)) { return TimelineElement::TrainTrip; }
0031     if (JsonLd::isA<BusReservation>(res)) { return TimelineElement::BusTrip; }
0032     if (JsonLd::isA<BoatReservation>(res)) { return TimelineElement::BoatTrip; }
0033     if (JsonLd::isA<FoodEstablishmentReservation>(res)) { return TimelineElement::Restaurant; }
0034     if (JsonLd::isA<TouristAttractionVisit>(res)) { return TimelineElement::TouristAttraction; }
0035     if (JsonLd::isA<EventReservation>(res)) { return TimelineElement::Event; }
0036     if (JsonLd::isA<RentalCarReservation>(res)) { return TimelineElement::CarRental; }
0037     return {};
0038 }
0039 
0040 TimelineElement::TimelineElement() = default;
0041 
0042 TimelineElement::TimelineElement(TimelineModel *model, TimelineElement::ElementType type, const QDateTime &dateTime, const QVariant &data)
0043     : dt(dateTime)
0044     , elementType(type)
0045     , m_content(data)
0046     , m_model(model)
0047 {
0048 }
0049 
0050 TimelineElement::TimelineElement(TimelineModel *model, const QString& resId, const QVariant& res, TimelineElement::RangeType rt)
0051     : dt(relevantDateTime(res, rt))
0052     , elementType(::elementType(res))
0053     , rangeType(rt)
0054     , m_content(resId)
0055     , m_model(model)
0056 {
0057 }
0058 
0059 TimelineElement::TimelineElement(TimelineModel *model, const ::Transfer &transfer)
0060     : dt(transfer.anchorTime())
0061     , elementType(Transfer)
0062     , rangeType(SelfContained)
0063     , m_content(QVariant::fromValue(transfer))
0064     , m_model(model)
0065 {
0066 }
0067 
0068 static bool operator<(TimelineElement::RangeType lhs, TimelineElement::RangeType rhs)
0069 {
0070     static const int order_map[] = { 1, 0, 2 };
0071     return order_map[lhs] < order_map[rhs];
0072 }
0073 
0074 bool TimelineElement::operator<(const TimelineElement &other) const
0075 {
0076     if (dt == other.dt) {
0077         if (rangeType == RangeEnd || other.rangeType == RangeEnd) {
0078             if (rangeType == RangeBegin || other.rangeType == RangeBegin) {
0079                 return rangeType < other.rangeType;
0080             }
0081             return elementType > other.elementType;
0082         }
0083         return elementType < other.elementType;
0084     }
0085     return dt < other.dt;
0086 }
0087 
0088 bool TimelineElement::operator<(const QDateTime &otherDt) const
0089 {
0090     return dt < otherDt;
0091 }
0092 
0093 bool TimelineElement::operator==(const TimelineElement &other) const
0094 {
0095     if (elementType != other.elementType || rangeType != other.rangeType || dt != other.dt || batchId() != other.batchId()) {
0096         return false;
0097     }
0098 
0099     switch (elementType) {
0100         case Transfer:
0101         {
0102             const auto lhsT = m_content.value<::Transfer>();
0103             const auto rhsT = other.m_content.value<::Transfer>();
0104             return lhsT.alignment() == rhsT.alignment();
0105         }
0106         default:
0107             return true;
0108     }
0109 }
0110 
0111 bool TimelineElement::isReservation() const
0112 {
0113     switch (elementType) {
0114         case Flight:
0115         case TrainTrip:
0116         case CarRental:
0117         case BusTrip:
0118         case BoatTrip:
0119         case Restaurant:
0120         case TouristAttraction:
0121         case Event:
0122         case Hotel:
0123             return true;
0124         case Undefined:
0125         case TodayMarker:
0126         case TripGroup:
0127         case WeatherForecast:
0128         case LocationInfo:
0129         case Transfer:
0130             return false;
0131     }
0132 
0133     Q_UNREACHABLE();
0134     return false;
0135 }
0136 
0137 QString TimelineElement::batchId() const
0138 {
0139     if (isReservation()) {
0140         return m_content.toString();
0141     }
0142     if (elementType == Transfer) {
0143         return m_content.value<::Transfer>().reservationId();
0144     }
0145     return {};
0146 }
0147 
0148 QVariant TimelineElement::content() const
0149 {
0150     return m_content;
0151 }
0152 
0153 void TimelineElement::setContent(const QVariant& content)
0154 {
0155     m_content = content;
0156 }
0157 
0158 QDateTime TimelineElement::relevantDateTime(const QVariant &res, TimelineElement::RangeType range)
0159 {
0160     if (range == TimelineElement::RangeBegin || range == TimelineElement::SelfContained) {
0161         return SortUtil::startDateTime(res);
0162     }
0163     if (range == TimelineElement::RangeEnd) {
0164         return SortUtil::endDateTime(res);
0165     }
0166 
0167     return {};
0168 }
0169 
0170 bool TimelineElement::isLocationChange() const
0171 {
0172     if (isReservation()) {
0173         // ### can be done without reservation lookup for some of the reservation element types
0174         const auto res = m_model->m_resMgr->reservation(batchId());
0175         return LocationUtil::isLocationChange(res);
0176     }
0177 
0178     if (elementType == Transfer) {
0179         return m_content.value<::Transfer>().state() == Transfer::Selected;
0180     }
0181 
0182     return false;
0183 }
0184 
0185 bool TimelineElement::isTimeBoxed() const
0186 {
0187     switch (elementType) {
0188         case Undefined:
0189         case TodayMarker:
0190         case TripGroup:
0191         case WeatherForecast:
0192         case LocationInfo:
0193             return false;
0194         case Transfer:
0195             return m_content.value<::Transfer>().state() == Transfer::Selected;
0196         case Flight:
0197         case TrainTrip:
0198         case BusTrip:
0199         case BoatTrip:
0200             return true;
0201         case Hotel:
0202         case CarRental:
0203             return false;
0204         case Restaurant:
0205         case TouristAttraction:
0206         case Event:
0207             return SortUtil::endDateTime(m_model->m_resMgr->reservation(batchId())).isValid();
0208     }
0209     return false;
0210 }
0211 
0212 bool TimelineElement::isInformational() const
0213 {
0214     switch (elementType) {
0215         case Undefined:
0216         case TodayMarker:
0217         case TripGroup:
0218         case WeatherForecast:
0219         case LocationInfo:
0220             return true;
0221         case Transfer:
0222         case Flight:
0223         case TrainTrip:
0224         case BusTrip:
0225         case BoatTrip:
0226         case Hotel:
0227         case CarRental:
0228         case Restaurant:
0229         case TouristAttraction:
0230         case Event:
0231             return false;
0232     }
0233     Q_UNREACHABLE();
0234 }
0235 
0236 bool TimelineElement::isCanceled() const
0237 {
0238     if (isReservation()) {
0239         const auto res = m_model->m_resMgr->reservation(batchId());
0240         return JsonLd::canConvert<Reservation>(res) && JsonLd::convert<Reservation>(res).reservationStatus() == Reservation::ReservationCancelled;
0241     }
0242 
0243     // TODO transfers with Disruption::NoService
0244     return false;
0245 }
0246 
0247 static KPublicTransport::Location destinationOfJourney(const KPublicTransport::Journey &jny)
0248 {
0249     if (jny.sections().empty()) {
0250         return {};
0251     }
0252     const auto &sections = jny.sections();
0253     auto loc = sections.back().arrival().stopPoint();
0254 
0255     if (sections.size() == 1 || sections.back().mode() != KPublicTransport::JourneySection::Walking || sections.back().distance() > 1000) {
0256         return loc;
0257     }
0258 
0259     // the last section is a short walk, its arrival location might not have all the data we have on the previous stop
0260     const auto prevLoc = sections[sections.size() - 2].arrival().stopPoint();
0261     const auto locName = loc.name();
0262 
0263     // "anonymous" location with the coordinates as name
0264     if (std::none_of(locName.begin(), locName.end(), [](QChar c) { return c.isLetter(); }) && !prevLoc.name().isEmpty()) {
0265         return prevLoc;
0266     }
0267 
0268     // propagate address information from the last stop to the final destination
0269     if (loc.locality().isEmpty()) { loc.setLocality(prevLoc.locality()); }
0270     if (loc.region().isEmpty()) { loc.setRegion(prevLoc.region()); }
0271     if (loc.country().isEmpty()) { loc.setLocality(prevLoc.country()); }
0272 
0273     return loc;
0274 }
0275 
0276 QVariant TimelineElement::destination() const
0277 {
0278     if (isReservation()) {
0279         const auto res = m_model->m_resMgr->reservation(batchId());
0280         if (LocationUtil::isLocationChange(res)) {
0281             return LocationUtil::arrivalLocation(res);
0282         }
0283         return LocationUtil::location(res);
0284     }
0285 
0286     if (elementType == Transfer) {
0287         const auto transfer = m_content.value<::Transfer>();
0288         if (transfer.state() == Transfer::Selected) {
0289             const auto loc = destinationOfJourney(transfer.journey());
0290             return loc.isEmpty() ? QVariant() : PublicTransport::placeFromLocation<KItinerary::Place>(loc);
0291         }
0292     }
0293 
0294     return {};
0295 }
0296 
0297 QDateTime TimelineElement::endDateTime() const
0298 {
0299     if (isReservation()) {
0300         const auto res = m_model->m_resMgr->reservation(batchId());
0301         return SortUtil::endDateTime(res);
0302     }
0303 
0304     if (elementType == Transfer) {
0305         const auto transfer = m_content.value<::Transfer>();
0306         if (transfer.state() == Transfer::Selected) {
0307             return transfer.journey().scheduledArrivalTime();
0308         }
0309     }
0310 
0311     return {};
0312 }
0313 
0314 #include "moc_timelineelement.cpp"