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(finished(QNetworkReply*)), SLOT(networkQueryFinished(QNetworkReply*)));
135 connect(mXmlParser, SIGNAL(parsingScheduleBegin()), conferenceModel, SLOT(newConferenceBegin()));
136 connect(mXmlParser, SIGNAL(parsingScheduleEnd(int)), conferenceModel, SLOT(newConferenceEnd(int)));
140 MainWindow::~MainWindow() {
145 void MainWindow::on_aboutAction_triggered()
147 QDialog dialog(this);
150 ui.labDescription->setText(ui.labDescription->text().arg(qApp->applicationVersion()));
152 dialog.setFixedWidth(width());
158 void MainWindow::on_reloadAction_triggered() {
159 int confId = Conference::activeConference();
160 if (confId== -1) return;
161 Conference active = Conference::getById(confId);
162 if (active.url().isEmpty()) return;
163 importFromNetwork(active.url(), confId);
168 void MainWindow::on_nowAction_triggered() {
169 int confId = Conference::activeConference();
170 if (confId== -1) return;
171 dayNavigator->setCurDate(QDate::currentDate());
172 dayTabContainer->expandTimeGroup(QTime::currentTime(), confId);
176 void MainWindow::on_searchAction_triggered() {
177 if (tabWidget->currentWidget() == searchTab)
178 searchTabContainer->showSearchDialog(!searchTabContainer->searchDialogIsVisible());
180 tabWidget->setCurrentWidget(searchTab);
181 searchTabContainer->showSearchDialog();
186 void MainWindow::on_expandAllAction_triggered() {
187 if (tabWidget->currentWidget() == favouritesTab) favsTabContainer->treeView->expandAll();
188 if (tabWidget->currentWidget() == dayViewTab) dayTabContainer->treeView->expandAll();
189 if (tabWidget->currentWidget() == tracksTab) tracksTabContainer->treeView->expandAll();
190 if (tabWidget->currentWidget() == roomsTab) roomsTabContainer->treeView->expandAll();
191 if (tabWidget->currentWidget() == searchTab) searchTabContainer->treeView->expandAll();
195 void MainWindow::on_collapseAllAction_triggered() {
196 if (tabWidget->currentWidget() == favouritesTab) favsTabContainer->treeView->collapseAll();
197 if (tabWidget->currentWidget() == dayViewTab) dayTabContainer->treeView->collapseAll();
198 if (tabWidget->currentWidget() == tracksTab) tracksTabContainer->treeView->collapseAll();
199 if (tabWidget->currentWidget() == roomsTab) roomsTabContainer->treeView->collapseAll();
200 if (tabWidget->currentWidget() == searchTab) searchTabContainer->treeView->collapseAll();
204 void MainWindow::onEventChanged(int aEventId, bool favouriteChanged) {
205 dayTabContainer->redisplayEvent(aEventId);
206 if (favouriteChanged) favsTabContainer->redisplayDate(dayNavigator->curDate());
207 else favsTabContainer->redisplayEvent(aEventId);
208 tracksTabContainer->redisplayEvent(aEventId);
209 roomsTabContainer->redisplayEvent(aEventId);
210 searchTabContainer->redisplayEvent(aEventId);
214 void MainWindow::onSearchResultChanged() {
215 // Are results found on the current date?
216 QDate date = dayNavigator->curDate();
217 int count = searchTabContainer->searchResultCount(date);
218 if (count > 0) {searchTabContainer->redisplayDate(date); return;}
220 // Are results found in the future?
221 for (date = date.addDays(1); date <= dayNavigator->endDate(); date = date.addDays(1)) {
222 int count = searchTabContainer->searchResultCount(date);
223 if (count > 0) {dayNavigator->setCurDate(date); return;}
226 // Are results found in the past?
227 for (date = dayNavigator->startDate(); date < dayNavigator->curDate(); date = date.addDays(1)) {
228 int count = searchTabContainer->searchResultCount(date);
229 if (count > 0) {dayNavigator->setCurDate(date); return;}
231 // No results were found
232 searchTabContainer->redisplayDate(dayNavigator->curDate());
236 void MainWindow::onSystemTrayMessageClicked() {
237 systemTrayIcon->hide();
241 void MainWindow::onAlarmTimerTimeout() {
242 // determine if an alarm is set on an event that's starting soon
243 QList<Event> events = Event::getImminentAlarmEvents(AppSettings::preEventAlarmSec(), Conference::activeConference());
244 if (events.empty()) return;
246 // build a message string
250 if (events.size() == 1) {
251 event = events.first();
252 title = tr("Next event at %1").arg(event.start().toString("HH:mm"));
253 message = tr("\"%1\"\n(%2)").arg(event.title()).arg(event.room()->name());
255 title = tr("%1 upcoming events").arg(events.size());
256 QStringList messages;
257 foreach (event, events) {
258 messages += tr("%1: \"%2\" (%3)").arg(event.start().toString("HH:mm")).arg(event.title()).arg(event.room()->name());
260 message = messages.join("\n");
263 // and delete the corresponding alarm
264 foreach (event, events) {
265 event.setHasAlarm(false);
266 event.update("alarm");
267 onEventChanged(event.id(), false);
271 systemTrayIcon->show();
272 // The next two lines are to prevent a very strange position of the message box the first time at X11/aweseome (not Win32/XP)
273 systemTrayIcon->showMessage("ConfClerk", "Your upcoming events", QSystemTrayIcon::Information);
274 qApp->processEvents();
275 systemTrayIcon->showMessage(title, message, QSystemTrayIcon::Information, 60*60*24*1000);
276 QApplication::alert(this);
277 QApplication::beep();
281 void MainWindow::useConference(int conferenceId)
283 if (conferenceId == -1) // in case no conference is active
289 int oldActiveConferenceId = Conference::activeConference();
290 bool switchActiveConference = conferenceId != oldActiveConferenceId;
291 if (switchActiveConference) Conference::getById(oldActiveConferenceId).update("active", 0);
292 Conference activeConference = Conference::getById(conferenceId);
293 if (switchActiveConference) activeConference.update("active",1);
295 // looks like it does not work at n900
296 setWindowTitle(activeConference.title());
299 // dont run initTabs() here
300 // it takes much CPU, making travelling between conferences in ConferenceEditor longer
301 // and is not seen in maemo WM anyway
302 // instead run it explicitly
304 // 2. when ConferenceEditor finished
305 // dont forget to protect the calls by try-catch!
307 // just in case, clear conference selection instead
310 // end of optimization
312 } catch (OrmException& e) {
313 // cannon set an active conference
314 unsetConference(); // TODO: as no active conference is now correctly managed this should be handled as a fatal error
320 void MainWindow::initTabs()
322 int confId = Conference::activeConference();
323 if (confId != -1) // only init tabs if a conference is active
325 Conference active = Conference::getById(confId);
326 QDate startDate = active.start();
327 QDate endDate = active.end();
329 // 'dayNavigator' emits signal 'dateChanged' after setting valid START:END dates
330 dayNavigator->setDates(startDate, endDate);
331 nowAction->trigger();
335 void MainWindow::clearTabs()
337 dayTabContainer->clearModel();
338 tracksTabContainer->clearModel();
339 roomsTabContainer->clearModel();
340 favsTabContainer->clearModel();
341 searchTabContainer->clearModel();
344 void MainWindow::unsetConference()
347 dayNavigator->unsetDates();
348 setWindowTitle(saved_title);
352 void MainWindow::showError(const QString& message) {
353 error_message(message);
357 void MainWindow::on_settingsAction_triggered()
359 SettingsDialog dialog;
360 dialog.loadDialogData();
361 if (dialog.exec() == QDialog::Accepted) {
362 dialog.saveDialogData();
364 AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : QNetworkProxy::HttpProxy,
365 AppSettings::proxyAddress(),
366 AppSettings::proxyPort(),
369 QNetworkProxy::setApplicationProxy(proxy);
373 /** Create and run ConferenceEditor dialog, making required connections for it.
375 This method manages, which classes actually perform changes in conference list.
377 There are several classes that modify the conferences:
379 deletion and URL update.
380 this, mXmlParser and mNetworkAccessManager:
381 addition and refresh.
383 void MainWindow::on_conferencesAction_triggered()
385 ConferenceEditor dialog(conferenceModel, this);
387 connect(&dialog, SIGNAL(haveConferenceUrl(const QString&, int)), SLOT(importFromNetwork(const QString&, int)));
388 connect(&dialog, SIGNAL(haveConferenceFile(const QString&, int)), SLOT(importFromFile(const QString&, int)));
389 connect(&dialog, SIGNAL(removeConferenceRequested(int)), SLOT(removeConference(int)));
390 connect(&dialog, SIGNAL(changeUrlRequested(int, const QString&)),
391 SLOT(changeConferenceUrl(int, const QString&)));
393 connect(&dialog, SIGNAL(haveConferenceSelected(int)), SLOT(useConference(int)));
394 connect(&dialog, SIGNAL(noneConferenceSelected()), SLOT(unsetConference()));
396 connect(mXmlParser, SIGNAL(parsingScheduleBegin()), &dialog, SLOT(importStarted()));
397 connect(mXmlParser, SIGNAL(progressStatus(int)), &dialog, SLOT(showParsingProgress(int)));
398 connect(mXmlParser, SIGNAL(parsingScheduleEnd(int)), &dialog, SLOT(importFinished(int)));
400 connect(this, SIGNAL(conferenceRemoved()), &dialog, SLOT(conferenceRemoved()));
404 // optimization, see useConference() code
407 } catch (OrmException) {
412 void MainWindow::networkQueryFinished(QNetworkReply *aReply) {
413 if (aReply->error() != QNetworkReply::NoError) {
414 error_message(QString("Error occurred during download: ") + aReply->errorString());
416 QUrl redirectUrl = aReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
417 if (!redirectUrl.isEmpty()) {
418 if (redirectUrl != aReply->request().url()) {
419 importFromNetwork(redirectUrl.toString(), aReply->request().attribute(QNetworkRequest::User).toInt());
420 return; // don't enable controls
422 error_message(QString("Error: Cyclic redirection from %1 to itself.").arg(redirectUrl.toString()));
425 importData(aReply->readAll(), aReply->url().toEncoded(), aReply->request().attribute(QNetworkRequest::User).toInt());
431 void MainWindow::importData(const QByteArray &aData, const QString& url, int conferenceId)
433 mXmlParser->parseData(aData, url, conferenceId);
436 void MainWindow::importFromNetwork(const QString& url, int conferenceId)
438 QNetworkRequest request;
439 QSslConfiguration qSslConfiguration = request.sslConfiguration();
440 qSslConfiguration.setProtocol(QSsl::AnyProtocol);
441 qSslConfiguration.setPeerVerifyMode(QSslSocket::QueryPeer);
442 request.setUrl(QUrl(url));
443 request.setSslConfiguration(qSslConfiguration);
444 request.setAttribute(QNetworkRequest::User, conferenceId);
446 mNetworkAccessManager->setProxy(QNetworkProxy::applicationProxy());
447 mNetworkAccessManager->get(request);
450 void MainWindow::importFromFile(const QString& filename, int conferenceId)
452 QFile file(filename);
453 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
454 static const QString format("Cannot read \"%1\": error %2");
455 error_message(format.arg(filename, QString::number(file.error())));
458 importData(file.readAll(), "", conferenceId);
462 void MainWindow::removeConference(int id) {
463 sqlEngine->deleteConference(id);
464 conferenceModel->conferenceRemoved();
465 emit conferenceRemoved();
469 void MainWindow::changeConferenceUrl(int id, const QString& url) {
470 Conference::getById(id).setUrl(url);