extend comment re systrayicon position
[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     // systemTrayIcon->hide();
222 }
223
224
225 void MainWindow::onAlarmTimerTimeout() {
226     // TODO: Show Message if an alarm is set on an event that's starting soon and delete the corresponding alarm.
227     /*
228     systemTrayIcon->show();
229     qDebug() << systemTrayIcon->geometry(); // gets moved to the correct location only at second usage. at least under X11/awesome, not on Win32/XP
230     systemTrayIcon->showMessage("title", "message", QSystemTrayIcon::Information, 1000);
231     QApplication::alert(this);
232     QApplication::beep();
233     */
234 }
235
236
237 void MainWindow::useConference(int conferenceId)
238 {
239     if (conferenceId == -1)  // in case no conference is active
240     {
241         unsetConference();
242         return;
243     }
244     try {
245         int oldActiveConferenceId = Conference::activeConference();
246         bool switchActiveConference = conferenceId != oldActiveConferenceId;
247         if (switchActiveConference) Conference::getById(oldActiveConferenceId).update("active", 0);
248         Conference activeConference = Conference::getById(conferenceId);
249         if (switchActiveConference) activeConference.update("active",1);
250
251         // looks like it does not work at n900
252         setWindowTitle(activeConference.title());
253
254         // optimization.
255         // dont run initTabs() here
256         // it takes much CPU, making travelling between conferences in ConferenceEditor longer
257         // and is not seen in maemo WM anyway
258         // instead run it explicitly
259         // 1. at startup
260         // 2. when ConferenceEditor finished
261         // dont forget to protect the calls by try-catch!
262
263         // just in case, clear conference selection instead
264         clearTabs();
265
266         // end of optimization
267         // initTabs();
268     } catch (OrmException& e) {
269         // cannon set an active conference
270         unsetConference();   // TODO: as no active conference is now correctly managed this should be handled as a fatal error
271         return;
272     }
273
274 }
275
276 void MainWindow::initTabs()
277 {
278     int confId = Conference::activeConference();
279     if (confId != -1)   // only init tabs if a conference is active
280     {
281         Conference active = Conference::getById(confId);
282         QDate startDate = active.start();
283         QDate endDate = active.end();
284
285         // 'dayNavigator' emits signal 'dateChanged' after setting valid START:END dates
286         dayNavigator->setDates(startDate, endDate);
287         nowAction->trigger();
288     }
289 }
290
291 void MainWindow::clearTabs()
292 {
293     dayTabContainer->clearModel();
294     tracksTabContainer->clearModel();
295     roomsTabContainer->clearModel();
296     favsTabContainer->clearModel();
297     searchTabContainer->clearModel();
298 }
299
300 void MainWindow::unsetConference()
301 {
302     clearTabs();
303     dayNavigator->unsetDates();
304     setWindowTitle(saved_title);
305 }
306
307
308 void MainWindow::showError(const QString& message) {
309     error_message(message);
310 }
311
312
313 void MainWindow::on_settingsAction_triggered()
314 {
315     SettingsDialog dialog;
316     dialog.loadDialogData();
317     if (dialog.exec() == QDialog::Accepted) {
318         dialog.saveDialogData();
319         QNetworkProxy proxy(
320                 AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : QNetworkProxy::HttpProxy,
321                 AppSettings::proxyAddress(),
322                 AppSettings::proxyPort(),
323                 PROXY_USERNAME,
324                 PROXY_PASSWD);
325         QNetworkProxy::setApplicationProxy(proxy);
326     }
327 }
328
329 /** Create and run ConferenceEditor dialog, making required connections for it.
330
331 This method manages, which classes actually perform changes in conference list.
332
333 There are several classes that modify the conferences:
334 this:
335  deletion and URL update.
336 this, mXmlParser and mNetworkAccessManager:
337  addition and refresh.
338 */
339 void MainWindow::on_conferencesAction_triggered()
340 {
341     ConferenceEditor dialog(conferenceModel, this);
342
343     connect(&dialog, SIGNAL(haveConferenceUrl(const QString&, int)), SLOT(importFromNetwork(const QString&, int)));
344     connect(&dialog, SIGNAL(haveConferenceFile(const QString&, int)), SLOT(importFromFile(const QString&, int)));
345     connect(&dialog, SIGNAL(removeConferenceRequested(int)), SLOT(removeConference(int)));
346     connect(&dialog, SIGNAL(changeUrlRequested(int, const QString&)),
347                     SLOT(changeConferenceUrl(int, const QString&)));
348
349     connect(&dialog, SIGNAL(haveConferenceSelected(int)), SLOT(useConference(int)));
350     connect(&dialog, SIGNAL(noneConferenceSelected()), SLOT(unsetConference()));
351
352     connect(mXmlParser, SIGNAL(parsingScheduleBegin()), &dialog, SLOT(importStarted()));
353     connect(mXmlParser, SIGNAL(progressStatus(int)), &dialog, SLOT(showParsingProgress(int)));
354     connect(mXmlParser, SIGNAL(parsingScheduleEnd(int)), &dialog, SLOT(importFinished(int)));
355
356     connect(this, SIGNAL(conferenceRemoved()), &dialog, SLOT(conferenceRemoved()));
357
358     dialog.exec();
359
360     // optimization, see useConference() code
361     try {
362         initTabs();
363     } catch (OrmException) {
364         clearTabs();
365     }
366 }
367
368 void MainWindow::networkQueryFinished(QNetworkReply *aReply) {
369     if (aReply->error() != QNetworkReply::NoError) {
370         error_message(QString("Error occured during download: ") + aReply->errorString());
371     } else {
372         QUrl redirectUrl = aReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
373         if (!redirectUrl.isEmpty()) {
374             if (redirectUrl != aReply->request().url()) {
375                 importFromNetwork(redirectUrl.toString(), aReply->request().attribute(QNetworkRequest::User).toInt());
376                 return; // don't enable controls
377             } else {
378                 error_message(QString("Error: Cyclic redirection from %1 to itself.").arg(redirectUrl.toString()));
379             }
380         } else {
381             importData(aReply->readAll(), aReply->url().toEncoded(), aReply->request().attribute(QNetworkRequest::User).toInt());
382         }
383     }
384     setEnabled(true);
385 }
386
387 void MainWindow::importData(const QByteArray &aData, const QString& url, int conferenceId)
388 {
389     mXmlParser->parseData(aData, url, conferenceId);
390 }
391
392 void MainWindow::importFromNetwork(const QString& url, int conferenceId)
393 {
394     QNetworkRequest request;
395     request.setUrl(QUrl(url));
396     request.setAttribute(QNetworkRequest::User, conferenceId);
397
398     mNetworkAccessManager->setProxy(QNetworkProxy::applicationProxy());
399     mNetworkAccessManager->get(request);
400 }
401
402 void MainWindow::importFromFile(const QString& filename, int conferenceId)
403 {
404     QFile file(filename);
405     if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {    
406         static const QString format("Cannot read \"%1\": error %2");
407         error_message(format.arg(filename, QString::number(file.error())));
408     }
409
410     importData(file.readAll(), "", conferenceId);
411 }
412
413
414 void MainWindow::removeConference(int id) {
415     sqlEngine->deleteConference(id);
416     conferenceModel->conferenceRemoved();
417     emit conferenceRemoved();
418 }
419
420
421 void MainWindow::changeConferenceUrl(int id, const QString& url) {
422     Conference::getById(id).setUrl(url);
423 }
424