2 * Copyright (C) 2010 Ixonos Plc.
3 * Copyright (C) 2011-2021 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 <QMessageBox>
31 #include "sqlengine.h"
33 #include "eventmodel.h"
36 #include "conference.h"
38 #include "eventdialog.h"
39 #include "daynavigatorwidget.h"
40 #include "settingsdialog.h"
41 #include "conferenceeditor.h"
42 #include "schedulexmlparser.h"
43 #include "errormessage.h"
44 #include "tabcontainer.h"
45 #include "appsettings.h"
46 #include "application.h"
48 MainWindow::MainWindow(QWidget* parent): QMainWindow(parent) {
52 sqlEngine = new SqlEngine(this);
53 searchTabContainer->setSqlEngine(sqlEngine);
54 connect(sqlEngine, SIGNAL(dbError(QString)), this, SLOT(showError(QString)));
56 sqlEngine->createOrUpdateDbSchema();
58 conferenceModel = new ConferenceModel(this);
59 mXmlParser = new ScheduleXmlParser(sqlEngine, this);
60 mNetworkAccessManager = new QNetworkAccessManager(this);
61 systemTrayIcon = new QSystemTrayIcon(qApp->windowIcon(), this);
62 alarmTimer = new QTimer(this);
64 alarmTimer->setInterval(60000);
66 saved_title = windowTitle();
69 tabWidget->setTabText(1,"Favs");
70 //tabWidget->setTabText(2,"Day");
73 // first time run aplication: -> let's have it direct connection in this case
74 if(!AppSettings::contains("proxyIsDirectConnection"))
75 AppSettings::setDirectConnection(true);
78 AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : (QNetworkProxy::ProxyType)AppSettings::proxyType(),
79 AppSettings::proxyAddress(),
80 AppSettings::proxyPort(),
81 AppSettings::proxyUsername(),
82 AppSettings::proxyPassword());
83 QNetworkProxy::setApplicationProxy(proxy);
85 // event details have changed
86 connect(dayTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
87 connect(favsTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
88 connect(tracksTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
89 connect(roomsTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
90 connect(searchTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
93 connect(dayNavigator, SIGNAL(dateChanged(QDate)), dayTabContainer, SLOT(redisplayDate(QDate)));
94 connect(dayNavigator, SIGNAL(dateChanged(QDate)), favsTabContainer, SLOT(redisplayDate(QDate)));
95 connect(dayNavigator, SIGNAL(dateChanged(QDate)), tracksTabContainer, SLOT(redisplayDate(QDate)));
96 connect(dayNavigator, SIGNAL(dateChanged(QDate)), roomsTabContainer, SLOT(redisplayDate(QDate)));
97 connect(dayNavigator, SIGNAL(dateChanged(QDate)), searchTabContainer, SLOT(redisplayDate(QDate)));
99 // search result has changed
100 connect(searchTabContainer, SIGNAL(searchResultChanged()), SLOT(onSearchResultChanged()));
103 connect(systemTrayIcon, SIGNAL(messageClicked()), SLOT(onSystemTrayMessageClicked()));
104 connect(systemTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), SLOT(onSystemTrayMessageClicked()));
107 connect(alarmTimer, SIGNAL(timeout()), SLOT(onAlarmTimerTimeout()));
109 // add the actions from the main menu to the window, otherwise the shortcuts don't work on MAEMO
110 addAction(conferencesAction);
111 addAction(settingsAction);
112 addAction(quitAction);
114 // make it impossible to hide the toolbar by disallowing its context menu
115 toolBar->setContextMenuPolicy(Qt::PreventContextMenu);
118 useConference(Conference::activeConference());
119 // optimization, see useConference() code
122 } catch (const OrmException& e) {
123 qDebug() << "OrmException:" << e.text();
127 connect(mNetworkAccessManager, SIGNAL(sslErrors(QNetworkReply*, QList<QSslError>)), SLOT(sslErrors(QNetworkReply*, QList<QSslError>)));
128 connect(mNetworkAccessManager, SIGNAL(finished(QNetworkReply*)), SLOT(networkQueryFinished(QNetworkReply*)));
129 connect(mXmlParser, SIGNAL(parsingScheduleBegin()), conferenceModel, SLOT(newConferenceBegin()));
130 connect(mXmlParser, SIGNAL(parsingScheduleEnd(int)), conferenceModel, SLOT(newConferenceEnd(int)));
134 MainWindow::~MainWindow() {
139 void MainWindow::on_aboutAction_triggered()
141 QDialog dialog(this);
144 ui.labDescription->setText(ui.labDescription->text().arg(qApp->applicationVersion()));
146 dialog.setFixedWidth(width());
152 void MainWindow::on_reloadAction_triggered() {
153 int confId = Conference::activeConference();
154 if (confId== -1) return;
155 Conference active = Conference::getById(confId);
156 if (active.url().isEmpty()) return;
157 importFromNetwork(active.url(), confId);
162 void MainWindow::on_nowAction_triggered() {
163 int confId = Conference::activeConference();
164 if (confId== -1) return;
165 dayNavigator->setCurDate(QDate::currentDate());
166 dayTabContainer->expandTimeGroup(QTime::currentTime(), confId);
170 void MainWindow::on_searchAction_triggered() {
171 if (tabWidget->currentWidget() == searchTab)
172 searchTabContainer->showSearchDialog(!searchTabContainer->searchDialogIsVisible());
174 tabWidget->setCurrentWidget(searchTab);
175 searchTabContainer->showSearchDialog();
180 void MainWindow::on_expandAllAction_triggered() {
181 if (tabWidget->currentWidget() == favouritesTab) favsTabContainer->treeView->expandAll();
182 if (tabWidget->currentWidget() == dayViewTab) dayTabContainer->treeView->expandAll();
183 if (tabWidget->currentWidget() == tracksTab) tracksTabContainer->treeView->expandAll();
184 if (tabWidget->currentWidget() == roomsTab) roomsTabContainer->treeView->expandAll();
185 if (tabWidget->currentWidget() == searchTab) searchTabContainer->treeView->expandAll();
189 void MainWindow::on_collapseAllAction_triggered() {
190 if (tabWidget->currentWidget() == favouritesTab) favsTabContainer->treeView->collapseAll();
191 if (tabWidget->currentWidget() == dayViewTab) dayTabContainer->treeView->collapseAll();
192 if (tabWidget->currentWidget() == tracksTab) tracksTabContainer->treeView->collapseAll();
193 if (tabWidget->currentWidget() == roomsTab) roomsTabContainer->treeView->collapseAll();
194 if (tabWidget->currentWidget() == searchTab) searchTabContainer->treeView->collapseAll();
198 void MainWindow::onEventChanged(int aEventId, bool favouriteChanged) {
199 dayTabContainer->redisplayEvent(aEventId);
200 if (favouriteChanged) favsTabContainer->redisplayDate(dayNavigator->curDate());
201 else favsTabContainer->redisplayEvent(aEventId);
202 tracksTabContainer->redisplayEvent(aEventId);
203 roomsTabContainer->redisplayEvent(aEventId);
204 searchTabContainer->redisplayEvent(aEventId);
208 void MainWindow::onSearchResultChanged() {
209 // Are results found on the current date?
210 QDate date = dayNavigator->curDate();
211 int count = searchTabContainer->searchResultCount(date);
212 if (count > 0) {searchTabContainer->redisplayDate(date); return;}
214 // Are results found in the future?
215 for (date = date.addDays(1); date <= dayNavigator->endDate(); date = date.addDays(1)) {
216 int count = searchTabContainer->searchResultCount(date);
217 if (count > 0) {dayNavigator->setCurDate(date); return;}
220 // Are results found in the past?
221 for (date = dayNavigator->startDate(); date < dayNavigator->curDate(); date = date.addDays(1)) {
222 int count = searchTabContainer->searchResultCount(date);
223 if (count > 0) {dayNavigator->setCurDate(date); return;}
225 // No results were found
226 searchTabContainer->redisplayDate(dayNavigator->curDate());
230 void MainWindow::onSystemTrayMessageClicked() {
231 systemTrayIcon->hide();
235 void MainWindow::onAlarmTimerTimeout() {
236 // determine if an alarm is set on an event that's starting soon
237 int conferenceId = Conference::activeConference();
238 QList<Event> events = Event::getImminentAlarmEvents(AppSettings::preEventAlarmSec(), conferenceId);
239 if (events.empty()) return;
241 // build a message string
242 const Conference conference = Conference::getById(conferenceId);
246 if (events.size() == 1) {
247 event = events.first();
248 title = tr("Next event at %1").arg(conference.shiftTime(event.start().time()).toString("HH:mm"));
249 message = tr("\"%1\"\n(%2)").arg(event.title()).arg(event.room()->name());
251 title = tr("%1 upcoming events").arg(events.size());
252 QStringList messages;
253 foreach (event, events) {
254 messages += tr("%1: \"%2\" (%3)").arg(conference.shiftTime(event.start().time()).toString("HH:mm")).arg(event.title()).arg(event.room()->name());
256 message = messages.join("\n");
259 // and delete the corresponding alarm
260 foreach (event, events) {
261 event.setHasAlarm(false);
262 event.update("alarm");
263 onEventChanged(event.id(), false);
267 systemTrayIcon->show();
268 // The next two lines are to prevent a very strange position of the message box the first time at X11/aweseome (not Win32/XP)
269 systemTrayIcon->showMessage("ConfClerk", tr("Your upcoming events"), QSystemTrayIcon::Information);
270 qApp->processEvents();
271 systemTrayIcon->showMessage(title, message, QSystemTrayIcon::Information, 60*60*24*1000);
272 QApplication::alert(this);
273 QApplication::beep();
277 void MainWindow::useConference(int conferenceId)
279 if (conferenceId == -1) // in case no conference is active
285 int oldActiveConferenceId = Conference::activeConference();
286 bool switchActiveConference = conferenceId != oldActiveConferenceId;
287 if (switchActiveConference) Conference::getById(oldActiveConferenceId).update("active", 0);
288 Conference activeConference = Conference::getById(conferenceId);
289 if (switchActiveConference) activeConference.update("active",1);
291 // looks like it does not work at n900
292 setWindowTitle(activeConference.title());
295 // dont run initTabs() here
296 // it takes much CPU, making travelling between conferences in ConferenceEditor longer
297 // and is not seen in maemo WM anyway
298 // instead run it explicitly
300 // 2. when ConferenceEditor finished
301 // dont forget to protect the calls by try-catch!
303 // just in case, clear conference selection instead
306 // end of optimization
308 } catch (const OrmException& e) {
309 qDebug() << "OrmException:" << e.text();
310 // cannon set an active conference
311 unsetConference(); // TODO: as no active conference is now correctly managed this should be handled as a fatal error
317 void MainWindow::initTabs()
319 int confId = Conference::activeConference();
320 if (confId != -1) // only init tabs if a conference is active
322 Conference active = Conference::getById(confId);
323 ((Application*) qApp)->setActiveConference(active);
324 QDate startDate = active.start();
325 QDate endDate = active.end();
327 // 'dayNavigator' emits signal 'dateChanged' after setting valid START:END dates
328 dayNavigator->setDates(startDate, endDate);
329 nowAction->trigger();
333 void MainWindow::clearTabs()
335 dayTabContainer->clearModel();
336 tracksTabContainer->clearModel();
337 roomsTabContainer->clearModel();
338 favsTabContainer->clearModel();
339 searchTabContainer->clearModel();
342 void MainWindow::unsetConference()
345 dayNavigator->unsetDates();
346 setWindowTitle(saved_title);
347 ((Application*) qApp)->unsetActiveConference();
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);