Set some SSL parameters for network request.
[toast/confclerk.git] / src / gui / mainwindow.cpp
1 /*
2  * Copyright (C) 2010 Ixonos Plc.
3  * Copyright (C) 2011-2017 Philipp Spitzer, gregor herrmann, Stefan Stahl
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 #include <QSslConfiguration>
28
29 #include "sqlengine.h"
30
31 #include "track.h"
32 #include "eventmodel.h"
33 #include "delegate.h"
34 #include "room.h"
35
36 #include "conference.h"
37
38 #include <QDialog>
39 #include <QMessageBox>
40
41 #include "ui_about.h"
42 #include "eventdialog.h"
43 #include "daynavigatorwidget.h"
44 #include "settingsdialog.h"
45 #include "conferenceeditor.h"
46 #include "schedulexmlparser.h"
47 #include "errormessage.h"
48
49 #include "tabcontainer.h"
50 #include "appsettings.h"
51
52 const QString PROXY_USERNAME;
53 const QString PROXY_PASSWD;
54
55 MainWindow::MainWindow(QWidget* parent): QMainWindow(parent) {
56     setupUi(this);
57
58     // Open database
59     sqlEngine = new SqlEngine(this);
60     searchTabContainer->setSqlEngine(sqlEngine);
61     connect(sqlEngine, SIGNAL(dbError(QString)), this, SLOT(showError(QString)));
62     sqlEngine->open();
63     sqlEngine->createOrUpdateDbSchema();
64
65     conferenceModel = new ConferenceModel(this);
66     mXmlParser = new ScheduleXmlParser(sqlEngine, this);
67     mNetworkAccessManager = new QNetworkAccessManager(this);
68     systemTrayIcon = new QSystemTrayIcon(qApp->windowIcon(), this);
69     alarmTimer = new QTimer(this);
70
71     alarmTimer->setInterval(60000);
72     alarmTimer->start();
73     saved_title = windowTitle();
74
75 #ifdef N810
76     tabWidget->setTabText(1,"Favs");
77     //tabWidget->setTabText(2,"Day");
78 #endif
79
80     // first time run aplication: -> let's have it direct connection in this case
81     if(!AppSettings::contains("proxyIsDirectConnection"))
82         AppSettings::setDirectConnection(true);
83
84     QNetworkProxy proxy(
85             AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : QNetworkProxy::HttpProxy,
86             AppSettings::proxyAddress(),
87             AppSettings::proxyPort(),
88             PROXY_USERNAME,
89             PROXY_PASSWD);
90     QNetworkProxy::setApplicationProxy(proxy);
91
92     // event details have changed
93     connect(dayTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
94     connect(favsTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
95     connect(tracksTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
96     connect(roomsTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
97     connect(searchTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
98
99     // date has changed
100     connect(dayNavigator, SIGNAL(dateChanged(QDate)), dayTabContainer, SLOT(redisplayDate(QDate)));
101     connect(dayNavigator, SIGNAL(dateChanged(QDate)), favsTabContainer, SLOT(redisplayDate(QDate)));
102     connect(dayNavigator, SIGNAL(dateChanged(QDate)), tracksTabContainer, SLOT(redisplayDate(QDate)));
103     connect(dayNavigator, SIGNAL(dateChanged(QDate)), roomsTabContainer, SLOT(redisplayDate(QDate)));
104     connect(dayNavigator, SIGNAL(dateChanged(QDate)), searchTabContainer, SLOT(redisplayDate(QDate)));
105
106     // search result has changed
107     connect(searchTabContainer, SIGNAL(searchResultChanged()), SLOT(onSearchResultChanged()));
108
109     // systm tray icon
110     connect(systemTrayIcon, SIGNAL(messageClicked()), SLOT(onSystemTrayMessageClicked()));
111     connect(systemTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), SLOT(onSystemTrayMessageClicked()));
112
113     // timer
114     connect(alarmTimer, SIGNAL(timeout()), SLOT(onAlarmTimerTimeout()));
115
116     // add the actions from the main menu to the window, otherwise the shortcuts don't work on MAEMO
117     addAction(conferencesAction);
118     addAction(settingsAction);
119     addAction(quitAction);
120
121     // make it impossible to hide the toolbar by disallowing its context menu
122     toolBar->setContextMenuPolicy(Qt::PreventContextMenu);
123
124     // open conference
125     useConference(Conference::activeConference());
126     // optimization, see useConference() code
127     try {
128         initTabs();
129     } catch (const OrmException& e) {
130         qDebug() << "OrmException:" << e.text();
131         clearTabs();
132     }
133
134     connect(mNetworkAccessManager, SIGNAL(finished(QNetworkReply*)), SLOT(networkQueryFinished(QNetworkReply*)));
135     connect(mXmlParser, SIGNAL(parsingScheduleBegin()), conferenceModel, SLOT(newConferenceBegin()));
136     connect(mXmlParser, SIGNAL(parsingScheduleEnd(int)), conferenceModel, SLOT(newConferenceEnd(int)));
137 }
138
139
140 MainWindow::~MainWindow() {
141     sqlEngine->close();
142 }
143
144
145 void MainWindow::on_aboutAction_triggered()
146 {
147     QDialog dialog(this);
148     Ui::AboutDialog ui;
149     ui.setupUi(&dialog);
150     ui.labDescription->setText(ui.labDescription->text().arg(qApp->applicationVersion()));
151 #ifdef N810
152     dialog.setFixedWidth(width());
153 #endif
154     dialog.exec();
155 }
156
157
158 void MainWindow::on_reloadAction_triggered() {
159     int confId = Conference::activeConference();
160     if (confId== -1) return;
161     Conference active = Conference::getById(confId);
162     if (active.url().isEmpty()) return;
163     importFromNetwork(active.url(), confId);
164     setEnabled(false);
165 }
166
167
168 void MainWindow::on_nowAction_triggered() {
169     int confId = Conference::activeConference();
170     if (confId== -1) return;
171     dayNavigator->setCurDate(QDate::currentDate());
172     dayTabContainer->expandTimeGroup(QTime::currentTime(), confId);
173 }
174
175
176 void MainWindow::on_searchAction_triggered() {
177     if (tabWidget->currentWidget() == searchTab)
178         searchTabContainer->showSearchDialog(!searchTabContainer->searchDialogIsVisible());
179     else {
180         tabWidget->setCurrentWidget(searchTab);
181         searchTabContainer->showSearchDialog();
182     }
183 }
184
185
186 void MainWindow::on_expandAllAction_triggered() {
187     if (tabWidget->currentWidget() == favouritesTab) favsTabContainer->treeView->expandAll();
188     if (tabWidget->currentWidget() == dayViewTab) dayTabContainer->treeView->expandAll();
189     if (tabWidget->currentWidget() == tracksTab) tracksTabContainer->treeView->expandAll();
190     if (tabWidget->currentWidget() == roomsTab) roomsTabContainer->treeView->expandAll();
191     if (tabWidget->currentWidget() == searchTab) searchTabContainer->treeView->expandAll();
192 }
193
194
195 void MainWindow::on_collapseAllAction_triggered() {
196     if (tabWidget->currentWidget() == favouritesTab) favsTabContainer->treeView->collapseAll();
197     if (tabWidget->currentWidget() == dayViewTab) dayTabContainer->treeView->collapseAll();
198     if (tabWidget->currentWidget() == tracksTab) tracksTabContainer->treeView->collapseAll();
199     if (tabWidget->currentWidget() == roomsTab) roomsTabContainer->treeView->collapseAll();
200     if (tabWidget->currentWidget() == searchTab) searchTabContainer->treeView->collapseAll();
201 }
202
203
204 void MainWindow::onEventChanged(int aEventId, bool favouriteChanged) {
205     dayTabContainer->redisplayEvent(aEventId);
206     if (favouriteChanged) favsTabContainer->redisplayDate(dayNavigator->curDate());
207     else favsTabContainer->redisplayEvent(aEventId);
208     tracksTabContainer->redisplayEvent(aEventId);
209     roomsTabContainer->redisplayEvent(aEventId);
210     searchTabContainer->redisplayEvent(aEventId);
211 }
212
213
214 void MainWindow::onSearchResultChanged() {
215     // Are results found on the current date?
216     QDate date = dayNavigator->curDate();
217     int count = searchTabContainer->searchResultCount(date);
218     if (count > 0) {searchTabContainer->redisplayDate(date); return;}
219
220     // Are results found in the future?
221     for (date = date.addDays(1); date <= dayNavigator->endDate(); date = date.addDays(1)) {
222         int count = searchTabContainer->searchResultCount(date);
223         if (count > 0) {dayNavigator->setCurDate(date); return;}
224     }
225
226     // Are results found in the past?
227     for (date = dayNavigator->startDate(); date < dayNavigator->curDate(); date = date.addDays(1)) {
228         int count = searchTabContainer->searchResultCount(date);
229         if (count > 0) {dayNavigator->setCurDate(date); return;}
230     }
231     // No results were found
232     searchTabContainer->redisplayDate(dayNavigator->curDate());
233 }
234
235
236 void MainWindow::onSystemTrayMessageClicked() {
237     systemTrayIcon->hide();
238 }
239
240
241 void MainWindow::onAlarmTimerTimeout() {
242     // determine if an alarm is set on an event that's starting soon
243     QList<Event> events = Event::getImminentAlarmEvents(AppSettings::preEventAlarmSec(), Conference::activeConference());
244     if (events.empty()) return;
245
246     // build a message string
247     Event event;
248     QString title;
249     QString message;
250     if (events.size() == 1) {
251         event = events.first();
252         title = tr("Next event at %1").arg(event.start().toString("HH:mm"));
253         message = tr("\"%1\"\n(%2)").arg(event.title()).arg(event.room()->name());
254     } else {
255         title = tr("%1 upcoming events").arg(events.size());
256         QStringList messages;
257         foreach (event, events) {
258             messages += tr("%1: \"%2\" (%3)").arg(event.start().toString("HH:mm")).arg(event.title()).arg(event.room()->name());
259         }
260         message = messages.join("\n");
261     }
262
263     // and delete the corresponding alarm
264     foreach (event, events) {
265         event.setHasAlarm(false);
266         event.update("alarm");
267         onEventChanged(event.id(), false);
268     }
269
270     // show message
271     systemTrayIcon->show();
272     // The next two lines are to prevent a very strange position of the message box the first time at X11/aweseome (not Win32/XP)
273     systemTrayIcon->showMessage("ConfClerk", "Your upcoming events", QSystemTrayIcon::Information);
274     qApp->processEvents();
275     systemTrayIcon->showMessage(title, message, QSystemTrayIcon::Information, 60*60*24*1000);
276     QApplication::alert(this);
277     QApplication::beep();
278 }
279
280
281 void MainWindow::useConference(int conferenceId)
282 {
283     if (conferenceId == -1)  // in case no conference is active
284     {
285         unsetConference();
286         return;
287     }
288     try {
289         int oldActiveConferenceId = Conference::activeConference();
290         bool switchActiveConference = conferenceId != oldActiveConferenceId;
291         if (switchActiveConference) Conference::getById(oldActiveConferenceId).update("active", 0);
292         Conference activeConference = Conference::getById(conferenceId);
293         if (switchActiveConference) activeConference.update("active",1);
294
295         // looks like it does not work at n900
296         setWindowTitle(activeConference.title());
297
298         // optimization.
299         // dont run initTabs() here
300         // it takes much CPU, making travelling between conferences in ConferenceEditor longer
301         // and is not seen in maemo WM anyway
302         // instead run it explicitly
303         // 1. at startup
304         // 2. when ConferenceEditor finished
305         // dont forget to protect the calls by try-catch!
306
307         // just in case, clear conference selection instead
308         clearTabs();
309
310         // end of optimization
311         // initTabs();
312     } catch (OrmException& e) {
313         // cannon set an active conference
314         unsetConference();   // TODO: as no active conference is now correctly managed this should be handled as a fatal error
315         return;
316     }
317
318 }
319
320 void MainWindow::initTabs()
321 {
322     int confId = Conference::activeConference();
323     if (confId != -1)   // only init tabs if a conference is active
324     {
325         Conference active = Conference::getById(confId);
326         QDate startDate = active.start();
327         QDate endDate = active.end();
328
329         // 'dayNavigator' emits signal 'dateChanged' after setting valid START:END dates
330         dayNavigator->setDates(startDate, endDate);
331         nowAction->trigger();
332     }
333 }
334
335 void MainWindow::clearTabs()
336 {
337     dayTabContainer->clearModel();
338     tracksTabContainer->clearModel();
339     roomsTabContainer->clearModel();
340     favsTabContainer->clearModel();
341     searchTabContainer->clearModel();
342 }
343
344 void MainWindow::unsetConference()
345 {
346     clearTabs();
347     dayNavigator->unsetDates();
348     setWindowTitle(saved_title);
349 }
350
351
352 void MainWindow::showError(const QString& message) {
353     error_message(message);
354 }
355
356
357 void MainWindow::on_settingsAction_triggered()
358 {
359     SettingsDialog dialog;
360     dialog.loadDialogData();
361     if (dialog.exec() == QDialog::Accepted) {
362         dialog.saveDialogData();
363         QNetworkProxy proxy(
364                 AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : QNetworkProxy::HttpProxy,
365                 AppSettings::proxyAddress(),
366                 AppSettings::proxyPort(),
367                 PROXY_USERNAME,
368                 PROXY_PASSWD);
369         QNetworkProxy::setApplicationProxy(proxy);
370     }
371 }
372
373 /** Create and run ConferenceEditor dialog, making required connections for it.
374
375 This method manages, which classes actually perform changes in conference list.
376
377 There are several classes that modify the conferences:
378 this:
379  deletion and URL update.
380 this, mXmlParser and mNetworkAccessManager:
381  addition and refresh.
382 */
383 void MainWindow::on_conferencesAction_triggered()
384 {
385     ConferenceEditor dialog(conferenceModel, this);
386
387     connect(&dialog, SIGNAL(haveConferenceUrl(const QString&, int)), SLOT(importFromNetwork(const QString&, int)));
388     connect(&dialog, SIGNAL(haveConferenceFile(const QString&, int)), SLOT(importFromFile(const QString&, int)));
389     connect(&dialog, SIGNAL(removeConferenceRequested(int)), SLOT(removeConference(int)));
390     connect(&dialog, SIGNAL(changeUrlRequested(int, const QString&)),
391                     SLOT(changeConferenceUrl(int, const QString&)));
392
393     connect(&dialog, SIGNAL(haveConferenceSelected(int)), SLOT(useConference(int)));
394     connect(&dialog, SIGNAL(noneConferenceSelected()), SLOT(unsetConference()));
395
396     connect(mXmlParser, SIGNAL(parsingScheduleBegin()), &dialog, SLOT(importStarted()));
397     connect(mXmlParser, SIGNAL(progressStatus(int)), &dialog, SLOT(showParsingProgress(int)));
398     connect(mXmlParser, SIGNAL(parsingScheduleEnd(int)), &dialog, SLOT(importFinished(int)));
399
400     connect(this, SIGNAL(conferenceRemoved()), &dialog, SLOT(conferenceRemoved()));
401
402     dialog.exec();
403
404     // optimization, see useConference() code
405     try {
406         initTabs();
407     } catch (OrmException) {
408         clearTabs();
409     }
410 }
411
412 void MainWindow::networkQueryFinished(QNetworkReply *aReply) {
413     if (aReply->error() != QNetworkReply::NoError) {
414         error_message(QString("Error occurred during download: ") + aReply->errorString());
415     } else {
416         QUrl redirectUrl = aReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
417         if (!redirectUrl.isEmpty()) {
418             if (redirectUrl != aReply->request().url()) {
419                 importFromNetwork(redirectUrl.toString(), aReply->request().attribute(QNetworkRequest::User).toInt());
420                 return; // don't enable controls
421             } else {
422                 error_message(QString("Error: Cyclic redirection from %1 to itself.").arg(redirectUrl.toString()));
423             }
424         } else {
425             importData(aReply->readAll(), aReply->url().toEncoded(), aReply->request().attribute(QNetworkRequest::User).toInt());
426         }
427     }
428     setEnabled(true);
429 }
430
431 void MainWindow::importData(const QByteArray &aData, const QString& url, int conferenceId)
432 {
433     mXmlParser->parseData(aData, url, conferenceId);
434 }
435
436 void MainWindow::importFromNetwork(const QString& url, int conferenceId)
437 {
438     QNetworkRequest request;
439     QSslConfiguration qSslConfiguration = request.sslConfiguration();
440     qSslConfiguration.setProtocol(QSsl::AnyProtocol);
441     qSslConfiguration.setPeerVerifyMode(QSslSocket::QueryPeer);
442     request.setUrl(QUrl(url));
443     request.setSslConfiguration(qSslConfiguration);
444     request.setAttribute(QNetworkRequest::User, conferenceId);
445
446     mNetworkAccessManager->setProxy(QNetworkProxy::applicationProxy());
447     mNetworkAccessManager->get(request);
448 }
449
450 void MainWindow::importFromFile(const QString& filename, int conferenceId)
451 {
452     QFile file(filename);
453     if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {    
454         static const QString format("Cannot read \"%1\": error %2");
455         error_message(format.arg(filename, QString::number(file.error())));
456     }
457
458     importData(file.readAll(), "", conferenceId);
459 }
460
461
462 void MainWindow::removeConference(int id) {
463     sqlEngine->deleteConference(id);
464     conferenceModel->conferenceRemoved();
465     emit conferenceRemoved();
466 }
467
468
469 void MainWindow::changeConferenceUrl(int id, const QString& url) {
470     Conference::getById(id).setUrl(url);
471 }
472