KPublicTransport

backendmodel.cpp
1 /*
2  SPDX-FileCopyrightText: 2019 Volker Krause <[email protected]>
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 
14 using namespace KPublicTransport;
15 
16 namespace KPublicTransport {
17 struct BackendInfo
18 {
19  Backend backend;
20  QString country;
21  bool isNationWide;
22  CoverageArea::Type coverageType;
23 };
24 
25 class BackendModelPrivate
26 {
27 public:
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 
39 void BackendModelPrivate::repopulateModel(BackendModel *q)
40 {
41  if (!mgr) {
42  return;
43  }
44 
45  q->beginResetModel();
46  rows.clear();
47  switch (mode) {
48  case BackendModel::Flat:
49  repopulateFlat();
50  break;
52  repopulateGrouped();
53  break;
54  }
55 
56  sortModel();
57  q->endResetModel();
58 }
59 
60 void 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 
70 void 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 
90 void 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 
158 BackendModel::BackendModel(QObject *parent)
159  : QAbstractListModel(parent)
160  , d(new BackendModelPrivate)
161 {
162 }
163 
164 BackendModel::~BackendModel() = default;
165 
166 Manager* BackendModel::manager() const
167 {
168  return d->mgr;
169 }
170 
171 void 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 
185 BackendModel::Mode BackendModel::mode() const
186 {
187  return d->mode;
188 }
189 
190 void 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 
201 int BackendModel::rowCount(const QModelIndex &parent) const
202 {
203  if (parent.isValid()) {
204  return 0;
205  }
206  return d->rows.size();
207 }
208 
209 QVariant 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());
232  case Qt::CheckStateRole:
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;
237  case PrimaryCountryCodeRole:
238  case CountryCodeRole:
239  return row.country;
240  }
241 
242  return {};
243 }
244 
245 bool 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;
252  case Qt::CheckStateRole:
253  d->mgr->setBackendEnabled(row.backend.identifier(), value.toInt() == Qt::Checked);
254  return true;
255  }
256  return false;
257 }
258 
259 Qt::ItemFlags BackendModel::flags(const QModelIndex &index) const
260 {
261  auto f = QAbstractListModel::flags(index);
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 
275 QHash<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 }
Information about a backend service queried for location/departure/journey data.
Definition: backend.h:21
Query operations and data types for accessing realtime public transport information from online servi...
Definition: attribution.cpp:16
virtual QHash< int, QByteArray > roleNames() const const
MESSAGECORE_EXPORT KMime::Content * next(KMime::Content *node, bool allowChildren=true)
Mode
Content grouping modes.
Definition: backendmodel.h:35
Each backend appears exactly once, grouping by country is possible (the data is order by country)...
Definition: backendmodel.h:36
A backend might occur multiple times, for each country it is associated with.
Definition: backendmodel.h:37
virtual Qt::ItemFlags flags(const QModelIndex &index) const const override
bool isValid() const const
int toInt(bool *ok) const const
Definition: location.h:16
CheckStateRole
int row() const const
if(recurs()&&!first)
Type
Coverage quality as defined by the Transport API Repository format.
Definition: coveragearea.h:27
bool toBool() const const
Unchecked
Model listing backends and allowing to configure which ones are active.
Definition: backendmodel.h:22
Entry point for starting public transport queries.
Definition: manager.h:41
typedef ItemFlags
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sat Oct 23 2021 23:05:20 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.