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 "sqlengine.h"
32 #include "eventmodel.h"
36 #include "conference.h"
39 #include <QMessageBox>
40 #include <application.h>
43 #include "eventdialog.h"
44 #include "daynavigatorwidget.h"
45 #include "settingsdialog.h"
46 #include "conferenceeditor.h"
47 #include "schedulexmlparser.h"
48 #include "errormessage.h"
50 #include "tabcontainer.h"
51 #include "appsettings.h"
53 MainWindow::MainWindow(QWidget* parent): QMainWindow(parent) {
57 sqlEngine = new SqlEngine(this);
58 searchTabContainer->setSqlEngine(sqlEngine);
59 connect(sqlEngine, SIGNAL(dbError(QString)), this, SLOT(showError(QString)));
61 sqlEngine->createOrUpdateDbSchema();
63 conferenceModel = new ConferenceModel(this);
64 mXmlParser = new ScheduleXmlParser(sqlEngine, this);
65 mNetworkAccessManager = new QNetworkAccessManager(this);
66 systemTrayIcon = new QSystemTrayIcon(qApp->windowIcon(), this);
67 alarmTimer = new QTimer(this);
69 alarmTimer->setInterval(60000);
71 saved_title = windowTitle();
74 tabWidget->setTabText(1,"Favs");
75 //tabWidget->setTabText(2,"Day");
78 // first time run aplication: -> let's have it direct connection in this case
79 if(!AppSettings::contains("proxyIsDirectConnection"))
80 AppSettings::setDirectConnection(true);
83 AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : (QNetworkProxy::ProxyType)AppSettings::proxyType(),
84 AppSettings::proxyAddress(),
85 AppSettings::proxyPort(),
86 AppSettings::proxyUsername(),
87 AppSettings::proxyPassword());
88 QNetworkProxy::setApplicationProxy(proxy);
90 // event details have changed
91 connect(dayTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
92 connect(favsTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
93 connect(tracksTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
94 connect(roomsTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
95 connect(searchTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool)));
98 connect(dayNavigator, SIGNAL(dateChanged(QDate)), dayTabContainer, SLOT(redisplayDate(QDate)));
99 connect(dayNavigator, SIGNAL(dateChanged(QDate)), favsTabContainer, SLOT(redisplayDate(QDate)));
100 connect(dayNavigator, SIGNAL(dateChanged(QDate)), tracksTabContainer, SLOT(redisplayDate(QDate)));
101 connect(dayNavigator, SIGNAL(dateChanged(QDate)), roomsTabContainer, SLOT(redisplayDate(QDate)));
102 connect(dayNavigator, SIGNAL(dateChanged(QDate)), searchTabContainer, SLOT(redisplayDate(QDate)));
104 // search result has changed
105 connect(searchTabContainer, SIGNAL(searchResultChanged()), SLOT(onSearchResultChanged()));
108 connect(systemTrayIcon, SIGNAL(messageClicked()), SLOT(onSystemTrayMessageClicked()));
109 connect(systemTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), SLOT(onSystemTrayMessageClicked()));
112 connect(alarmTimer, SIGNAL(timeout()), SLOT(onAlarmTimerTimeout()));
114 // add the actions from the main menu to the window, otherwise the shortcuts don't work on MAEMO
115 addAction(conferencesAction);
116 addAction(settingsAction);
117 addAction(quitAction);
119 // make it impossible to hide the toolbar by disallowing its context menu
120 toolBar->setContextMenuPolicy(Qt::PreventContextMenu);
123 useConference(Conference::activeConference());
124 // optimization, see useConference() code
127 } catch (const OrmException& e) {
128 qDebug() << "OrmException:" << e.text();
132 connect(mNetworkAccessManager, SIGNAL(sslErrors(QNetworkReply*, QList<QSslError>)), SLOT(sslErrors(QNetworkReply*, QList<QSslError>)));
133 connect(mNetworkAccessManager, SIGNAL(finished(QNetworkReply*)), SLOT(networkQueryFinished(QNetworkReply*)));
134 connect(mXmlParser, SIGNAL(parsingScheduleBegin()), conferenceModel, SLOT(newConferenceBegin()));
135 connect(mXmlParser, SIGNAL(parsingScheduleEnd(int)), conferenceModel, SLOT(newConferenceEnd(int)));
139 MainWindow::~MainWindow() {
144 void MainWindow::on_aboutAction_triggered()
146 QDialog dialog(this);
149 ui.labDescription->setText(ui.labDescription->text().arg(qApp->applicationVersion()));
151 dialog.setFixedWidth(width());
157 void MainWindow::on_reloadAction_triggered() {
158 int confId = Conference::activeConference();
159 if (confId== -1) return;
160 Conference active = Conference::getById(confId);
161 if (active.url().isEmpty()) return;
162 importFromNetwork(active.url(), confId);
167 void MainWindow::on_nowAction_triggered() {
168 int confId = Conference::activeConference();
169 if (confId== -1) return;
170 dayNavigator->setCurDate(QDate::currentDate());
171 dayTabContainer->expandTimeGroup(QTime::currentTime(), confId);
175 void MainWindow::on_searchAction_triggered() {
176 if (tabWidget->currentWidget() == searchTab)
177 searchTabContainer->showSearchDialog(!searchTabContainer->searchDialogIsVisible());
179 tabWidget->setCurrentWidget(searchTab);
180 searchTabContainer->showSearchDialog();
185 void MainWindow::on_expandAllAction_triggered() {
186 if (tabWidget->currentWidget() == favouritesTab) favsTabContainer->treeView->expandAll();
187 if (tabWidget->currentWidget() == dayViewTab) dayTabContainer->treeView->expandAll();
188 if (tabWidget->currentWidget() == tracksTab) tracksTabContainer->treeView->expandAll();
189 if (tabWidget->currentWidget() == roomsTab) roomsTabContainer->treeView->expandAll();
190 if (tabWidget->currentWidget() == searchTab) searchTabContainer->treeView->expandAll();
194 void MainWindow::on_collapseAllAction_triggered() {
195 if (tabWidget->currentWidget() == favouritesTab) favsTabContainer->treeView->collapseAll();
196 if (tabWidget->currentWidget() == dayViewTab) dayTabContainer->treeView->collapseAll();
197 if (tabWidget->currentWidget() == tracksTab) tracksTabContainer->treeView->collapseAll();
198 if (tabWidget->currentWidget() == roomsTab) roomsTabContainer->treeView->collapseAll();
199 if (tabWidget->currentWidget() == searchTab) searchTabContainer->treeView->collapseAll();
203 void MainWindow::onEventChanged(int aEventId, bool favouriteChanged) {
204 dayTabContainer->redisplayEvent(aEventId);
205 if (favouriteChanged) favsTabContainer->redisplayDate(dayNavigator->curDate());
206 else favsTabContainer->redisplayEvent(aEventId);
207 tracksTabContainer->redisplayEvent(aEventId);
208 roomsTabContainer->redisplayEvent(aEventId);
209 searchTabContainer->redisplayEvent(aEventId);
213 void MainWindow::onSearchResultChanged() {
214 // Are results found on the current date?
215 QDate date = dayNavigator->curDate();
216 int count = searchTabContainer->searchResultCount(date);
217 if (count > 0) {searchTabContainer->redisplayDate(date); return;}
219 // Are results found in the future?
220 for (date = date.addDays(1); date <= dayNavigator->endDate(); date = date.addDays(1)) {
221 int count = searchTabContainer->searchResultCount(date);
222 if (count > 0) {dayNavigator->setCurDate(date); return;}
225 // Are results found in the past?
226 for (date = dayNavigator->startDate(); date < dayNavigator->curDate(); date = date.addDays(1)) {
227 int count = searchTabContainer->searchResultCount(date);
228 if (count > 0) {dayNavigator->setCurDate(date); return;}
230 // No results were found
231 searchTabContainer->redisplayDate(dayNavigator->curDate());
235 void MainWindow::onSystemTrayMessageClicked() {
236 systemTrayIcon->hide();
240 void MainWindow::onAlarmTimerTimeout() {
241 // determine if an alarm is set on an event that's starting soon
242 int conferenceId = Conference::activeConference();
243 QList<Event> events = Event::getImminentAlarmEvents(AppSettings::preEventAlarmSec(), conferenceId);
244 if (events.empty()) return;
246 // build a message string
247 const Conference conference = Conference::getById(conferenceId);
251 if (events.size() == 1) {
252 event = events.first();
253 title = tr("Next event at %1").arg(conference.shiftTime(event.start().time()).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(conference.shiftTime(event.start().time()).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 ((Application*) qApp)->setActiveConference(active);
329 QDate startDate = active.start();
330 QDate endDate = active.end();
332 // 'dayNavigator' emits signal 'dateChanged' after setting valid START:END dates
333 dayNavigator->setDates(startDate, endDate);
334 nowAction->trigger();
338 void MainWindow::clearTabs()
340 dayTabContainer->clearModel();
341 tracksTabContainer->clearModel();
342 roomsTabContainer->clearModel();
343 favsTabContainer->clearModel();
344 searchTabContainer->clearModel();
347 void MainWindow::unsetConference()
350 dayNavigator->unsetDates();
351 setWindowTitle(saved_title);
352 ((Application*) qApp)->unsetActiveConference();
356 void MainWindow::showError(const QString& message) {
357 error_message(message);
361 void MainWindow::on_settingsAction_triggered()
363 SettingsDialog dialog;
364 dialog.loadDialogData();
365 if (dialog.exec() == QDialog::Accepted) {
366 dialog.saveDialogData();
368 AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : (QNetworkProxy::ProxyType)AppSettings::proxyType(),
369 AppSettings::proxyAddress(),
370 AppSettings::proxyPort(),
371 AppSettings::proxyUsername(),
372 AppSettings::proxyPassword());
373 QNetworkProxy::setApplicationProxy(proxy);
377 /** Create and run ConferenceEditor dialog, making required connections for it.
379 This method manages, which classes actually perform changes in conference list.
381 There are several classes that modify the conferences:
383 deletion and URL update.
384 this, mXmlParser and mNetworkAccessManager:
385 addition and refresh.
387 void MainWindow::on_conferencesAction_triggered()
389 ConferenceEditor dialog(conferenceModel, this);
391 connect(&dialog, SIGNAL(haveConferenceUrl(const QString&, int)), SLOT(importFromNetwork(const QString&, int)));
392 connect(&dialog, SIGNAL(haveConferenceFile(const QString&, int)), SLOT(importFromFile(const QString&, int)));
393 connect(&dialog, SIGNAL(removeConferenceRequested(int)), SLOT(removeConference(int)));
394 connect(&dialog, SIGNAL(changeUrlRequested(int, const QString&)),
395 SLOT(changeConferenceUrl(int, const QString&)));
397 connect(&dialog, SIGNAL(haveConferenceSelected(int)), SLOT(useConference(int)));
398 connect(&dialog, SIGNAL(noneConferenceSelected()), SLOT(unsetConference()));
400 connect(mXmlParser, SIGNAL(parsingScheduleBegin()), &dialog, SLOT(importStarted()));
401 connect(mXmlParser, SIGNAL(progressStatus(int)), &dialog, SLOT(showParsingProgress(int)));
402 connect(mXmlParser, SIGNAL(parsingScheduleEnd(int)), &dialog, SLOT(importFinished(int)));
404 connect(this, SIGNAL(conferenceRemoved()), &dialog, SLOT(conferenceRemoved()));
408 // optimization, see useConference() code
411 } catch (const OrmException& e) {
412 qDebug() << "OrmException:" << e.text();
417 void MainWindow::sslErrors(QNetworkReply *aReply, const QList<QSslError> &errors) {
419 foreach (const QSslError &error, errors) {
420 if (!errorString.isEmpty()) {
423 errorString += error.errorString();
426 if (QMessageBox::warning(
429 tr("One or more SSL errors have occurred: %1", 0, errors.size()).arg(errorString),
430 QMessageBox::Ignore | QMessageBox::Cancel) == QMessageBox::Ignore) {
431 aReply->ignoreSslErrors();
437 void MainWindow::networkQueryFinished(QNetworkReply *aReply) {
438 if (aReply->error() != QNetworkReply::NoError) {
439 error_message(tr("Error occurred during download: %1").arg(aReply->errorString()));
441 QUrl redirectUrl = aReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
442 if (!redirectUrl.isEmpty()) {
443 if (redirectUrl != aReply->request().url()) {
444 importFromNetwork(redirectUrl.toString(), aReply->request().attribute(QNetworkRequest::User).toInt());
445 return; // don't enable controls
447 error_message(tr("Error: Cyclic redirection from %1 to itself.").arg(redirectUrl.toString()));
450 importData(aReply->readAll(), aReply->url().toEncoded(), aReply->request().attribute(QNetworkRequest::User).toInt());
456 void MainWindow::importData(const QByteArray &aData, const QString& url, int conferenceId)
458 mXmlParser->parseData(aData, url, conferenceId);
461 void MainWindow::importFromNetwork(const QString& url, int conferenceId)
463 QNetworkRequest request;
464 QSslConfiguration qSslConfiguration = request.sslConfiguration();
465 qSslConfiguration.setProtocol(QSsl::AnyProtocol);
466 qSslConfiguration.setPeerVerifyMode(QSslSocket::QueryPeer);
467 request.setUrl(QUrl(url));
468 request.setSslConfiguration(qSslConfiguration);
469 request.setAttribute(QNetworkRequest::User, conferenceId);
471 mNetworkAccessManager->setProxy(QNetworkProxy::applicationProxy());
472 mNetworkAccessManager->get(request);
475 void MainWindow::importFromFile(const QString& filename, int conferenceId)
477 QFile file(filename);
478 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
479 static const QString format("Cannot read \"%1\": error %2");
480 error_message(format.arg(filename, QString::number(file.error())));
483 importData(file.readAll(), "", conferenceId);
487 void MainWindow::removeConference(int id) {
488 sqlEngine->deleteConference(id);
489 conferenceModel->conferenceRemoved();
490 emit conferenceRemoved();
494 void MainWindow::changeConferenceUrl(int id, const QString& url) {
495 Conference::getById(id).setUrl(url);