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