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 useConference(Conference::activeConference());
116 // optimization, see useConference() code
119 } catch (const OrmException& e) {
120 qDebug() << "OrmException:" << e.text();
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)));
130 void MainWindow::on_aboutAction_triggered()
132 QDialog dialog(this);
135 ui.labDescription->setText(ui.labDescription->text().arg(qApp->applicationVersion()));
137 dialog.setFixedWidth(width());
143 void MainWindow::on_reloadAction_triggered() {
144 int confId = Conference::activeConference();
145 if (confId== -1) return;
146 Conference active = Conference::getById(confId);
147 if (active.url().isEmpty()) return;
148 importFromNetwork(active.url(), confId);
153 void MainWindow::on_nowAction_triggered() {
154 int confId = Conference::activeConference();
155 if (confId== -1) return;
156 dayNavigator->setCurDate(QDate::currentDate());
157 dayTabContainer->expandTimeGroup(QTime::currentTime(), confId);
161 void MainWindow::on_searchAction_triggered() {
162 if (tabWidget->currentWidget() == searchTab)
163 searchTabContainer->showSearchDialog(!searchTabContainer->searchDialogIsVisible());
165 tabWidget->setCurrentWidget(searchTab);
166 searchTabContainer->showSearchDialog();
171 void MainWindow::on_expandAllAction_triggered() {
172 if (tabWidget->currentWidget() == favouritesTab) favsTabContainer->treeView->expandAll();
173 if (tabWidget->currentWidget() == dayViewTab) dayTabContainer->treeView->expandAll();
174 if (tabWidget->currentWidget() == tracksTab) tracksTabContainer->treeView->expandAll();
175 if (tabWidget->currentWidget() == roomsTab) roomsTabContainer->treeView->expandAll();
176 if (tabWidget->currentWidget() == searchTab) searchTabContainer->treeView->expandAll();
180 void MainWindow::on_collapseAllAction_triggered() {
181 if (tabWidget->currentWidget() == favouritesTab) favsTabContainer->treeView->collapseAll();
182 if (tabWidget->currentWidget() == dayViewTab) dayTabContainer->treeView->collapseAll();
183 if (tabWidget->currentWidget() == tracksTab) tracksTabContainer->treeView->collapseAll();
184 if (tabWidget->currentWidget() == roomsTab) roomsTabContainer->treeView->collapseAll();
185 if (tabWidget->currentWidget() == searchTab) searchTabContainer->treeView->collapseAll();
189 void MainWindow::onEventChanged(int aEventId, bool favouriteChanged) {
190 dayTabContainer->redisplayEvent(aEventId);
191 if (favouriteChanged) favsTabContainer->redisplayDate(dayNavigator->curDate());
192 else favsTabContainer->redisplayEvent(aEventId);
193 tracksTabContainer->redisplayEvent(aEventId);
194 roomsTabContainer->redisplayEvent(aEventId);
195 searchTabContainer->redisplayEvent(aEventId);
199 void MainWindow::onSearchResultChanged() {
200 // Are results found on the current date?
201 QDate date = dayNavigator->curDate();
202 int count = searchTabContainer->searchResultCount(date);
203 if (count > 0) {searchTabContainer->redisplayDate(date); return;}
205 // Are results found in the future?
206 for (date = date.addDays(1); date <= dayNavigator->endDate(); date = date.addDays(1)) {
207 int count = searchTabContainer->searchResultCount(date);
208 if (count > 0) {dayNavigator->setCurDate(date); return;}
211 // Are results found in the past?
212 for (date = dayNavigator->startDate(); date < dayNavigator->curDate(); date = date.addDays(1)) {
213 int count = searchTabContainer->searchResultCount(date);
214 if (count > 0) {dayNavigator->setCurDate(date); return;}
216 // No results were found
217 searchTabContainer->redisplayDate(dayNavigator->curDate());
221 void MainWindow::onSystemTrayMessageClicked() {
222 systemTrayIcon->hide();
226 void MainWindow::onAlarmTimerTimeout() {
227 // determine if an alarm is set on an event that's starting soon
228 QList<Event> events = Event::getImminentAlarmEvents(AppSettings::preEventAlarmSec(), Conference::activeConference());
229 if (events.empty()) return;
231 // build a message string
235 if (events.size() == 1) {
236 event = events.first();
237 title = tr("Next event at %1").arg(event.start().toString("HH:mm"));
238 message = tr("\"%1\"\n(%2)").arg(event.title()).arg(event.room()->name());
240 title = tr("%1 upcoming events").arg(events.size());
241 QStringList messages;
242 foreach (event, events) {
243 messages += tr("%1: \"%2\" (%3)").arg(event.start().toString("HH:mm")).arg(event.title()).arg(event.room()->name());
245 message = messages.join("\n");
248 // and delete the corresponding alarm
249 foreach (event, events) {
250 event.setHasAlarm(false);
251 event.update("alarm");
252 onEventChanged(event.id(), false);
256 systemTrayIcon->show();
257 // The next two lines are to prevent a very strange position of the message box the first time at X11/aweseome (not Win32/XP)
258 systemTrayIcon->showMessage("ConfClerk", "Your upcoming events", QSystemTrayIcon::Information);
259 qApp->processEvents();
260 systemTrayIcon->showMessage(title, message, QSystemTrayIcon::Information, 60*60*24*1000);
261 QApplication::alert(this);
262 QApplication::beep();
266 void MainWindow::useConference(int conferenceId)
268 if (conferenceId == -1) // in case no conference is active
274 int oldActiveConferenceId = Conference::activeConference();
275 bool switchActiveConference = conferenceId != oldActiveConferenceId;
276 if (switchActiveConference) Conference::getById(oldActiveConferenceId).update("active", 0);
277 Conference activeConference = Conference::getById(conferenceId);
278 if (switchActiveConference) activeConference.update("active",1);
280 // looks like it does not work at n900
281 setWindowTitle(activeConference.title());
284 // dont run initTabs() here
285 // it takes much CPU, making travelling between conferences in ConferenceEditor longer
286 // and is not seen in maemo WM anyway
287 // instead run it explicitly
289 // 2. when ConferenceEditor finished
290 // dont forget to protect the calls by try-catch!
292 // just in case, clear conference selection instead
295 // end of optimization
297 } catch (OrmException& e) {
298 // cannon set an active conference
299 unsetConference(); // TODO: as no active conference is now correctly managed this should be handled as a fatal error
305 void MainWindow::initTabs()
307 int confId = Conference::activeConference();
308 if (confId != -1) // only init tabs if a conference is active
310 Conference active = Conference::getById(confId);
311 QDate startDate = active.start();
312 QDate endDate = active.end();
314 // 'dayNavigator' emits signal 'dateChanged' after setting valid START:END dates
315 dayNavigator->setDates(startDate, endDate);
316 nowAction->trigger();
320 void MainWindow::clearTabs()
322 dayTabContainer->clearModel();
323 tracksTabContainer->clearModel();
324 roomsTabContainer->clearModel();
325 favsTabContainer->clearModel();
326 searchTabContainer->clearModel();
329 void MainWindow::unsetConference()
332 dayNavigator->unsetDates();
333 setWindowTitle(saved_title);
337 void MainWindow::showError(const QString& message) {
338 error_message(message);
342 void MainWindow::on_settingsAction_triggered()
344 SettingsDialog dialog;
345 dialog.loadDialogData();
346 if (dialog.exec() == QDialog::Accepted) {
347 dialog.saveDialogData();
349 AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : QNetworkProxy::HttpProxy,
350 AppSettings::proxyAddress(),
351 AppSettings::proxyPort(),
354 QNetworkProxy::setApplicationProxy(proxy);
358 /** Create and run ConferenceEditor dialog, making required connections for it.
360 This method manages, which classes actually perform changes in conference list.
362 There are several classes that modify the conferences:
364 deletion and URL update.
365 this, mXmlParser and mNetworkAccessManager:
366 addition and refresh.
368 void MainWindow::on_conferencesAction_triggered()
370 ConferenceEditor dialog(conferenceModel, this);
372 connect(&dialog, SIGNAL(haveConferenceUrl(const QString&, int)), SLOT(importFromNetwork(const QString&, int)));
373 connect(&dialog, SIGNAL(haveConferenceFile(const QString&, int)), SLOT(importFromFile(const QString&, int)));
374 connect(&dialog, SIGNAL(removeConferenceRequested(int)), SLOT(removeConference(int)));
375 connect(&dialog, SIGNAL(changeUrlRequested(int, const QString&)),
376 SLOT(changeConferenceUrl(int, const QString&)));
378 connect(&dialog, SIGNAL(haveConferenceSelected(int)), SLOT(useConference(int)));
379 connect(&dialog, SIGNAL(noneConferenceSelected()), SLOT(unsetConference()));
381 connect(mXmlParser, SIGNAL(parsingScheduleBegin()), &dialog, SLOT(importStarted()));
382 connect(mXmlParser, SIGNAL(progressStatus(int)), &dialog, SLOT(showParsingProgress(int)));
383 connect(mXmlParser, SIGNAL(parsingScheduleEnd(int)), &dialog, SLOT(importFinished(int)));
385 connect(this, SIGNAL(conferenceRemoved()), &dialog, SLOT(conferenceRemoved()));
389 // optimization, see useConference() code
392 } catch (OrmException) {
397 void MainWindow::networkQueryFinished(QNetworkReply *aReply) {
398 if (aReply->error() != QNetworkReply::NoError) {
399 error_message(QString("Error occured during download: ") + aReply->errorString());
401 QUrl redirectUrl = aReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
402 if (!redirectUrl.isEmpty()) {
403 if (redirectUrl != aReply->request().url()) {
404 importFromNetwork(redirectUrl.toString(), aReply->request().attribute(QNetworkRequest::User).toInt());
405 return; // don't enable controls
407 error_message(QString("Error: Cyclic redirection from %1 to itself.").arg(redirectUrl.toString()));
410 importData(aReply->readAll(), aReply->url().toEncoded(), aReply->request().attribute(QNetworkRequest::User).toInt());
416 void MainWindow::importData(const QByteArray &aData, const QString& url, int conferenceId)
418 mXmlParser->parseData(aData, url, conferenceId);
421 void MainWindow::importFromNetwork(const QString& url, int conferenceId)
423 QNetworkRequest request;
424 request.setUrl(QUrl(url));
425 request.setAttribute(QNetworkRequest::User, conferenceId);
427 mNetworkAccessManager->setProxy(QNetworkProxy::applicationProxy());
428 mNetworkAccessManager->get(request);
431 void MainWindow::importFromFile(const QString& filename, int conferenceId)
433 QFile file(filename);
434 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
435 static const QString format("Cannot read \"%1\": error %2");
436 error_message(format.arg(filename, QString::number(file.error())));
439 importData(file.readAll(), "", conferenceId);
443 void MainWindow::removeConference(int id) {
444 sqlEngine->deleteConference(id);
445 conferenceModel->conferenceRemoved();
446 emit conferenceRemoved();
450 void MainWindow::changeConferenceUrl(int id, const QString& url) {
451 Conference::getById(id).setUrl(url);