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