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