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