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);
120 // make it impossible to hide the toolbar by disallowing its context menu
121 toolBar->setContextMenuPolicy(Qt::PreventContextMenu);
124 useConference(Conference::activeConference());
125 // optimization, see useConference() code
128 } catch (const OrmException& e) {
129 qDebug() << "OrmException:" << e.text();
133 connect(mNetworkAccessManager, SIGNAL(finished(QNetworkReply*)), SLOT(networkQueryFinished(QNetworkReply*)));
134 connect(mXmlParser, SIGNAL(parsingScheduleBegin()), conferenceModel, SLOT(newConferenceBegin()));
135 connect(mXmlParser, SIGNAL(parsingScheduleEnd(int)), conferenceModel, SLOT(newConferenceEnd(int)));
139 MainWindow::~MainWindow() {
144 void MainWindow::on_aboutAction_triggered()
146 QDialog dialog(this);
149 ui.labDescription->setText(ui.labDescription->text().arg(qApp->applicationVersion()));
151 dialog.setFixedWidth(width());
157 void MainWindow::on_reloadAction_triggered() {
158 int confId = Conference::activeConference();
159 if (confId== -1) return;
160 Conference active = Conference::getById(confId);
161 if (active.url().isEmpty()) return;
162 importFromNetwork(active.url(), confId);
167 void MainWindow::on_nowAction_triggered() {
168 int confId = Conference::activeConference();
169 if (confId== -1) return;
170 dayNavigator->setCurDate(QDate::currentDate());
171 dayTabContainer->expandTimeGroup(QTime::currentTime(), confId);
175 void MainWindow::on_searchAction_triggered() {
176 if (tabWidget->currentWidget() == searchTab)
177 searchTabContainer->showSearchDialog(!searchTabContainer->searchDialogIsVisible());
179 tabWidget->setCurrentWidget(searchTab);
180 searchTabContainer->showSearchDialog();
185 void MainWindow::on_expandAllAction_triggered() {
186 if (tabWidget->currentWidget() == favouritesTab) favsTabContainer->treeView->expandAll();
187 if (tabWidget->currentWidget() == dayViewTab) dayTabContainer->treeView->expandAll();
188 if (tabWidget->currentWidget() == tracksTab) tracksTabContainer->treeView->expandAll();
189 if (tabWidget->currentWidget() == roomsTab) roomsTabContainer->treeView->expandAll();
190 if (tabWidget->currentWidget() == searchTab) searchTabContainer->treeView->expandAll();
194 void MainWindow::on_collapseAllAction_triggered() {
195 if (tabWidget->currentWidget() == favouritesTab) favsTabContainer->treeView->collapseAll();
196 if (tabWidget->currentWidget() == dayViewTab) dayTabContainer->treeView->collapseAll();
197 if (tabWidget->currentWidget() == tracksTab) tracksTabContainer->treeView->collapseAll();
198 if (tabWidget->currentWidget() == roomsTab) roomsTabContainer->treeView->collapseAll();
199 if (tabWidget->currentWidget() == searchTab) searchTabContainer->treeView->collapseAll();
203 void MainWindow::onEventChanged(int aEventId, bool favouriteChanged) {
204 dayTabContainer->redisplayEvent(aEventId);
205 if (favouriteChanged) favsTabContainer->redisplayDate(dayNavigator->curDate());
206 else favsTabContainer->redisplayEvent(aEventId);
207 tracksTabContainer->redisplayEvent(aEventId);
208 roomsTabContainer->redisplayEvent(aEventId);
209 searchTabContainer->redisplayEvent(aEventId);
213 void MainWindow::onSearchResultChanged() {
214 // Are results found on the current date?
215 QDate date = dayNavigator->curDate();
216 int count = searchTabContainer->searchResultCount(date);
217 if (count > 0) {searchTabContainer->redisplayDate(date); return;}
219 // Are results found in the future?
220 for (date = date.addDays(1); date <= dayNavigator->endDate(); date = date.addDays(1)) {
221 int count = searchTabContainer->searchResultCount(date);
222 if (count > 0) {dayNavigator->setCurDate(date); return;}
225 // Are results found in the past?
226 for (date = dayNavigator->startDate(); date < dayNavigator->curDate(); date = date.addDays(1)) {
227 int count = searchTabContainer->searchResultCount(date);
228 if (count > 0) {dayNavigator->setCurDate(date); return;}
230 // No results were found
231 searchTabContainer->redisplayDate(dayNavigator->curDate());
235 void MainWindow::onSystemTrayMessageClicked() {
236 systemTrayIcon->hide();
240 void MainWindow::onAlarmTimerTimeout() {
241 // determine if an alarm is set on an event that's starting soon
242 QList<Event> events = Event::getImminentAlarmEvents(AppSettings::preEventAlarmSec(), Conference::activeConference());
243 if (events.empty()) return;
245 // build a message string
249 if (events.size() == 1) {
250 event = events.first();
251 title = tr("Next event at %1").arg(event.start().toString("HH:mm"));
252 message = tr("\"%1\"\n(%2)").arg(event.title()).arg(event.room()->name());
254 title = tr("%1 upcoming events").arg(events.size());
255 QStringList messages;
256 foreach (event, events) {
257 messages += tr("%1: \"%2\" (%3)").arg(event.start().toString("HH:mm")).arg(event.title()).arg(event.room()->name());
259 message = messages.join("\n");
262 // and delete the corresponding alarm
263 foreach (event, events) {
264 event.setHasAlarm(false);
265 event.update("alarm");
266 onEventChanged(event.id(), false);
270 systemTrayIcon->show();
271 // The next two lines are to prevent a very strange position of the message box the first time at X11/aweseome (not Win32/XP)
272 systemTrayIcon->showMessage("ConfClerk", "Your upcoming events", QSystemTrayIcon::Information);
273 qApp->processEvents();
274 systemTrayIcon->showMessage(title, message, QSystemTrayIcon::Information, 60*60*24*1000);
275 QApplication::alert(this);
276 QApplication::beep();
280 void MainWindow::useConference(int conferenceId)
282 if (conferenceId == -1) // in case no conference is active
288 int oldActiveConferenceId = Conference::activeConference();
289 bool switchActiveConference = conferenceId != oldActiveConferenceId;
290 if (switchActiveConference) Conference::getById(oldActiveConferenceId).update("active", 0);
291 Conference activeConference = Conference::getById(conferenceId);
292 if (switchActiveConference) activeConference.update("active",1);
294 // looks like it does not work at n900
295 setWindowTitle(activeConference.title());
298 // dont run initTabs() here
299 // it takes much CPU, making travelling between conferences in ConferenceEditor longer
300 // and is not seen in maemo WM anyway
301 // instead run it explicitly
303 // 2. when ConferenceEditor finished
304 // dont forget to protect the calls by try-catch!
306 // just in case, clear conference selection instead
309 // end of optimization
311 } catch (OrmException& e) {
312 // cannon set an active conference
313 unsetConference(); // TODO: as no active conference is now correctly managed this should be handled as a fatal error
319 void MainWindow::initTabs()
321 int confId = Conference::activeConference();
322 if (confId != -1) // only init tabs if a conference is active
324 Conference active = Conference::getById(confId);
325 QDate startDate = active.start();
326 QDate endDate = active.end();
328 // 'dayNavigator' emits signal 'dateChanged' after setting valid START:END dates
329 dayNavigator->setDates(startDate, endDate);
330 nowAction->trigger();
334 void MainWindow::clearTabs()
336 dayTabContainer->clearModel();
337 tracksTabContainer->clearModel();
338 roomsTabContainer->clearModel();
339 favsTabContainer->clearModel();
340 searchTabContainer->clearModel();
343 void MainWindow::unsetConference()
346 dayNavigator->unsetDates();
347 setWindowTitle(saved_title);
351 void MainWindow::showError(const QString& message) {
352 error_message(message);
356 void MainWindow::on_settingsAction_triggered()
358 SettingsDialog dialog;
359 dialog.loadDialogData();
360 if (dialog.exec() == QDialog::Accepted) {
361 dialog.saveDialogData();
363 AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : QNetworkProxy::HttpProxy,
364 AppSettings::proxyAddress(),
365 AppSettings::proxyPort(),
368 QNetworkProxy::setApplicationProxy(proxy);
372 /** Create and run ConferenceEditor dialog, making required connections for it.
374 This method manages, which classes actually perform changes in conference list.
376 There are several classes that modify the conferences:
378 deletion and URL update.
379 this, mXmlParser and mNetworkAccessManager:
380 addition and refresh.
382 void MainWindow::on_conferencesAction_triggered()
384 ConferenceEditor dialog(conferenceModel, this);
386 connect(&dialog, SIGNAL(haveConferenceUrl(const QString&, int)), SLOT(importFromNetwork(const QString&, int)));
387 connect(&dialog, SIGNAL(haveConferenceFile(const QString&, int)), SLOT(importFromFile(const QString&, int)));
388 connect(&dialog, SIGNAL(removeConferenceRequested(int)), SLOT(removeConference(int)));
389 connect(&dialog, SIGNAL(changeUrlRequested(int, const QString&)),
390 SLOT(changeConferenceUrl(int, const QString&)));
392 connect(&dialog, SIGNAL(haveConferenceSelected(int)), SLOT(useConference(int)));
393 connect(&dialog, SIGNAL(noneConferenceSelected()), SLOT(unsetConference()));
395 connect(mXmlParser, SIGNAL(parsingScheduleBegin()), &dialog, SLOT(importStarted()));
396 connect(mXmlParser, SIGNAL(progressStatus(int)), &dialog, SLOT(showParsingProgress(int)));
397 connect(mXmlParser, SIGNAL(parsingScheduleEnd(int)), &dialog, SLOT(importFinished(int)));
399 connect(this, SIGNAL(conferenceRemoved()), &dialog, SLOT(conferenceRemoved()));
403 // optimization, see useConference() code
406 } catch (OrmException) {
411 void MainWindow::networkQueryFinished(QNetworkReply *aReply) {
412 if (aReply->error() != QNetworkReply::NoError) {
413 error_message(QString("Error occured during download: ") + aReply->errorString());
415 QUrl redirectUrl = aReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
416 if (!redirectUrl.isEmpty()) {
417 if (redirectUrl != aReply->request().url()) {
418 importFromNetwork(redirectUrl.toString(), aReply->request().attribute(QNetworkRequest::User).toInt());
419 return; // don't enable controls
421 error_message(QString("Error: Cyclic redirection from %1 to itself.").arg(redirectUrl.toString()));
424 importData(aReply->readAll(), aReply->url().toEncoded(), aReply->request().attribute(QNetworkRequest::User).toInt());
430 void MainWindow::importData(const QByteArray &aData, const QString& url, int conferenceId)
432 mXmlParser->parseData(aData, url, conferenceId);
435 void MainWindow::importFromNetwork(const QString& url, int conferenceId)
437 QNetworkRequest request;
438 request.setUrl(QUrl(url));
439 request.setAttribute(QNetworkRequest::User, conferenceId);
441 mNetworkAccessManager->setProxy(QNetworkProxy::applicationProxy());
442 mNetworkAccessManager->get(request);
445 void MainWindow::importFromFile(const QString& filename, int conferenceId)
447 QFile file(filename);
448 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
449 static const QString format("Cannot read \"%1\": error %2");
450 error_message(format.arg(filename, QString::number(file.error())));
453 importData(file.readAll(), "", conferenceId);
457 void MainWindow::removeConference(int id) {
458 sqlEngine->deleteConference(id);
459 conferenceModel->conferenceRemoved();
460 emit conferenceRemoved();
464 void MainWindow::changeConferenceUrl(int id, const QString& url) {
465 Conference::getById(id).setUrl(url);