Initial Commit
[uktrainplanner] / src / departureboard.cpp
1 #include "departureboard.h"
2 #include "ui_departureboard.h"
3
4 DepartureBoard::DepartureBoard(QWidget *parent) : QMainWindow(parent), ui(new Ui::DepartureBoard)
5 {
6     ui->setupUi(this);
7
8     manager = new QNetworkAccessManager(this);
9     connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(readResponse(QNetworkReply*)));
10
11     ui->scrollArea->setProperty("FingerScrollable", true);
12     setupDialog();
13 }
14
15 /*
16  * Initialises the progress dialog for the network request
17  */
18 void DepartureBoard::setupDialog()
19 {
20     dialog = new QDialog(0);
21         layout = new QHBoxLayout();
22             progress = new QProgressBar();
23             progress->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
24             cancelButton = new QPushButton("Cancel");
25
26             layout->addWidget(progress);
27             layout->addWidget(cancelButton);
28         dialog->setLayout(layout);
29     dialog->show();
30
31     connect(cancelButton, SIGNAL(clicked()), dialog, SLOT(reject()));
32     connect(dialog, SIGNAL(rejected()), this, SLOT(cancel()));
33 }
34
35 /*
36  * Loads the departure board for the requested station
37  */
38 void DepartureBoard::show(QString fromCode)
39 {
40     from = fromCode;
41     //Load the departure board data.
42     setupRequest();
43     sendRequest();
44     //Note that QMainWindow::show() is absent here. Instead we show a progress dialog, then show the
45     //departure board only when we have loaded the data & created the display.
46     errorFlag = false;
47     firstTime = true;
48 }
49
50 /*
51  * Creates the network request, including the SOAP request XML.
52  */
53 void DepartureBoard::setupRequest()
54 {
55     QDomDocument document;
56
57     QDomElement env = document.createElement("soapenv:Envelope");
58     env.setAttribute("xmlns:soapenv", "http://schemas.xmlsoap.org/soap/envelope/");
59     env.setAttribute("xmlns:typ", "http://thalesgroup.com/RTTI/2008-02-20/ldb/types");
60
61         QDomElement header = document.createElement("soapenv:Header");
62         QDomElement body = document.createElement("soapenv:Body");
63
64             QDomElement typ = document.createElement("typ:GetDepartureBoardRequest");
65
66                 QDomElement numRows = document.createElement("typ:numRows");
67                 numRows.appendChild(document.createTextNode("20"));
68                 QDomElement crs = document.createElement("typ:crs");
69                 crs.appendChild(document.createTextNode(from));
70                 //QDomElement filterCrs = document.createElement("typ:filterCrs");
71                 //filterCrs.appendChild(document.createTextNode(to));
72                 QDomElement filterType = document.createElement("typ:filterType");
73                 filterType.appendChild(document.createTextNode("to"));
74                 //QDomElement timeOffset = document.createElement("typ:timeOffset");
75                 //timeOffset.appendChild(document.createTextNode("0"));
76
77     document.appendChild(env);
78         env.appendChild(header);
79         env.appendChild(body);
80             body.appendChild(typ);
81                 typ.appendChild(numRows);
82                 typ.appendChild(crs);
83                 //typ.appendChild(filterCrs);
84                 typ.appendChild(filterType);
85                 //typ.appendChild(timeOffset);
86
87     requestData = document.toString(-1).prepend("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
88
89     request = QNetworkRequest(QUrl("http://realtime.nationalrail.co.uk/LDBWS/ldb2.asmx"));
90     request.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("text/xml;charset=utf-8"));
91     request.setRawHeader("SOAPAction", "http://thalesgroup.com/RTTI/2008-02-20/ldb/GetDepartureBoard");
92 }
93
94 /*
95  * Sends the SOAP request
96  */
97 void DepartureBoard::sendRequest()
98 {
99     m_reply = manager->post(request, requestData.toUtf8().constData());
100     connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(networkError(QNetworkReply::NetworkError)));
101     connect(m_reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(downloadProgress(qint64, qint64)));
102     connect(m_reply, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(uploadProgress(qint64, qint64)));
103 }
104
105 /*
106  * Cancel the current request
107  */
108 void DepartureBoard::cancel()
109 {
110     //Abort the request; an error signal will be emitted, so we don't need to delete the network reply here
111     m_reply->abort();
112 }
113
114 /*
115  * Updates the progress bar when making the request
116  */
117 void DepartureBoard::uploadProgress(qint64 rec, qint64 total)
118 {
119     dialog->setWindowTitle("Sending request");
120     progress->setRange(0, total);
121     progress->setValue(rec);
122 }
123
124 /*
125  * Updates the progress bar when receiving the reply to the request
126  */
127 void DepartureBoard::downloadProgress(qint64 rec, qint64 total)
128 {
129     dialog->setWindowTitle("Receiving reply");
130     progress->setRange(0, total);
131     progress->setValue(rec);
132 }
133
134 /*
135  * Slot for when there is a network error
136  */
137 void DepartureBoard::networkError(QNetworkReply::NetworkError error)
138 {
139     //If the request was cancelled, we show no error message
140     if(error == QNetworkReply::OperationCanceledError)
141     {
142         dialog->hide();
143         //Set the error flag to true, so that we can exit out of the readResponse method
144         errorFlag = true;
145     }
146     else
147     {
148         //If this error is a result of the first network request, we assume there is no connection to the internet
149         if(firstTime)
150         {
151             firstTime = false;
152             ensureConnection();
153             errorFlag = true;
154         }
155         else
156         {
157             QMessageBox msg;
158             msg.setText(QString("Network error: %1, %2").arg(m_reply->errorString()).arg(error));
159             msg.exec();
160             dialog->hide();
161             errorFlag = true;
162         }
163     }
164 }
165
166 /*
167  * Attempt to wait for connection on either wifi or gprs
168  */
169 void DepartureBoard::ensureConnection()
170 {
171     dialog->setWindowTitle("Connecting");
172
173     QNetworkInterface wlan = QNetworkInterface::interfaceFromName("wlan0");
174     QNetworkInterface gprs = QNetworkInterface::interfaceFromName("gprs0");
175
176     //qDebug() << "wlan0" << wlan.flags().testFlag(QNetworkInterface::IsUp) << wlan.flags().testFlag(QNetworkInterface::IsRunning);
177     //qDebug() << "\tgprs0" << gprs.flags().testFlag(QNetworkInterface::IsUp) << gprs.flags().testFlag(QNetworkInterface::IsRunning);
178
179     if( (wlan.isValid() && wlan.flags().testFlag(QNetworkInterface::IsUp)) || (gprs.isValid() && gprs.flags().testFlag(QNetworkInterface::IsUp)) )
180     {
181         //We are connected, so try sending the network request again
182         errorFlag = false;
183         sendRequest();
184     }
185     else
186     {
187         QTimer::singleShot(100, this, SLOT(ensureConnection()));
188     }
189 }
190
191 #define NAMESPACE   "declare namespace t=\"http://thalesgroup.com/RTTI/2007-10-10/ldb/types\";" \
192                     "declare function local:if-absent( $arg as item()*, $value as item()* ) as item()* { if (exists($arg)) then $arg else $value } ;"
193
194 /*
195  * Reads the response to the network request, and extracts the data we want.
196  */
197 void DepartureBoard::readResponse(QNetworkReply* reply)
198 {
199     QByteArray in = reply->readAll();
200
201     if(errorFlag == true)
202     {
203         m_reply->deleteLater();
204         return;
205     }
206
207     received.setData(in);
208     received.open(QIODevice::ReadOnly);
209
210     QXmlQuery query;
211     query.bindVariable("data", &received);
212
213     QString name = queryOne(query, "for $x in doc($data)//t:GetDepartureBoardResult/t:locationName/text() return fn:string($x)");
214     ui->label_name->setText(name);
215
216     QString time = queryOne(query, "for $x in doc($data)//t:generatedAt/text() return fn:string($x)");
217     time.mid(time.indexOf('T') + 1, 8);
218
219     setWindowTitle(time.mid(time.indexOf('T') + 1, 8).prepend("Last updated: "));
220
221     QStringList serviceFrom = queryList(query, "for $x in doc($data)//t:trainServices//t:origin//t:locationName/text() return fn:string($x)");
222     QStringList serviceTo = queryList(query, "for $x in doc($data)//t:trainServices//t:destination//t:locationName/text() return fn:string($x)");
223     QStringList serviceVia = queryList(query, "for $x in doc($data)//t:trainServices/t:service/t:destination/t:location return string(local:if-absent($x/t:via, ''))");
224
225     QStringList std = queryList(query, "for $x in doc($data)//t:trainServices//t:std/text() return fn:string($x)");
226     QStringList etd = queryList(query, "for $x in doc($data)//t:trainServices//t:etd/text() return fn:string($x)");
227     QStringList platform = queryList(query, "for $x in doc($data)//t:trainServices/t:service return string(local:if-absent($x/t:platform, '-'))");
228     //QStringList op = queryList(query, "for $x in doc($data)//t:trainServices//t:operator/text() return fn:string($x)");
229     serviceID = queryList(query, "for $x in doc($data)//t:trainServices//t:serviceID/text() return fn:string($x)");
230
231     for(int i = 0; i < serviceFrom.size(); i++)
232     {
233         QString to = QString("%1 %2").arg(serviceTo.at(i)).arg(serviceVia.at(i));
234         DepartureWidget * w = new DepartureWidget(std.at(i), to, etd.at(i), platform.at(i), serviceID.at(i), ui->departures);
235         departureWidgets.append(w);
236         w->setObjectName(QString("%1").arg(i));
237         connect(w, SIGNAL(clicked()), this, SLOT(departureClicked()));
238         ui->departures->layout()->addWidget(w);
239     }
240
241     dialog->accept();
242     m_reply->deleteLater();
243     received.close();
244
245     QMainWindow::show();
246 }
247
248 /*
249  * Shows the details of a service when it is clicked in the list of departures
250  */
251 void DepartureBoard::departureClicked()
252 {
253     int i = sender()->objectName().toInt();
254     QString id = departureWidgets.at(i)->getServiceID();
255
256     serviceWindow = new ServiceWindow();
257     serviceWindow->show(id);
258 }
259
260 QString DepartureBoard::queryOne(QXmlQuery & q, QString query)
261 {
262     QStringList output;
263     output.clear();
264     q.setQuery(query.prepend(NAMESPACE));
265     q.evaluateTo(&output);
266     return (output.size() == 0) ? QString() : output.at(0);
267 }
268
269 QStringList DepartureBoard::queryList(QXmlQuery & q, QString query)
270 {
271     QStringList output;
272     output.clear();
273     q.setQuery(query.prepend(NAMESPACE));
274     q.evaluateTo(&output);
275     return output;
276 }
277
278 DepartureBoard::~DepartureBoard()
279 {
280     delete ui;
281     delete manager;
282     delete dialog;
283 }
284
285 void DepartureBoard::changeEvent(QEvent *e)
286 {
287     QMainWindow::changeEvent(e);
288     switch (e->type()) {
289     case QEvent::LanguageChange:
290         ui->retranslateUi(this);
291         break;
292     default:
293         break;
294     }
295 }