Commit bf27bc2d authored by Volker Krause's avatar Volker Krause
Browse files

Add a way to determine the current reservation batch

That is, the batch that is currently active, or otherwise closest to the
current time. We'll use this to implement a "ticket check" shortcut.
parent 22c4a353
......@@ -562,6 +562,47 @@ private Q_SLOTS:
ctrl.importFromUrl(QUrl::fromLocalFile(QLatin1String(SOURCE_DIR "/data/timeline/") + baseName + QLatin1String(".json")));
QVERIFY(vp.verify(&model));
}
void testCurrentReservation()
{
ReservationManager resMgr;
Test::clearAll(&resMgr);
ApplicationController ctrl;
ctrl.setReservationManager(&resMgr);
TimelineModel model;
model.setCurrentDateTime(QDateTime({2017, 8, 1}, {23, 0}, QTimeZone("Europe/Zurich")));
QAbstractItemModelTester tester(&model);
model.setReservationManager(&resMgr);
QSignalSpy currentResChangedSpy(&model, &TimelineModel::currentBatchChanged);
ctrl.importFromUrl(QUrl::fromLocalFile(QLatin1String(SOURCE_DIR "/../tests/randa2017.json")));
QCOMPARE(model.rowCount(), 13);
QVERIFY(!currentResChangedSpy.empty());
model.setCurrentDateTime(QDateTime({2017, 8, 1}, {23, 0}, QTimeZone("Europe/Zurich")));
QVERIFY(model.currentBatchId().isEmpty());
model.setCurrentDateTime(QDateTime({2017, 9, 9}, {23, 0}, QTimeZone("Europe/Zurich")));
QVERIFY(!model.currentBatchId().isEmpty());
QCOMPARE(model.currentBatchId(), model.index(1, 0).data(TimelineModel::BatchIdRole).toString());
model.setCurrentDateTime(QDateTime({2017, 9, 10}, {14, 0}, QTimeZone("Europe/Zurich")));
QCOMPARE(model.index(2, 0).data(TimelineModel::ElementTypeRole), TimelineElement::TrainTrip);
QVERIFY(!model.currentBatchId().isEmpty());
QCOMPARE(model.currentBatchId(), model.index(2, 0).data(TimelineModel::BatchIdRole).toString());
model.setCurrentDateTime(QDateTime({2017, 9, 10}, {14, 5}, QTimeZone("Europe/Zurich")));
QVERIFY(!model.currentBatchId().isEmpty());
QCOMPARE(model.currentBatchId(), model.index(3, 0).data(TimelineModel::BatchIdRole).toString());
model.setCurrentDateTime(QDateTime({2017, 9, 10}, {20, 0}, QTimeZone("Europe/Zurich")));
QVERIFY(model.currentBatchId().isEmpty());
model.setCurrentDateTime(QDateTime({2019, 1, 1}, {0, 0}, QTimeZone("Europe/Zurich")));
QVERIFY(model.currentBatchId().isEmpty());
}
};
QTEST_GUILESS_MAIN(TimelineModelTest)
......
......@@ -8,4 +8,8 @@
namespace Constants {
constexpr std::chrono::seconds MaximumLayoverTime = std::chrono::hours(4);
constexpr std::chrono::seconds CurrentBatchLeadingMargin = std::chrono::hours(48);
constexpr std::chrono::seconds CurrentBatchTrailingMargin = std::chrono::hours(4);
}
......@@ -72,6 +72,11 @@ bool TimelineElement::operator<(const TimelineElement &other) const
return dt < other.dt;
}
bool TimelineElement::operator<(const QDateTime &otherDt) const
{
return dt < otherDt;
}
bool TimelineElement::operator==(const TimelineElement &other) const
{
if (elementType != other.elementType || rangeType != other.rangeType || dt != other.dt || batchId() != other.batchId()) {
......
......@@ -52,6 +52,7 @@ public:
/** Timeline order. This considers only position in the timeline, not content. */
bool operator<(const TimelineElement &other) const;
bool operator<(const QDateTime &otherDt) const;
bool operator==(const TimelineElement &other) const;
/** Element is holds a reservation type. */
......
......@@ -5,6 +5,7 @@
*/
#include "timelinemodel.h"
#include "constants.h"
#include "locationhelper.h"
#include "locationinformation.h"
#include "pkpassmanager.h"
......@@ -75,6 +76,7 @@ TimelineModel::TimelineModel(QObject *parent)
: QAbstractListModel(parent)
{
connect(&m_dayUpdateTimer, &QTimer::timeout, this, &TimelineModel::dayChanged);
m_dayUpdateTimer.setTimerType(Qt::VeryCoarseTimer);
m_dayUpdateTimer.setSingleShot(true);
m_dayUpdateTimer.setInterval((QTime::currentTime().secsTo({23, 59, 59}) + 1) * 1000);
m_dayUpdateTimer.start();
......@@ -88,6 +90,11 @@ TimelineModel::TimelineModel(QObject *parent)
m_todayEmpty = !m_todayEmpty;
Q_EMIT dataChanged(idx, idx);
});
connect(&m_currentBatchTimer, &QTimer::timeout, this, &TimelineModel::currentBatchChanged);
connect(&m_currentBatchTimer, &QTimer::timeout, this, &TimelineModel::scheduleCurrentBatchTimer);
m_currentBatchTimer.setTimerType(Qt::VeryCoarseTimer);
m_currentBatchTimer.setSingleShot(true);
}
TimelineModel::~TimelineModel() = default;
......@@ -135,6 +142,9 @@ void TimelineModel::setReservationManager(ReservationManager* mgr)
updateInformationElements();
Q_EMIT todayRowChanged();
scheduleCurrentBatchTimer();
Q_EMIT currentBatchChanged();
}
void TimelineModel::setWeatherForecastManager(WeatherForecastManager* mgr)
......@@ -295,6 +305,9 @@ void TimelineModel::batchAdded(const QString &resId)
updateInformationElements();
updateTransfersForBatch(resId);
Q_EMIT todayRowChanged();
scheduleCurrentBatchTimer();
Q_EMIT currentBatchChanged();
}
void TimelineModel::insertElement(TimelineElement &&elem)
......@@ -343,6 +356,9 @@ void TimelineModel::batchChanged(const QString &resId)
}
updateInformationElements();
scheduleCurrentBatchTimer();
Q_EMIT currentBatchChanged();
}
void TimelineModel::batchRenamed(const QString& oldBatchId, const QString& newBatchId)
......@@ -405,6 +421,9 @@ void TimelineModel::batchRemoved(const QString &resId)
}
updateInformationElements();
scheduleCurrentBatchTimer();
Q_EMIT currentBatchChanged();
}
void TimelineModel::dayChanged()
......@@ -414,6 +433,9 @@ void TimelineModel::dayChanged()
m_dayUpdateTimer.setInterval((QTime::currentTime().secsTo({23, 59, 59}) + 1) * 1000);
m_dayUpdateTimer.start();
scheduleCurrentBatchTimer();
Q_EMIT currentBatchChanged();
}
void TimelineModel::updateTodayMarker()
......@@ -665,6 +687,8 @@ void TimelineModel::setCurrentDateTime(const QDateTime &dt)
if (dayDiffers && !m_elements.empty()) {
dayChanged();
}
scheduleCurrentBatchTimer();
}
void TimelineModel::tripGroupAdded(const QString& groupId)
......@@ -746,3 +770,112 @@ void TimelineModel::transferRemoved(const QString &resId, Transfer::Alignment al
Q_EMIT todayRowChanged();
}
static bool isSelectableElement(const TimelineElement &elem)
{
return elem.isReservation() && elem.rangeType == TimelineElement::SelfContained;
}
QString TimelineModel::currentBatchId() const
{
if (m_elements.empty()) {
return {};
}
// find the next reservation
auto it = std::lower_bound(m_elements.begin(), m_elements.end(), now());
for (; it != m_elements.end() && !isSelectableElement(*it); ++it) {}
QString nextResId;
QDateTime nextStartTime;
if (it != m_elements.end() && now().secsTo((*it).dt) < Constants::CurrentBatchLeadingMargin.count()) {
nextResId = (*it).batchId();
nextStartTime = (*it).dt;
}
// find the previous or current reservation
if (it != m_elements.begin()) {
--it;
for (; it != m_elements.begin() && (it == m_elements.end() || !isSelectableElement(*it)); --it) {}
}
const auto resId = (*it).batchId();
const auto res = m_resMgr->reservation(resId);
auto endTime = SortUtil::endDateTime(res);
if (endTime.secsTo(now()) > Constants::CurrentBatchTrailingMargin.count()) {
endTime = {};
}
// only one side found
if (!endTime.isValid()) {
return nextResId;
}
if (!nextStartTime.isValid()) {
return resId;
}
// (*it) is still active
if (endTime >= now()) {
return resId;
}
// take the one that is closer
return endTime.secsTo(now()) < now().secsTo(nextStartTime) ? resId : nextResId;
}
void TimelineModel::scheduleCurrentBatchTimer()
{
if (m_elements.empty()) {
return;
}
// we need the smallest valid time > now() of any of the following:
// - end time of the current element + margin
// - start time - margin of the next res
// - end time of current + start time of next - endtime of current / 2
// - end time of the next element (in case we are in the leading margin already)
QDateTime triggerTime;
const auto updateTriggerTime = [&triggerTime, this](const QDateTime &dt) {
if (!dt.isValid() || dt <= now()) {
return;
}
if (!triggerTime.isValid()) {
triggerTime = dt;
} else {
triggerTime = std::min(triggerTime, dt);
}
};
// find the next reservation
auto it = std::lower_bound(m_elements.begin(), m_elements.end(), now());
for (; it != m_elements.end() && !isSelectableElement(*it); ++it) {}
QDateTime nextStartTime;
if (it != m_elements.end()) {
nextStartTime = (*it).dt;
updateTriggerTime(nextStartTime.addSecs(-Constants::CurrentBatchLeadingMargin.count()));
updateTriggerTime(SortUtil::endDateTime(m_resMgr->reservation((*it).batchId())));
}
// find the previous or current reservation
if (it != m_elements.begin()) {
--it;
for (; it != m_elements.begin() && (it == m_elements.end() || !isSelectableElement(*it)); --it) {}
}
const auto res = m_resMgr->reservation((*it).batchId());
auto endTime = SortUtil::endDateTime(res);
updateTriggerTime(endTime.addSecs(Constants::CurrentBatchTrailingMargin.count()));
if (nextStartTime.isValid() && endTime.isValid()) {
updateTriggerTime(endTime.addSecs(endTime.secsTo(nextStartTime) / 2));
}
// QTimer only has 31bit for its msec interval, so don't schedule beyond a day
// for longer distances we re-run this in the midnight timer above
if (triggerTime.isValid() && triggerTime.date() == today()) {
m_currentBatchTimer.setInterval(std::chrono::seconds(std::max<qint64>(60, now().secsTo(triggerTime))));
m_currentBatchTimer.start();
}
}
......@@ -27,6 +27,7 @@ class TimelineModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int todayRow READ todayRow NOTIFY todayRowChanged)
Q_PROPERTY(QString currentBatchId READ currentBatchId NOTIFY currentBatchChanged)
public:
enum Role {
......@@ -60,6 +61,9 @@ public:
int todayRow() const;
/** The most "current" batch to show with the "ticket check" action. */
QString currentBatchId() const;
// for unit testing
void setCurrentDateTime(const QDateTime &dt);
QDateTime now() const;
......@@ -67,6 +71,7 @@ public:
Q_SIGNALS:
void todayRowChanged();
void currentBatchChanged();
private:
void batchAdded(const QString &resId);
......@@ -90,6 +95,8 @@ private:
void updateWeatherElements();
void updateTransfersForBatch(const QString &batchId);
void scheduleCurrentBatchTimer();
ReservationManager *m_resMgr = nullptr;
WeatherForecastManager *m_weatherMgr = nullptr;
TripGroupManager *m_tripGroupManager = nullptr;
......@@ -98,6 +105,7 @@ private:
QString m_homeCountry;
QDateTime m_unitTestTime;
QTimer m_dayUpdateTimer;
QTimer m_currentBatchTimer;
bool m_todayEmpty = true;
};
......
Markdown is supported
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