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