source: src/gui/mainwindow.cpp @ 79a7671

qt5
Last change on this file since 79a7671 was e4c166a, checked in by Philipp Spitzer <philipp@…>, 5 years ago

Write debug message in case of silently catched exceptions.

  • Property mode set to 100644
File size: 17.5 KB
Line 
1/*
2 * Copyright (C) 2010 Ixonos Plc.
3 * Copyright (C) 2011-2017 Philipp Spitzer, gregor herrmann, Stefan Stahl
4 *
5 * This file is part of ConfClerk.
6 *
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)
10 * any later version.
11 *
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
15 * more details.
16 *
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/>.
19 */
20#include "mainwindow.h"
21
22#include <QTreeView>
23#include <QFile>
24#include <QNetworkProxy>
25#include <QNetworkAccessManager>
26#include <QNetworkReply>
27#include <QSslConfiguration>
28
29#include "sqlengine.h"
30
31#include "track.h"
32#include "eventmodel.h"
33#include "delegate.h"
34#include "room.h"
35
36#include "conference.h"
37
38#include <QDialog>
39#include <QMessageBox>
40
41#include "ui_about.h"
42#include "eventdialog.h"
43#include "daynavigatorwidget.h"
44#include "settingsdialog.h"
45#include "conferenceeditor.h"
46#include "schedulexmlparser.h"
47#include "errormessage.h"
48
49#include "tabcontainer.h"
50#include "appsettings.h"
51
52const QString PROXY_USERNAME;
53const QString PROXY_PASSWD;
54
55MainWindow::MainWindow(QWidget* parent): QMainWindow(parent) {
56    setupUi(this);
57
58    // Open database
59    sqlEngine = new SqlEngine(this);
60    searchTabContainer->setSqlEngine(sqlEngine);
61    connect(sqlEngine, SIGNAL(dbError(QString)), this, SLOT(showError(QString)));
62    sqlEngine->open();
63    sqlEngine->createOrUpdateDbSchema();
64
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);
70
71    alarmTimer->setInterval(60000);
72    alarmTimer->start();
73    saved_title = windowTitle();
74
75#ifdef N810
76    tabWidget->setTabText(1,"Favs");
77    //tabWidget->setTabText(2,"Day");
78#endif
79
80    // first time run aplication: -> let's have it direct connection in this case
81    if(!AppSettings::contains("proxyIsDirectConnection"))
82        AppSettings::setDirectConnection(true);
83
84    QNetworkProxy proxy(
85            AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : QNetworkProxy::HttpProxy,
86            AppSettings::proxyAddress(),
87            AppSettings::proxyPort(),
88            PROXY_USERNAME,
89            PROXY_PASSWD);
90    QNetworkProxy::setApplicationProxy(proxy);
91
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)));
98
99    // date has changed
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)));
105
106    // search result has changed
107    connect(searchTabContainer, SIGNAL(searchResultChanged()), SLOT(onSearchResultChanged()));
108
109    // systm tray icon
110    connect(systemTrayIcon, SIGNAL(messageClicked()), SLOT(onSystemTrayMessageClicked()));
111    connect(systemTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), SLOT(onSystemTrayMessageClicked()));
112
113    // timer
114    connect(alarmTimer, SIGNAL(timeout()), SLOT(onAlarmTimerTimeout()));
115
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);
120
121    // make it impossible to hide the toolbar by disallowing its context menu
122    toolBar->setContextMenuPolicy(Qt::PreventContextMenu);
123
124    // open conference
125    useConference(Conference::activeConference());
126    // optimization, see useConference() code
127    try {
128        initTabs();
129    } catch (const OrmException& e) {
130        qDebug() << "OrmException:" << e.text();
131        clearTabs();
132    }
133
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)));
138}
139
140
141MainWindow::~MainWindow() {
142    sqlEngine->close();
143}
144
145
146void MainWindow::on_aboutAction_triggered()
147{
148    QDialog dialog(this);
149    Ui::AboutDialog ui;
150    ui.setupUi(&dialog);
151    ui.labDescription->setText(ui.labDescription->text().arg(qApp->applicationVersion()));
152#ifdef N810
153    dialog.setFixedWidth(width());
154#endif
155    dialog.exec();
156}
157
158
159void 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);
165    setEnabled(false);
166}
167
168
169void 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);
174}
175
176
177void MainWindow::on_searchAction_triggered() {
178    if (tabWidget->currentWidget() == searchTab)
179        searchTabContainer->showSearchDialog(!searchTabContainer->searchDialogIsVisible());
180    else {
181        tabWidget->setCurrentWidget(searchTab);
182        searchTabContainer->showSearchDialog();
183    }
184}
185
186
187void 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();
193}
194
195
196void 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();
202}
203
204
205void 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);
212}
213
214
215void 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;}
220
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;}
225    }
226
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;}
231    }
232    // No results were found
233    searchTabContainer->redisplayDate(dayNavigator->curDate());
234}
235
236
237void MainWindow::onSystemTrayMessageClicked() {
238    systemTrayIcon->hide();
239}
240
241
242void 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;
246
247    // build a message string
248    Event event;
249    QString title;
250    QString message;
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());
255    } else {
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());
260        }
261        message = messages.join("\n");
262    }
263
264    // and delete the corresponding alarm
265    foreach (event, events) {
266        event.setHasAlarm(false);
267        event.update("alarm");
268        onEventChanged(event.id(), false);
269    }
270
271    // show message
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();
279}
280
281
282void MainWindow::useConference(int conferenceId)
283{
284    if (conferenceId == -1)  // in case no conference is active
285    {
286        unsetConference();
287        return;
288    }
289    try {
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);
295
296        // looks like it does not work at n900
297        setWindowTitle(activeConference.title());
298
299        // optimization.
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
304        // 1. at startup
305        // 2. when ConferenceEditor finished
306        // dont forget to protect the calls by try-catch!
307
308        // just in case, clear conference selection instead
309        clearTabs();
310
311        // end of optimization
312        // initTabs();
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
317        return;
318    }
319
320}
321
322void MainWindow::initTabs()
323{
324    int confId = Conference::activeConference();
325    if (confId != -1)   // only init tabs if a conference is active
326    {
327        Conference active = Conference::getById(confId);
328        QDate startDate = active.start();
329        QDate endDate = active.end();
330
331        // 'dayNavigator' emits signal 'dateChanged' after setting valid START:END dates
332        dayNavigator->setDates(startDate, endDate);
333        nowAction->trigger();
334    }
335}
336
337void MainWindow::clearTabs()
338{
339    dayTabContainer->clearModel();
340    tracksTabContainer->clearModel();
341    roomsTabContainer->clearModel();
342    favsTabContainer->clearModel();
343    searchTabContainer->clearModel();
344}
345
346void MainWindow::unsetConference()
347{
348    clearTabs();
349    dayNavigator->unsetDates();
350    setWindowTitle(saved_title);
351}
352
353
354void MainWindow::showError(const QString& message) {
355    error_message(message);
356}
357
358
359void MainWindow::on_settingsAction_triggered()
360{
361    SettingsDialog dialog;
362    dialog.loadDialogData();
363    if (dialog.exec() == QDialog::Accepted) {
364        dialog.saveDialogData();
365        QNetworkProxy proxy(
366                AppSettings::isDirectConnection() ? QNetworkProxy::NoProxy : QNetworkProxy::HttpProxy,
367                AppSettings::proxyAddress(),
368                AppSettings::proxyPort(),
369                PROXY_USERNAME,
370                PROXY_PASSWD);
371        QNetworkProxy::setApplicationProxy(proxy);
372    }
373}
374
375/** Create and run ConferenceEditor dialog, making required connections for it.
376
377This method manages, which classes actually perform changes in conference list.
378
379There are several classes that modify the conferences:
380this:
381 deletion and URL update.
382this, mXmlParser and mNetworkAccessManager:
383 addition and refresh.
384*/
385void MainWindow::on_conferencesAction_triggered()
386{
387    ConferenceEditor dialog(conferenceModel, this);
388
389    connect(&dialog, SIGNAL(haveConferenceUrl(const QString&, int)), SLOT(importFromNetwork(const QString&, int)));
390    connect(&dialog, SIGNAL(haveConferenceFile(const QString&, int)), SLOT(importFromFile(const QString&, int)));
391    connect(&dialog, SIGNAL(removeConferenceRequested(int)), SLOT(removeConference(int)));
392    connect(&dialog, SIGNAL(changeUrlRequested(int, const QString&)),
393                    SLOT(changeConferenceUrl(int, const QString&)));
394
395    connect(&dialog, SIGNAL(haveConferenceSelected(int)), SLOT(useConference(int)));
396    connect(&dialog, SIGNAL(noneConferenceSelected()), SLOT(unsetConference()));
397
398    connect(mXmlParser, SIGNAL(parsingScheduleBegin()), &dialog, SLOT(importStarted()));
399    connect(mXmlParser, SIGNAL(progressStatus(int)), &dialog, SLOT(showParsingProgress(int)));
400    connect(mXmlParser, SIGNAL(parsingScheduleEnd(int)), &dialog, SLOT(importFinished(int)));
401
402    connect(this, SIGNAL(conferenceRemoved()), &dialog, SLOT(conferenceRemoved()));
403
404    dialog.exec();
405
406    // optimization, see useConference() code
407    try {
408        initTabs();
409    } catch (const OrmException& e) {
410        qDebug() << "OrmException:" << e.text();
411        clearTabs();
412    }
413}
414
415void MainWindow::sslErrors(QNetworkReply *aReply, const QList<QSslError> &errors) {
416    QString errorString;
417    foreach (const QSslError &error, errors) {
418        if (!errorString.isEmpty()) {
419            errorString += ", ";
420        }
421        errorString += error.errorString();
422    }
423
424    if (QMessageBox::warning(
425                this,
426                tr("SSL errors"),
427                tr("One or more SSL errors have occurred: %1", 0, errors.size()).arg(errorString),
428                QMessageBox::Ignore | QMessageBox::Cancel) == QMessageBox::Ignore) {
429        aReply->ignoreSslErrors();
430    } else {
431        aReply->abort();
432    }
433}
434
435void MainWindow::networkQueryFinished(QNetworkReply *aReply) {
436    if (aReply->error() != QNetworkReply::NoError) {
437        error_message(tr("Error occurred during download: %1").arg(aReply->errorString()));
438    } else {
439        QUrl redirectUrl = aReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
440        if (!redirectUrl.isEmpty()) {
441            if (redirectUrl != aReply->request().url()) {
442                importFromNetwork(redirectUrl.toString(), aReply->request().attribute(QNetworkRequest::User).toInt());
443                return; // don't enable controls
444            } else {
445                error_message(tr("Error: Cyclic redirection from %1 to itself.").arg(redirectUrl.toString()));
446            }
447        } else {
448            importData(aReply->readAll(), aReply->url().toEncoded(), aReply->request().attribute(QNetworkRequest::User).toInt());
449        }
450    }
451    setEnabled(true);
452}
453
454void MainWindow::importData(const QByteArray &aData, const QString& url, int conferenceId)
455{
456    mXmlParser->parseData(aData, url, conferenceId);
457}
458
459void MainWindow::importFromNetwork(const QString& url, int conferenceId)
460{
461    QNetworkRequest request;
462    QSslConfiguration qSslConfiguration = request.sslConfiguration();
463    qSslConfiguration.setProtocol(QSsl::AnyProtocol);
464    qSslConfiguration.setPeerVerifyMode(QSslSocket::QueryPeer);
465    request.setUrl(QUrl(url));
466    request.setSslConfiguration(qSslConfiguration);
467    request.setAttribute(QNetworkRequest::User, conferenceId);
468
469    mNetworkAccessManager->setProxy(QNetworkProxy::applicationProxy());
470    mNetworkAccessManager->get(request);
471}
472
473void MainWindow::importFromFile(const QString& filename, int conferenceId)
474{
475    QFile file(filename);
476    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {   
477        static const QString format("Cannot read \"%1\": error %2");
478        error_message(format.arg(filename, QString::number(file.error())));
479    }
480
481    importData(file.readAll(), "", conferenceId);
482}
483
484
485void MainWindow::removeConference(int id) {
486    sqlEngine->deleteConference(id);
487    conferenceModel->conferenceRemoved();
488    emit conferenceRemoved();
489}
490
491
492void MainWindow::changeConferenceUrl(int id, const QString& url) {
493    Conference::getById(id).setUrl(url);
494}
495
Note: See TracBrowser for help on using the repository browser.