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 MainWindow::~MainWindow() {
135 void MainWindow::on_aboutAction_triggered()
137 QDialog dialog(this);
140 ui.labDescription->setText(ui.labDescription->text().arg(qApp->applicationVersion()));
142 dialog.setFixedWidth(width());
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);
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);
166 void MainWindow::on_searchAction_triggered() {
167 if (tabWidget->currentWidget() == searchTab)
168 searchTabContainer->showSearchDialog(!searchTabContainer->searchDialogIsVisible());
170 tabWidget->setCurrentWidget(searchTab);
171 searchTabContainer->showSearchDialog();
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();
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();
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);
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;}
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;}
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;}
221 // No results were found
222 searchTabContainer->redisplayDate(dayNavigator->curDate());
226 void MainWindow::onSystemTrayMessageClicked() {
227 systemTrayIcon->hide();
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;
236 // build a message string
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());
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());
250 message = messages.join("\n");
253 // and delete the corresponding alarm
254 foreach (event, events) {
255 event.setHasAlarm(false);
256 event.update("alarm");
257 onEventChanged(event.id(), false);
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();
271 void MainWindow::useConference(int conferenceId)
273 if (conferenceId == -1) // in case no conference is active
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);
285 // looks like it does not work at n900
286 setWindowTitle(activeConference.title());
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
294 // 2. when ConferenceEditor finished
295 // dont forget to protect the calls by try-catch!
297 // just in case, clear conference selection instead
300 // end of optimization
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
310 void MainWindow::initTabs()
312 int confId = Conference::activeConference();
313 if (confId != -1) // only init tabs if a conference is active
315 Conference active = Conference::getById(confId);
316 QDate startDate = active.start();
317 QDate endDate = active.end();
319 // 'dayNavigator' emits signal 'dateChanged' after setting valid START:END dates
320 dayNavigator->setDates(startDate, endDate);
321 nowAction->trigger();
325 void MainWindow::clearTabs()
327 dayTabContainer->clearModel();
328 tracksTabContainer->clearModel();
329 roomsTabContainer->clearModel();
330 favsTabContainer->clearModel();
331 searchTabContainer->clearModel();
334 void MainWindow::unsetConference()
337 dayNavigator->unsetDates();
338 setWindowTitle(saved_title);
342 void MainWindow::showError(const QString& message) {
343 error_message(message);
347 void MainWindow::on_settingsAction_triggered()
349 SettingsDialog dialog;
350 dialog.loadDialogData();
351 if (dialog.exec() == QDialog::Accepted) {
352 dialog.saveDialogData();
354 AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : QNetworkProxy::HttpProxy,
355 AppSettings::proxyAddress(),
356 AppSettings::proxyPort(),
359 QNetworkProxy::setApplicationProxy(proxy);
363 /** Create and run ConferenceEditor dialog, making required connections for it.
365 This method manages, which classes actually perform changes in conference list.
367 There are several classes that modify the conferences:
369 deletion and URL update.
370 this, mXmlParser and mNetworkAccessManager:
371 addition and refresh.
373 void MainWindow::on_conferencesAction_triggered()
375 ConferenceEditor dialog(conferenceModel, this);
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&)));
383 connect(&dialog, SIGNAL(haveConferenceSelected(int)), SLOT(useConference(int)));
384 connect(&dialog, SIGNAL(noneConferenceSelected()), SLOT(unsetConference()));
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)));
390 connect(this, SIGNAL(conferenceRemoved()), &dialog, SLOT(conferenceRemoved()));
394 // optimization, see useConference() code
397 } catch (OrmException) {
402 void MainWindow::networkQueryFinished(QNetworkReply *aReply) {
403 if (aReply->error() != QNetworkReply::NoError) {
404 error_message(QString("Error occured during download: ") + aReply->errorString());
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
412 error_message(QString("Error: Cyclic redirection from %1 to itself.").arg(redirectUrl.toString()));
415 importData(aReply->readAll(), aReply->url().toEncoded(), aReply->request().attribute(QNetworkRequest::User).toInt());
421 void MainWindow::importData(const QByteArray &aData, const QString& url, int conferenceId)
423 mXmlParser->parseData(aData, url, conferenceId);
426 void MainWindow::importFromNetwork(const QString& url, int conferenceId)
428 QNetworkRequest request;
429 request.setUrl(QUrl(url));
430 request.setAttribute(QNetworkRequest::User, conferenceId);
432 mNetworkAccessManager->setProxy(QNetworkProxy::applicationProxy());
433 mNetworkAccessManager->get(request);
436 void MainWindow::importFromFile(const QString& filename, int conferenceId)
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())));
444 importData(file.readAll(), "", conferenceId);
448 void MainWindow::removeConference(int id) {
449 sqlEngine->deleteConference(id);
450 conferenceModel->conferenceRemoved();
451 emit conferenceRemoved();
455 void MainWindow::changeConferenceUrl(int id, const QString& url) {
456 Conference::getById(id).setUrl(url);