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 MainWindow::MainWindow(QWidget* parent): QMainWindow(parent) {
56 sqlEngine = new SqlEngine(this);
57 searchTabContainer->setSqlEngine(sqlEngine);
58 connect(sqlEngine, SIGNAL(dbError(QString)), this, SLOT(showError(QString)));
60 sqlEngine->createOrUpdateDbSchema();
62 conferenceModel = new ConferenceModel(this);
63 mXmlParser = new ScheduleXmlParser(sqlEngine, this);
64 mNetworkAccessManager = new QNetworkAccessManager(this);
65 systemTrayIcon = new QSystemTrayIcon(qApp->windowIcon(), this);
66 alarmTimer = new QTimer(this);
68 alarmTimer->setInterval(60000);
70 saved_title = windowTitle();
73 tabWidget->setTabText(1,"Favs");
74 //tabWidget->setTabText(2,"Day");
77 // first time run aplication: -> let's have it direct connection in this case
78 if(!AppSettings::contains("proxyIsDirectConnection"))
79 AppSettings::setDirectConnection(true);
82 AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : (QNetworkProxy::ProxyType)AppSettings::proxyType(),
83 AppSettings::proxyAddress(),
84 AppSettings::proxyPort(),
85 AppSettings::proxyUsername(),
86 AppSettings::proxyPassword());
87 QNetworkProxy::setApplicationProxy(proxy);
89 // event details have changed
90 connect(dayTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
91 connect(favsTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
92 connect(tracksTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
93 connect(roomsTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
94 connect(searchTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
97 connect(dayNavigator, SIGNAL(dateChanged(QDate)), dayTabContainer, SLOT(redisplayDate(QDate)));
98 connect(dayNavigator, SIGNAL(dateChanged(QDate)), favsTabContainer, SLOT(redisplayDate(QDate)));
99 connect(dayNavigator, SIGNAL(dateChanged(QDate)), tracksTabContainer, SLOT(redisplayDate(QDate)));
100 connect(dayNavigator, SIGNAL(dateChanged(QDate)), roomsTabContainer, SLOT(redisplayDate(QDate)));
101 connect(dayNavigator, SIGNAL(dateChanged(QDate)), searchTabContainer, SLOT(redisplayDate(QDate)));
103 // search result has changed
104 connect(searchTabContainer, SIGNAL(searchResultChanged()), SLOT(onSearchResultChanged()));
107 connect(systemTrayIcon, SIGNAL(messageClicked()), SLOT(onSystemTrayMessageClicked()));
108 connect(systemTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), SLOT(onSystemTrayMessageClicked()));
111 connect(alarmTimer, SIGNAL(timeout()), SLOT(onAlarmTimerTimeout()));
113 // add the actions from the main menu to the window, otherwise the shortcuts don't work on MAEMO
114 addAction(conferencesAction);
115 addAction(settingsAction);
116 addAction(quitAction);
118 // make it impossible to hide the toolbar by disallowing its context menu
119 toolBar->setContextMenuPolicy(Qt::PreventContextMenu);
122 useConference(Conference::activeConference());
123 // optimization, see useConference() code
126 } catch (const OrmException& e) {
127 qDebug() << "OrmException:" << e.text();
131 connect(mNetworkAccessManager, SIGNAL(sslErrors(QNetworkReply*, QList<QSslError>)), SLOT(sslErrors(QNetworkReply*, QList<QSslError>)));
132 connect(mNetworkAccessManager, SIGNAL(finished(QNetworkReply*)), SLOT(networkQueryFinished(QNetworkReply*)));
133 connect(mXmlParser, SIGNAL(parsingScheduleBegin()), conferenceModel, SLOT(newConferenceBegin()));
134 connect(mXmlParser, SIGNAL(parsingScheduleEnd(int)), conferenceModel, SLOT(newConferenceEnd(int)));
138 MainWindow::~MainWindow() {
143 void MainWindow::on_aboutAction_triggered()
145 QDialog dialog(this);
148 ui.labDescription->setText(ui.labDescription->text().arg(qApp->applicationVersion()));
150 dialog.setFixedWidth(width());
156 void MainWindow::on_reloadAction_triggered() {
157 int confId = Conference::activeConference();
158 if (confId== -1) return;
159 Conference active = Conference::getById(confId);
160 if (active.url().isEmpty()) return;
161 importFromNetwork(active.url(), confId);
166 void MainWindow::on_nowAction_triggered() {
167 int confId = Conference::activeConference();
168 if (confId== -1) return;
169 dayNavigator->setCurDate(QDate::currentDate());
170 dayTabContainer->expandTimeGroup(QTime::currentTime(), confId);
174 void MainWindow::on_searchAction_triggered() {
175 if (tabWidget->currentWidget() == searchTab)
176 searchTabContainer->showSearchDialog(!searchTabContainer->searchDialogIsVisible());
178 tabWidget->setCurrentWidget(searchTab);
179 searchTabContainer->showSearchDialog();
184 void MainWindow::on_expandAllAction_triggered() {
185 if (tabWidget->currentWidget() == favouritesTab) favsTabContainer->treeView->expandAll();
186 if (tabWidget->currentWidget() == dayViewTab) dayTabContainer->treeView->expandAll();
187 if (tabWidget->currentWidget() == tracksTab) tracksTabContainer->treeView->expandAll();
188 if (tabWidget->currentWidget() == roomsTab) roomsTabContainer->treeView->expandAll();
189 if (tabWidget->currentWidget() == searchTab) searchTabContainer->treeView->expandAll();
193 void MainWindow::on_collapseAllAction_triggered() {
194 if (tabWidget->currentWidget() == favouritesTab) favsTabContainer->treeView->collapseAll();
195 if (tabWidget->currentWidget() == dayViewTab) dayTabContainer->treeView->collapseAll();
196 if (tabWidget->currentWidget() == tracksTab) tracksTabContainer->treeView->collapseAll();
197 if (tabWidget->currentWidget() == roomsTab) roomsTabContainer->treeView->collapseAll();
198 if (tabWidget->currentWidget() == searchTab) searchTabContainer->treeView->collapseAll();
202 void MainWindow::onEventChanged(int aEventId, bool favouriteChanged) {
203 dayTabContainer->redisplayEvent(aEventId);
204 if (favouriteChanged) favsTabContainer->redisplayDate(dayNavigator->curDate());
205 else favsTabContainer->redisplayEvent(aEventId);
206 tracksTabContainer->redisplayEvent(aEventId);
207 roomsTabContainer->redisplayEvent(aEventId);
208 searchTabContainer->redisplayEvent(aEventId);
212 void MainWindow::onSearchResultChanged() {
213 // Are results found on the current date?
214 QDate date = dayNavigator->curDate();
215 int count = searchTabContainer->searchResultCount(date);
216 if (count > 0) {searchTabContainer->redisplayDate(date); return;}
218 // Are results found in the future?
219 for (date = date.addDays(1); date <= dayNavigator->endDate(); date = date.addDays(1)) {
220 int count = searchTabContainer->searchResultCount(date);
221 if (count > 0) {dayNavigator->setCurDate(date); return;}
224 // Are results found in the past?
225 for (date = dayNavigator->startDate(); date < dayNavigator->curDate(); date = date.addDays(1)) {
226 int count = searchTabContainer->searchResultCount(date);
227 if (count > 0) {dayNavigator->setCurDate(date); return;}
229 // No results were found
230 searchTabContainer->redisplayDate(dayNavigator->curDate());
234 void MainWindow::onSystemTrayMessageClicked() {
235 systemTrayIcon->hide();
239 void MainWindow::onAlarmTimerTimeout() {
240 // determine if an alarm is set on an event that's starting soon
241 QList<Event> events = Event::getImminentAlarmEvents(AppSettings::preEventAlarmSec(), Conference::activeConference());
242 if (events.empty()) return;
244 // build a message string
248 if (events.size() == 1) {
249 event = events.first();
250 title = tr("Next event at %1").arg(event.start().toString("HH:mm"));
251 message = tr("\"%1\"\n(%2)").arg(event.title()).arg(event.room()->name());
253 title = tr("%1 upcoming events").arg(events.size());
254 QStringList messages;
255 foreach (event, events) {
256 messages += tr("%1: \"%2\" (%3)").arg(event.start().toString("HH:mm")).arg(event.title()).arg(event.room()->name());
258 message = messages.join("\n");
261 // and delete the corresponding alarm
262 foreach (event, events) {
263 event.setHasAlarm(false);
264 event.update("alarm");
265 onEventChanged(event.id(), false);
269 systemTrayIcon->show();
270 // The next two lines are to prevent a very strange position of the message box the first time at X11/aweseome (not Win32/XP)
271 systemTrayIcon->showMessage("ConfClerk", tr("Your upcoming events"), QSystemTrayIcon::Information);
272 qApp->processEvents();
273 systemTrayIcon->showMessage(title, message, QSystemTrayIcon::Information, 60*60*24*1000);
274 QApplication::alert(this);
275 QApplication::beep();
279 void MainWindow::useConference(int conferenceId)
281 if (conferenceId == -1) // in case no conference is active
287 int oldActiveConferenceId = Conference::activeConference();
288 bool switchActiveConference = conferenceId != oldActiveConferenceId;
289 if (switchActiveConference) Conference::getById(oldActiveConferenceId).update("active", 0);
290 Conference activeConference = Conference::getById(conferenceId);
291 if (switchActiveConference) activeConference.update("active",1);
293 // looks like it does not work at n900
294 setWindowTitle(activeConference.title());
297 // dont run initTabs() here
298 // it takes much CPU, making travelling between conferences in ConferenceEditor longer
299 // and is not seen in maemo WM anyway
300 // instead run it explicitly
302 // 2. when ConferenceEditor finished
303 // dont forget to protect the calls by try-catch!
305 // just in case, clear conference selection instead
308 // end of optimization
310 } catch (const OrmException& e) {
311 qDebug() << "OrmException:" << e.text();
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::ProxyType)AppSettings::proxyType(),
364 AppSettings::proxyAddress(),
365 AppSettings::proxyPort(),
366 AppSettings::proxyUsername(),
367 AppSettings::proxyPassword());
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 (const OrmException& e) {
407 qDebug() << "OrmException:" << e.text();
412 void MainWindow::sslErrors(QNetworkReply *aReply, const QList<QSslError> &errors) {
414 foreach (const QSslError &error, errors) {
415 if (!errorString.isEmpty()) {
418 errorString += error.errorString();
421 if (QMessageBox::warning(
424 tr("One or more SSL errors have occurred: %1", 0, errors.size()).arg(errorString),
425 QMessageBox::Ignore | QMessageBox::Cancel) == QMessageBox::Ignore) {
426 aReply->ignoreSslErrors();
432 void MainWindow::networkQueryFinished(QNetworkReply *aReply) {
433 if (aReply->error() != QNetworkReply::NoError) {
434 error_message(tr("Error occurred during download: %1").arg(aReply->errorString()));
436 QUrl redirectUrl = aReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
437 if (!redirectUrl.isEmpty()) {
438 if (redirectUrl != aReply->request().url()) {
439 importFromNetwork(redirectUrl.toString(), aReply->request().attribute(QNetworkRequest::User).toInt());
440 return; // don't enable controls
442 error_message(tr("Error: Cyclic redirection from %1 to itself.").arg(redirectUrl.toString()));
445 importData(aReply->readAll(), aReply->url().toEncoded(), aReply->request().attribute(QNetworkRequest::User).toInt());
451 void MainWindow::importData(const QByteArray &aData, const QString& url, int conferenceId)
453 mXmlParser->parseData(aData, url, conferenceId);
456 void MainWindow::importFromNetwork(const QString& url, int conferenceId)
458 QNetworkRequest request;
459 QSslConfiguration qSslConfiguration = request.sslConfiguration();
460 qSslConfiguration.setProtocol(QSsl::AnyProtocol);
461 qSslConfiguration.setPeerVerifyMode(QSslSocket::QueryPeer);
462 request.setUrl(QUrl(url));
463 request.setSslConfiguration(qSslConfiguration);
464 request.setAttribute(QNetworkRequest::User, conferenceId);
466 mNetworkAccessManager->setProxy(QNetworkProxy::applicationProxy());
467 mNetworkAccessManager->get(request);
470 void MainWindow::importFromFile(const QString& filename, int conferenceId)
472 QFile file(filename);
473 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
474 static const QString format("Cannot read \"%1\": error %2");
475 error_message(format.arg(filename, QString::number(file.error())));
478 importData(file.readAll(), "", conferenceId);
482 void MainWindow::removeConference(int id) {
483 sqlEngine->deleteConference(id);
484 conferenceModel->conferenceRemoved();
485 emit conferenceRemoved();
489 void MainWindow::changeConferenceUrl(int id, const QString& url) {
490 Conference::getById(id).setUrl(url);