Added type property to StationScheduleModel
[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 <QDebug>
28 #include <QWebElement>
29 #include <QWebFrame>
30 #include <QWebPage>
31
32 StationScheduleModel::StationScheduleModel(const QString &name, QObject *parent) :
33     QAbstractListModel(parent),
34     m_name(name)
35
36 {
37     DataProvider *provider = DataProvider::instance();
38     QHash<int, QByteArray> roles;
39     roles[TrainRole] = "train";
40     roles[DepartureStationRole] = "departureStation";
41     roles[DepartureTimeRole] = "departureTime";
42     roles[ArrivalStationRole] = "arrivalStation";
43     roles[ArrivalTimeRole] = "arrivalTime";
44     roles[DetailsUrlRole] = "detailsUrl";
45     roles[DelayRole] = "delay";
46     roles[DelayClassRole] = "delayClassRole";
47     setRoleNames(roles);
48
49     connect(provider, SIGNAL(stationScheduleReady(QByteArray,QUrl)),
50             this, SLOT(parse(QByteArray,QUrl)));
51 }
52
53 QString & StationScheduleModel::name()
54 {
55     return m_name;
56 }
57
58 void StationScheduleModel::setName(const QString &name)
59 {
60     if (name != m_name) {
61         m_name = name;
62         emit nameChanged();
63     }
64 }
65
66 StationScheduleModel::ScheduleType StationScheduleModel::type()
67 {
68     return m_scheduleType;
69 }
70
71 void StationScheduleModel::setType(StationScheduleModel::ScheduleType type)
72 {
73     if (type != m_scheduleType) {
74         emit layoutAboutToBeChanged();
75         beginResetModel();
76         m_scheduleType = type;
77         emit typeChanged();
78         endResetModel();
79         emit layoutChanged();
80         Settings *settings = Settings::instance();
81         settings->setShowArrivalsPreferred(m_scheduleType == ArrivalSchedule ? true : false);
82     }
83 }
84
85 static void parseDelayClass(const QWebElement &element, StationScheduleItem &item)
86 {
87     if (!element.isNull()) {
88         QWebElement image = element.findFirst("img");
89         if (!image.isNull()) {
90             int delayClass = 42;
91             QString imageName = image.attribute("src");
92             if (!imageName.isEmpty()) {
93                 QRegExp delayClassRegexp("pallinoRit([0-9])\\.png");
94                 int pos = delayClassRegexp.indexIn(imageName);
95                 qDebug() << "regexp matched at pos:" << pos << "match:" << delayClassRegexp.cap(0);
96                 delayClass =  (pos >= 0) ? delayClassRegexp.cap(1).toInt() : 0;
97             }
98             item.setDelayClass(delayClass);
99         } else {
100             qDebug() << "img not found";
101         }
102     } else {
103         qDebug() << "div.bloccotreno not found";
104     }
105 }
106
107 static void parseDetailsUrl(const QWebElement &element, StationScheduleItem &item)
108 {
109     if (!element.isNull()) {
110         QWebElement link = element.findFirst("a");
111         QString url = link.attribute("href");
112         item.setDetailsUrl(url);
113     } else {
114         qDebug() << "link not found";
115     }
116 }
117
118 static void parseTrain(const QString &text, StationScheduleItem &item)
119 {
120     QRegExp filter("^(Per|Da) (.*)\\n"
121                    "Delle ore (.*)\n"
122                    "Binario Previsto: (.*)\n"
123                    "Binario Reale: (.*)\n"
124                    "(.*)$");
125     int pos = filter.indexIn(text);
126     if (pos >= 0) {
127         if (filter.cap(1) == "Per") {
128             item.setDepartureStation(filter.cap(2));
129             item.setDepartureTime(filter.cap(3));
130         } else {
131             item.setArrivalStation(filter.cap(2));
132             item.setArrivalTime(filter.cap(3));
133         }
134         item.setDelay(filter.cap(6));
135     } else {
136         qDebug() << "could not parse" << text;
137     }
138 }
139
140 StationScheduleItem parseResult(const QWebElement &result)
141 {
142     StationScheduleItem item;
143
144     QWebElement current = result.findFirst("h2");
145     if (!current.isNull()) {
146         item.setTrain(current.toPlainText());
147     }
148     parseDetailsUrl(result, item);
149     current = result.findFirst("div.bloccotreno");
150     parseDelayClass(current, item);
151     QString rawText = current.toPlainText();
152     parseTrain(rawText, item);
153
154     qDebug() << "train:" << item.train();
155     qDebug() << "delayClass:" << item.delayClass();
156     qDebug() << "detailsUrl:" << item.detailsUrl();
157     qDebug() << "departureStation:" << item.departureStation();
158     qDebug() << "departureTime:" << item.departureTime();
159     qDebug() << "arrivalStation:" << item.arrivalStation();
160     qDebug() << "arrivalTime:" << item.arrivalTime();
161     qDebug() << "delay:" << item.delay();
162     return item;
163 }
164
165 void StationScheduleModel::parse(const QByteArray &htmlReply, const QUrl &baseUrl)
166 {
167     Q_UNUSED(baseUrl);
168     qDebug() << "--- start of query result --- cut here ------";
169     qDebug() << QString::fromUtf8(htmlReply.constData());
170     qDebug() << "--- end of query result ----- cut here ------";
171
172     emit layoutAboutToBeChanged();
173     beginResetModel();
174     QWebPage page;
175     page.mainFrame()->setContent(htmlReply, "text/html", baseUrl);
176     QWebElement doc = page.mainFrame()->documentElement();
177
178     // Find the first div
179     QWebElement current = doc.findFirst("div");
180
181     QStringList departures, arrivals;
182     qDebug() << "skipping to the departures";
183     // Skip to the first div of class corpocentrale, which contains the first
184     // departure-related contents
185     while (!current.classes().contains("corpocentrale")) {
186         current = current.nextSibling();
187         qDebug() << "skipping to the next element";
188         if (current.isNull())
189             break;
190     }
191     // Mark every div as a departure class element; the next corpocentrale
192     // marks the start of the arrivals section
193     qDebug() << "marking departures";
194     do {
195         if (current.classes().contains("bloccorisultato")) {
196             departures << current.toPlainText();
197             StationScheduleItem schedule = parseResult(current);
198             if (schedule.isValid()) {
199                 m_departureSchedules << schedule;
200             }
201         }
202         current = current.nextSibling();
203         qDebug() << "marking as departures";
204         if (current.isNull())
205             break;
206     } while (!current.classes().contains("corpocentrale"));
207
208     // Mark everything as an arrival, until reaching the footer
209     while (!current.classes().contains("footer")) {
210         if (current.classes().contains("bloccorisultato")) {
211             arrivals << current.toPlainText();
212             StationScheduleItem schedule = parseResult(current);
213             if (schedule.isValid()) {
214                 m_arrivalSchedules << schedule;
215             }
216         }
217         current = current.nextSibling();
218         qDebug() << "marking as arrival";
219         if (current.isNull())
220             break;
221     }
222     endResetModel();
223     emit layoutChanged();
224 }
225
226 void StationScheduleModel::fetch(const QString &name)
227 {
228     DataProvider *provider = DataProvider::instance();
229
230     provider->fetchStationSchedule(name);
231     setName(name);
232 }
233
234 int StationScheduleModel::rowCount(const QModelIndex &parent) const
235 {
236     Q_UNUSED(parent);
237     if (m_scheduleType == DepartureSchedule) {
238         qDebug() << "schedule.count" << m_departureSchedules.count();
239         return m_departureSchedules.count();
240     } else {
241         qDebug() << "schedule.count" << m_arrivalSchedules.count();
242         return m_arrivalSchedules.count();
243     }
244 }
245
246 QVariant StationScheduleModel::data(const QModelIndex &index, int role) const
247 {
248     qDebug() << "getting data for role" << role;
249     if (!index.isValid()) {
250         return QVariant();
251     }
252     const QList<StationScheduleItem> &schedules =
253             (m_scheduleType == DepartureSchedule) ? m_departureSchedules : m_arrivalSchedules;
254     if (index.row() < 0 || index.row() >= schedules.count()) {
255         return QVariant();
256     }
257     StationScheduleItem item = schedules[index.row()];
258     switch (role) {
259     case Qt::DisplayRole:
260     case TrainRole:
261         return QVariant::fromValue(item.train());
262     case DepartureStationRole:
263         return QVariant::fromValue(item.departureStation());
264     case DepartureTimeRole:
265         return QVariant::fromValue(item.departureTime());
266     case ArrivalStationRole:
267         return QVariant::fromValue(item.arrivalStation());
268     case ArrivalTimeRole:
269         return QVariant::fromValue(item.arrivalTime());
270     case DetailsUrlRole:
271         return QVariant::fromValue(item.detailsUrl());
272     case DelayRole:
273         return QVariant::fromValue(item.delay());
274     case DelayClassRole:
275         return QVariant::fromValue(item.delayClass());
276     default:
277         return QVariant::fromValue(QString("Unknown role requested"));
278     }
279 }