Commit fd601092 authored by Volker Krause's avatar Volker Krause

Highlight current elements in the timeline

Also show progress for transit elements. For this to work efficiently we
need a C++ object for the delegate containing the logic. This still needs
to be extended to also cover live data access.
parent 0c5da34f
......@@ -7,6 +7,7 @@ ecm_add_test(tripgrouptest.cpp LINK_LIBRARIES Qt5::Test itinerary)
ecm_add_test(timelinemodeltest.cpp modelverificationpoint.cpp TEST_NAME timelinemodeltest LINK_LIBRARIES Qt5::Test itinerary)
ecm_add_test(tripgroupproxytest.cpp modelverificationpoint.cpp TEST_NAME tripgroupproxytest LINK_LIBRARIES Qt5::Test itinerary)
ecm_add_test(tripgroupinfoprovidertest.cpp TEST_NAME tripgroupinfoprovidertest LINK_LIBRARIES Qt5::Test itinerary)
ecm_add_test(timelinedelegatecontrollertest.cpp TEST_NAME timelinedelegatecontrollertest LINK_LIBRARIES Qt5::Test itinerary)
ecm_add_test(weathertest.cpp LINK_LIBRARIES Qt5::Test itinerary-weather)
target_include_directories(weathertest PRIVATE ${CMAKE_BINARY_DIR})
/*
Copyright (C) 2019 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <timelinedelegatecontroller.h>
#include <reservationmanager.h>
#include <KItinerary/Reservation>
#include <KItinerary/TrainTrip>
#include <QUrl>
#include <QtTest/qtest.h>
#include <QSignalSpy>
#include <QStandardPaths>
using namespace KItinerary;
class TimelineDelegateControllerTest : public QObject
{
Q_OBJECT
private:
void clearReservations(ReservationManager *mgr)
{
const auto batches = mgr->batches(); // copy, as this is getting modified in the process
for (const auto &id : batches) {
mgr->removeBatch(id);
}
QCOMPARE(mgr->batches().size(), 0);
}
private Q_SLOTS:
void initTestCase()
{
qputenv("TZ", "UTC");
QStandardPaths::setTestModeEnabled(true);
}
void testEmptyController()
{
TimelineDelegateController controller;
QCOMPARE(controller.isCurrent(), false);
QCOMPARE(controller.progress(), 0.0f);
controller.setBatchId(QStringLiteral("foo"));
QCOMPARE(controller.isCurrent(), false);
QCOMPARE(controller.progress(), 0.0f);
ReservationManager mgr;
controller.setReservationManager(&mgr);
QCOMPARE(controller.isCurrent(), false);
QCOMPARE(controller.progress(), 0.0f);
}
void testController()
{
ReservationManager mgr;
clearReservations(&mgr);
TrainTrip trip;
trip.setTrainNumber(QStringLiteral("TGV 1235"));
trip.setDepartureTime(QDateTime::currentDateTime().addDays(-1));
TrainReservation res;
res.setReservationNumber(QStringLiteral("XXX007"));
res.setReservationFor(trip);
TimelineDelegateController controller;
QSignalSpy currentSpy(&controller, &TimelineDelegateController::currentChanged);
controller.setReservationManager(&mgr);
mgr.addReservation(res);
QCOMPARE(mgr.batches().size(), 1);
const auto batchId = mgr.batches().at(0);
controller.setBatchId(batchId);
QCOMPARE(controller.isCurrent(), false);
QCOMPARE(controller.progress(), 0.0f);
trip.setArrivalTime(QDateTime::currentDateTime().addDays(1));
res.setReservationFor(trip);
mgr.updateReservation(batchId, res);
QCOMPARE(controller.isCurrent(), true);
QCOMPARE(controller.progress(), 0.5f);
QCOMPARE(currentSpy.size(), 1);
}
};
QTEST_GUILESS_MAIN(TimelineDelegateControllerTest)
#include "timelinedelegatecontrollertest.moc"
......@@ -9,6 +9,7 @@ set(itinerary_srcs
pkpassmanager.cpp
pkpassimageprovider.cpp
reservationmanager.cpp
timelinedelegatecontroller.cpp
timelinemodel.cpp
tripgroup.cpp
tripgroupinfoprovider.cpp
......
......@@ -25,7 +25,7 @@ import "." as App
Kirigami.AbstractCard {
id: root
/** Reservation batch identifier (@see _reservationManager). */
property var batchId
property alias batchId: controller.batchId
/** All reservations that are part of this patch. */
property var resIds: _reservationManager.reservationsForBatch(root.batchId)
/** A random reservation object, in case there's more than one.
......@@ -43,9 +43,14 @@ Kirigami.AbstractCard {
showClickFeedback: true
TimelineDelegateController {
id: controller
reservationManager: _reservationManager
}
header: Rectangle {
id: headerBackground
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
Kirigami.Theme.colorSet: controller.isCurrent ? Kirigami.Theme.Selection : Kirigami.Theme.Complementary
Kirigami.Theme.inherit: false
color: Kirigami.Theme.backgroundColor
implicitHeight: headerLayout.implicitHeight + Kirigami.Units.largeSpacing * 2
......@@ -66,6 +71,16 @@ Kirigami.AbstractCard {
isMask: true
}
}
Rectangle {
id: progressBar
visible: controller.isCurrent
anchors.bottom: headerBackground.bottom
anchors.left: headerBackground.left
height: Kirigami.Units.smallSpacing
width: controller.progress * headerBackground.width
color: Kirigami.Theme.visitedLinkColor
}
}
onHeaderItemChanged: {
......
......@@ -37,6 +37,7 @@
#include "tripgroupmanager.h"
#include "tripgroupproxymodel.h"
#include "util.h"
#include "timelinedelegatecontroller.h"
#include "weatherforecastmodel.h"
#include <weatherforecastmanager.h>
......@@ -214,6 +215,7 @@ int main(int argc, char **argv)
});
qmlRegisterType<TicketTokenModel>("org.kde.itinerary", 1, 0, "TicketTokenModel");
qmlRegisterUncreatableType<TimelineModel>("org.kde.itinerary", 1, 0, "TimelineModel", {});
qmlRegisterType<TimelineDelegateController>("org.kde.itinerary", 1, 0, "TimelineDelegateController");
qmlRegisterSingletonType<Util>("org.kde.itinerary", 1, 0, "Util", [](QQmlEngine*, QJSEngine*) -> QObject*{
return new Util;
});
......
/*
Copyright (C) 2019 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "timelinedelegatecontroller.h"
#include "reservationmanager.h"
#include <KItinerary/LocationUtil>
#include <KItinerary/SortUtil>
#include <QCoreApplication>
#include <QDateTime>
#include <QDebug>
#include <QTimer>
QTimer* TimelineDelegateController::s_currentTimer = nullptr;
int TimelineDelegateController::s_progressRefCount = 0;
QTimer* TimelineDelegateController::s_progressTimer = nullptr;
using namespace KItinerary;
TimelineDelegateController::TimelineDelegateController(QObject *parent)
: QObject(parent)
{
if (!s_currentTimer) {
s_currentTimer = new QTimer(QCoreApplication::instance());
s_currentTimer->setSingleShot(true);
s_currentTimer->setTimerType(Qt::VeryCoarseTimer);
}
connect(s_currentTimer, &QTimer::timeout, this, [this]() { checkForUpdate(m_batchId); });
}
TimelineDelegateController::~TimelineDelegateController() = default;
QObject* TimelineDelegateController::reservationManager() const
{
return m_resMgr;
}
void TimelineDelegateController::setReservationManager(QObject *resMgr)
{
if (m_resMgr == resMgr)
return;
m_resMgr = qobject_cast<ReservationManager*>(resMgr);
emit setupChanged();
connect(m_resMgr, &ReservationManager::batchChanged, this, &TimelineDelegateController::checkForUpdate);
connect(m_resMgr, &ReservationManager::batchContentChanged, this, &TimelineDelegateController::checkForUpdate);
checkForUpdate(m_batchId);
}
QString TimelineDelegateController::batchId() const
{
return m_batchId;
}
void TimelineDelegateController::setBatchId(const QString &batchId)
{
if (m_batchId == batchId)
return;
m_batchId = batchId;
emit contentChanged();
checkForUpdate(batchId);
}
bool TimelineDelegateController::isCurrent() const
{
return m_isCurrent;
}
void TimelineDelegateController::setCurrent(bool current, const QVariant &res)
{
if (current == m_isCurrent) {
return;
}
m_isCurrent = current;
emit currentChanged();
if (!LocationUtil::isLocationChange(res)) {
return;
}
if (!s_progressTimer && m_isCurrent) {
s_progressTimer = new QTimer(QCoreApplication::instance());
s_progressTimer->setInterval(std::chrono::minutes(1));
s_progressTimer->setTimerType(Qt::VeryCoarseTimer);
s_progressTimer->setSingleShot(false);
}
if (m_isCurrent) {
connect(s_progressTimer, &QTimer::timeout, this, &TimelineDelegateController::progressChanged);
if (s_progressRefCount++ == 0) {
s_progressTimer->start();
}
} else {
disconnect(s_progressTimer, &QTimer::timeout, this, &TimelineDelegateController::progressChanged);
if (--s_progressRefCount == 0) {
s_progressTimer->stop();
}
}
}
float TimelineDelegateController::progress() const
{
if (!m_resMgr || m_batchId.isEmpty() || !m_isCurrent) {
return 0.0f;
}
const auto res = m_resMgr->reservation(m_batchId);
// TODO this needs to consider live data
const auto startTime = SortUtil::startDateTime(res);
const auto endTime = SortUtil::endtDateTime(res);
const auto tripLength = startTime.secsTo(endTime);
if (tripLength <= 0) {
return 0.0f;
}
const auto progress = startTime.secsTo(QDateTime::currentDateTime());
return std::min(std::max(0.0f, (float)progress / (float)tripLength), 1.0f);
}
void TimelineDelegateController::checkForUpdate(const QString& batchId)
{
if (!m_resMgr || m_batchId.isEmpty()) {
setCurrent(false);
return;
}
if (batchId != m_batchId) {
return;
}
const auto res = m_resMgr->reservation(batchId);
const auto now = QDateTime::currentDateTime();
// TODO this needs to consider live data and boarding times
const auto startTime = SortUtil::startDateTime(res);
const auto endTime = SortUtil::endtDateTime(res);
setCurrent(startTime < now && now < endTime, res);
if (now < startTime) {
scheduleNextUpdate(std::chrono::seconds(now.secsTo(startTime) + 1));
} else if (now < endTime) {
scheduleNextUpdate(std::chrono::seconds(now.secsTo(endTime) + 1));
}
}
void TimelineDelegateController::scheduleNextUpdate(std::chrono::milliseconds ms)
{
if (s_currentTimer->isActive() && s_currentTimer->remainingTimeAsDuration() < ms) {
return;
}
s_currentTimer->start(ms);
}
/*
Copyright (C) 2019 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef TIMELINEDELEGATECONTROLLER_H
#define TIMELINEDELEGATECONTROLLER_H
#include <QObject>
#include <QVariant>
#include <chrono>
class QTimer;
class ReservationManager;
/** C++ side logic for timeline delegates. */
class TimelineDelegateController : public QObject
{
Q_OBJECT
Q_PROPERTY(QObject* reservationManager READ reservationManager WRITE setReservationManager NOTIFY setupChanged)
Q_PROPERTY(QString batchId READ batchId WRITE setBatchId NOTIFY contentChanged)
Q_PROPERTY(bool isCurrent READ isCurrent NOTIFY currentChanged)
Q_PROPERTY(float progress READ progress NOTIFY progressChanged)
public:
TimelineDelegateController(QObject *parent = nullptr);
~TimelineDelegateController();
QObject *reservationManager() const;
void setReservationManager(QObject *resMgr);
QString batchId() const;
void setBatchId(const QString &batchId);
bool isCurrent() const;
float progress() const;
Q_SIGNALS:
void setupChanged();
void contentChanged();
void currentChanged();
void progressChanged();
private:
void setCurrent(bool current, const QVariant &res = {});
void checkForUpdate(const QString &batchId);
ReservationManager *m_resMgr = nullptr; // ### should this be static?
QString m_batchId;
bool m_isCurrent = false;
static void scheduleNextUpdate(std::chrono::milliseconds ms);
static QTimer *s_currentTimer;
static int s_progressRefCount;
static QTimer *s_progressTimer;
};
#endif // TIMELINEDELEGATECONTROLLER_H
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