2 * Copyright (C) 2010 Ixonos Plc.
3 * Copyright (C) 2011-2013 Philipp Spitzer, gregor herrmann, Stefan Stahl
5 * This file is part of ConfClerk.
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)
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
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/>.
20 #include "mainwindow.h"
24 #include <QNetworkProxy>
25 #include <QNetworkAccessManager>
26 #include <QNetworkReply>
28 #include "sqlengine.h"
31 #include "eventmodel.h"
35 #include "conference.h"
38 #include <QMessageBox>
41 #include "eventdialog.h"
42 #include "daynavigatorwidget.h"
43 #include "settingsdialog.h"
44 #include "conferenceeditor.h"
45 #include "schedulexmlparser.h"
46 #include "errormessage.h"
48 #include "tabcontainer.h"
49 #include "appsettings.h"
51 const QString PROXY_USERNAME;
52 const QString PROXY_PASSWD;
54 MainWindow::MainWindow(QWidget* parent): QMainWindow(parent) {
58 sqlEngine = new SqlEngine(this);
59 searchTabContainer->setSqlEngine(sqlEngine);
60 connect(sqlEngine, SIGNAL(dbError(QString)), this, SLOT(showError(QString)));
62 sqlEngine->createOrUpdateDbSchema();
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);
70 alarmTimer->setInterval(60000);
72 saved_title = windowTitle();
75 tabWidget->setTabText(1,"Favs");
76 //tabWidget->setTabText(2,"Day");
79 // first time run aplication: -> let's have it direct connection in this case
80 if(!AppSettings::contains("proxyIsDirectConnection"))
81 AppSettings::setDirectConnection(true);
84 AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : QNetworkProxy::HttpProxy,
85 AppSettings::proxyAddress(),
86 AppSettings::proxyPort(),
89 QNetworkProxy::setApplicationProxy(proxy);
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)));
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)));
105 // search result has changed
106 connect(searchTabContainer, SIGNAL(searchResultChanged()), SLOT(onSearchResultChanged()));
109 connect(systemTrayIcon, SIGNAL(messageClicked()), SLOT(onSystemTrayMessageClicked()));
110 connect(systemTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), SLOT(onSystemTrayMessageClicked()));
113 connect(alarmTimer, SIGNAL(timeout()), SLOT(onAlarmTimerTimeout()));
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);
121 useConference(Conference::activeConference());
122 // optimization, see useConference() code
125 } catch (const OrmException& e) {
126 qDebug() << "OrmException:" << e.text();
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)));
136 MainWindow::~MainWindow() {
141 void MainWindow::on_aboutAction_triggered()
143 QDialog dialog(this);
146 ui.labDescription->setText(ui.labDescription->text().arg(qApp->applicationVersion()));
148 dialog.setFixedWidth(width());
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);
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);
172 void MainWindow::on_searchAction_triggered() {
173 if (tabWidget->currentWidget() == searchTab)
174 searchTabContainer->showSearchDialog(!searchTabContainer->searchDialogIsVisible());
176 tabWidget->setCurrentWidget(searchTab);
177 searchTabContainer->showSearchDialog();
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();
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();
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);
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;}
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;}
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;}
227 // No results were found
228 searchTabContainer->redisplayDate(dayNavigator->curDate());
232 void MainWindow::onSystemTrayMessageClicked() {
233 systemTrayIcon->hide();
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;
242 // build a message string
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());
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());
256 message = messages.join("\n");
259 // and delete the corresponding alarm
260 foreach (event, events) {
261 event.setHasAlarm(false);
262 event.update("alarm");
263 onEventChanged(event.id(), false);
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();
277 void MainWindow::useConference(int conferenceId)
279 if (conferenceId == -1) // in case no conference is active
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);
291 // looks like it does not work at n900
292 setWindowTitle(activeConference.title());
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
300 // 2. when ConferenceEditor finished
301 // dont forget to protect the calls by try-catch!
303 // just in case, clear conference selection instead
306 // end of optimization
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
316 void MainWindow::initTabs()
318 int confId = Conference::activeConference();
319 if (confId != -1) // only init tabs if a conference is active
321 Conference active = Conference::getById(confId);
322 QDate startDate = active.start();
323 QDate endDate = active.end();
325 // 'dayNavigator' emits signal 'dateChanged' after setting valid START:END dates
326 dayNavigator->setDates(startDate, endDate);
327 nowAction->trigger();
331 void MainWindow::clearTabs()
333 dayTabContainer->clearModel();
334 tracksTabContainer->clearModel();
335 roomsTabContainer->clearModel();
336 favsTabContainer->clearModel();
337 searchTabContainer->clearModel();
340 void MainWindow::unsetConference()
343 dayNavigator->unsetDates();
344 setWindowTitle(saved_title);
348 void MainWindow::showError(const QString& message) {
349 error_message(message);
353 void MainWindow::on_settingsAction_triggered()
355 SettingsDialog dialog;
356 dialog.loadDialogData();
357 if (dialog.exec() == QDialog::Accepted) {
358 dialog.saveDialogData();
360 AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : QNetworkProxy::HttpProxy,
361 AppSettings::proxyAddress(),
362 AppSettings::proxyPort(),
365 QNetworkProxy::setApplicationProxy(proxy);
369 /** Create and run ConferenceEditor dialog, making required connections for it.
371 This method manages, which classes actually perform changes in conference list.
373 There are several classes that modify the conferences:
375 deletion and URL update.
376 this, mXmlParser and mNetworkAccessManager:
377 addition and refresh.
379 void MainWindow::on_conferencesAction_triggered()
381 ConferenceEditor dialog(conferenceModel, this);
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&)));
389 connect(&dialog, SIGNAL(haveConferenceSelected(int)), SLOT(useConference(int)));
390 connect(&dialog, SIGNAL(noneConferenceSelected()), SLOT(unsetConference()));
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)));
396 connect(this, SIGNAL(conferenceRemoved()), &dialog, SLOT(conferenceRemoved()));
400 // optimization, see useConference() code
403 } catch (OrmException) {
408 void MainWindow::networkQueryFinished(QNetworkReply *aReply) {
409 if (aReply->error() != QNetworkReply::NoError) {
410 error_message(QString("Error occured during download: ") + aReply->errorString());
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
418 error_message(QString("Error: Cyclic redirection from %1 to itself.").arg(redirectUrl.toString()));
421 importData(aReply->readAll(), aReply->url().toEncoded(), aReply->request().attribute(QNetworkRequest::User).toInt());
427 void MainWindow::importData(const QByteArray &aData, const QString& url, int conferenceId)
429 mXmlParser->parseData(aData, url, conferenceId);
432 void MainWindow::importFromNetwork(const QString& url, int conferenceId)
434 QNetworkRequest request;
435 request.setUrl(QUrl(url));
436 request.setAttribute(QNetworkRequest::User, conferenceId);
438 mNetworkAccessManager->setProxy(QNetworkProxy::applicationProxy());
439 mNetworkAccessManager->get(request);
442 void MainWindow::importFromFile(const QString& filename, int conferenceId)
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())));
450 importData(file.readAll(), "", conferenceId);
454 void MainWindow::removeConference(int id) {
455 sqlEngine->deleteConference(id);
456 conferenceModel->conferenceRemoved();
457 emit conferenceRemoved();
461 void MainWindow::changeConferenceUrl(int id, const QString& url) {
462 Conference::getById(id).setUrl(url);