Implement list sections for Harmattan
[quandoparte] / application / stationschedulemodel.cpp
1 /*
2
3 Copyright (C) 2011 Luciano Montanaro <mikelima@cirulla.net>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; see the file COPYING.  If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19
20 */
21
22 #include "stationschedulemodel.h"
23
24 #include "dataprovider.h"
25 #include "settings.h"
26
27 #include <QtGlobal>
28 #include <QDebug>
29 #if (QT_VERSION >= QT_VERSION_CHECK(5, 1, 0))
30 #include <QtWebKitWidgets/QtWebKitWidgets>
31 #else
32 #include <QWebElement>
33 #include <QWebFrame>
34 #include <QWebPage>
35 #endif
36
37 static QHash<int, QByteArray> roles;
38
39 StationScheduleModel::StationScheduleModel(const QString &name, QObject *parent) :
40     QAbstractListModel(parent),
41     m_name(name),
42     m_error(QString())
43
44 {
45     DataProvider *provider = DataProvider::instance();
46     QHash<int, QByteArray> roles;
47     roles[TrainRole] = "train";
48     roles[DepartureStationRole] = "departureStation";
49     roles[DepartureTimeRole] = "departureTime";
50     roles[ArrivalStationRole] = "arrivalStation";
51     roles[ArrivalTimeRole] = "arrivalTime";
52     roles[DetailsUrlRole] = "detailsUrl";
53     roles[DelayRole] = "delay";
54     roles[DelayClassRole] = "delayClass";
55     roles[ExpectedPlatformRole] = "expectedPlatform";
56     roles[ActualPlatformRole] = "actualPlatform";
57 #if (QT_VERSION <= QT_VERSION_CHECK(5, 0, 0))
58     setRoleNames(roles);
59 #endif
60
61     connect(provider, SIGNAL(stationScheduleReady(QByteArray,QUrl)),
62             this, SLOT(parse(QByteArray,QUrl)));
63     connect(provider, SIGNAL(error()),
64             this, SLOT(onNetworkError()));
65     Settings *settings = Settings::instance();
66     m_scheduleType = settings->showArrivalsPreferred() ? ArrivalSchedule : DepartureSchedule;
67 }
68
69 const QString &StationScheduleModel::name()
70 {
71     return m_name;
72 }
73
74 void StationScheduleModel::setName(const QString &name)
75 {
76     if (name != m_name) {
77         m_name = name;
78         emit nameChanged();
79     }
80 }
81
82 const QString &StationScheduleModel::code()
83 {
84     return m_code;
85 }
86
87 void StationScheduleModel::setCode(const QString &code)
88 {
89     if (code != m_code) {
90         m_code = code;
91         emit codeChanged();
92     }
93 }
94
95 const QString &StationScheduleModel::error()
96 {
97     return m_error;
98 }
99
100 void StationScheduleModel::setError(const QString &error)
101 {
102     if (error != m_error) {
103         m_error = error;
104         emit errorChanged();
105     }
106 }
107
108 QHash<int, QByteArray> StationScheduleModel::roleNames() const
109 {
110     return roles;
111 }
112
113 StationScheduleModel::ScheduleType StationScheduleModel::type()
114 {
115     return m_scheduleType;
116 }
117
118 void StationScheduleModel::setType(StationScheduleModel::ScheduleType type)
119 {
120     if (type != m_scheduleType) {
121         emit layoutAboutToBeChanged();
122         beginResetModel();
123         m_scheduleType = type;
124         emit typeChanged();
125         endResetModel();
126         emit layoutChanged();
127         Settings *settings = Settings::instance();
128         settings->setShowArrivalsPreferred(m_scheduleType == ArrivalSchedule ? true : false);
129     }
130 }
131
132 static void parseDelayClass(const QWebElement &element, StationScheduleItem &item)
133 {
134     if (!element.isNull()) {
135         QWebElement image = element.findFirst("img");
136         if (!image.isNull()) {
137             int delayClass = 42;
138             QString imageName = image.attribute("src");
139             if (!imageName.isEmpty()) {
140                 QRegExp delayClassRegexp("pallinoRit([0-9])\\.png");
141                 int pos = delayClassRegexp.indexIn(imageName);
142                 qDebug() << "regexp matched at pos:" << pos << "match:" << delayClassRegexp.cap(0);
143                 delayClass =  (pos >= 0) ? delayClassRegexp.cap(1).toInt() : 0;
144             }
145             item.setDelayClass(delayClass);
146         } else {
147             qDebug() << "img not found";
148         }
149     } else {
150         qDebug() << "div.bloccotreno not found";
151     }
152 }
153
154 static void parseDetailsUrl(const QWebElement &element, StationScheduleItem &item)
155 {
156     if (!element.isNull()) {
157         QWebElement link = element.findFirst("a");
158         QString url = link.attribute("href");
159         item.setDetailsUrl(url);
160     } else {
161         qDebug() << "link not found";
162     }
163 }
164
165 static void parseTrain(const QString &text, StationScheduleItem &item)
166 {
167     QRegExp filter("^(Per|Da) (.*)\\n"
168                    "Delle ore (.*)\n"
169                    "Binario Previsto: (.*)\n"
170                    "Binario Reale: (.*)\n"
171                    "(.*)$");
172     int pos = filter.indexIn(text);
173     if (pos >= 0) {
174         if (filter.cap(1) == "Per") {
175             item.setDepartureStation(filter.cap(2));
176             item.setDepartureTime(filter.cap(3));
177         } else {
178             item.setArrivalStation(filter.cap(2));
179             item.setArrivalTime(filter.cap(3));
180         }
181         item.setDelay(filter.cap(6));
182         item.setExpectedPlatform(filter.cap(4));
183         item.setActualPlatform(filter.cap(5));
184     } else {
185         qDebug() << "could not parse" << text;
186     }
187 }
188
189 StationScheduleItem parseResult(const QWebElement &result)
190 {
191     StationScheduleItem item;
192
193     QWebElement current = result.findFirst("h2");
194     if (!current.isNull()) {
195         item.setTrain(current.toPlainText());
196     }
197     parseDetailsUrl(result, item);
198     current = result.findFirst("div.bloccotreno");
199     parseDelayClass(current, item);
200     QString rawText = current.toPlainText();
201     parseTrain(rawText, item);
202
203     qDebug() << "train:" << item.train();
204     qDebug() << "delayClass:" << item.delayClass();
205     qDebug() << "detailsUrl:" << item.detailsUrl();
206     qDebug() << "departureStation:" << item.departureStation();
207     qDebug() << "departureTime:" << item.departureTime();
208     qDebug() << "arrivalStation:" << item.arrivalStation();
209     qDebug() << "arrivalTime:" << item.arrivalTime();
210     qDebug() << "expectedPlatform:" << item.expectedPlatform();
211     qDebug() << "actualPlatform:" << item.actualPlatform();
212     qDebug() << "delay:" << item.delay();
213     return item;
214 }
215
216 void StationScheduleModel::parse(const QByteArray &htmlReply, const QUrl &baseUrl)
217 {
218     Q_UNUSED(baseUrl);
219     qDebug() << "--- start of query result --- cut here ------";
220     qDebug() << QString::fromUtf8(htmlReply.constData());
221     qDebug() << "--- end of query result ----- cut here ------";
222
223     emit layoutAboutToBeChanged();
224     beginResetModel();
225 #if (QT_VERSION <= QT_VERSION_CHECK(5, 0, 0))
226     QWebPage page;
227     page.mainFrame()->setContent(htmlReply, "text/html", baseUrl);
228     QWebElement doc = page.mainFrame()->documentElement();
229
230     // Check if the page is reporting an error before parsing it
231     QWebElement errorElement = doc.findFirst("span.errore");
232     if (!errorElement.isNull()) {
233         m_departureSchedules.clear();
234         m_arrivalSchedules.clear();
235         QString errorText = errorElement.toPlainText().trimmed();
236         if (errorText == "localita' non trovata") {
237             setError(tr("Unknown station"));
238         } else {
239             setError(tr("Unknown error"));
240         }
241         qDebug() << "error:" << error();
242         return;
243     }
244     // Find the first div
245     QWebElement current = doc.findFirst("div");
246
247     qDebug() << "skipping to the departures";
248     // Skip to the first div of class corpocentrale, which contains the first
249     // departure-related contents
250     while (!current.classes().contains("corpocentrale")) {
251         current = current.nextSibling();
252         qDebug() << "skipping to the next element";
253         if (current.isNull())
254             break;
255     }
256     // Mark every div as a departure class element; the next corpocentrale
257     // marks the start of the arrivals section
258     qDebug() << "marking departures";
259     do {
260         if (current.classes().contains("bloccorisultato")) {
261             StationScheduleItem schedule = parseResult(current);
262             if (schedule.isValid()) {
263                 m_departureSchedules << schedule;
264             }
265         }
266         current = current.nextSibling();
267         qDebug() << "marking as departures";
268         if (current.isNull())
269             break;
270     } while (!current.classes().contains("corpocentrale"));
271
272     // Mark everything as an arrival, until reaching the footer
273     while (!current.classes().contains("footer")) {
274         if (current.classes().contains("bloccorisultato")) {
275             StationScheduleItem schedule = parseResult(current);
276             if (schedule.isValid()) {
277                 m_arrivalSchedules << schedule;
278             }
279         }
280         current = current.nextSibling();
281         qDebug() << "marking as arrival";
282         if (current.isNull())
283             break;
284     }
285 #endif
286     endResetModel();
287     emit layoutChanged();
288 }
289
290 void StationScheduleModel::onNetworkError()
291 {
292     qDebug()<< "Station Schedule Model got a Network Error";
293     m_error = tr("Network error");
294     emit errorChanged();
295 }
296
297 void StationScheduleModel::fetch(const QString &name, const QString &code)
298 {
299     DataProvider *provider = DataProvider::instance();
300
301     if (!error().isEmpty())
302         setError(QString());
303     m_departureSchedules.clear();
304     m_arrivalSchedules.clear();
305     provider->fetchStationSchedule(name, code);
306     setName(name);
307     setCode(code);
308 }
309
310 int StationScheduleModel::rowCount(const QModelIndex &parent) const
311 {
312     Q_UNUSED(parent);
313     if (m_scheduleType == DepartureSchedule) {
314         return m_departureSchedules.count();
315     } else {
316         return m_arrivalSchedules.count();
317     }
318 }
319
320 QVariant StationScheduleModel::data(const QModelIndex &index, int role) const
321 {
322     if (!index.isValid()) {
323         return QVariant();
324     }
325     const QList<StationScheduleItem> &schedules =
326             (m_scheduleType == DepartureSchedule) ? m_departureSchedules : m_arrivalSchedules;
327     if (index.row() < 0 || index.row() >= schedules.count()) {
328         return QVariant();
329     }
330     StationScheduleItem item = schedules[index.row()];
331     switch (role) {
332     case Qt::DisplayRole:
333     case TrainRole:
334         return QVariant::fromValue(item.train());
335     case DepartureStationRole:
336         return QVariant::fromValue(item.departureStation());
337     case DepartureTimeRole:
338         return QVariant::fromValue(item.departureTime());
339     case ArrivalStationRole:
340         return QVariant::fromValue(item.arrivalStation());
341     case ArrivalTimeRole:
342         return QVariant::fromValue(item.arrivalTime());
343     case DetailsUrlRole:
344         return QVariant::fromValue(item.detailsUrl());
345     case DelayRole:
346         return QVariant::fromValue(item.delay());
347     case DelayClassRole:
348         return QVariant::fromValue(item.delayClass());
349     case ExpectedPlatformRole:
350         return QVariant::fromValue(item.expectedPlatform());
351     case ActualPlatformRole:
352         return QVariant::fromValue(item.actualPlatform());
353     default:
354         return QVariant::fromValue(QString("Unknown role requested"));
355     }
356 }