Handle redirects when importing schedules over the network.
[toast/confclerk.git] / src / gui / mainwindow.cpp
1 /*
2  * Copyright (C) 2010 Ixonos Plc.
3  * Copyright (C) 2011-2012 Philipp Spitzer, gregor herrmann
4  *
5  * This file is part of ConfClerk.
6  *
7  * ConfClerk is free software: you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the Free
9  * Software Foundation, either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * ConfClerk is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
15  * more details.
16  *
17  * You should have received a copy of the GNU General Public License along with
18  * ConfClerk.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 #include "mainwindow.h"
21
22 #include <QTreeView>
23 #include <QFile>
24 #include <QNetworkProxy>
25 #include <QNetworkAccessManager>
26 #include <QNetworkReply>
27
28 #include <sqlengine.h>
29
30 #include <track.h>
31 #include <eventmodel.h>
32 #include <delegate.h>
33
34 #include <conference.h>
35
36 #include <QDialog>
37 #include <QMessageBox>
38
39 #include "ui_about.h"
40 #include <eventdialog.h>
41 #include "daynavigatorwidget.h"
42 #include "settingsdialog.h"
43 #include "conferenceeditor.h"
44 #include "schedulexmlparser.h"
45 #include "errormessage.h"
46
47 #include <tabcontainer.h>
48 #include <appsettings.h>
49
50 const QString PROXY_USERNAME;
51 const QString PROXY_PASSWD;
52
53 MainWindow::MainWindow(int aEventId, QWidget *aParent)
54     : QMainWindow(aParent)
55     , conferenceModel(new ConferenceModel(this))
56     , mXmlParser(new ScheduleXmlParser(this))
57     , mNetworkAccessManager(new QNetworkAccessManager(this))
58 {
59     setupUi(this);
60
61     saved_title = windowTitle();
62
63 #ifdef N810
64     tabWidget->setTabText(1,"Favs");
65     //tabWidget->setTabText(2,"Day");
66 #endif
67
68     // first time run aplication: -> let's have it direct connection in this case
69     if(!AppSettings::contains("proxyIsDirectConnection"))
70         AppSettings::setDirectConnection(true);
71
72     /*
73     if(AppSettings::isDirectConnection())
74     {
75         qDebug() << "Setting-up proxy: " << AppSettings::proxyAddress() << ":" << AppSettings::proxyPort();
76     }
77     */
78     QNetworkProxy proxy(
79             AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : QNetworkProxy::HttpProxy,
80             AppSettings::proxyAddress(),
81             AppSettings::proxyPort(),
82             PROXY_USERNAME,
83             PROXY_PASSWD);
84     QNetworkProxy::setApplicationProxy(proxy);
85
86     // event details have changed
87     connect(dayTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
88     connect(favsTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
89     connect(tracksTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
90     connect(roomsTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
91     connect(searchTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
92
93     // date has changed
94     connect(dayNavigator, SIGNAL(dateChanged(QDate)), dayTabContainer, SLOT(redisplayDate(QDate)));
95     connect(dayNavigator, SIGNAL(dateChanged(QDate)), favsTabContainer, SLOT(redisplayDate(QDate)));
96     connect(dayNavigator, SIGNAL(dateChanged(QDate)), tracksTabContainer, SLOT(redisplayDate(QDate)));
97     connect(dayNavigator, SIGNAL(dateChanged(QDate)), roomsTabContainer, SLOT(redisplayDate(QDate)));
98     connect(dayNavigator, SIGNAL(dateChanged(QDate)), searchTabContainer, SLOT(redisplayDate(QDate)));
99
100     // search result has changed
101     connect(searchTabContainer, SIGNAL(searchResultChanged()), SLOT(onSearchResultChanged()));
102
103
104     useConference(Conference::activeConference());
105     // optimization, see useConference() code
106     try {
107         initTabs();
108     } catch (const OrmException& e) {
109         qDebug() << "OrmException:" << e.text();
110         clearTabs();
111     }
112
113     // open dialog for given Event ID
114     // this is used in case Alarm Dialog request application to start
115     if(aEventId)
116     {
117         try
118         {
119             EventDialog dialog(aEventId,this);
120             dialog.exec();
121         }
122         catch(OrmNoObjectException&) {} // just start application
123         catch(...) {} // just start application
124     }
125
126     connect(mNetworkAccessManager, SIGNAL(finished(QNetworkReply*)), SLOT(networkQueryFinished(QNetworkReply*)));
127
128     connect(mXmlParser, SIGNAL(parsingScheduleBegin()), conferenceModel, SLOT(newConferenceBegin()));
129     connect(mXmlParser, SIGNAL(parsingScheduleEnd(const QString&)), conferenceModel, SLOT(newConferenceEnd(const QString&)));
130 }
131
132 void MainWindow::on_aboutAction_triggered()
133 {
134     QDialog dialog(this);
135     Ui::AboutDialog ui;
136     ui.setupUi(&dialog);
137     ui.labDescription->setText(ui.labDescription->text().arg(qApp->applicationVersion()));
138 #ifdef N810
139     dialog.setFixedWidth(width());
140 #endif
141     dialog.exec();
142 }
143
144
145 void MainWindow::on_reloadAction_triggered() {
146     int confId = Conference::activeConference();
147     if (confId== -1) return;
148     Conference active = Conference::getById(confId);
149     if (active.url().isEmpty()) return;
150     importFromNetwork(active.url());
151     setEnabled(false);
152 }
153
154
155 void MainWindow::on_nowAction_triggered() {
156     int confId = Conference::activeConference();
157     if (confId== -1) return;
158     dayNavigator->setCurDate(QDate::currentDate());
159     dayTabContainer->expandTimeGroup(QTime::currentTime(), confId);
160 }
161
162
163 void MainWindow::on_searchAction_triggered() {
164     if (tabWidget->currentWidget() == searchTab)
165         searchTabContainer->showSearchDialog(!searchTabContainer->searchDialogIsVisible());
166     else {
167         tabWidget->setCurrentWidget(searchTab);
168         searchTabContainer->showSearchDialog();
169     }
170 }
171
172
173 void MainWindow::on_expandAllAction_triggered() {
174
175 }
176
177
178 void MainWindow::on_collapseAllAction_triggered() {
179
180 }
181
182
183 void MainWindow::onEventChanged(int aEventId, bool favouriteChanged) {
184     dayTabContainer->redisplayEvent(aEventId);
185     if (favouriteChanged) favsTabContainer->redisplayDate(dayNavigator->curDate());
186     else favsTabContainer->redisplayEvent(aEventId);
187     tracksTabContainer->redisplayEvent(aEventId);
188     roomsTabContainer->redisplayEvent(aEventId);
189     searchTabContainer->redisplayEvent(aEventId);
190 }
191
192
193 void MainWindow::onSearchResultChanged() {
194     // Are results found on the current date?
195     QDate date = dayNavigator->curDate();
196     int count = searchTabContainer->searchResultCount(date);
197     if (count > 0) {searchTabContainer->redisplayDate(date); return;}
198
199     // Are results found in the future?
200     for (date = date.addDays(1); date <= dayNavigator->endDate(); date = date.addDays(1)) {
201         int count = searchTabContainer->searchResultCount(date);
202         if (count > 0) {dayNavigator->setCurDate(date); return;}
203     }
204
205     // Are results found in the past?
206     for (date = dayNavigator->startDate(); date < dayNavigator->curDate(); date = date.addDays(1)) {
207         int count = searchTabContainer->searchResultCount(date);
208         if (count > 0) {dayNavigator->setCurDate(date); return;}
209     }
210     // No results were found
211     searchTabContainer->redisplayDate(dayNavigator->curDate());
212 }
213
214
215 void MainWindow::useConference(int id)
216 {
217     if (id == -1)  // in case no conference is active
218     {
219         unsetConference();
220         return;
221     }
222     try {
223         Conference::getById(Conference::activeConference()).update("active",0);
224         Conference new_active = Conference::getById(id);
225         new_active.update("active",1);
226
227         // looks like it does not work at n900
228         setWindowTitle(new_active.title());
229
230         // optimization.
231         // dont run initTabs() here
232         // it takes much CPU, making travelling between conferences in ConferenceEditor longer
233         // and is not seen in maemo WM anyway
234         // instead run it explicitly
235         // 1. at startup
236         // 2. when ConferenceEditor finished
237         // dont forget to protect the calls by try-catch!
238
239         // just in case, clear conference selection instead
240         clearTabs();
241
242         // end of optimization
243         // initTabs();
244     } catch (OrmException& e) {
245         // cannon set an active conference
246         unsetConference();   // TODO: as no active conference is now correctly managed this should be handled as a fatal error
247         return;
248     }
249
250 }
251
252 void MainWindow::initTabs()
253 {
254     int confId = Conference::activeConference();
255     if (confId != -1)   // only init tabs if a conference is active
256     {
257         Conference active = Conference::getById(confId);
258         QDate startDate = active.start();
259         QDate endDate = active.end();
260
261         // 'dayNavigator' emits signal 'dateChanged' after setting valid START:END dates
262         dayNavigator->setDates(startDate, endDate);
263     }
264 }
265
266 void MainWindow::clearTabs()
267 {
268     dayTabContainer->clearModel();
269     tracksTabContainer->clearModel();
270     roomsTabContainer->clearModel();
271     favsTabContainer->clearModel();
272     searchTabContainer->clearModel();
273 }
274
275 void MainWindow::unsetConference()
276 {
277     clearTabs();
278     dayNavigator->unsetDates();
279     setWindowTitle(saved_title);
280 }
281
282 void MainWindow::on_settingsAction_triggered()
283 {
284     SettingsDialog dialog;
285     dialog.loadDialogData();
286     if (dialog.exec() == QDialog::Accepted) {
287         dialog.saveDialogData();
288         QNetworkProxy proxy(
289                 AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : QNetworkProxy::HttpProxy,
290                 AppSettings::proxyAddress(),
291                 AppSettings::proxyPort(),
292                 PROXY_USERNAME,
293                 PROXY_PASSWD);
294         QNetworkProxy::setApplicationProxy(proxy);
295     }
296 }
297
298 /** Create and run ConferenceEditor dialog, making required connections for it.
299
300 This method manages, which classes actually perform changes in conference list.
301
302 There are several classes that modify the conferences:
303 this:
304  deletion and URL update.
305 this, mXmlParser and mNetworkAccessManager:
306  addition and refresh.
307 */
308 void MainWindow::on_conferencesAction_triggered()
309 {
310     ConferenceEditor dialog(conferenceModel, this);
311
312     connect(&dialog, SIGNAL(haveConferenceUrl(const QString&)), SLOT(importFromNetwork(const QString&)));
313     connect(&dialog, SIGNAL(haveConferenceFile(const QString&)), SLOT(importFromFile(const QString&)));
314     connect(&dialog, SIGNAL(removeConferenceRequested(int)), SLOT(removeConference(int)));
315     connect(&dialog, SIGNAL(changeUrlRequested(int, const QString&)),
316                     SLOT(changeConferenceUrl(int, const QString&)));
317
318     connect(&dialog, SIGNAL(haveConferenceSelected(int)), SLOT(useConference(int)));
319     connect(&dialog, SIGNAL(noneConferenceSelected()), SLOT(unsetConference()));
320
321     connect(mXmlParser, SIGNAL(parsingScheduleBegin()), &dialog, SLOT(importStarted()));
322     connect(mXmlParser, SIGNAL(progressStatus(int)), &dialog, SLOT(showParsingProgress(int)));
323     connect(mXmlParser, SIGNAL(parsingScheduleEnd(const QString&)), &dialog, SLOT(importFinished(const QString&)));
324
325     connect(this, SIGNAL(conferenceRemoved()), &dialog, SLOT(conferenceRemoved()));
326
327     dialog.exec();
328
329     // optimization, see useConference() code
330     try {
331         initTabs();
332     } catch (OrmException) {
333         clearTabs();
334     }
335 }
336
337 void MainWindow::networkQueryFinished(QNetworkReply *aReply) {
338     if (aReply->error() != QNetworkReply::NoError) {
339         error_message(QString("Error occured during download: ") + aReply->errorString());
340     } else {
341         QUrl redirectUrl = aReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
342         if (!redirectUrl.isEmpty()) {
343             if (redirectUrl != aReply->request().url()) {
344                 importFromNetwork(redirectUrl.toString());
345                 return; // don't enable controls
346             } else {
347                 error_message(QString("Error: Cyclic redirection from %1 to itself.").arg(redirectUrl.toString()));
348             }
349         } else {
350             importData(aReply->readAll(), aReply->url().toEncoded());
351         }
352     }
353     setEnabled(true);
354 }
355
356 void MainWindow::importData(const QByteArray &aData, const QString& url)
357 {
358     mXmlParser->parseData(aData, url);
359 }
360
361 void MainWindow::importFromNetwork(const QString& url)
362 {
363     QNetworkRequest request;
364     request.setUrl(QUrl(url));
365
366     mNetworkAccessManager->setProxy(QNetworkProxy::applicationProxy());
367     mNetworkAccessManager->get(request);
368 }
369
370 void MainWindow::importFromFile(const QString& filename)
371 {
372     QFile file(filename);
373     if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {    
374         static const QString format("Cannot read \"%1\": error %2");
375         error_message(format.arg(filename, QString::number(file.error())));
376     }
377
378     importData(file.readAll(), "");
379 }
380
381 void MainWindow::removeConference(int id)
382 {
383     Conference::deleteConference(id);
384     conferenceModel->conferenceRemoved();
385
386     emit conferenceRemoved();
387 }
388
389 void MainWindow::changeConferenceUrl(int id, const QString& url)
390 {
391     Conference::getById(id).setUrl(url);
392 }
393