2 * Copyright (C) 2010 Ixonos Plc.
3 * Copyright (C) 2011-2017 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>
27 #include <QSslConfiguration>
29 #include "sqlengine.h"
32 #include "eventmodel.h"
36 #include "conference.h"
39 #include <QMessageBox>
42 #include "eventdialog.h"
43 #include "daynavigatorwidget.h"
44 #include "settingsdialog.h"
45 #include "conferenceeditor.h"
46 #include "schedulexmlparser.h"
47 #include "errormessage.h"
49 #include "tabcontainer.h"
50 #include "appsettings.h"
52 const QString PROXY_USERNAME;
53 const QString PROXY_PASSWD;
55 MainWindow::MainWindow(QWidget* parent): QMainWindow(parent) {
59 sqlEngine = new SqlEngine(this);
60 searchTabContainer->setSqlEngine(sqlEngine);
61 connect(sqlEngine, SIGNAL(dbError(QString)), this, SLOT(showError(QString)));
63 sqlEngine->createOrUpdateDbSchema();
65 conferenceModel = new ConferenceModel(this);
66 mXmlParser = new ScheduleXmlParser(sqlEngine, this);
67 mNetworkAccessManager = new QNetworkAccessManager(this);
68 systemTrayIcon = new QSystemTrayIcon(qApp->windowIcon(), this);
69 alarmTimer = new QTimer(this);
71 alarmTimer->setInterval(60000);
73 saved_title = windowTitle();
76 tabWidget->setTabText(1,"Favs");
77 //tabWidget->setTabText(2,"Day");
80 // first time run aplication: -> let's have it direct connection in this case
81 if(!AppSettings::contains("proxyIsDirectConnection"))
82 AppSettings::setDirectConnection(true);
85 AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : (QNetworkProxy::ProxyType)AppSettings::proxyType(),
86 AppSettings::proxyAddress(),
87 AppSettings::proxyPort(),
90 QNetworkProxy::setApplicationProxy(proxy);
92 // event details have changed
93 connect(dayTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
94 connect(favsTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
95 connect(tracksTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
96 connect(roomsTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
97 connect(searchTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
100 connect(dayNavigator, SIGNAL(dateChanged(QDate)), dayTabContainer, SLOT(redisplayDate(QDate)));
101 connect(dayNavigator, SIGNAL(dateChanged(QDate)), favsTabContainer, SLOT(redisplayDate(QDate)));
102 connect(dayNavigator, SIGNAL(dateChanged(QDate)), tracksTabContainer, SLOT(redisplayDate(QDate)));
103 connect(dayNavigator, SIGNAL(dateChanged(QDate)), roomsTabContainer, SLOT(redisplayDate(QDate)));
104 connect(dayNavigator, SIGNAL(dateChanged(QDate)), searchTabContainer, SLOT(redisplayDate(QDate)));
106 // search result has changed
107 connect(searchTabContainer, SIGNAL(searchResultChanged()), SLOT(onSearchResultChanged()));
110 connect(systemTrayIcon, SIGNAL(messageClicked()), SLOT(onSystemTrayMessageClicked()));
111 connect(systemTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), SLOT(onSystemTrayMessageClicked()));
114 connect(alarmTimer, SIGNAL(timeout()), SLOT(onAlarmTimerTimeout()));
116 // add the actions from the main menu to the window, otherwise the shortcuts don't work on MAEMO
117 addAction(conferencesAction);
118 addAction(settingsAction);
119 addAction(quitAction);
121 // make it impossible to hide the toolbar by disallowing its context menu
122 toolBar->setContextMenuPolicy(Qt::PreventContextMenu);
125 useConference(Conference::activeConference());
126 // optimization, see useConference() code
129 } catch (const OrmException& e) {
130 qDebug() << "OrmException:" << e.text();
134 connect(mNetworkAccessManager, SIGNAL(sslErrors(QNetworkReply*, QList<QSslError>)), SLOT(sslErrors(QNetworkReply*, QList<QSslError>)));
135 connect(mNetworkAccessManager, SIGNAL(finished(QNetworkReply*)), SLOT(networkQueryFinished(QNetworkReply*)));
136 connect(mXmlParser, SIGNAL(parsingScheduleBegin()), conferenceModel, SLOT(newConferenceBegin()));
137 connect(mXmlParser, SIGNAL(parsingScheduleEnd(int)), conferenceModel, SLOT(newConferenceEnd(int)));
141 MainWindow::~MainWindow() {
146 void MainWindow::on_aboutAction_triggered()
148 QDialog dialog(this);
151 ui.labDescription->setText(ui.labDescription->text().arg(qApp->applicationVersion()));
153 dialog.setFixedWidth(width());
159 void MainWindow::on_reloadAction_triggered() {
160 int confId = Conference::activeConference();
161 if (confId== -1) return;
162 Conference active = Conference::getById(confId);
163 if (active.url().isEmpty()) return;
164 importFromNetwork(active.url(), confId);
169 void MainWindow::on_nowAction_triggered() {
170 int confId = Conference::activeConference();
171 if (confId== -1) return;
172 dayNavigator->setCurDate(QDate::currentDate());
173 dayTabContainer->expandTimeGroup(QTime::currentTime(), confId);
177 void MainWindow::on_searchAction_triggered() {
178 if (tabWidget->currentWidget() == searchTab)
179 searchTabContainer->showSearchDialog(!searchTabContainer->searchDialogIsVisible());
181 tabWidget->setCurrentWidget(searchTab);
182 searchTabContainer->showSearchDialog();
187 void MainWindow::on_expandAllAction_triggered() {
188 if (tabWidget->currentWidget() == favouritesTab) favsTabContainer->treeView->expandAll();
189 if (tabWidget->currentWidget() == dayViewTab) dayTabContainer->treeView->expandAll();
190 if (tabWidget->currentWidget() == tracksTab) tracksTabContainer->treeView->expandAll();
191 if (tabWidget->currentWidget() == roomsTab) roomsTabContainer->treeView->expandAll();
192 if (tabWidget->currentWidget() == searchTab) searchTabContainer->treeView->expandAll();
196 void MainWindow::on_collapseAllAction_triggered() {
197 if (tabWidget->currentWidget() == favouritesTab) favsTabContainer->treeView->collapseAll();
198 if (tabWidget->currentWidget() == dayViewTab) dayTabContainer->treeView->collapseAll();
199 if (tabWidget->currentWidget() == tracksTab) tracksTabContainer->treeView->collapseAll();
200 if (tabWidget->currentWidget() == roomsTab) roomsTabContainer->treeView->collapseAll();
201 if (tabWidget->currentWidget() == searchTab) searchTabContainer->treeView->collapseAll();
205 void MainWindow::onEventChanged(int aEventId, bool favouriteChanged) {
206 dayTabContainer->redisplayEvent(aEventId);
207 if (favouriteChanged) favsTabContainer->redisplayDate(dayNavigator->curDate());
208 else favsTabContainer->redisplayEvent(aEventId);
209 tracksTabContainer->redisplayEvent(aEventId);
210 roomsTabContainer->redisplayEvent(aEventId);
211 searchTabContainer->redisplayEvent(aEventId);
215 void MainWindow::onSearchResultChanged() {
216 // Are results found on the current date?
217 QDate date = dayNavigator->curDate();
218 int count = searchTabContainer->searchResultCount(date);
219 if (count > 0) {searchTabContainer->redisplayDate(date); return;}
221 // Are results found in the future?
222 for (date = date.addDays(1); date <= dayNavigator->endDate(); date = date.addDays(1)) {
223 int count = searchTabContainer->searchResultCount(date);
224 if (count > 0) {dayNavigator->setCurDate(date); return;}
227 // Are results found in the past?
228 for (date = dayNavigator->startDate(); date < dayNavigator->curDate(); date = date.addDays(1)) {
229 int count = searchTabContainer->searchResultCount(date);
230 if (count > 0) {dayNavigator->setCurDate(date); return;}
232 // No results were found
233 searchTabContainer->redisplayDate(dayNavigator->curDate());
237 void MainWindow::onSystemTrayMessageClicked() {
238 systemTrayIcon->hide();
242 void MainWindow::onAlarmTimerTimeout() {
243 // determine if an alarm is set on an event that's starting soon
244 QList<Event> events = Event::getImminentAlarmEvents(AppSettings::preEventAlarmSec(), Conference::activeConference());
245 if (events.empty()) return;
247 // build a message string
251 if (events.size() == 1) {
252 event = events.first();
253 title = tr("Next event at %1").arg(event.start().toString("HH:mm"));
254 message = tr("\"%1\"\n(%2)").arg(event.title()).arg(event.room()->name());
256 title = tr("%1 upcoming events").arg(events.size());
257 QStringList messages;
258 foreach (event, events) {
259 messages += tr("%1: \"%2\" (%3)").arg(event.start().toString("HH:mm")).arg(event.title()).arg(event.room()->name());
261 message = messages.join("\n");
264 // and delete the corresponding alarm
265 foreach (event, events) {
266 event.setHasAlarm(false);
267 event.update("alarm");
268 onEventChanged(event.id(), false);
272 systemTrayIcon->show();
273 // The next two lines are to prevent a very strange position of the message box the first time at X11/aweseome (not Win32/XP)
274 systemTrayIcon->showMessage("ConfClerk", tr("Your upcoming events"), QSystemTrayIcon::Information);
275 qApp->processEvents();
276 systemTrayIcon->showMessage(title, message, QSystemTrayIcon::Information, 60*60*24*1000);
277 QApplication::alert(this);
278 QApplication::beep();
282 void MainWindow::useConference(int conferenceId)
284 if (conferenceId == -1) // in case no conference is active
290 int oldActiveConferenceId = Conference::activeConference();
291 bool switchActiveConference = conferenceId != oldActiveConferenceId;
292 if (switchActiveConference) Conference::getById(oldActiveConferenceId).update("active", 0);
293 Conference activeConference = Conference::getById(conferenceId);
294 if (switchActiveConference) activeConference.update("active",1);
296 // looks like it does not work at n900
297 setWindowTitle(activeConference.title());
300 // dont run initTabs() here
301 // it takes much CPU, making travelling between conferences in ConferenceEditor longer
302 // and is not seen in maemo WM anyway
303 // instead run it explicitly
305 // 2. when ConferenceEditor finished
306 // dont forget to protect the calls by try-catch!
308 // just in case, clear conference selection instead
311 // end of optimization
313 } catch (const OrmException& e) {
314 qDebug() << "OrmException:" << e.text();
315 // cannon set an active conference
316 unsetConference(); // TODO: as no active conference is now correctly managed this should be handled as a fatal error
322 void MainWindow::initTabs()
324 int confId = Conference::activeConference();
325 if (confId != -1) // only init tabs if a conference is active
327 Conference active = Conference::getById(confId);
328 QDate startDate = active.start();
329 QDate endDate = active.end();
331 // 'dayNavigator' emits signal 'dateChanged' after setting valid START:END dates
332 dayNavigator->setDates(startDate, endDate);
333 nowAction->trigger();
337 void MainWindow::clearTabs()
339 dayTabContainer->clearModel();
340 tracksTabContainer->clearModel();
341 roomsTabContainer->clearModel();
342 favsTabContainer->clearModel();
343 searchTabContainer->clearModel();
346 void MainWindow::unsetConference()
349 dayNavigator->unsetDates();
350 setWindowTitle(saved_title);
354 void MainWindow::showError(const QString& message) {
355 error_message(message);
359 void MainWindow::on_settingsAction_triggered()
361 SettingsDialog dialog;
362 dialog.loadDialogData();
363 if (dialog.exec() == QDialog::Accepted) {
364 dialog.saveDialogData();
366 AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : (QNetworkProxy::ProxyType)AppSettings::proxyType(),
367 AppSettings::proxyAddress(),
368 AppSettings::proxyPort(),
371 QNetworkProxy::setApplicationProxy(proxy);
375 /** Create and run ConferenceEditor dialog, making required connections for it.
377 This method manages, which classes actually perform changes in conference list.
379 There are several classes that modify the conferences:
381 deletion and URL update.
382 this, mXmlParser and mNetworkAccessManager:
383 addition and refresh.
385 void MainWindow::on_conferencesAction_triggered()
387 ConferenceEditor dialog(conferenceModel, this);
389 connect(&dialog, SIGNAL(haveConferenceUrl(const QString&, int)), SLOT(importFromNetwork(const QString&, int)));
390 connect(&dialog, SIGNAL(haveConferenceFile(const QString&, int)), SLOT(importFromFile(const QString&, int)));
391 connect(&dialog, SIGNAL(removeConferenceRequested(int)), SLOT(removeConference(int)));
392 connect(&dialog, SIGNAL(changeUrlRequested(int, const QString&)),
393 SLOT(changeConferenceUrl(int, const QString&)));
395 connect(&dialog, SIGNAL(haveConferenceSelected(int)), SLOT(useConference(int)));
396 connect(&dialog, SIGNAL(noneConferenceSelected()), SLOT(unsetConference()));
398 connect(mXmlParser, SIGNAL(parsingScheduleBegin()), &dialog, SLOT(importStarted()));
399 connect(mXmlParser, SIGNAL(progressStatus(int)), &dialog, SLOT(showParsingProgress(int)));
400 connect(mXmlParser, SIGNAL(parsingScheduleEnd(int)), &dialog, SLOT(importFinished(int)));
402 connect(this, SIGNAL(conferenceRemoved()), &dialog, SLOT(conferenceRemoved()));
406 // optimization, see useConference() code
409 } catch (const OrmException& e) {
410 qDebug() << "OrmException:" << e.text();
415 void MainWindow::sslErrors(QNetworkReply *aReply, const QList<QSslError> &errors) {
417 foreach (const QSslError &error, errors) {
418 if (!errorString.isEmpty()) {
421 errorString += error.errorString();
424 if (QMessageBox::warning(
427 tr("One or more SSL errors have occurred: %1", 0, errors.size()).arg(errorString),
428 QMessageBox::Ignore | QMessageBox::Cancel) == QMessageBox::Ignore) {
429 aReply->ignoreSslErrors();
435 void MainWindow::networkQueryFinished(QNetworkReply *aReply) {
436 if (aReply->error() != QNetworkReply::NoError) {
437 error_message(tr("Error occurred during download: %1").arg(aReply->errorString()));
439 QUrl redirectUrl = aReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
440 if (!redirectUrl.isEmpty()) {
441 if (redirectUrl != aReply->request().url()) {
442 importFromNetwork(redirectUrl.toString(), aReply->request().attribute(QNetworkRequest::User).toInt());
443 return; // don't enable controls
445 error_message(tr("Error: Cyclic redirection from %1 to itself.").arg(redirectUrl.toString()));
448 importData(aReply->readAll(), aReply->url().toEncoded(), aReply->request().attribute(QNetworkRequest::User).toInt());
454 void MainWindow::importData(const QByteArray &aData, const QString& url, int conferenceId)
456 mXmlParser->parseData(aData, url, conferenceId);
459 void MainWindow::importFromNetwork(const QString& url, int conferenceId)
461 QNetworkRequest request;
462 QSslConfiguration qSslConfiguration = request.sslConfiguration();
463 qSslConfiguration.setProtocol(QSsl::AnyProtocol);
464 qSslConfiguration.setPeerVerifyMode(QSslSocket::QueryPeer);
465 request.setUrl(QUrl(url));
466 request.setSslConfiguration(qSslConfiguration);
467 request.setAttribute(QNetworkRequest::User, conferenceId);
469 mNetworkAccessManager->setProxy(QNetworkProxy::applicationProxy());
470 mNetworkAccessManager->get(request);
473 void MainWindow::importFromFile(const QString& filename, int conferenceId)
475 QFile file(filename);
476 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
477 static const QString format("Cannot read \"%1\": error %2");
478 error_message(format.arg(filename, QString::number(file.error())));
481 importData(file.readAll(), "", conferenceId);
485 void MainWindow::removeConference(int id) {
486 sqlEngine->deleteConference(id);
487 conferenceModel->conferenceRemoved();
488 emit conferenceRemoved();
492 void MainWindow::changeConferenceUrl(int id, const QString& url) {
493 Conference::getById(id).setUrl(url);