diff --git a/src/lib/datatypes/line.h b/src/lib/datatypes/line.h index 1cdba24..b705876 100644 --- a/src/lib/datatypes/line.h +++ b/src/lib/datatypes/line.h @@ -1,164 +1,168 @@ /* Copyright (C) 2018 Volker Krause 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 . */ #ifndef KPUBLICTRANSPORT_LINE_H #define KPUBLICTRANSPORT_LINE_H #include "datatypes.h" #include "location.h" namespace KPublicTransport { class Line; class LinePrivate; class LineMetaData; namespace LineUtil{ void setMetaData(Line&, LineMetaData); } /** A public transport line. */ class KPUBLICTRANSPORT_EXPORT Line { KPUBLICTRANSPORT_GADGET(Line) public: - enum Mode { // ### direct copy from Navitia, we maybe can reduce that a bit + /** Mode of transportation. + * @toto direct copy from Navitia, we maybe can reduce that a bit + */ + enum Mode { Unknown, Air, Boat, Bus, BusRapidTransit, Coach, Ferry, Funicular, LocalTrain, LongDistanceTrain, Metro, RailShuttle, RapidTransit, Shuttle, Taxi, Train, Tramway, + RideShare, ///< peer-to-peer ride sharing/car pooling }; Q_ENUM(Mode) /** Name of the line. */ KPUBLICTRANSPORT_PROPERTY(QString, name, setName) /** Color of the line. */ KPUBLICTRANSPORT_PROPERTY(QColor, color, setColor) /** @c true if a line color is set. */ Q_PROPERTY(bool hasColor READ hasColor STORED false) /** Text color to use on top of the line color. */ KPUBLICTRANSPORT_PROPERTY(QColor, textColor, setTextColor) /** @c true if a text color is set. */ Q_PROPERTY(bool hasTextColor READ hasTextColor STORED false) /** Type of transport. */ KPUBLICTRANSPORT_PROPERTY(KPublicTransport::Line::Mode, mode, setMode) /** Human readable representation of the type of transport. * This is not necessarily a simple 1:1 mapping from mode, but can contain * e.g. a product name. */ KPUBLICTRANSPORT_PROPERTY(QString, modeString, setModeString) /** Path of a local file containing the line logo. * A line logo is typically a simple icon containing the short line name * and color. * This is downloaded on demand, and therefore might not be available * immediately. */ Q_PROPERTY(QString logo READ logo STORED false) /** @c true if the line has a logo. */ Q_PROPERTY(bool hasLogo READ hasLogo STORED false) /** Path of a local file containing the line mode logo. * A mode logo is the logo of the mode of transportation, or "product" * this line belongs to, such as the general logo for a subway or metro * service of this operator or in this city. * This is downloaded on demand, and therefore might not be available * immediately. */ Q_PROPERTY(QString modeLogo READ modeLogo STORED false) /** @c true if the line has a mode logo. */ Q_PROPERTY(bool hasModeLogo READ hasModeLogo STORED false) public: bool hasColor() const; bool hasTextColor() const; QString logo() const; bool hasLogo() const; QString modeLogo() const; bool hasModeLogo() const; /** Checks if to instances refer to the same line (which does not necessarily mean they are exactly equal). */ static bool isSame(const Line &lhs, const Line &rhs); /** Merge two Line instances. * This assumes isSame(lhs, rhs) and tries to preserve the most detailed information. */ static Line merge(const Line &lhs, const Line &rhs); /** Serializes one object to JSON. */ static QJsonObject toJson(const Line &l); /** Deserialize an object from JSON. */ static Line fromJson(const QJsonObject &obj); private: friend void LineUtil::setMetaData(Line&, LineMetaData); void setMetaData(LineMetaData metaData); }; class RoutePrivate; /** A route of a public transport line. */ class KPUBLICTRANSPORT_EXPORT Route { KPUBLICTRANSPORT_GADGET(Route) /** Line this route belongs to. */ KPUBLICTRANSPORT_PROPERTY(KPublicTransport::Line, line, setLine) /** Direction of the route. * The direction of the the route is what is displayed on front of a train for example. * For directional lines it matches the destination. For circular lines there is no destination * however, the direction is then clockwise" for example. */ KPUBLICTRANSPORT_PROPERTY(QString, direction, setDirection) /** Destination of the route. * If this is set it should match the direction of the line. Circular lines for example do * not have a destination location though. */ KPUBLICTRANSPORT_PROPERTY(KPublicTransport::Location, destination, setDestination) public: /** Checks if to instances refer to the same route (which does not necessarily mean they are exactly equal). */ static bool isSame(const Route &lhs, const Route &rhs); /** Merge two Route instances. * This assumes isSame(lhs, rhs) and tries to preserve the most detailed information. */ static Route merge(const Route &lhs, const Route &rhs); /** Serializes one object to JSON. */ static QJsonObject toJson(const Route &r); /** Deserialize an object from JSON. */ static Route fromJson(const QJsonObject &obj); }; } Q_DECLARE_METATYPE(KPublicTransport::Line) Q_DECLARE_METATYPE(KPublicTransport::Route) #endif // KPUBLICTRANSPORT_LINE_H diff --git a/src/lib/gtfs/hvt.cpp b/src/lib/gtfs/hvt.cpp index 3bbfbe7..f56caeb 100644 --- a/src/lib/gtfs/hvt.cpp +++ b/src/lib/gtfs/hvt.cpp @@ -1,201 +1,204 @@ /* Copyright (C) 2020 Volker Krause 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 . */ #include "hvt.h" #include using namespace KPublicTransport; Line::Mode Gtfs::Hvt::typeToMode(int hvt) { if (hvt < 0) { return Line::Unknown; } // individually handled cases - https://developers.google.com/transit/gtfs/reference/extended-route-types switch (hvt) { case 101: case 102: return Line::LongDistanceTrain; case 106: return Line::LocalTrain; case 109: return Line::RapidTransit; case 401: case 402: return Line::Metro; case 403: return Line::RapidTransit; case 717: return Line::Taxi; case 1502: return Line::Boat; } // coarse top-level types - https://developers.google.com/transit/gtfs/reference/#routestxt switch (hvt) { case 0: case 5: // cable car case 6: // TODO gondola return Line::Tramway; case 1: return Line::Metro; case 2: return Line::Train; case 3: return Line::Bus; case 4: return Line::Ferry; case 7: return Line::Funicular; } // type ranges - https://developers.google.com/transit/gtfs/reference/extended-route-types if (hvt >= 100 && hvt < 199) { return Line::Train; } if (hvt >= 200 && hvt < 299) { return Line::Coach; } if (hvt >= 400 && hvt < 499) { return Line::RapidTransit; } if (hvt >= 700 && hvt < 899) { return Line::Bus; } if (hvt >= 900 && hvt < 999) { return Line::Tramway; } if (hvt >= 1000 && hvt < 1099) { return Line::Boat; } if (hvt >= 1100 && hvt < 1199) { return Line::Air; } if (hvt >= 1200 && hvt < 1299) { return Line::Ferry; } if (hvt >= 1300 && hvt < 1399) { return Line::Tramway; // TODO gondola/aerial lift } if (hvt >= 1400 && hvt < 1499) { return Line::Funicular; } if (hvt >= 1500 && hvt < 1599) { return Line::Taxi; } + if (hvt == 1700) { // not officially specified, but de-facto standard in a number of OTP deployments + return Line::RideShare; + } qDebug() << "encountered unknown GTFS (extended) route type:" << hvt; return Line::Unknown; } // top-level types, complete list struct { const char *typeName; Line::Mode mode; } static const coarse_mode_map[] = { { "air", Line::Air }, { "bus", Line::Bus }, { "cableway", Line::Tramway }, // TODO { "coach", Line::Coach }, { "funicular", Line::Funicular }, { "lift", Line::Tramway }, // ??? { "metro", Line::Metro }, { "rail", Line::Train }, { "subway", Line::Metro }, { "tram", Line::Tramway }, { "unknown", Line::Unknown }, { "water", Line::Boat }, }; // fine-grained types, special cases that can't be found by pattern matches struct { const char *typeName; Line::Mode mode; } static const fine_mode_map[] = { { "airportlinkrail", Line::RapidTransit }, { "airshipservice", Line::Air }, { "blackcab", Line::Taxi }, { "canalbarge", Line::Boat }, { "cablecar", Line::Tramway }, // TODO { "helicopterservice", Line::Air }, { "international", Line::LongDistanceTrain }, { "interregionalrail", Line::Train }, { "local", Line::LocalTrain }, { "longdistance", Line::LongDistanceTrain }, { "minicab", Line::Taxi }, { "regionalrail", Line::LocalTrain }, { "riverbus", Line::Boat }, { "streetcablecar", Line::Tramway }, { "suburbanrailway", Line::RapidTransit }, { "trainFerry", Line::Ferry }, // disambiguate as this matches multiple patterns { "tube", Line::Metro }, { "urbanrailway", Line::RapidTransit }, { "watertaxi", Line::Boat }, }; // patterns for groups of fine-grained types struct { const char *namePattern; Line::Mode mode; } static const mode_pattern_map[] = { { "boat", Line::Boat }, { "bus", Line::Bus }, { "coach", Line::Coach }, { "ferry", Line::Ferry }, { "flight", Line::Air }, { "funicular", Line::Funicular }, { "highspeed", Line::LongDistanceTrain }, { "lift", Line::Tramway }, // ??? { "rail", Line::Train }, { "taxi", Line::Taxi }, { "telecabin", Line::Tramway }, // ??? { "train", Line::Train }, { "tram", Line::Tramway }, }; Line::Mode Gtfs::Hvt::typeToMode(const QString &hvt) { if (hvt.isEmpty()) { return Line::Unknown; } // fine-grained types, exact matches for (const auto &m : fine_mode_map) { if (hvt.compare(QLatin1String(m.typeName), Qt::CaseInsensitive) == 0) { return m.mode; } } // top-level types, exact matches for (const auto &m : coarse_mode_map) { if (hvt.compare(QLatin1String(m.typeName), Qt::CaseInsensitive) == 0) { return m.mode; } } // pattern matches on fine grained types for (const auto &m : mode_pattern_map) { if (hvt.contains(QLatin1String(m.namePattern), Qt::CaseInsensitive)) { return m.mode; } } qDebug() << "encountered unknown GTFS (extended) route type:" << hvt; return Line::Unknown; } diff --git a/tests/departurequery.qml b/tests/departurequery.qml index 16e9eb4..493ae26 100644 --- a/tests/departurequery.qml +++ b/tests/departurequery.qml @@ -1,357 +1,358 @@ /* Copyright (C) 2018 Volker Krause 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 . */ import QtQuick 2.5 import QtQuick.Layouts 1.1 import QtQuick.Controls 2.1 as QQC2 import Qt.labs.platform 1.0 as Platform import Qt.labs.settings 1.0 import org.kde.kirigami 2.0 as Kirigami import org.kde.kpublictransport 1.0 import org.kde.example 1.0 Kirigami.ApplicationWindow { title: "Departure Query" reachableModeEnabled: false width: 540 height: 800 pageStack.initialPage: departureQueryPage globalDrawer: Kirigami.GlobalDrawer { actions: [ Kirigami.Action { text: i18n("Save...") iconName: "document-save" onTriggered: fileDialog.open(); }, Kirigami.Action { iconName: "help-about-symbolic" text: i18n("Data Sources") enabled: departureModel.attributions.length > 0 onTriggered: aboutSheet.sheetOpen = true; }, Kirigami.Action { iconName: "settings-configure" text: "Backends" onTriggered: pageStack.push(backendPage) } ] } Platform.FileDialog { id: fileDialog title: i18n("Save Departure Data") fileMode: Platform.FileDialog.SaveFile nameFilters: ["JSON files (*.json)"] onAccepted: ExampleUtil.saveTo(departureModel, fileDialog.file); } TestLocationsModel { id: exampleModel } AttributionSheet { id: aboutSheet attributions: departureModel.attributions } LocationDetailsSheet { id: locationDetailsSheet } Manager { id: ptMgr; } Settings { id: settings property alias allowInsecureBackends: ptMgr.allowInsecureBackends property alias enabledBackends: ptMgr.enabledBackends property alias disabledBackends: ptMgr.disabledBackends } StopoverQueryModel { id: departureModel manager: ptMgr } Component { id: vehicleLayoutPage VehicleLayoutPage { publicTransportManager: ptMgr } } Component { id: departureDelegate Kirigami.AbstractListItem { enabled: departure.disruptionEffect != Disruption.NoService highlighted: false RowLayout { id: delegateLayout Kirigami.Icon { id: icon source: departure.route.line.hasLogo ? departure.route.line.logo : departure.route.line.modeLogo width: height height: Kirigami.Units.iconSizes.large visible: source != "" } Rectangle { id: colorBar width: Kirigami.Units.largeSpacing color: departure.route.line.hasColor ? departure.route.line.color : "transparent" Layout.fillHeight: true visible: icon.source == "" } QQC2.Label { text: { switch (departure.route.line.mode) { case Line.Air: return "✈️"; case Line.Boat: return "🛥️"; case Line.Bus: return "🚍"; case Line.BusRapidTransit: return "🚌"; case Line.Coach: return "🚌"; case Line.Ferry: return "⛴️"; case Line.Funicular: return "🚞"; case Line.LocalTrain: return "🚆"; case Line.LongDistanceTrain: return "🚄"; case Line.Metro: return "🚇"; case Line.RailShuttle: return "🚅"; case Line.RapidTransit: return "🚊"; case Line.Shuttle: return "🚐"; case Line.Taxi: return "🚕"; case Line.Train: return "🚆"; case Line.Tramway: return "🚈"; + case Line.RideShare: return "🚗"; default: return "?"; } } font.pointSize: Kirigami.Theme.defaultFont.pointSize * 2 visible: icon.source == "" } ColumnLayout { Layout.fillWidth: true QQC2.Label { Layout.fillWidth: true text: departure.route.line.modeString + " " + departure.route.line.name + " to " + departure.route.direction + "" onLinkActivated: { locationDetailsSheet.location = departure.route.destination; locationDetailsSheet.sheetOpen = true; } } RowLayout { QQC2.Label { text: "Arrival: " + departure.scheduledArrivalTime.toTimeString() } QQC2.Label { text: (departure.arrivalDelay >= 0 ? "+" : "") + departure.arrivalDelay color: departure.arrivalDelay > 1 ? Kirigami.Theme.negativeTextColor : Kirigami.Theme.positiveTextColor visible: departure.hasExpectedArrivalTime } QQC2.Label { text: "Departure: " + departure.scheduledDepartureTime.toTimeString() } QQC2.Label { text: (departure.departureDelay >= 0 ? "+" : "") + departure.departureDelay color: departure.departureDelay > 1 ? Kirigami.Theme.negativeTextColor : Kirigami.Theme.positiveTextColor visible: departure.hasExpectedDepartureTime } QQC2.Label { text: "vehicle" visible: departure.route.line.mode == Line.LongDistanceTrain onLinkActivated: applicationWindow().pageStack.push(vehicleLayoutPage, {"departure": departure }); Layout.fillWidth: true horizontalAlignment: Text.Right } } RowLayout { QQC2.Label { text: "From: " + departure.stopPoint.name + "" onLinkActivated: { locationDetailsSheet.location = departure.stopPoint; locationDetailsSheet.sheetOpen = true; } } QQC2.Label { visible: departure.scheduledPlatform != "" text: "Platform: " + departure.scheduledPlatform + (platformChange.visible ? " -> " : "") color: (!platformChange.visible && departure.hasExpectedPlatform) ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.textColor } QQC2.Label { id: platformChange text: departure.expectedPlatform visible: departure.hasExpectedPlatform && departure.scheduledPlatform != departure.expectedPlatform color: Kirigami.Theme.negativeTextColor } } QQC2.Label { text: departure.notes.join("
") visible: departure.notes.length > 0 font.italic: true textFormat: Text.RichText } } } } } Component { id: backendPage BackendPage { publicTransportManager: ptMgr } } Component { id: departureQueryPage Kirigami.Page { ColumnLayout { anchors.fill: parent QQC2.CheckBox { id: arrivalBox text: checked ? "Arrival" : "Departure" } QQC2.CheckBox { text: "Allow insecure backends" checked: ptMgr.allowInsecureBackends onToggled: ptMgr.allowInsecureBackends = checked } RowLayout { QQC2.CheckBox { id: backendBox text: "Select Backend:" } QQC2.ComboBox { id: backendSelector Layout.fillWidth: true textRole: "identifier" model: BackendModel { manager: ptMgr } enabled: backendBox.checked } } QQC2.ComboBox { id: exampleSelector Layout.fillWidth: true model: exampleModel textRole: "label" onCurrentIndexChanged: { var obj = exampleModel.get(currentIndex); nameQuery.text = obj.name == undefined ? obj.label : obj.name; lonQuery.text = obj.lon; latQuery.text = obj.lat; } } RowLayout { Layout.fillWidth: true QQC2.TextField { Layout.fillWidth: true id: nameQuery } QQC2.TextField { id: lonQuery Layout.preferredWidth: 100 } QQC2.TextField { id: latQuery Layout.preferredWidth: 100 } } RowLayout { Layout.fillWidth: true QQC2.Button { text: "Query" onClicked: { var stop = departureModel.request.stop; stop.latitude = latQuery.text; stop.longitude = lonQuery.text; stop.name = nameQuery.text; departureModel.request.stop = stop; departureModel.request.mode = arrivalBox.checked ? StopoverRequest.QueryArrival : StopoverRequest.QueryDeparture; departureModel.request.backends = backendBox.checked ? [ backendSelector.currentText ] : []; departureModel.request.downloadAssets = true } } QQC2.Button { text: "Query Name" onClicked: { var stop = departureModel.request.stop; stop.latitude = NaN; stop.longitude = NaN; stop.name = nameQuery.text; departureModel.request.stop = stop; departureModel.request.mode = arrivalBox.checked ? StopoverRequest.QueryArrival : StopoverRequest.QueryDeparture; departureModel.request.backends = backendBox.checked ? [ backendSelector.currentText ] : []; departureModel.request.downloadAssets = true } } QQC2.Button { text: "Query Coord" onClicked: { var stop = departureModel.request.stop; stop.latitude = latQuery.text; stop.longitude = lonQuery.text; stop.name = ""; departureModel.request.stop = stop; departureModel.request.mode = arrivalBox.checked ? StopoverRequest.QueryArrival : StopoverRequest.QueryDeparture; departureModel.request.backends = backendBox.checked ? [ backendSelector.currentText ] : []; departureModel.request.downloadAssets = true } } QQC2.Button { text: "Earlier" enabled: departureModel.canQueryPrevious onClicked: departureModel.queryPrevious() } QQC2.Button { text: "Later" enabled: departureModel.canQueryNext onClicked: departureModel.queryNext() } } ListView { Layout.fillHeight: true Layout.fillWidth: true model: departureModel clip: true delegate: departureDelegate QQC2.BusyIndicator { anchors.centerIn: parent running: departureModel.loading } QQC2.Label { anchors.centerIn: parent width: parent.width text: departureModel.errorMessage color: Kirigami.Theme.negativeTextColor wrapMode: Text.Wrap } } } } } } diff --git a/tests/journeyquery.qml b/tests/journeyquery.qml index 8946893..d83d5ed 100644 --- a/tests/journeyquery.qml +++ b/tests/journeyquery.qml @@ -1,485 +1,486 @@ /* Copyright (C) 2018 Volker Krause 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 . */ import QtQuick 2.5 import QtQuick.Layouts 1.1 import QtQuick.Controls 2.1 as QQC2 import Qt.labs.platform 1.0 as Platform import Qt.labs.settings 1.0 import org.kde.kirigami 2.0 as Kirigami import org.kde.kpublictransport 1.0 import org.kde.example 1.0 Kirigami.ApplicationWindow { title: "Journey Query" reachableModeEnabled: false width: 640 height: 800 pageStack.initialPage: journyQueryPage Manager { id: ptMgr; } Settings { id: settings property alias allowInsecureBackends: ptMgr.allowInsecureBackends property alias enabledBackends: ptMgr.enabledBackends property alias disabledBackends: ptMgr.disabledBackends } JourneyQueryModel { id: journeyModel manager: ptMgr } JourneyTitleModel { id: titleModel sourceModel: journeyModel } globalDrawer: Kirigami.GlobalDrawer { actions: [ Kirigami.Action { text: i18n("Save...") iconName: "document-save" onTriggered: fileDialog.open(); }, Kirigami.Action { iconName: "help-about-symbolic" text: i18n("Current Data Sources") enabled: journeyModel.attributions.length > 0 onTriggered: { aboutSheet.attributions = Qt.binding(function() { return journeyModel.attributions; }); aboutSheet.sheetOpen = true; } }, Kirigami.Action { iconName: "help-about-symbolic" text: i18n("All Data Sources") onTriggered: { aboutSheet.attributions = Qt.binding(function() { return ptMgr.attributions; }); aboutSheet.sheetOpen = true; } }, Kirigami.Action { iconName: "settings-configure" text: "Backends" onTriggered: pageStack.push(backendPage) } ] } Platform.FileDialog { id: fileDialog title: i18n("Save Journey Data") fileMode: Platform.FileDialog.SaveFile nameFilters: ["JSON files (*.json)"] onAccepted: ExampleUtil.saveTo(journeyModel, fileDialog.file); } TestLocationsModel { id: exampleModel } AttributionSheet { id: aboutSheet } LocationDetailsSheet { id:locationDetailsSheet } function displayDuration(dur) { if (dur < 60) return "<1min"; if (dur < 3600) return Math.floor(dur/60) + "min"; return Math.floor(dur/3600) + ":" + Math.floor((dur % 3600)/60) } function displayDistance(dist) { if (dist == 0) return ""; if (dist < 1000) return dist + "m"; return Math.floor(dist/1000) + "km"; } Component { id: vehicleLayoutPage VehicleLayoutPage { publicTransportManager: ptMgr } } Component { id: journeyDelegate Kirigami.AbstractListItem { enabled: modelData.disruptionEffect != Disruption.NoService highlighted: false RowLayout { id: topLayout Kirigami.Icon { id: icon source: modelData.route.line.hasLogo ? modelData.route.line.logo : modelData.route.line.modeLogo width: height height: Kirigami.Units.iconSizes.large visible: source != "" } Rectangle { id: colorBar width: Kirigami.Units.largeSpacing color: modelData.route.line.hasColor ? modelData.route.line.color : "transparent" Layout.fillHeight: true visible: icon.source == "" } QQC2.Label { text: { switch (modelData.mode) { case JourneySection.PublicTransport: { switch (modelData.route.line.mode) { case Line.Air: return "✈️"; case Line.Boat: return "🛥️"; case Line.Bus: return "🚍"; case Line.BusRapidTransit: return "🚌"; case Line.Coach: return "🚌"; case Line.Ferry: return "⛴️"; case Line.Funicular: return "🚞"; case Line.LocalTrain: return "🚆"; case Line.LongDistanceTrain: return "🚄"; case Line.Metro: return "🚇"; case Line.RailShuttle: return "🚅"; case Line.RapidTransit: return "🚊"; case Line.Shuttle: return "🚐"; case Line.Taxi: return "🚕"; case Line.Train: return "🚆"; case Line.Tramway: return "🚈"; + case Line.RideShare: return "🚗"; default: return "?"; } break; } case JourneySection.Walking: return "🚶"; case JourneySection.Waiting: return "⌛"; case JourneySection.Transfer: return "⇄"; default: return "?"; } } font.pointSize: Kirigami.Theme.defaultFont.pointSize * 2 visible: icon.source == "" } ColumnLayout { Layout.fillWidth: true RowLayout { QQC2.Label { text: "From: " + modelData.from.name + " Platform: " + modelData.scheduledDeparturePlatform onLinkActivated: { locationDetailsSheet.location = modelData.from; locationDetailsSheet.sheetOpen = true; } } QQC2.Label { text: modelData.expectedDeparturePlatform color: modelData.departurePlatformChanged ? Kirigami.Theme.negativeTextColor : Kirigami.Theme.positiveTextColor visible: modelData.hasExpectedDeparturePlatform } } RowLayout { QQC2.Label { text: "Departure: " + modelData.scheduledDepartureTime.toTimeString() } QQC2.Label { text: (modelData.departureDelay >= 0 ? "+" : "") + modelData.departureDelay color: modelData.departureDelay > 1 ? Kirigami.Theme.negativeTextColor : Kirigami.Theme.positiveTextColor visible: modelData.hasExpectedDepartureTime } QQC2.Label { text: "vehicle" visible: modelData.route.line.mode == Line.LongDistanceTrain onLinkActivated: applicationWindow().pageStack.push(vehicleLayoutPage, {"departure": modelData.departure }); Layout.fillWidth: true horizontalAlignment: Text.Right } } QQC2.Label { Layout.fillWidth: true text: { switch (modelData.mode) { case JourneySection.PublicTransport: return modelData.route.line.modeString + " " + modelData.route.line.name + " " + displayDuration(modelData.duration) + " / " + displayDistance(modelData.distance) case JourneySection.Walking: return "Walk " + displayDuration(modelData.duration) + " / " + displayDistance(modelData.distance) case JourneySection.Transfer: return "Transfer " + displayDuration(modelData.duration) + " / " + displayDistance(modelData.distance) case JourneySection.Waiting: return "Wait " + displayDuration(modelData.duration) return "???"; }} } RowLayout { QQC2.Label { text: "To: " + modelData.to.name + " Platform: " + modelData.scheduledArrivalPlatform onLinkActivated: { locationDetailsSheet.location = modelData.to; locationDetailsSheet.sheetOpen = true; } } QQC2.Label { text: modelData.expectedArrivalPlatform color: modelData.arrivalPlatformChanged ? Kirigami.Theme.negativeTextColor : Kirigami.Theme.positiveTextColor visible: modelData.hasExpectedArrivalPlatform } } RowLayout { QQC2.Label { text: "Arrival: " + modelData.scheduledArrivalTime.toTimeString() } QQC2.Label { text: (modelData.arrivalDelay >= 0 ? "+" : "") + modelData.arrivalDelay color: modelData.arrivalDelay > 1 ? Kirigami.Theme.negativeTextColor : Kirigami.Theme.positiveTextColor visible: modelData.hasExpectedArrivalTime } } QQC2.Label { text: modelData.notes.join("
") textFormat: Text.RichText visible: modelData.notes.length > 0 font.italic: true } } } onClicked: { if (modelData.mode == JourneySection.PublicTransport) { applicationWindow().pageStack.push(journeySectionPathPage, {"journeySection": modelData}); } } } } Component { id: backendPage BackendPage { publicTransportManager: ptMgr } } Component { id: journeySectionPathPage JourneySectionPage {} } Component { id: journyQueryPage Kirigami.Page { ColumnLayout { anchors.fill: parent QQC2.CheckBox { id: searchDirection text: checked ? "Time is arrival" : "Time is departure" } QQC2.CheckBox { text: "Allow insecure backends" checked: ptMgr.allowInsecureBackends onToggled: ptMgr.allowInsecureBackends = checked } RowLayout { QQC2.CheckBox { id: backendBox text: "Select Backend:" } QQC2.ComboBox { id: backendSelector Layout.fillWidth: true textRole: "identifier" model: BackendModel { manager: ptMgr } enabled: backendBox.checked } } QQC2.ComboBox { id: fromSelector Layout.fillWidth: true model: exampleModel textRole: "label" onCurrentIndexChanged: { var obj = exampleModel.get(currentIndex); fromName.text = obj.name == undefined ? obj.label : obj.name; fromLon.text = obj.lon; fromLat.text = obj.lat; if (toSelector.currentIndex == currentIndex) { toSelector.currentIndex = (currentIndex + 1) % count; } } } RowLayout { QQC2.TextField { id: fromName } QQC2.TextField { id: fromLon } QQC2.TextField { id: fromLat } } QQC2.ComboBox { id: toSelector Layout.fillWidth: true model: exampleModel textRole: "label" onCurrentIndexChanged: { var obj = exampleModel.get(currentIndex); toName.text = obj.name == undefined ? obj.label : obj.name; toLon.text = obj.lon; toLat.text = obj.lat; if (fromSelector.currentIndex == currentIndex) { fromSelector.currentIndex = (currentIndex - 1 + count) % count; } } } RowLayout { QQC2.TextField { id: toName } QQC2.TextField { id: toLon } QQC2.TextField { id: toLat } } RowLayout { QQC2.Button { text: "Query" onClicked: { var from = journeyModel.request.from; from.name = fromName.text; from.latitude = fromLat.text; from.longitude = fromLon.text; journeyModel.request.from = from; var to = journeyModel.request.to; to.name = toName.text; to.latitude = toLat.text; to.longitude = toLon.text; journeyModel.request.to = to; journeyModel.request.dateTimeMode = searchDirection.checked ? JourneyRequest.Arrival : JourneyRequest.Departure; journeyModel.request.dateTime = new Date(new Date().getTime() + (searchDirection.checked ? 7200000 : 0)); journeyModel.request.backends = backendBox.checked ? [ backendSelector.currentText ] : []; journeyModel.request.downloadAssets = true } } QQC2.Button { text: "Query Name" onClicked: { var from = journeyModel.request.from; from.name = fromName.text; from.latitude = NaN; from.longitude = NaN; journeyModel.request.from = from; var to = journeyModel.request.to; to.name = toName.text; to.latitude = NaN; to.longitude = NaN; journeyModel.request.to = to; journeyModel.request.dateTimeMode = searchDirection.checked ? JourneyRequest.Arrival : JourneyRequest.Departure; journeyModel.request.dateTime = new Date(new Date().getTime() + (searchDirection.checked ? 7200000 : 0)); journeyModel.request.backends = backendBox.checked ? [ backendSelector.currentText ] : []; journeyModel.request.downloadAssets = true } } QQC2.Button { text: "Query Coord" onClicked: { var from = journeyModel.request.from; from.name = ""; from.latitude = fromLat.text; from.longitude = fromLon.text; journeyModel.request.from = from; var to = journeyModel.request.to; to.name = ""; to.latitude = toLat.text; to.longitude = toLon.text; journeyModel.request.to = to; journeyModel.request.dateTimeMode = searchDirection.checked ? JourneyRequest.Arrival : JourneyRequest.Departure; journeyModel.request.dateTime = new Date(new Date().getTime() + (searchDirection.checked ? 7200000 : 0)); journeyModel.request.backends = backendBox.checked ? [ backendSelector.currentText ] : []; journeyModel.request.downloadAssets = true } } QQC2.Button { text: "Clear" onClicked: { fromName.text = ""; fromLon.text = ""; fromLat.text = ""; toName.text = ""; toLon.text = ""; toLat.text = ""; } } } RowLayout { QQC2.ToolButton { id: prevQueryButton icon.name: "go-previous" enabled: journeyModel.canQueryPrevious onClicked: journeyModel.queryPrevious() } QQC2.ComboBox { id: journeySelector Layout.fillWidth: true model: titleModel textRole: "display" } QQC2.ToolButton { id: nextQueryButton icon.name: "go-next" enabled: journeyModel.canQueryNext onClicked: journeyModel.queryNext() } } ListView { Layout.fillHeight: true Layout.fillWidth: true model: journeyModel.data(journeyModel.index(journeySelector.currentIndex, 0), 256).sections clip: true delegate: journeyDelegate QQC2.BusyIndicator { anchors.centerIn: parent running: journeyModel.loading } QQC2.Label { anchors.centerIn: parent width: parent.width text: journeyModel.errorMessage color: Kirigami.Theme.negativeTextColor wrapMode: Text.Wrap } } } } } }