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::HttpProxy,
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 (OrmException& e) {
314 // cannon set an active conference
315 unsetConference(); // TODO: as no active conference is now correctly managed this should be handled as a fatal error
321 void MainWindow::initTabs()
323 int confId = Conference::activeConference();
324 if (confId != -1) // only init tabs if a conference is active
326 Conference active = Conference::getById(confId);
327 QDate startDate = active.start();
328 QDate endDate = active.end();
330 // 'dayNavigator' emits signal 'dateChanged' after setting valid START:END dates
331 dayNavigator->setDates(startDate, endDate);
332 nowAction->trigger();
336 void MainWindow::clearTabs()
338 dayTabContainer->clearModel();
339 tracksTabContainer->clearModel();
340 roomsTabContainer->clearModel();
341 favsTabContainer->clearModel();
342 searchTabContainer->clearModel();
345 void MainWindow::unsetConference()
348 dayNavigator->unsetDates();
349 setWindowTitle(saved_title);
353 void MainWindow::showError(const QString& message) {
354 error_message(message);
358 void MainWindow::on_settingsAction_triggered()
360 SettingsDialog dialog;
361 dialog.loadDialogData();
362 if (dialog.exec() == QDialog::Accepted) {
363 dialog.saveDialogData();
365 AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : QNetworkProxy::HttpProxy,
366 AppSettings::proxyAddress(),
367 AppSettings::proxyPort(),
370 QNetworkProxy::setApplicationProxy(proxy);
374 /** Create and run ConferenceEditor dialog, making required connections for it.
376 This method manages, which classes actually perform changes in conference list.
378 There are several classes that modify the conferences:
380 deletion and URL update.
381 this, mXmlParser and mNetworkAccessManager:
382 addition and refresh.
384 void MainWindow::on_conferencesAction_triggered()
386 ConferenceEditor dialog(conferenceModel, this);
388 connect(&dialog, SIGNAL(haveConferenceUrl(const QString&, int)), SLOT(importFromNetwork(const QString&, int)));
389 connect(&dialog, SIGNAL(haveConferenceFile(const QString&, int)), SLOT(importFromFile(const QString&, int)));
390 connect(&dialog, SIGNAL(removeConferenceRequested(int)), SLOT(removeConference(int)));
391 connect(&dialog, SIGNAL(changeUrlRequested(int, const QString&)),
392 SLOT(changeConferenceUrl(int, const QString&)));
394 connect(&dialog, SIGNAL(haveConferenceSelected(int)), SLOT(useConference(int)));
395 connect(&dialog, SIGNAL(noneConferenceSelected()), SLOT(unsetConference()));
397 connect(mXmlParser, SIGNAL(parsingScheduleBegin()), &dialog, SLOT(importStarted()));
398 connect(mXmlParser, SIGNAL(progressStatus(int)), &dialog, SLOT(showParsingProgress(int)));
399 connect(mXmlParser, SIGNAL(parsingScheduleEnd(int)), &dialog, SLOT(importFinished(int)));
401 connect(this, SIGNAL(conferenceRemoved()), &dialog, SLOT(conferenceRemoved()));
405 // optimization, see useConference() code
408 } catch (OrmException) {
413 void MainWindow::sslErrors(QNetworkReply *aReply, const QList<QSslError> &errors) {
415 foreach (const QSslError &error, errors) {
416 if (!errorString.isEmpty()) {
419 errorString += error.errorString();
422 if (QMessageBox::warning(
425 tr("One or more SSL errors have occurred: %1", 0, errors.size()).arg(errorString),
426 QMessageBox::Ignore | QMessageBox::Cancel) == QMessageBox::Ignore) {
427 aReply->ignoreSslErrors();
433 void MainWindow::networkQueryFinished(QNetworkReply *aReply) {
434 if (aReply->error() != QNetworkReply::NoError) {
435 error_message(tr("Error occurred during download: %1").arg(aReply->errorString()));
437 QUrl redirectUrl = aReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
438 if (!redirectUrl.isEmpty()) {
439 if (redirectUrl != aReply->request().url()) {
440 importFromNetwork(redirectUrl.toString(), aReply->request().attribute(QNetworkRequest::User).toInt());
441 return; // don't enable controls
443 error_message(tr("Error: Cyclic redirection from %1 to itself.").arg(redirectUrl.toString()));
446 importData(aReply->readAll(), aReply->url().toEncoded(), aReply->request().attribute(QNetworkRequest::User).toInt());
452 void MainWindow::importData(const QByteArray &aData, const QString& url, int conferenceId)
454 mXmlParser->parseData(aData, url, conferenceId);
457 void MainWindow::importFromNetwork(const QString& url, int conferenceId)
459 QNetworkRequest request;
460 QSslConfiguration qSslConfiguration = request.sslConfiguration();
461 qSslConfiguration.setProtocol(QSsl::AnyProtocol);
462 qSslConfiguration.setPeerVerifyMode(QSslSocket::QueryPeer);
463 request.setUrl(QUrl(url));
464 request.setSslConfiguration(qSslConfiguration);
465 request.setAttribute(QNetworkRequest::User, conferenceId);
467 mNetworkAccessManager->setProxy(QNetworkProxy::applicationProxy());
468 mNetworkAccessManager->get(request);
471 void MainWindow::importFromFile(const QString& filename, int conferenceId)
473 QFile file(filename);
474 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
475 static const QString format("Cannot read \"%1\": error %2");
476 error_message(format.arg(filename, QString::number(file.error())));
479 importData(file.readAll(), "", conferenceId);
483 void MainWindow::removeConference(int id) {
484 sqlEngine->deleteConference(id);
485 conferenceModel->conferenceRemoved();
486 emit conferenceRemoved();
490 void MainWindow::changeConferenceUrl(int id, const QString& url) {
491 Conference::getById(id).setUrl(url);