KPublicTransport

backendmodel.cpp
1/*
2 SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "backendmodel.h"
8
9#include <KPublicTransport/Backend>
10#include <KPublicTransport/Manager>
11
12#include <QDebug>
13
14using namespace KPublicTransport;
15
16namespace KPublicTransport {
17struct BackendInfo
18{
19 Backend backend;
20 QString country;
21 bool isNationWide;
22 CoverageArea::Type coverageType;
23};
24
25class BackendModelPrivate
26{
27public:
28 void repopulateModel(BackendModel *q);
29 void repopulateFlat();
30 void repopulateGrouped();
31 void sortModel();
32
33 Manager *mgr = nullptr;
34 std::vector<BackendInfo> rows;
36};
37}
38
39void BackendModelPrivate::repopulateModel(BackendModel *q)
40{
41 if (!mgr) {
42 return;
43 }
44
45 q->beginResetModel();
46 rows.clear();
47 switch (mode) {
49 repopulateFlat();
50 break;
52 repopulateGrouped();
53 break;
54 }
55
56 sortModel();
57 q->endResetModel();
58}
59
60void BackendModelPrivate::repopulateFlat()
61{
62 rows.reserve(mgr->backends().size());
63 for (const auto &b : mgr->backends()) {
64 if (b.identifier().size() > 3 && b.identifier().at(2) == QLatin1Char('_')) {
65 rows.push_back({ b, b.identifier().left(2).toUpper(), true, CoverageArea::Any });
66 }
67 }
68}
69
70void BackendModelPrivate::repopulateGrouped()
71{
72 for (const auto &b : mgr->backends()) {
73 for (const auto type : { CoverageArea::Realtime, CoverageArea::Regular, CoverageArea::Any }) {
74 const auto c = b.coverageArea(type);
75 if (c.isEmpty()) {
76 continue;
77 }
78
79 for (const auto &region: c.regions()) {
80 const auto country = region.left(2);
81 if (!rows.empty() && rows.back().backend.identifier() == b.identifier() && rows.back().country == country) {
82 continue; // deduplicate backends covering multiple regions in the same country
83 }
84 rows.push_back({ b, country, region.size() == 2, type });
85 }
86 }
87 }
88}
89
90void BackendModelPrivate::sortModel()
91{
92 // group by country
93 const auto orderByCountry = [](const auto &lhs, const auto &rhs) {
94 return lhs.country < rhs.country;
95 };
96 std::sort(rows.begin(), rows.end(), orderByCountry);
97
98 // process each country individually
99 for (auto next = rows.begin(); next != rows.end();) {
100 const auto [begin, end] = std::equal_range(next, rows.end(), *next, orderByCountry);
101 next = end;
102
103 // find best coverage quality for nation-wide services
104 CoverageArea::Type bestCoverageType = CoverageArea::Any;
105 for (auto it = begin; it != end; ++it) {
106 if ((*it).isNationWide) {
107 bestCoverageType = std::min(bestCoverageType, (*it).coverageType);
108 }
109 }
110
111 // sort within one country: nation wide with best quality first, then regional, then nation-wide with subpar coverage
112 std::sort(begin, end, [bestCoverageType](const auto &lhs, const auto &rhs) {
113 if (lhs.isNationWide && rhs.isNationWide) {
114 if ((lhs.coverageType > bestCoverageType && rhs.coverageType > bestCoverageType)
115 || (lhs.coverageType <= bestCoverageType && rhs.coverageType <= bestCoverageType)) {
116 return lhs.backend.name() < rhs.backend.name();
117 }
118 return lhs.coverageType < rhs.coverageType;
119 }
120 if (lhs.isNationWide && !rhs.isNationWide) {
121 return lhs.coverageType <= bestCoverageType;
122 }
123 if (!lhs.isNationWide && rhs.isNationWide) {
124 return rhs.coverageType > bestCoverageType;
125 }
126 assert(!lhs.isNationWide && !rhs.isNationWide);
127 return lhs.backend.name() < rhs.backend.name();
128 });
129
130 // drop entries for nationwide providers that are only the second best coverage quality for a country
131 // and that have better coverage elsewhere
132 // since removing entries here immediately would mess with the active iterators, we just clear the country
133 // and do the actual deletion below
134 if (mode == BackendModel::GroupByCountry) {
135 for (auto it = begin; it != end; ++it) {
136 if (!(*it).isNationWide || (*it).coverageType <= bestCoverageType) {
137 continue;
138 }
139
140 for (auto type : { CoverageArea::Realtime, CoverageArea::Regular }) {
141 if (type >= (*it).coverageType) {
142 break;
143 }
144 if (!(*it).backend.coverageArea(type).isEmpty()) {
145 (*it).country.clear();
146 break;
147 }
148 }
149 }
150 }
151 }
152
153 // clean up entries marked for deletion above
154 rows.erase(std::remove_if(rows.begin(), rows.end(), [](const auto &r) { return r.country.isEmpty(); }), rows.end());
155}
156
157
158BackendModel::BackendModel(QObject *parent)
159 : QAbstractListModel(parent)
160 , d(new BackendModelPrivate)
161{
162}
163
164BackendModel::~BackendModel() = default;
165
167{
168 return d->mgr;
169}
170
171void BackendModel::setManager(Manager *mgr)
172{
173 if (d->mgr == mgr) {
174 return;
175 }
176
177 d->mgr = mgr;
178 connect(mgr, &Manager::configurationChanged, this, [this]() {
179 Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0));
180 });
181 d->repopulateModel(this);
182 Q_EMIT managerChanged();
183}
184
186{
187 return d->mode;
188}
189
190void BackendModel::setMode(BackendModel::Mode mode)
191{
192 if (d->mode == mode) {
193 return;
194 }
195
196 d->mode = mode;
197 Q_EMIT modeChanged();
198 d->repopulateModel(this);
199}
200
201int BackendModel::rowCount(const QModelIndex &parent) const
202{
203 if (parent.isValid()) {
204 return 0;
205 }
206 return d->rows.size();
207}
208
209QVariant BackendModel::data(const QModelIndex &index, int role) const
210{
211 if (!index.isValid() || !d->mgr) {
212 return {};
213 }
214
215 const auto &row = d->rows[index.row()];
216 switch (role) {
217 case NameRole:
218 return row.backend.name();
219 case DescriptionRole:
220 return row.backend.description();
221 case IdentifierRole:
222 return row.backend.identifier();
223 case SecureRole:
224 return row.backend.isSecure();
225 case ItemEnabledRole:
226 return row.backend.isSecure() || d->mgr->allowInsecureBackends();
227 case BackendEnabledRole:
228 if (!row.backend.isSecure() && !d->mgr->allowInsecureBackends()) {
229 return false;
230 }
231 return d->mgr->isBackendEnabled(row.backend.identifier());
233 if (!row.backend.isSecure() && !d->mgr->allowInsecureBackends()) {
234 return Qt::Unchecked;
235 }
236 return d->mgr->isBackendEnabled(row.backend.identifier()) ? Qt::Checked : Qt::Unchecked;
238 case CountryCodeRole:
239 return row.country;
240 }
241
242 return {};
243}
244
245bool BackendModel::setData(const QModelIndex &index, const QVariant &value, int role)
246{
247 const auto &row = d->rows[index.row()];
248 switch (role) {
249 case BackendModel::BackendEnabledRole:
250 d->mgr->setBackendEnabled(row.backend.identifier(), value.toBool());
251 return true;
253 d->mgr->setBackendEnabled(row.backend.identifier(), value.toInt() == Qt::Checked);
254 return true;
255 }
256 return false;
257}
258
259Qt::ItemFlags BackendModel::flags(const QModelIndex &index) const
260{
262 if (!d->mgr || !index.isValid()) {
263 return f;
264 }
266
267 const auto &row = d->rows[index.row()];
268 if (!d->mgr->allowInsecureBackends() && !row.backend.isSecure()) {
269 return f & ~Qt::ItemIsEnabled;
270 }
271
272 return f;
273}
274
275QHash<int, QByteArray> BackendModel::roleNames() const
276{
277 auto names = QAbstractListModel::roleNames();
278 names.insert(NameRole, "name");
279 names.insert(DescriptionRole, "description");
280 names.insert(IdentifierRole, "identifier");
281 names.insert(SecureRole, "isSecure");
282 names.insert(ItemEnabledRole, "itemEnabled");
283 names.insert(BackendEnabledRole, "backendEnabled");
284 names.insert(PrimaryCountryCodeRole, "primaryCountryCode");
285 names.insert(CountryCodeRole, "countryCode");
286 return names;
287}
Model listing backends and allowing to configure which ones are active.
Mode mode
Configures the grouping mode for the content of this model.
Mode
Content grouping modes.
@ GroupByCountry
A backend might occur multiple times, for each country it is associated with.
@ Flat
Each backend appears exactly once, grouping by country is possible (the data is order by country),...
@ CountryCodeRole
a ISO 3166-1 code usable for grouping content by country
KPublicTransport::Manager * manager
Sets the KPublicTransport::Manager instance.
Information about a backend service queried for location/departure/journey data.
Definition backend.h:22
Type
Coverage quality as defined by the Transport API Repository format.
QVariantList backends
QML-compatible access to backends().
Definition manager.h:57
Query operations and data types for accessing realtime public transport information from online servi...
const QList< QKeySequence > & next()
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList< int > &roles)
virtual QHash< int, QByteArray > roleNames() const const
virtual Qt::ItemFlags flags(const QModelIndex &index) const const override
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const override
bool isValid() const const
int row() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
Unchecked
CheckStateRole
typedef ItemFlags
QTextStream & left(QTextStream &stream)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:13:06 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.