/* * Copyright (C) 2010 Ixonos Plc. * Copyright (C) 2011-2017 Philipp Spitzer, gregor herrmann, Stefan Stahl * * This file is part of ConfClerk. * * ConfClerk is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, either version 2 of the License, or (at your option) * any later version. * * ConfClerk is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * ConfClerk. If not, see . */ #include "mainwindow.h" #include #include #include #include #include #include #include "sqlengine.h" #include "track.h" #include "eventmodel.h" #include "delegate.h" #include "room.h" #include "conference.h" #include #include #include "ui_about.h" #include "eventdialog.h" #include "daynavigatorwidget.h" #include "settingsdialog.h" #include "conferenceeditor.h" #include "schedulexmlparser.h" #include "errormessage.h" #include "tabcontainer.h" #include "appsettings.h" const QString PROXY_USERNAME; const QString PROXY_PASSWD; MainWindow::MainWindow(QWidget* parent): QMainWindow(parent) { setupUi(this); // Open database sqlEngine = new SqlEngine(this); searchTabContainer->setSqlEngine(sqlEngine); connect(sqlEngine, SIGNAL(dbError(QString)), this, SLOT(showError(QString))); sqlEngine->open(); sqlEngine->createOrUpdateDbSchema(); conferenceModel = new ConferenceModel(this); mXmlParser = new ScheduleXmlParser(sqlEngine, this); mNetworkAccessManager = new QNetworkAccessManager(this); systemTrayIcon = new QSystemTrayIcon(qApp->windowIcon(), this); alarmTimer = new QTimer(this); alarmTimer->setInterval(60000); alarmTimer->start(); saved_title = windowTitle(); #ifdef N810 tabWidget->setTabText(1,"Favs"); //tabWidget->setTabText(2,"Day"); #endif // first time run aplication: -> let's have it direct connection in this case if(!AppSettings::contains("proxyIsDirectConnection")) AppSettings::setDirectConnection(true); QNetworkProxy proxy( AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : QNetworkProxy::HttpProxy, AppSettings::proxyAddress(), AppSettings::proxyPort(), PROXY_USERNAME, PROXY_PASSWD); QNetworkProxy::setApplicationProxy(proxy); // event details have changed connect(dayTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool))); connect(favsTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool))); connect(tracksTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool))); connect(roomsTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool))); connect(searchTabContainer, SIGNAL(eventChanged(int,bool)), SLOT(onEventChanged(int,bool))); // date has changed connect(dayNavigator, SIGNAL(dateChanged(QDate)), dayTabContainer, SLOT(redisplayDate(QDate))); connect(dayNavigator, SIGNAL(dateChanged(QDate)), favsTabContainer, SLOT(redisplayDate(QDate))); connect(dayNavigator, SIGNAL(dateChanged(QDate)), tracksTabContainer, SLOT(redisplayDate(QDate))); connect(dayNavigator, SIGNAL(dateChanged(QDate)), roomsTabContainer, SLOT(redisplayDate(QDate))); connect(dayNavigator, SIGNAL(dateChanged(QDate)), searchTabContainer, SLOT(redisplayDate(QDate))); // search result has changed connect(searchTabContainer, SIGNAL(searchResultChanged()), SLOT(onSearchResultChanged())); // systm tray icon connect(systemTrayIcon, SIGNAL(messageClicked()), SLOT(onSystemTrayMessageClicked())); connect(systemTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), SLOT(onSystemTrayMessageClicked())); // timer connect(alarmTimer, SIGNAL(timeout()), SLOT(onAlarmTimerTimeout())); // add the actions from the main menu to the window, otherwise the shortcuts don't work on MAEMO addAction(conferencesAction); addAction(settingsAction); addAction(quitAction); // make it impossible to hide the toolbar by disallowing its context menu toolBar->setContextMenuPolicy(Qt::PreventContextMenu); // open conference useConference(Conference::activeConference()); // optimization, see useConference() code try { initTabs(); } catch (const OrmException& e) { qDebug() << "OrmException:" << e.text(); clearTabs(); } connect(mNetworkAccessManager, SIGNAL(sslErrors(QNetworkReply*, QList)), SLOT(sslErrors(QNetworkReply*, QList))); connect(mNetworkAccessManager, SIGNAL(finished(QNetworkReply*)), SLOT(networkQueryFinished(QNetworkReply*))); connect(mXmlParser, SIGNAL(parsingScheduleBegin()), conferenceModel, SLOT(newConferenceBegin())); connect(mXmlParser, SIGNAL(parsingScheduleEnd(int)), conferenceModel, SLOT(newConferenceEnd(int))); } MainWindow::~MainWindow() { sqlEngine->close(); } void MainWindow::on_aboutAction_triggered() { QDialog dialog(this); Ui::AboutDialog ui; ui.setupUi(&dialog); ui.labDescription->setText(ui.labDescription->text().arg(qApp->applicationVersion())); #ifdef N810 dialog.setFixedWidth(width()); #endif dialog.exec(); } void MainWindow::on_reloadAction_triggered() { int confId = Conference::activeConference(); if (confId== -1) return; Conference active = Conference::getById(confId); if (active.url().isEmpty()) return; importFromNetwork(active.url(), confId); setEnabled(false); } void MainWindow::on_nowAction_triggered() { int confId = Conference::activeConference(); if (confId== -1) return; dayNavigator->setCurDate(QDate::currentDate()); dayTabContainer->expandTimeGroup(QTime::currentTime(), confId); } void MainWindow::on_searchAction_triggered() { if (tabWidget->currentWidget() == searchTab) searchTabContainer->showSearchDialog(!searchTabContainer->searchDialogIsVisible()); else { tabWidget->setCurrentWidget(searchTab); searchTabContainer->showSearchDialog(); } } void MainWindow::on_expandAllAction_triggered() { if (tabWidget->currentWidget() == favouritesTab) favsTabContainer->treeView->expandAll(); if (tabWidget->currentWidget() == dayViewTab) dayTabContainer->treeView->expandAll(); if (tabWidget->currentWidget() == tracksTab) tracksTabContainer->treeView->expandAll(); if (tabWidget->currentWidget() == roomsTab) roomsTabContainer->treeView->expandAll(); if (tabWidget->currentWidget() == searchTab) searchTabContainer->treeView->expandAll(); } void MainWindow::on_collapseAllAction_triggered() { if (tabWidget->currentWidget() == favouritesTab) favsTabContainer->treeView->collapseAll(); if (tabWidget->currentWidget() == dayViewTab) dayTabContainer->treeView->collapseAll(); if (tabWidget->currentWidget() == tracksTab) tracksTabContainer->treeView->collapseAll(); if (tabWidget->currentWidget() == roomsTab) roomsTabContainer->treeView->collapseAll(); if (tabWidget->currentWidget() == searchTab) searchTabContainer->treeView->collapseAll(); } void MainWindow::onEventChanged(int aEventId, bool favouriteChanged) { dayTabContainer->redisplayEvent(aEventId); if (favouriteChanged) favsTabContainer->redisplayDate(dayNavigator->curDate()); else favsTabContainer->redisplayEvent(aEventId); tracksTabContainer->redisplayEvent(aEventId); roomsTabContainer->redisplayEvent(aEventId); searchTabContainer->redisplayEvent(aEventId); } void MainWindow::onSearchResultChanged() { // Are results found on the current date? QDate date = dayNavigator->curDate(); int count = searchTabContainer->searchResultCount(date); if (count > 0) {searchTabContainer->redisplayDate(date); return;} // Are results found in the future? for (date = date.addDays(1); date <= dayNavigator->endDate(); date = date.addDays(1)) { int count = searchTabContainer->searchResultCount(date); if (count > 0) {dayNavigator->setCurDate(date); return;} } // Are results found in the past? for (date = dayNavigator->startDate(); date < dayNavigator->curDate(); date = date.addDays(1)) { int count = searchTabContainer->searchResultCount(date); if (count > 0) {dayNavigator->setCurDate(date); return;} } // No results were found searchTabContainer->redisplayDate(dayNavigator->curDate()); } void MainWindow::onSystemTrayMessageClicked() { systemTrayIcon->hide(); } void MainWindow::onAlarmTimerTimeout() { // determine if an alarm is set on an event that's starting soon QList events = Event::getImminentAlarmEvents(AppSettings::preEventAlarmSec(), Conference::activeConference()); if (events.empty()) return; // build a message string Event event; QString title; QString message; if (events.size() == 1) { event = events.first(); title = tr("Next event at %1").arg(event.start().toString("HH:mm")); message = tr("\"%1\"\n(%2)").arg(event.title()).arg(event.room()->name()); } else { title = tr("%1 upcoming events").arg(events.size()); QStringList messages; foreach (event, events) { messages += tr("%1: \"%2\" (%3)").arg(event.start().toString("HH:mm")).arg(event.title()).arg(event.room()->name()); } message = messages.join("\n"); } // and delete the corresponding alarm foreach (event, events) { event.setHasAlarm(false); event.update("alarm"); onEventChanged(event.id(), false); } // show message systemTrayIcon->show(); // The next two lines are to prevent a very strange position of the message box the first time at X11/aweseome (not Win32/XP) systemTrayIcon->showMessage("ConfClerk", "Your upcoming events", QSystemTrayIcon::Information); qApp->processEvents(); systemTrayIcon->showMessage(title, message, QSystemTrayIcon::Information, 60*60*24*1000); QApplication::alert(this); QApplication::beep(); } void MainWindow::useConference(int conferenceId) { if (conferenceId == -1) // in case no conference is active { unsetConference(); return; } try { int oldActiveConferenceId = Conference::activeConference(); bool switchActiveConference = conferenceId != oldActiveConferenceId; if (switchActiveConference) Conference::getById(oldActiveConferenceId).update("active", 0); Conference activeConference = Conference::getById(conferenceId); if (switchActiveConference) activeConference.update("active",1); // looks like it does not work at n900 setWindowTitle(activeConference.title()); // optimization. // dont run initTabs() here // it takes much CPU, making travelling between conferences in ConferenceEditor longer // and is not seen in maemo WM anyway // instead run it explicitly // 1. at startup // 2. when ConferenceEditor finished // dont forget to protect the calls by try-catch! // just in case, clear conference selection instead clearTabs(); // end of optimization // initTabs(); } catch (OrmException& e) { // cannon set an active conference unsetConference(); // TODO: as no active conference is now correctly managed this should be handled as a fatal error return; } } void MainWindow::initTabs() { int confId = Conference::activeConference(); if (confId != -1) // only init tabs if a conference is active { Conference active = Conference::getById(confId); QDate startDate = active.start(); QDate endDate = active.end(); // 'dayNavigator' emits signal 'dateChanged' after setting valid START:END dates dayNavigator->setDates(startDate, endDate); nowAction->trigger(); } } void MainWindow::clearTabs() { dayTabContainer->clearModel(); tracksTabContainer->clearModel(); roomsTabContainer->clearModel(); favsTabContainer->clearModel(); searchTabContainer->clearModel(); } void MainWindow::unsetConference() { clearTabs(); dayNavigator->unsetDates(); setWindowTitle(saved_title); } void MainWindow::showError(const QString& message) { error_message(message); } void MainWindow::on_settingsAction_triggered() { SettingsDialog dialog; dialog.loadDialogData(); if (dialog.exec() == QDialog::Accepted) { dialog.saveDialogData(); QNetworkProxy proxy( AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : QNetworkProxy::HttpProxy, AppSettings::proxyAddress(), AppSettings::proxyPort(), PROXY_USERNAME, PROXY_PASSWD); QNetworkProxy::setApplicationProxy(proxy); } } /** Create and run ConferenceEditor dialog, making required connections for it. This method manages, which classes actually perform changes in conference list. There are several classes that modify the conferences: this: deletion and URL update. this, mXmlParser and mNetworkAccessManager: addition and refresh. */ void MainWindow::on_conferencesAction_triggered() { ConferenceEditor dialog(conferenceModel, this); connect(&dialog, SIGNAL(haveConferenceUrl(const QString&, int)), SLOT(importFromNetwork(const QString&, int))); connect(&dialog, SIGNAL(haveConferenceFile(const QString&, int)), SLOT(importFromFile(const QString&, int))); connect(&dialog, SIGNAL(removeConferenceRequested(int)), SLOT(removeConference(int))); connect(&dialog, SIGNAL(changeUrlRequested(int, const QString&)), SLOT(changeConferenceUrl(int, const QString&))); connect(&dialog, SIGNAL(haveConferenceSelected(int)), SLOT(useConference(int))); connect(&dialog, SIGNAL(noneConferenceSelected()), SLOT(unsetConference())); connect(mXmlParser, SIGNAL(parsingScheduleBegin()), &dialog, SLOT(importStarted())); connect(mXmlParser, SIGNAL(progressStatus(int)), &dialog, SLOT(showParsingProgress(int))); connect(mXmlParser, SIGNAL(parsingScheduleEnd(int)), &dialog, SLOT(importFinished(int))); connect(this, SIGNAL(conferenceRemoved()), &dialog, SLOT(conferenceRemoved())); dialog.exec(); // optimization, see useConference() code try { initTabs(); } catch (OrmException) { clearTabs(); } } void MainWindow::sslErrors(QNetworkReply *aReply, const QList &errors) { QString errorString; foreach (const QSslError &error, errors) { if (!errorString.isEmpty()) { errorString += ", "; } errorString += error.errorString(); } if (QMessageBox::warning( this, QString("SSL errors"), QString("One or more SSL errors have occurred: ") + errorString, QMessageBox::Ignore | QMessageBox::Cancel) == QMessageBox::Ignore) { aReply->ignoreSslErrors(); } else { aReply->abort(); } } void MainWindow::networkQueryFinished(QNetworkReply *aReply) { if (aReply->error() != QNetworkReply::NoError) { error_message(QString("Error occurred during download: ") + aReply->errorString()); } else { QUrl redirectUrl = aReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); if (!redirectUrl.isEmpty()) { if (redirectUrl != aReply->request().url()) { importFromNetwork(redirectUrl.toString(), aReply->request().attribute(QNetworkRequest::User).toInt()); return; // don't enable controls } else { error_message(QString("Error: Cyclic redirection from %1 to itself.").arg(redirectUrl.toString())); } } else { importData(aReply->readAll(), aReply->url().toEncoded(), aReply->request().attribute(QNetworkRequest::User).toInt()); } } setEnabled(true); } void MainWindow::importData(const QByteArray &aData, const QString& url, int conferenceId) { mXmlParser->parseData(aData, url, conferenceId); } void MainWindow::importFromNetwork(const QString& url, int conferenceId) { QNetworkRequest request; QSslConfiguration qSslConfiguration = request.sslConfiguration(); qSslConfiguration.setProtocol(QSsl::AnyProtocol); qSslConfiguration.setPeerVerifyMode(QSslSocket::QueryPeer); request.setUrl(QUrl(url)); request.setSslConfiguration(qSslConfiguration); request.setAttribute(QNetworkRequest::User, conferenceId); mNetworkAccessManager->setProxy(QNetworkProxy::applicationProxy()); mNetworkAccessManager->get(request); } void MainWindow::importFromFile(const QString& filename, int conferenceId) { QFile file(filename); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { static const QString format("Cannot read \"%1\": error %2"); error_message(format.arg(filename, QString::number(file.error()))); } importData(file.readAll(), "", conferenceId); } void MainWindow::removeConference(int id) { sqlEngine->deleteConference(id); conferenceModel->conferenceRemoved(); emit conferenceRemoved(); } void MainWindow::changeConferenceUrl(int id, const QString& url) { Conference::getById(id).setUrl(url); }