Commit 1c9ab0e5 authored by Volker Krause's avatar Volker Krause
Browse files

Don't limit the timeline weather and country element logic to reservations

This now considers transfers as well for this.
parent 0fe0e7cb
......@@ -60,20 +60,20 @@ private Q_SLOTS:
const auto dt = QDateTime::currentDateTimeUtc();
TimelineElement group(TimelineElement::TripGroup, dt);
TimelineElement group(nullptr, TimelineElement::TripGroup, dt);
group.rangeType = TimelineElement::RangeBegin;
QTest::newRow("transfer-group-begin") << group << TimelineElement(TimelineElement::Transfer, dt);
QTest::newRow("transfer-group-begin") << group << TimelineElement(nullptr, TimelineElement::Transfer, dt);
group.rangeType = TimelineElement::RangeEnd;
QTest::newRow("transfer-group-end") << TimelineElement(TimelineElement::Transfer, dt) << group;
QTest::newRow("transfer-group-end") << TimelineElement(nullptr, TimelineElement::Transfer, dt) << group;
TimelineElement locInfo(TimelineElement::LocationInfo, dt);
TimelineElement hotel(TimelineElement::Hotel, dt);
TimelineElement locInfo(nullptr, TimelineElement::LocationInfo, dt);
TimelineElement hotel(nullptr, TimelineElement::Hotel, dt);
hotel.rangeType = TimelineElement::RangeBegin;
QTest::newRow("locinfo-before-hotel") << locInfo << hotel;
TimelineElement flight(TimelineElement::Flight, dt);
TimelineElement flight(nullptr, TimelineElement::Flight, dt);
QTest::newRow("locinfo-before-flight") << locInfo << flight;
QTest::newRow("location-info-before-transfer") << TimelineElement(TimelineElement::LocationInfo, dt) << TimelineElement(TimelineElement::Transfer, dt);
QTest::newRow("location-info-before-transfer") << TimelineElement(nullptr, TimelineElement::LocationInfo, dt) << TimelineElement(nullptr, TimelineElement::Transfer, dt);
}
void testElementCompare()
......
......@@ -5,13 +5,22 @@
*/
#include "timelineelement.h"
#include "locationhelper.h"
#include "reservationmanager.h"
#include "timelinemodel.h"
#include "transfer.h"
#include <KItinerary/Event>
#include <KItinerary/LocationUtil>
#include <KItinerary/Place>
#include <KItinerary/Reservation>
#include <KItinerary/SortUtil>
#include <KItinerary/Visit>
#include <KPublicTransport/Journey>
#include <KPublicTransport/Stopover>
using namespace KItinerary;
static TimelineElement::ElementType elementType(const QVariant &res)
......@@ -29,26 +38,29 @@ static TimelineElement::ElementType elementType(const QVariant &res)
TimelineElement::TimelineElement() = default;
TimelineElement::TimelineElement(TimelineElement::ElementType type, const QDateTime &dateTime, const QVariant &data)
TimelineElement::TimelineElement(TimelineModel *model, TimelineElement::ElementType type, const QDateTime &dateTime, const QVariant &data)
: dt(dateTime)
, elementType(type)
, m_content(data)
, m_model(model)
{
}
TimelineElement::TimelineElement(const QString& resId, const QVariant& res, TimelineElement::RangeType rt)
TimelineElement::TimelineElement(TimelineModel *model, const QString& resId, const QVariant& res, TimelineElement::RangeType rt)
: dt(relevantDateTime(res, rt))
, elementType(::elementType(res))
, rangeType(rt)
, m_content(resId)
, m_model(model)
{
}
TimelineElement::TimelineElement(const ::Transfer &transfer)
TimelineElement::TimelineElement(TimelineModel *model, const ::Transfer &transfer)
: dt(transfer.anchorTime())
, elementType(Transfer)
, rangeType(SelfContained)
, m_content(QVariant::fromValue(transfer))
, m_model(model)
{
}
......@@ -144,3 +156,116 @@ QDateTime TimelineElement::relevantDateTime(const QVariant &res, TimelineElement
return {};
}
bool TimelineElement::isLocationChange() const
{
if (isReservation()) {
// ### can be done without reservation lookup for some of the reservation element types
const auto res = m_model->m_resMgr->reservation(batchId());
return LocationUtil::isLocationChange(res);
}
if (elementType == Transfer) {
return m_content.value<::Transfer>().state() == Transfer::Selected;
}
return false;
}
bool TimelineElement::isTimeBoxed() const
{
switch (elementType) {
case Undefined:
case TodayMarker:
case TripGroup:
case WeatherForecast:
case LocationInfo:
return false;
case Transfer:
return m_content.value<::Transfer>().state() == Transfer::Selected;
case Flight:
case TrainTrip:
case BusTrip:
return true;
case Hotel:
case CarRental:
return false;
case Restaurant:
case TouristAttraction:
case Event:
return SortUtil::endDateTime(m_model->m_resMgr->reservation(batchId())).isValid();
}
return false;
}
bool TimelineElement::isCanceled() const
{
if (isReservation()) {
const auto res = m_model->m_resMgr->reservation(batchId());
return JsonLd::canConvert<Reservation>(res) && JsonLd::convert<Reservation>(res).reservationStatus() == Reservation::ReservationCancelled;
}
// TODO transfers with Disruption::NoService
return false;
}
QString TimelineElement::destinationCountry() const
{
if (isReservation()) {
const auto res = m_model->m_resMgr->reservation(batchId());
return LocationHelper::destinationCountry(res);
}
if (elementType == Transfer) {
const auto transfer = m_content.value<::Transfer>();
if (transfer.state() == Transfer::Selected) {
const auto &sections = transfer.journey().sections();
if (!sections.empty()) {
return sections.back().arrival().stopPoint().country();
}
}
}
return {};
}
KItinerary::GeoCoordinates TimelineElement::destinationCoordinates() const
{
if (isReservation()) {
const auto res = m_model->m_resMgr->reservation(batchId());
if (LocationUtil::isLocationChange(res)) {
return LocationUtil::geo(LocationUtil::arrivalLocation(res));
}
return LocationUtil::geo(LocationUtil::location(res));
}
if (elementType == Transfer) {
const auto transfer = m_content.value<::Transfer>();
if (transfer.state() == Transfer::Selected) {
const auto &sections = transfer.journey().sections();
if (!sections.empty()) {
const auto loc = sections.back().arrival().stopPoint();
return KItinerary::GeoCoordinates(loc.latitude(), loc.longitude());
}
}
}
return {};
}
QDateTime TimelineElement::endDateTime() const
{
if (isReservation()) {
const auto res = m_model->m_resMgr->reservation(batchId());
return SortUtil::endDateTime(res);
}
if (elementType == Transfer) {
const auto transfer = m_content.value<::Transfer>();
if (transfer.state() == Transfer::Selected) {
return transfer.journey().scheduledArrivalTime();
}
}
return {};
}
......@@ -11,8 +11,13 @@
#include <QString>
#include <QVariant>
class TimelineModel;
class Transfer;
namespace KItinerary {
class GeoCoordinates;
}
/** TimelineModel items. */
class TimelineElement {
Q_GADGET
......@@ -46,9 +51,9 @@ public:
Q_ENUM(RangeType)
explicit TimelineElement();
explicit TimelineElement(ElementType type, const QDateTime &dateTime, const QVariant &data = {});
explicit TimelineElement(const QString &resId, const QVariant &res, RangeType rt);
explicit TimelineElement(const ::Transfer &transfer);
explicit TimelineElement(TimelineModel *model, ElementType type, const QDateTime &dateTime, const QVariant &data = {});
explicit TimelineElement(TimelineModel *model, const QString &resId, const QVariant &res, RangeType rt);
explicit TimelineElement(TimelineModel *model, const ::Transfer &transfer);
/** Timeline order. This considers only position in the timeline, not content. */
bool operator<(const TimelineElement &other) const;
......@@ -66,6 +71,37 @@ public:
QVariant content() const;
void setContent(const QVariant &content);
/** Returns @c true if this element changes our location after taking effect. */
bool isLocationChange() const;
/** Returns @c true if this is element has an exclusively assigned defined
* time range. That's location change elements or e.g. events with a fixed end time,
* all things for which an end time is meaningful.
* The opposite to this, besides informational elements, are hotel or rental car bookings for example.
*/
bool isTimeBoxed() const;
/** Returns @c true if this element is cancelled or otherwise not happening,
* and thus its effect (such as a location change) wont actually happen.
*/
bool isCanceled() const;
/** Destination country code, ie. the country we are in when/after
* this element took effect.
*/
QString destinationCountry() const;
/** Geo coordinates of/after this element, ie. the location we are in when/after
* this element took effect.
*/
KItinerary::GeoCoordinates destinationCoordinates() const;
/** End or arrival time.
* For location changes this is the arrival time.
* For timeboxed elements this is the end time.
*/
QDateTime endDateTime() const;
/** The time @p res is added to the timeline, for range type @p range. */
static QDateTime relevantDateTime(const QVariant &res, TimelineElement::RangeType range);
......@@ -75,6 +111,7 @@ public:
private:
QVariant m_content;
TimelineModel *m_model = nullptr;
};
Q_DECLARE_METATYPE(TimelineElement)
......
......@@ -53,25 +53,11 @@ static bool needsSplitting(const QVariant &res)
|| JsonLd::isA<RentalCarReservation>(res);
}
static QTimeZone destinationTimeZone(const QVariant &res)
static QTimeZone timeZone(const QDateTime &dt)
{
const auto dt = SortUtil::endDateTime(res);
return dt.timeSpec() == Qt::TimeZone ? dt.timeZone() : QTimeZone();
}
static GeoCoordinates geoCoordinate(const QVariant &res)
{
if (LocationUtil::isLocationChange(res)) {
return LocationUtil::geo(LocationUtil::arrivalLocation(res));
}
return LocationUtil::geo(LocationUtil::location(res));
}
static bool isCanceled(const QVariant &res)
{
return JsonLd::canConvert<Reservation>(res) && JsonLd::convert<Reservation>(res).reservationStatus() == Reservation::ReservationCancelled;
}
TimelineModel::TimelineModel(QObject *parent)
: QAbstractListModel(parent)
{
......@@ -119,18 +105,18 @@ void TimelineModel::setReservationManager(ReservationManager* mgr)
m_resMgr = mgr;
for (const auto &resId : mgr->batches()) {
const auto res = m_resMgr->reservation(resId);
auto elem = TimelineElement(resId, res, TimelineElement::SelfContained);
auto elem = TimelineElement(this, resId, res, TimelineElement::SelfContained);
if (!elem.isReservation()) { // a type we can't handle
continue;
}
if (needsSplitting(res)) {
m_elements.push_back(TimelineElement{resId, res, TimelineElement::RangeBegin});
m_elements.push_back(TimelineElement{resId, res, TimelineElement::RangeEnd});
m_elements.push_back(TimelineElement{this, resId, res, TimelineElement::RangeBegin});
m_elements.push_back(TimelineElement{this, resId, res, TimelineElement::RangeEnd});
} else {
m_elements.push_back(std::move(elem));
}
}
m_elements.push_back(TimelineElement{TimelineElement::TodayMarker, QDateTime(today(), QTime(0, 0))});
m_elements.push_back(TimelineElement{this, TimelineElement::TodayMarker, QDateTime(today(), QTime(0, 0))});
std::sort(m_elements.begin(), m_elements.end());
connect(mgr, &ReservationManager::batchAdded, this, &TimelineModel::batchAdded);
......@@ -291,10 +277,10 @@ void TimelineModel::batchAdded(const QString &resId)
{
const auto res = m_resMgr->reservation(resId);
if (needsSplitting(res)) {
insertElement(TimelineElement{resId, res, TimelineElement::RangeBegin});
insertElement(TimelineElement{resId, res, TimelineElement::RangeEnd});
insertElement(TimelineElement{this, resId, res, TimelineElement::RangeBegin});
insertElement(TimelineElement{this, resId, res, TimelineElement::RangeEnd});
} else {
insertElement(TimelineElement{resId, res, TimelineElement::SelfContained});
insertElement(TimelineElement{this, resId, res, TimelineElement::SelfContained});
}
updateInformationElements();
......@@ -389,7 +375,7 @@ void TimelineModel::updateElement(const QString &resId, const QVariant &res, Tim
beginRemoveRows({}, row, row);
m_elements.erase(it);
endRemoveRows();
insertElement(TimelineElement{resId, res, rangeType});
insertElement(TimelineElement{this, resId, res, rangeType});
} else {
Q_EMIT dataChanged(index(row, 0), index(row, 0));
}
......@@ -443,7 +429,7 @@ void TimelineModel::updateTodayMarker()
Q_ASSERT(oldRow < newRow);
beginInsertRows({}, newRow, newRow);
m_elements.insert(it, TimelineElement{TimelineElement::TodayMarker, QDateTime(today(), QTime(0, 0))});
m_elements.insert(it, TimelineElement{this, TimelineElement::TodayMarker, QDateTime(today(), QTime(0, 0))});
endInsertRows();
beginRemoveRows({}, oldRow, oldRow);
......@@ -474,28 +460,23 @@ void TimelineModel::updateInformationElements()
continue;
}
if (!(*it).isReservation()) {
++it;
continue;
}
const auto res = m_resMgr->reservation((*it).batchId());
if (isCanceled(res)) {
if ((*it).isCanceled()) {
++it;
continue;
}
auto newCountry = homeCountry;
newCountry.setIsoCode(LocationHelper::destinationCountry(res));
newCountry.setIsoCode((*it).destinationCountry());
newCountry.setTimeZone(previousCountry.timeZone(), (*it).dt);
newCountry.setTimeZone(destinationTimeZone(res), (*it).dt);
newCountry.setTimeZone(timeZone((*it).endDateTime()), (*it).dt);
if (newCountry == previousCountry) {
++it;
continue;
}
if (!(newCountry == homeCountry) || newCountry.hasRelevantTimeZoneChange(previousCountry)) {
// for location changes, we want this after the corresponding element
const auto dt = LocationUtil::isLocationChange(res) ? SortUtil::endDateTime(res) : SortUtil::startDateTime(res);
it = insertOrUpdate(it, TimelineElement{TimelineElement::LocationInfo, dt, QVariant::fromValue(newCountry)});
const auto dt = (*it).isLocationChange() ? (*it).endDateTime() : (*it).dt;
it = insertOrUpdate(it, TimelineElement{this, TimelineElement::LocationInfo, dt, QVariant::fromValue(newCountry)});
}
++it;
......@@ -525,16 +506,13 @@ void TimelineModel::updateWeatherElements()
continue;
}
if (!(*it).isReservation()) {
if ((*it).isCanceled()) {
++it;
continue;
}
const auto res = m_resMgr->reservation((*it).batchId());
if (!isCanceled(res)) {
const auto newGeo = geoCoordinate(res);
if (LocationUtil::isLocationChange(res) || newGeo.isValid()) {
geo = newGeo;
}
const auto newGeo = (*it).destinationCoordinates();
if ((*it).isLocationChange() || newGeo.isValid()) {
geo = newGeo;
}
++it;
......@@ -562,16 +540,13 @@ void TimelineModel::updateWeatherElements()
}
// track where we are
if (!(*it).isReservation()) {
if ((*it).isCanceled()) {
++it;
continue;
}
const auto res = m_resMgr->reservation((*it).batchId());
if (!isCanceled(res)) {
const auto newGeo = geoCoordinate(res);
if (LocationUtil::isLocationChange(res) || newGeo.isValid()) {
geo = newGeo;
}
const auto newGeo = (*it).destinationCoordinates();
if ((*it).isLocationChange() || newGeo.isValid()) {
geo = newGeo;
}
++it;
......@@ -587,15 +562,11 @@ void TimelineModel::updateWeatherElements()
if ((*it2).dt >= endTime) {
break;
}
if (!(*it2).isReservation()) {
continue;
}
const auto res = m_resMgr->reservation((*it2).batchId());
if (LocationUtil::isLocationChange(res)) {
if ((*it2).isLocationChange()) {
// exclude the actual travel time from forecast ranges
endTime = std::min(endTime, TimelineElement::relevantDateTime(res, TimelineElement::RangeBegin));
nextStartTime = std::max(endTime, TimelineElement::relevantDateTime(res, TimelineElement::RangeEnd));
newGeo = geoCoordinate(res);
endTime = std::min(endTime, (*it2).dt);
nextStartTime = std::max(endTime, (*it2).endDateTime());
newGeo = (*it2).destinationCoordinates();
break;
}
}
......@@ -609,7 +580,7 @@ void TimelineModel::updateWeatherElements()
// updated or new data
if (fc.isValid()) {
it = insertOrUpdate(it, TimelineElement{TimelineElement::WeatherForecast, date, QVariant::fromValue(fc)});
it = insertOrUpdate(it, TimelineElement{this, TimelineElement::WeatherForecast, date, QVariant::fromValue(fc)});
}
// we have no forecast data, but a matching weather element: remove
else if ((*it).elementType == TimelineElement::WeatherForecast && (*it).dt == date) {
......@@ -633,7 +604,7 @@ void TimelineModel::updateWeatherElements()
if (fc.isValid()) {
const auto row = std::distance(m_elements.begin(), it);
beginInsertRows({}, row, row);
it = m_elements.insert(it, TimelineElement{TimelineElement::WeatherForecast, date, QVariant::fromValue(fc)});
it = m_elements.insert(it, TimelineElement{this, TimelineElement::WeatherForecast, date, QVariant::fromValue(fc)});
++it;
endInsertRows();
}
......@@ -690,11 +661,11 @@ void TimelineModel::tripGroupAdded(const QString& groupId)
{
const auto g = m_tripGroupManager->tripGroup(groupId);
TimelineElement beginElem{TimelineElement::TripGroup, g.beginDateTime(), groupId};
TimelineElement beginElem{this, TimelineElement::TripGroup, g.beginDateTime(), groupId};
beginElem.rangeType = TimelineElement::RangeBegin;
insertElement(std::move(beginElem));
TimelineElement endElem{TimelineElement::TripGroup, g.endDateTime(), groupId};
TimelineElement endElem{this, TimelineElement::TripGroup, g.endDateTime(), groupId};
endElem.rangeType = TimelineElement::RangeEnd;
insertElement(std::move(endElem));
}
......@@ -743,7 +714,7 @@ void TimelineModel::transferChanged(const Transfer& transfer)
--it;
}
}
insertOrUpdate(it, TimelineElement(transfer));
insertOrUpdate(it, TimelineElement(this, transfer));
Q_EMIT todayRowChanged();
}
......@@ -883,13 +854,10 @@ QVariant TimelineModel::locationAtTime(const QDateTime& dt) const
}
for (--it; it != m_elements.begin(); --it) {
if (!(*it).isReservation()) {
if (!(*it).isReservation() || !(*it).isLocationChange()) {
continue;
}
const auto res = m_resMgr->reservation((*it).batchId());
if (!LocationUtil::isLocationChange(res)) {
continue;
}
return LocationUtil::arrivalLocation(res);
}
return {};
......
......@@ -99,6 +99,7 @@ private:
void scheduleCurrentBatchTimer();
bool isDateEmpty(const QDate &date) const;
friend class TimelineElement;
ReservationManager *m_resMgr = nullptr;
WeatherForecastManager *m_weatherMgr = nullptr;
TripGroupManager *m_tripGroupManager = nullptr;
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment