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